summaryrefslogtreecommitdiffstats
path: root/src/lib-index/mail-index-map-hdr.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-index/mail-index-map-hdr.c')
-rw-r--r--src/lib-index/mail-index-map-hdr.c359
1 files changed, 359 insertions, 0 deletions
diff --git a/src/lib-index/mail-index-map-hdr.c b/src/lib-index/mail-index-map-hdr.c
new file mode 100644
index 0000000..3287a28
--- /dev/null
+++ b/src/lib-index/mail-index-map-hdr.c
@@ -0,0 +1,359 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "mail-index-private.h"
+
+int mail_index_map_parse_extensions(struct mail_index_map *map)
+{
+ struct mail_index *index = map->index;
+ const struct mail_index_ext_header *ext_hdr;
+ unsigned int i, old_count, offset;
+ const char *name, *error;
+ uint32_t ext_id, ext_map_idx, ext_offset;
+
+ /* extension headers always start from 64bit offsets, so if base header
+ doesn't happen to be 64bit aligned we'll skip some bytes */
+ offset = MAIL_INDEX_HEADER_SIZE_ALIGN(map->hdr.base_header_size);
+ if (offset >= map->hdr.header_size && map->extension_pool == NULL) {
+ /* nothing to do, skip allocations and all */
+ return 0;
+ }
+
+ old_count = array_count(&index->extensions);
+ mail_index_map_init_extbufs(map, old_count + 5);
+
+ ext_id = (uint32_t)-1;
+ for (i = 0; i < old_count; i++)
+ array_push_back(&map->ext_id_map, &ext_id);
+
+ for (i = 0; offset < map->hdr.header_size; i++) {
+ ext_offset = offset;
+
+ if (mail_index_map_ext_get_next(map, &offset,
+ &ext_hdr, &name) < 0) {
+ mail_index_set_error(index, "Corrupted index file %s: "
+ "Header extension #%d (%s) goes outside header",
+ index->filepath, i, name);
+ return -1;
+ }
+
+ if (mail_index_map_ext_hdr_check(&map->hdr, ext_hdr,
+ name, &error) < 0) {
+ mail_index_set_error(index, "Corrupted index file %s: "
+ "Broken extension #%d (%s): %s",
+ index->filepath, i, name, error);
+ return -1;
+ }
+ if (mail_index_map_lookup_ext(map, name, &ext_map_idx)) {
+ mail_index_set_error(index, "Corrupted index file %s: "
+ "Duplicate header extension %s",
+ index->filepath, name);
+ return -1;
+ }
+
+ (void)mail_index_map_register_ext(map, name, ext_offset, ext_hdr);
+ }
+ return 0;
+}
+
+int mail_index_map_parse_keywords(struct mail_index_map *map)
+{
+ struct mail_index *index = map->index;
+ const struct mail_index_ext *ext;
+ const struct mail_index_keyword_header *kw_hdr;
+ const struct mail_index_keyword_header_rec *kw_rec;
+ const char *name;
+ unsigned int i, name_area_end_offset, old_count;
+ uint32_t idx;
+
+ if (!mail_index_map_lookup_ext(map, MAIL_INDEX_EXT_KEYWORDS, &idx)) {
+ if (array_is_created(&map->keyword_idx_map))
+ array_clear(&map->keyword_idx_map);
+ return 0;
+ }
+ ext = array_idx(&map->extensions, idx);
+
+ /* Extension header contains:
+ - struct mail_index_keyword_header
+ - struct mail_index_keyword_header_rec * keywords_count
+ - const char names[] * keywords_count
+
+ The mail_index_keyword_header_rec are rather unnecessary nowadays.
+ They were originally an optimization when dovecot.index header kept
+ changing constantly, but nowadays the changes are usually read from
+ the .log changes, so re-reading dovecot.index header isn't common.
+ In a later version we could even remove it.
+ */
+ i_assert(ext->hdr_offset < map->hdr.header_size);
+ kw_hdr = MAIL_INDEX_MAP_HDR_OFFSET(map, ext->hdr_offset);
+ kw_rec = (const void *)(kw_hdr + 1);
+ name = (const char *)(kw_rec + kw_hdr->keywords_count);
+
+ old_count = !array_is_created(&map->keyword_idx_map) ? 0 :
+ array_count(&map->keyword_idx_map);
+
+ /* make sure the header is valid */
+ if (kw_hdr->keywords_count < old_count) {
+ mail_index_set_error(index, "Corrupted index file %s: "
+ "Keywords removed unexpectedly",
+ index->filepath);
+ return -1;
+ }
+
+ if ((size_t)(name - (const char *)kw_hdr) > ext->hdr_size) {
+ mail_index_set_error(index, "Corrupted index file %s: "
+ "keywords_count larger than header size",
+ index->filepath);
+ return -1;
+ }
+
+ name_area_end_offset = (const char *)kw_hdr + ext->hdr_size - name;
+ for (i = 0; i < kw_hdr->keywords_count; i++) {
+ if (kw_rec[i].name_offset > name_area_end_offset) {
+ mail_index_set_error(index, "Corrupted index file %s: "
+ "name_offset points outside allocated header",
+ index->filepath);
+ return -1;
+ }
+ }
+ if (name[name_area_end_offset-1] != '\0') {
+ mail_index_set_error(index, "Corrupted index file %s: "
+ "Keyword header doesn't end with NUL",
+ index->filepath);
+ return -1;
+ }
+
+ /* create file -> index mapping */
+ if (!array_is_created(&map->keyword_idx_map))
+ i_array_init(&map->keyword_idx_map, kw_hdr->keywords_count);
+
+ size_t name_offset = 0;
+ /* Check that existing headers are still the same. */
+ for (i = 0; i < array_count(&map->keyword_idx_map); i++) {
+ const char *keyword = name + kw_rec[i].name_offset;
+ const unsigned int *old_idx;
+ unsigned int kw_idx;
+
+ if (kw_rec[i].name_offset != name_offset) {
+ /* this shouldn't happen, but the old code didn't check
+ for this so for safety keep this as a warning. */
+ e_warning(index->event,
+ "Corrupted index file %s: "
+ "Mismatching keyword name_offset",
+ index->filepath);
+ }
+ name_offset += strlen(keyword) + 1;
+
+ old_idx = array_idx(&map->keyword_idx_map, i);
+ if (!mail_index_keyword_lookup(index, keyword, &kw_idx) ||
+ kw_idx != *old_idx) {
+ mail_index_set_error(index, "Corrupted index file %s: "
+ "Keywords changed unexpectedly",
+ index->filepath);
+ return -1;
+ }
+ }
+
+ /* Register the newly seen keywords */
+ i = array_count(&map->keyword_idx_map);
+ for (; i < kw_hdr->keywords_count; i++) {
+ const char *keyword = name + kw_rec[i].name_offset;
+ unsigned int kw_idx;
+
+ if (kw_rec[i].name_offset != name_offset) {
+ /* this shouldn't happen, but the old code didn't check
+ for this so for safety keep this as a warning. */
+ e_warning(index->event,
+ "Corrupted index file %s: "
+ "Mismatching keyword name_offset",
+ index->filepath);
+ }
+ name_offset += strlen(keyword) + 1;
+
+ if (*keyword == '\0') {
+ mail_index_set_error(index, "Corrupted index file %s: "
+ "Empty keyword name in header",
+ index->filepath);
+ return -1;
+ }
+ mail_index_keyword_lookup_or_create(index, keyword, &kw_idx);
+ array_push_back(&map->keyword_idx_map, &kw_idx);
+ }
+ return 0;
+}
+
+bool mail_index_check_header_compat(struct mail_index *index,
+ const struct mail_index_header *hdr,
+ uoff_t file_size, const char **error_r)
+{
+ enum mail_index_header_compat_flags compat_flags = 0;
+
+#ifndef WORDS_BIGENDIAN
+ compat_flags |= MAIL_INDEX_COMPAT_LITTLE_ENDIAN;
+#endif
+
+ if (hdr->major_version != MAIL_INDEX_MAJOR_VERSION) {
+ /* major version change */
+ *error_r = t_strdup_printf("Major version changed (%u != %u)",
+ hdr->major_version, MAIL_INDEX_MAJOR_VERSION);
+ return FALSE;
+ }
+ if ((hdr->flags & MAIL_INDEX_HDR_FLAG_CORRUPTED) != 0) {
+ /* we've already complained about it */
+ *error_r = "Header's corrupted flag is set";
+ return FALSE;
+ }
+
+ if (hdr->compat_flags != compat_flags) {
+ /* architecture change */
+ *error_r = "CPU architecture changed";
+ return FALSE;
+ }
+
+ if (hdr->base_header_size < MAIL_INDEX_HEADER_MIN_SIZE ||
+ hdr->header_size < hdr->base_header_size) {
+ *error_r = t_strdup_printf(
+ "Corrupted header sizes (base %u, full %u)",
+ hdr->base_header_size, hdr->header_size);
+ return FALSE;
+ }
+ if (hdr->header_size > file_size) {
+ *error_r = t_strdup_printf(
+ "Header size is larger than file (%u > %"PRIuUOFF_T")",
+ hdr->header_size, file_size);
+ return FALSE;
+ }
+
+ if (hdr->indexid != index->indexid) {
+ if (index->indexid != 0) {
+ mail_index_set_error(index, "Index file %s: "
+ "indexid changed: %u -> %u",
+ index->filepath, index->indexid,
+ hdr->indexid);
+ }
+ index->indexid = hdr->indexid;
+ mail_transaction_log_indexid_changed(index->log);
+ }
+ return TRUE;
+}
+
+static void mail_index_map_clear_recent_flags(struct mail_index_map *map)
+{
+ struct mail_index_record *rec;
+ uint32_t seq;
+
+ for (seq = 1; seq <= map->hdr.messages_count; seq++) {
+ rec = MAIL_INDEX_REC_AT_SEQ(map, seq);
+ rec->flags &= ENUM_NEGATE(MAIL_RECENT);
+ }
+}
+
+int mail_index_map_check_header(struct mail_index_map *map,
+ const char **error_r)
+{
+ struct mail_index *index = map->index;
+ const struct mail_index_header *hdr = &map->hdr;
+
+ if (!mail_index_check_header_compat(index, hdr, UOFF_T_MAX, error_r))
+ return 0;
+
+ /* following some extra checks that only take a bit of CPU */
+ if (hdr->record_size < sizeof(struct mail_index_record)) {
+ *error_r = t_strdup_printf(
+ "record_size too small (%u < %zu)",
+ hdr->record_size, sizeof(struct mail_index_record));
+ return -1;
+ }
+
+ if (hdr->uid_validity == 0 && hdr->next_uid != 1) {
+ *error_r = t_strdup_printf(
+ "uidvalidity=0, but next_uid=%u", hdr->next_uid);
+ return 0;
+ }
+ if (hdr->next_uid == 0) {
+ *error_r = "next_uid=0";
+ return 0;
+ }
+ if (hdr->messages_count > map->rec_map->records_count) {
+ *error_r = t_strdup_printf(
+ "messages_count is higher in header than record map (%u > %u)",
+ hdr->messages_count, map->rec_map->records_count);
+ return 0;
+ }
+
+ if (hdr->seen_messages_count > hdr->messages_count) {
+ *error_r = t_strdup_printf(
+ "seen_messages_count %u > messages_count %u",
+ hdr->seen_messages_count, hdr->messages_count);
+ return 0;
+ }
+ if (hdr->deleted_messages_count > hdr->messages_count) {
+ *error_r = t_strdup_printf(
+ "deleted_messages_count %u > messages_count %u",
+ hdr->deleted_messages_count, hdr->messages_count);
+ return 0;
+ }
+ switch (hdr->minor_version) {
+ case 0:
+ /* upgrade silently from v1.0 */
+ map->hdr.unused_old_recent_messages_count = 0;
+ if (hdr->first_recent_uid == 0)
+ map->hdr.first_recent_uid = 1;
+ if (index->need_recreate == NULL)
+ index->need_recreate = i_strdup("Upgrading from index version 1.0");
+ /* fall through */
+ case 1:
+ /* pre-v1.1.rc6: make sure the \Recent flags are gone */
+ mail_index_map_clear_recent_flags(map);
+ map->hdr.minor_version = MAIL_INDEX_MINOR_VERSION;
+ /* fall through */
+ case 2:
+ /* pre-v2.2 (although should have been done in v2.1 already):
+ make sure the old unused fields are cleared */
+ map->hdr.unused_old_sync_size_part1 = 0;
+ map->hdr.log2_rotate_time = 0;
+ map->hdr.last_temp_file_scan = 0;
+ }
+ if (hdr->first_recent_uid == 0) {
+ *error_r = "first_recent_uid=0";
+ return 0;
+ }
+ if (hdr->first_recent_uid > hdr->next_uid) {
+ *error_r = t_strdup_printf(
+ "first_recent_uid %u > next_uid %u",
+ hdr->first_recent_uid, hdr->next_uid);
+ return 0;
+ }
+ if (hdr->first_unseen_uid_lowwater > hdr->next_uid) {
+ *error_r = t_strdup_printf(
+ "first_unseen_uid_lowwater %u > next_uid %u",
+ hdr->first_unseen_uid_lowwater, hdr->next_uid);
+ return 0;
+ }
+ if (hdr->first_deleted_uid_lowwater > hdr->next_uid) {
+ *error_r = t_strdup_printf(
+ "first_deleted_uid_lowwater %u > next_uid %u",
+ hdr->first_deleted_uid_lowwater, hdr->next_uid);
+ return 0;
+ }
+
+ if (hdr->messages_count > 0) {
+ /* last message's UID must be smaller than next_uid.
+ also make sure it's not zero. */
+ const struct mail_index_record *rec;
+
+ rec = MAIL_INDEX_REC_AT_SEQ(map, hdr->messages_count);
+ if (rec->uid == 0) {
+ *error_r = "last message has uid=0";
+ return -1;
+ }
+ if (rec->uid >= hdr->next_uid) {
+ *error_r = t_strdup_printf(
+ "last message uid %u >= next_uid %u",
+ rec->uid, hdr->next_uid);
+ return 0;
+ }
+ }
+ return 1;
+}