/src/dovecot/src/lib-index/mail-index-map-hdr.c
Line | Count | Source |
1 | | /* Copyright (c) Dovecot authors, see top-level COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "array.h" |
5 | | #include "mail-transaction-log-private.h" |
6 | | #include "mail-index-private.h" |
7 | | |
8 | | int mail_index_map_parse_extensions(struct mail_index_map *map) |
9 | 0 | { |
10 | 0 | struct mail_index *index = map->index; |
11 | 0 | const struct mail_index_ext_header *ext_hdr; |
12 | 0 | unsigned int i, old_count, offset; |
13 | 0 | const char *name, *error; |
14 | 0 | uint32_t ext_id, ext_map_idx, ext_offset; |
15 | | |
16 | | /* extension headers always start from 64bit offsets, so if base header |
17 | | doesn't happen to be 64bit aligned we'll skip some bytes */ |
18 | 0 | offset = MAIL_INDEX_HEADER_SIZE_ALIGN(map->hdr.base_header_size); |
19 | 0 | if (offset >= map->hdr.header_size && map->extension_pool == NULL) { |
20 | | /* nothing to do, skip allocations and all */ |
21 | 0 | return 0; |
22 | 0 | } |
23 | | |
24 | 0 | old_count = array_count(&index->extensions); |
25 | 0 | mail_index_map_init_extbufs(map, old_count + 5); |
26 | |
|
27 | 0 | ext_id = (uint32_t)-1; |
28 | 0 | for (i = 0; i < old_count; i++) |
29 | 0 | array_push_back(&map->ext_id_map, &ext_id); |
30 | |
|
31 | 0 | for (i = 0; offset < map->hdr.header_size; i++) { |
32 | 0 | ext_offset = offset; |
33 | |
|
34 | 0 | if (mail_index_map_ext_get_next(map, &offset, |
35 | 0 | &ext_hdr, &name) < 0) { |
36 | 0 | mail_index_set_error(index, "Corrupted index file %s: " |
37 | 0 | "Header extension #%d (%s) goes outside header", |
38 | 0 | index->filepath, i, name); |
39 | 0 | return -1; |
40 | 0 | } |
41 | | |
42 | 0 | if (mail_index_map_ext_hdr_check(&map->hdr, ext_hdr, |
43 | 0 | name, &error) < 0) { |
44 | 0 | mail_index_set_error(index, "Corrupted index file %s: " |
45 | 0 | "Broken extension #%d (%s): %s", |
46 | 0 | index->filepath, i, name, error); |
47 | 0 | return -1; |
48 | 0 | } |
49 | 0 | if (mail_index_map_lookup_ext(map, name, &ext_map_idx)) { |
50 | 0 | mail_index_set_error(index, "Corrupted index file %s: " |
51 | 0 | "Duplicate header extension %s", |
52 | 0 | index->filepath, name); |
53 | 0 | return -1; |
54 | 0 | } |
55 | | |
56 | 0 | (void)mail_index_map_register_ext(map, name, ext_offset, ext_hdr); |
57 | 0 | } |
58 | 0 | return 0; |
59 | 0 | } |
60 | | |
61 | | int mail_index_map_parse_keywords(struct mail_index_map *map) |
62 | 0 | { |
63 | 0 | struct mail_index *index = map->index; |
64 | 0 | const struct mail_index_ext *ext; |
65 | 0 | const struct mail_index_keyword_header *kw_hdr; |
66 | 0 | const struct mail_index_keyword_header_rec *kw_rec; |
67 | 0 | const char *name; |
68 | 0 | unsigned int i, name_area_end_offset, old_count; |
69 | 0 | uint32_t idx; |
70 | |
|
71 | 0 | if (!mail_index_map_lookup_ext(map, MAIL_INDEX_EXT_KEYWORDS, &idx)) { |
72 | 0 | if (array_is_created(&map->keyword_idx_map)) |
73 | 0 | array_clear(&map->keyword_idx_map); |
74 | 0 | return 0; |
75 | 0 | } |
76 | 0 | ext = array_idx(&map->extensions, idx); |
77 | | |
78 | | /* Extension header contains: |
79 | | - struct mail_index_keyword_header |
80 | | - struct mail_index_keyword_header_rec * keywords_count |
81 | | - const char names[] * keywords_count |
82 | | |
83 | | The mail_index_keyword_header_rec are rather unnecessary nowadays. |
84 | | They were originally an optimization when dovecot.index header kept |
85 | | changing constantly, but nowadays the changes are usually read from |
86 | | the .log changes, so re-reading dovecot.index header isn't common. |
87 | | In a later version we could even remove it. |
88 | | */ |
89 | 0 | i_assert(ext->hdr_offset < map->hdr.header_size); |
90 | 0 | kw_hdr = MAIL_INDEX_MAP_HDR_OFFSET(map, ext->hdr_offset); |
91 | 0 | kw_rec = (const void *)(kw_hdr + 1); |
92 | 0 | name = (const char *)(kw_rec + kw_hdr->keywords_count); |
93 | |
|
94 | 0 | old_count = !array_is_created(&map->keyword_idx_map) ? 0 : |
95 | 0 | array_count(&map->keyword_idx_map); |
96 | | |
97 | | /* make sure the header is valid */ |
98 | 0 | if (kw_hdr->keywords_count < old_count) { |
99 | 0 | mail_index_set_error(index, "Corrupted index file %s: " |
100 | 0 | "Keywords removed unexpectedly", |
101 | 0 | index->filepath); |
102 | 0 | return -1; |
103 | 0 | } |
104 | | |
105 | 0 | if ((size_t)(name - (const char *)kw_hdr) > ext->hdr_size) { |
106 | 0 | mail_index_set_error(index, "Corrupted index file %s: " |
107 | 0 | "keywords_count larger than header size", |
108 | 0 | index->filepath); |
109 | 0 | return -1; |
110 | 0 | } |
111 | | |
112 | 0 | name_area_end_offset = (const char *)kw_hdr + ext->hdr_size - name; |
113 | 0 | for (i = 0; i < kw_hdr->keywords_count; i++) { |
114 | 0 | if (kw_rec[i].name_offset > name_area_end_offset) { |
115 | 0 | mail_index_set_error(index, "Corrupted index file %s: " |
116 | 0 | "name_offset points outside allocated header", |
117 | 0 | index->filepath); |
118 | 0 | return -1; |
119 | 0 | } |
120 | 0 | } |
121 | | /* A header with no keyword names at all (e.g. keywords_count==0) has an |
122 | | empty name area; name_area_end_offset is then 0 and there is no |
123 | | terminating NUL to check. Guard against the unsigned underflow of |
124 | | name_area_end_offset-1, which would otherwise read ~4 GB out of |
125 | | bounds. */ |
126 | 0 | if (name_area_end_offset > 0 && |
127 | 0 | name[name_area_end_offset - 1] != '\0') { |
128 | 0 | mail_index_set_error(index, "Corrupted index file %s: " |
129 | 0 | "Keyword header doesn't end with NUL", |
130 | 0 | index->filepath); |
131 | 0 | return -1; |
132 | 0 | } |
133 | | |
134 | | /* create file -> index mapping */ |
135 | 0 | if (!array_is_created(&map->keyword_idx_map)) |
136 | 0 | i_array_init(&map->keyword_idx_map, kw_hdr->keywords_count); |
137 | |
|
138 | 0 | size_t name_offset = 0; |
139 | | /* Check that existing headers are still the same. */ |
140 | 0 | for (i = 0; i < array_count(&map->keyword_idx_map); i++) { |
141 | 0 | const char *keyword = name + kw_rec[i].name_offset; |
142 | 0 | const unsigned int *old_idx; |
143 | 0 | unsigned int kw_idx; |
144 | |
|
145 | 0 | if (kw_rec[i].name_offset != name_offset) { |
146 | | /* this shouldn't happen, but the old code didn't check |
147 | | for this so for safety keep this as a warning. */ |
148 | 0 | e_warning(index->event, |
149 | 0 | "Corrupted index file %s: " |
150 | 0 | "Mismatching keyword name_offset", |
151 | 0 | index->filepath); |
152 | 0 | } |
153 | 0 | name_offset += strlen(keyword) + 1; |
154 | |
|
155 | 0 | old_idx = array_idx(&map->keyword_idx_map, i); |
156 | 0 | if (!mail_index_keyword_lookup(index, keyword, &kw_idx) || |
157 | 0 | kw_idx != *old_idx) { |
158 | 0 | mail_index_set_error(index, "Corrupted index file %s: " |
159 | 0 | "Keywords changed unexpectedly", |
160 | 0 | index->filepath); |
161 | 0 | return -1; |
162 | 0 | } |
163 | 0 | } |
164 | | |
165 | | /* Register the newly seen keywords */ |
166 | 0 | i = array_count(&map->keyword_idx_map); |
167 | 0 | for (; i < kw_hdr->keywords_count; i++) { |
168 | 0 | const char *keyword = name + kw_rec[i].name_offset; |
169 | 0 | unsigned int kw_idx; |
170 | |
|
171 | 0 | if (kw_rec[i].name_offset != name_offset) { |
172 | | /* this shouldn't happen, but the old code didn't check |
173 | | for this so for safety keep this as a warning. */ |
174 | 0 | e_warning(index->event, |
175 | 0 | "Corrupted index file %s: " |
176 | 0 | "Mismatching keyword name_offset", |
177 | 0 | index->filepath); |
178 | 0 | } |
179 | 0 | name_offset += strlen(keyword) + 1; |
180 | |
|
181 | 0 | if (*keyword == '\0') { |
182 | 0 | mail_index_set_error(index, "Corrupted index file %s: " |
183 | 0 | "Empty keyword name in header", |
184 | 0 | index->filepath); |
185 | 0 | return -1; |
186 | 0 | } |
187 | 0 | mail_index_keyword_lookup_or_create(index, keyword, &kw_idx); |
188 | 0 | array_push_back(&map->keyword_idx_map, &kw_idx); |
189 | 0 | } |
190 | 0 | return 0; |
191 | 0 | } |
192 | | |
193 | | bool mail_index_hdr_check_indexid(struct mail_index *index, |
194 | | const struct mail_index_header *hdr) |
195 | 0 | { |
196 | 0 | const char *reason; |
197 | |
|
198 | 0 | if (index->indexid == 0) { |
199 | | /* this happens only when .log file is lost/corrupted */ |
200 | 0 | i_assert(index->log->head == NULL); |
201 | 0 | index->indexid = hdr->indexid; |
202 | 0 | } |
203 | 0 | if (hdr->indexid == index->indexid) |
204 | 0 | return TRUE; |
205 | | |
206 | 0 | if (mail_transaction_log_has_changed(index->log, TRUE, |
207 | 0 | &reason) != 0) { |
208 | | /* Transaction log has either changed, or there was some IO |
209 | | error when doing the check. Either way, we can't continue. */ |
210 | 0 | return FALSE; |
211 | 0 | } |
212 | | |
213 | | /* indexid has a permanent mismatch - delete the index. */ |
214 | 0 | mail_index_set_error(index, "Index file %s: " |
215 | 0 | "indexid changed: %u -> %u - deleting", |
216 | 0 | index->filepath, index->indexid, hdr->indexid); |
217 | 0 | if (!index->readonly) |
218 | 0 | i_unlink_if_exists(index->filepath); |
219 | 0 | return FALSE; |
220 | 0 | } |
221 | | |
222 | | bool mail_index_check_header_compat(const struct mail_index_header *hdr, |
223 | | uoff_t file_size, const char **error_r) |
224 | 0 | { |
225 | 0 | enum mail_index_header_compat_flags compat_flags = 0; |
226 | |
|
227 | 0 | #ifndef WORDS_BIGENDIAN |
228 | 0 | compat_flags |= MAIL_INDEX_COMPAT_LITTLE_ENDIAN; |
229 | 0 | #endif |
230 | |
|
231 | 0 | if (hdr->major_version != MAIL_INDEX_MAJOR_VERSION) { |
232 | | /* major version change */ |
233 | 0 | *error_r = t_strdup_printf("Major version changed (%u != %u)", |
234 | 0 | hdr->major_version, MAIL_INDEX_MAJOR_VERSION); |
235 | 0 | return FALSE; |
236 | 0 | } |
237 | 0 | if ((hdr->flags & MAIL_INDEX_HDR_FLAG_CORRUPTED) != 0) { |
238 | | /* we've already complained about it */ |
239 | 0 | *error_r = "Header's corrupted flag is set"; |
240 | 0 | return FALSE; |
241 | 0 | } |
242 | | |
243 | 0 | if (hdr->compat_flags != compat_flags) { |
244 | | /* architecture change */ |
245 | 0 | *error_r = "CPU architecture changed"; |
246 | 0 | return FALSE; |
247 | 0 | } |
248 | | |
249 | 0 | if (hdr->base_header_size < MAIL_INDEX_HEADER_MIN_SIZE || |
250 | 0 | hdr->header_size < hdr->base_header_size) { |
251 | 0 | *error_r = t_strdup_printf( |
252 | 0 | "Corrupted header sizes (base %u, full %u)", |
253 | 0 | hdr->base_header_size, hdr->header_size); |
254 | 0 | return FALSE; |
255 | 0 | } |
256 | 0 | if (hdr->record_size < sizeof(struct mail_index_record)) { |
257 | 0 | *error_r = t_strdup_printf( |
258 | 0 | "record_size too small (%u < %zu)", |
259 | 0 | hdr->record_size, sizeof(struct mail_index_record)); |
260 | 0 | return FALSE; |
261 | 0 | } |
262 | 0 | if (hdr->header_size > file_size) { |
263 | 0 | *error_r = t_strdup_printf( |
264 | 0 | "Header size is larger than file (%u > %"PRIuUOFF_T")", |
265 | 0 | hdr->header_size, file_size); |
266 | 0 | return FALSE; |
267 | 0 | } |
268 | 0 | return TRUE; |
269 | 0 | } |
270 | | |
271 | | static void mail_index_map_clear_recent_flags(struct mail_index_map *map) |
272 | 0 | { |
273 | 0 | struct mail_index_record *rec; |
274 | 0 | uint32_t seq; |
275 | |
|
276 | 0 | for (seq = 1; seq <= map->hdr.messages_count; seq++) { |
277 | 0 | rec = MAIL_INDEX_REC_AT_SEQ(map, seq); |
278 | 0 | rec->flags &= ENUM_NEGATE(MAIL_RECENT); |
279 | 0 | } |
280 | 0 | } |
281 | | |
282 | | int mail_index_map_check_header(struct mail_index_map *map, |
283 | | const char **error_r) |
284 | 0 | { |
285 | 0 | struct mail_index *index = map->index; |
286 | 0 | const struct mail_index_header *hdr = &map->hdr; |
287 | |
|
288 | 0 | if (!mail_index_check_header_compat(hdr, UOFF_T_MAX, error_r)) |
289 | 0 | return 0; |
290 | | |
291 | | /* following some extra checks that only take a bit of CPU */ |
292 | 0 | if (hdr->record_size < sizeof(struct mail_index_record)) { |
293 | 0 | *error_r = t_strdup_printf( |
294 | 0 | "record_size too small (%u < %zu)", |
295 | 0 | hdr->record_size, sizeof(struct mail_index_record)); |
296 | 0 | return -1; |
297 | 0 | } |
298 | | |
299 | 0 | if (hdr->uid_validity == 0 && hdr->next_uid != 1) { |
300 | 0 | *error_r = t_strdup_printf( |
301 | 0 | "uidvalidity=0, but next_uid=%u", hdr->next_uid); |
302 | 0 | return 0; |
303 | 0 | } |
304 | 0 | if (hdr->next_uid == 0) { |
305 | 0 | *error_r = "next_uid=0"; |
306 | 0 | return 0; |
307 | 0 | } |
308 | 0 | if (hdr->messages_count > map->rec_map->records_count) { |
309 | 0 | *error_r = t_strdup_printf( |
310 | 0 | "messages_count is higher in header than record map (%u > %u)", |
311 | 0 | hdr->messages_count, map->rec_map->records_count); |
312 | 0 | return 0; |
313 | 0 | } |
314 | | |
315 | 0 | if (hdr->seen_messages_count > hdr->messages_count) { |
316 | 0 | *error_r = t_strdup_printf( |
317 | 0 | "seen_messages_count %u > messages_count %u", |
318 | 0 | hdr->seen_messages_count, hdr->messages_count); |
319 | 0 | return 0; |
320 | 0 | } |
321 | 0 | if (hdr->deleted_messages_count > hdr->messages_count) { |
322 | 0 | *error_r = t_strdup_printf( |
323 | 0 | "deleted_messages_count %u > messages_count %u", |
324 | 0 | hdr->deleted_messages_count, hdr->messages_count); |
325 | 0 | return 0; |
326 | 0 | } |
327 | 0 | switch (hdr->minor_version) { |
328 | 0 | case 0: |
329 | | /* upgrade silently from v1.0 */ |
330 | 0 | map->hdr.unused_old_recent_messages_count = 0; |
331 | 0 | if (hdr->first_recent_uid == 0) |
332 | 0 | map->hdr.first_recent_uid = 1; |
333 | 0 | if (index->need_recreate == NULL) |
334 | 0 | index->need_recreate = i_strdup("Upgrading from index version 1.0"); |
335 | | /* fall through */ |
336 | 0 | case 1: |
337 | | /* pre-v1.1.rc6: make sure the \Recent flags are gone */ |
338 | 0 | mail_index_map_clear_recent_flags(map); |
339 | | /* fall through */ |
340 | 0 | case 2: |
341 | | /* pre-v2.2 (although should have been done in v2.1 already): |
342 | | make sure the old unused fields are cleared */ |
343 | 0 | map->hdr.unused_old_sync_size_part1 = 0; |
344 | 0 | map->hdr.log2_rotate_time = 0; |
345 | 0 | map->hdr.last_temp_file_scan = 0; |
346 | 0 | } |
347 | | /* The old index format is now updated, set minor_version to latest |
348 | | value. However, if minor_version is larger than what we know of, |
349 | | keep it as it is to avoid breaking anything. */ |
350 | 0 | if (map->hdr.minor_version < MAIL_INDEX_MINOR_VERSION) |
351 | 0 | map->hdr.minor_version = MAIL_INDEX_MINOR_VERSION; |
352 | 0 | if (hdr->first_recent_uid == 0) { |
353 | 0 | *error_r = "first_recent_uid=0"; |
354 | 0 | return 0; |
355 | 0 | } |
356 | 0 | if (hdr->first_recent_uid > hdr->next_uid) { |
357 | 0 | *error_r = t_strdup_printf( |
358 | 0 | "first_recent_uid %u > next_uid %u", |
359 | 0 | hdr->first_recent_uid, hdr->next_uid); |
360 | 0 | return 0; |
361 | 0 | } |
362 | 0 | if (hdr->first_unseen_uid_lowwater > hdr->next_uid) { |
363 | 0 | *error_r = t_strdup_printf( |
364 | 0 | "first_unseen_uid_lowwater %u > next_uid %u", |
365 | 0 | hdr->first_unseen_uid_lowwater, hdr->next_uid); |
366 | 0 | return 0; |
367 | 0 | } |
368 | 0 | if (hdr->first_deleted_uid_lowwater > hdr->next_uid) { |
369 | 0 | *error_r = t_strdup_printf( |
370 | 0 | "first_deleted_uid_lowwater %u > next_uid %u", |
371 | 0 | hdr->first_deleted_uid_lowwater, hdr->next_uid); |
372 | 0 | return 0; |
373 | 0 | } |
374 | | |
375 | 0 | if (hdr->messages_count > 0) { |
376 | | /* last message's UID must be smaller than next_uid. |
377 | | also make sure it's not zero. */ |
378 | 0 | const struct mail_index_record *rec; |
379 | |
|
380 | 0 | rec = MAIL_INDEX_REC_AT_SEQ(map, hdr->messages_count); |
381 | 0 | if (rec->uid == 0) { |
382 | 0 | *error_r = "last message has uid=0"; |
383 | 0 | return -1; |
384 | 0 | } |
385 | 0 | if (rec->uid >= hdr->next_uid) { |
386 | 0 | *error_r = t_strdup_printf( |
387 | 0 | "last message uid %u >= next_uid %u", |
388 | 0 | rec->uid, hdr->next_uid); |
389 | 0 | return 0; |
390 | 0 | } |
391 | 0 | } |
392 | 0 | return 1; |
393 | 0 | } |