1 /* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
2 
3 /*
4    Version 1 format has been used for most versions of Dovecot up to v1.0.x.
5    It's also compatible with Courier IMAP's courierimapuiddb file.
6    The format is:
7 
8    header: 1 <uid validity> <next uid>
9    entry: <uid> <filename>
10 
11    --
12 
13    Version 2 format was written by a few development Dovecot versions, but
14    v1.0.x still parses the format. The format has <flags> field after <uid>.
15 
16    --
17 
18    Version 3 format is an extensible format used by Dovecot v1.1 and later.
19    It's also parsed by v1.0.2 (and later). The format is:
20 
21    header: 3 [<key><value> ...]
22    entry: <uid> [<key><value> ...] :<filename>
23 
24    See enum maildir_uidlist_*_ext_key for used keys.
25 */
26 
27 #include "lib.h"
28 #include "array.h"
29 #include "hash.h"
30 #include "istream.h"
31 #include "ostream.h"
32 #include "str.h"
33 #include "file-dotlock.h"
34 #include "nfs-workarounds.h"
35 #include "eacces-error.h"
36 #include "maildir-storage.h"
37 #include "maildir-filename.h"
38 #include "maildir-uidlist.h"
39 
40 #include <stdio.h>
41 #include <sys/stat.h>
42 
43 /* NFS: How many times to retry reading dovecot-uidlist file if ESTALE
44    error occurs in the middle of reading it */
45 #define UIDLIST_ESTALE_RETRY_COUNT NFS_ESTALE_RETRY_COUNT
46 
47 #define UIDLIST_VERSION 3
48 #define UIDLIST_COMPRESS_PERCENTAGE 75
49 
50 #define UIDLIST_IS_LOCKED(uidlist) \
51 	((uidlist)->lock_count > 0)
52 
53 struct maildir_uidlist_rec {
54 	uint32_t uid;
55 	uint32_t flags;
56 	char *filename;
57 	unsigned char *extensions; /* <data>\0[<data>\0 ...]\0 */
58 };
59 ARRAY_DEFINE_TYPE(maildir_uidlist_rec_p, struct maildir_uidlist_rec *);
60 
61 HASH_TABLE_DEFINE_TYPE(path_to_maildir_uidlist_rec,
62 		       char *, struct maildir_uidlist_rec *);
63 
64 struct maildir_uidlist {
65 	struct mailbox *box;
66 	char *path;
67 	struct maildir_index_header *mhdr;
68 
69 	int fd;
70 	dev_t fd_dev;
71 	ino_t fd_ino;
72 	off_t fd_size;
73 
74 	unsigned int lock_count;
75 
76 	struct dotlock_settings dotlock_settings;
77 	struct dotlock *dotlock;
78 
79 	pool_t record_pool;
80 	ARRAY_TYPE(maildir_uidlist_rec_p) records;
81 	HASH_TABLE_TYPE(path_to_maildir_uidlist_rec) files;
82 	unsigned int change_counter;
83 
84 	unsigned int version;
85 	unsigned int uid_validity, next_uid, prev_read_uid, last_seen_uid;
86 	unsigned int hdr_next_uid;
87 	unsigned int read_records_count, read_line_count;
88 	uoff_t last_read_offset;
89 	string_t *hdr_extensions;
90 
91 	guid_128_t mailbox_guid;
92 
93 	bool recreate:1;
94 	bool recreate_on_change:1;
95 	bool initial_read:1;
96 	bool initial_hdr_read:1;
97 	bool retry_rewind:1;
98 	bool locked_refresh:1;
99 	bool unsorted:1;
100 	bool have_mailbox_guid:1;
101 	bool opened_readonly:1;
102 };
103 
104 struct maildir_uidlist_sync_ctx {
105 	struct maildir_uidlist *uidlist;
106 	enum maildir_uidlist_sync_flags sync_flags;
107 
108 	pool_t record_pool;
109 	ARRAY_TYPE(maildir_uidlist_rec_p) records;
110 	HASH_TABLE_TYPE(path_to_maildir_uidlist_rec) files;
111 
112 	unsigned int first_unwritten_pos, first_new_pos;
113 	unsigned int new_files_count;
114 	unsigned int finish_change_counter;
115 
116 	bool partial:1;
117 	bool finished:1;
118 	bool changed:1;
119 	bool failed:1;
120 	bool locked:1;
121 };
122 
123 struct maildir_uidlist_iter_ctx {
124 	struct maildir_uidlist *uidlist;
125 	struct maildir_uidlist_rec *const *next, *const *end;
126 
127 	unsigned int change_counter;
128 	uint32_t prev_uid;
129 };
130 
131 static int maildir_uidlist_open_latest(struct maildir_uidlist *uidlist);
132 static bool maildir_uidlist_iter_next_rec(struct maildir_uidlist_iter_ctx *ctx,
133 					  struct maildir_uidlist_rec **rec_r);
134 
maildir_uidlist_lock_timeout(struct maildir_uidlist * uidlist,bool nonblock,bool refresh,bool refresh_when_locked)135 static int maildir_uidlist_lock_timeout(struct maildir_uidlist *uidlist,
136 					bool nonblock, bool refresh,
137 					bool refresh_when_locked)
138 {
139 	struct mailbox *box = uidlist->box;
140 	const struct mailbox_permissions *perm = mailbox_get_permissions(box);
141 	const char *path = uidlist->path;
142 	mode_t old_mask;
143 	const enum dotlock_create_flags dotlock_flags =
144 		nonblock ? DOTLOCK_CREATE_FLAG_NONBLOCK : 0;
145 	int i, ret;
146 
147 	if (uidlist->lock_count > 0) {
148 		if (!uidlist->locked_refresh && refresh_when_locked) {
149 			if (maildir_uidlist_refresh(uidlist) < 0)
150 				return -1;
151 		}
152 		uidlist->lock_count++;
153 		return 1;
154 	}
155 
156         index_storage_lock_notify_reset(box);
157 
158 	for (i = 0;; i++) {
159 		old_mask = umask(0777 & ~perm->file_create_mode);
160 		ret = file_dotlock_create(&uidlist->dotlock_settings, path,
161 					  dotlock_flags, &uidlist->dotlock);
162 		umask(old_mask);
163 		if (ret > 0)
164 			break;
165 
166 		/* failure */
167 		if (ret == 0) {
168 			mail_storage_set_error(box->storage,
169 				MAIL_ERROR_TEMP, MAIL_ERRSTR_LOCK_TIMEOUT);
170 			return 0;
171 		}
172 		if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT) {
173 			if (errno == EACCES) {
174 				mailbox_set_critical(box, "%s",
175 					eacces_error_get_creating("file_dotlock_create", path));
176 			} else {
177 				mailbox_set_critical(box,
178 					"file_dotlock_create(%s) failed: %m",
179 					path);
180 			}
181 			return -1;
182 		}
183 		/* the control dir doesn't exist. create it unless the whole
184 		   mailbox was just deleted. */
185 		if (!maildir_set_deleted(uidlist->box))
186 			return -1;
187 	}
188 
189 	uidlist->lock_count++;
190 	uidlist->locked_refresh = FALSE;
191 
192 	if (refresh) {
193 		/* make sure we have the latest changes before
194 		   changing anything */
195 		if (maildir_uidlist_refresh(uidlist) < 0) {
196 			maildir_uidlist_unlock(uidlist);
197 			return -1;
198 		}
199 	}
200 	return 1;
201 }
202 
maildir_uidlist_lock(struct maildir_uidlist * uidlist)203 int maildir_uidlist_lock(struct maildir_uidlist *uidlist)
204 {
205 	return maildir_uidlist_lock_timeout(uidlist, FALSE, TRUE, FALSE);
206 }
207 
maildir_uidlist_try_lock(struct maildir_uidlist * uidlist)208 int maildir_uidlist_try_lock(struct maildir_uidlist *uidlist)
209 {
210 	return maildir_uidlist_lock_timeout(uidlist, TRUE, TRUE, FALSE);
211 }
212 
maildir_uidlist_lock_touch(struct maildir_uidlist * uidlist)213 int maildir_uidlist_lock_touch(struct maildir_uidlist *uidlist)
214 {
215 	i_assert(UIDLIST_IS_LOCKED(uidlist));
216 
217 	return file_dotlock_touch(uidlist->dotlock);
218 }
219 
maildir_uidlist_is_locked(struct maildir_uidlist * uidlist)220 bool maildir_uidlist_is_locked(struct maildir_uidlist *uidlist)
221 {
222 	return UIDLIST_IS_LOCKED(uidlist);
223 }
224 
maildir_uidlist_is_read(struct maildir_uidlist * uidlist)225 bool maildir_uidlist_is_read(struct maildir_uidlist *uidlist)
226 {
227 	return uidlist->initial_read;
228 }
229 
maildir_uidlist_is_open(struct maildir_uidlist * uidlist)230 bool maildir_uidlist_is_open(struct maildir_uidlist *uidlist)
231 {
232 	return uidlist->fd != -1;
233 }
234 
maildir_uidlist_unlock(struct maildir_uidlist * uidlist)235 void maildir_uidlist_unlock(struct maildir_uidlist *uidlist)
236 {
237 	i_assert(uidlist->lock_count > 0);
238 
239 	if (--uidlist->lock_count > 0)
240 		return;
241 
242 	uidlist->locked_refresh = FALSE;
243 	file_dotlock_delete(&uidlist->dotlock);
244 }
245 
dotlock_callback(unsigned int secs_left,bool stale,void * context)246 static bool dotlock_callback(unsigned int secs_left, bool stale, void *context)
247 {
248 	struct mailbox *box = context;
249 
250 	index_storage_lock_notify(box, stale ?
251 				  MAILBOX_LOCK_NOTIFY_MAILBOX_OVERRIDE :
252 				  MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT,
253 				  secs_left);
254 	return TRUE;
255 }
256 
maildir_uidlist_init(struct maildir_mailbox * mbox)257 struct maildir_uidlist *maildir_uidlist_init(struct maildir_mailbox *mbox)
258 {
259 	struct mailbox *box = &mbox->box;
260 	struct maildir_uidlist *uidlist;
261 	const char *control_dir;
262 
263 	if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_CONTROL,
264 				&control_dir) <= 0)
265 		i_unreached();
266 
267 	uidlist = i_new(struct maildir_uidlist, 1);
268 	uidlist->box = box;
269 	uidlist->mhdr = &mbox->maildir_hdr;
270 	uidlist->fd = -1;
271 	uidlist->path = i_strconcat(control_dir, "/"MAILDIR_UIDLIST_NAME, NULL);
272 	i_array_init(&uidlist->records, 128);
273 	hash_table_create(&uidlist->files, default_pool, 4096,
274 			  maildir_filename_base_hash,
275 			  maildir_filename_base_cmp);
276 	uidlist->next_uid = 1;
277 	uidlist->hdr_extensions = str_new(default_pool, 128);
278 
279 	uidlist->dotlock_settings.use_io_notify = TRUE;
280 	uidlist->dotlock_settings.use_excl_lock =
281 		box->storage->set->dotlock_use_excl;
282 	uidlist->dotlock_settings.nfs_flush =
283 		box->storage->set->mail_nfs_storage;
284 	uidlist->dotlock_settings.timeout =
285 		mail_storage_get_lock_timeout(box->storage,
286 			MAILDIR_UIDLIST_LOCK_STALE_TIMEOUT + 2);
287 	uidlist->dotlock_settings.stale_timeout =
288 		MAILDIR_UIDLIST_LOCK_STALE_TIMEOUT;
289 	uidlist->dotlock_settings.callback = dotlock_callback;
290 	uidlist->dotlock_settings.context = box;
291 	uidlist->dotlock_settings.temp_prefix = mbox->storage->temp_prefix;
292 	return uidlist;
293 }
294 
maildir_uidlist_close(struct maildir_uidlist * uidlist)295 static void maildir_uidlist_close(struct maildir_uidlist *uidlist)
296 {
297 	if (uidlist->fd != -1) {
298 		if (close(uidlist->fd) < 0) {
299 			mailbox_set_critical(uidlist->box,
300 				"close(%s) failed: %m", uidlist->path);
301 		}
302 		uidlist->fd = -1;
303 		uidlist->fd_ino = 0;
304 	}
305 	uidlist->last_read_offset = 0;
306 	uidlist->read_line_count = 0;
307 }
308 
maildir_uidlist_reset(struct maildir_uidlist * uidlist)309 static void maildir_uidlist_reset(struct maildir_uidlist *uidlist)
310 {
311 	maildir_uidlist_close(uidlist);
312 	uidlist->last_seen_uid = 0;
313 	uidlist->initial_hdr_read = FALSE;
314 	uidlist->read_records_count = 0;
315 
316 	hash_table_clear(uidlist->files, FALSE);
317 	array_clear(&uidlist->records);
318 }
319 
maildir_uidlist_deinit(struct maildir_uidlist ** _uidlist)320 void maildir_uidlist_deinit(struct maildir_uidlist **_uidlist)
321 {
322 	struct maildir_uidlist *uidlist = *_uidlist;
323 
324 	i_assert(!UIDLIST_IS_LOCKED(uidlist));
325 
326 	*_uidlist = NULL;
327 	(void)maildir_uidlist_update(uidlist);
328 	maildir_uidlist_close(uidlist);
329 
330 	hash_table_destroy(&uidlist->files);
331 	pool_unref(&uidlist->record_pool);
332 
333 	array_free(&uidlist->records);
334 	str_free(&uidlist->hdr_extensions);
335 	i_free(uidlist->path);
336 	i_free(uidlist);
337 }
338 
maildir_uid_cmp(struct maildir_uidlist_rec * const * rec1,struct maildir_uidlist_rec * const * rec2)339 static int maildir_uid_cmp(struct maildir_uidlist_rec *const *rec1,
340 			   struct maildir_uidlist_rec *const *rec2)
341 {
342 	return (*rec1)->uid < (*rec2)->uid ? -1 :
343 		(*rec1)->uid > (*rec2)->uid ? 1 : 0;
344 }
345 
346 static void ATTR_FORMAT(2, 3)
maildir_uidlist_set_corrupted(struct maildir_uidlist * uidlist,const char * fmt,...)347 maildir_uidlist_set_corrupted(struct maildir_uidlist *uidlist,
348 			      const char *fmt, ...)
349 {
350 	va_list args;
351 
352 	va_start(args, fmt);
353 	if (uidlist->retry_rewind) {
354 		mailbox_set_critical(uidlist->box,
355 			"Broken or unexpectedly changed file %s "
356 			"line %u: %s - re-reading from beginning",
357 			uidlist->path, uidlist->read_line_count,
358 			t_strdup_vprintf(fmt, args));
359 	} else {
360 		mailbox_set_critical(uidlist->box, "Broken file %s line %u: %s",
361 			uidlist->path, uidlist->read_line_count,
362 			t_strdup_vprintf(fmt, args));
363 	}
364 	va_end(args);
365 }
366 
maildir_uidlist_update_hdr(struct maildir_uidlist * uidlist,const struct stat * st)367 static void maildir_uidlist_update_hdr(struct maildir_uidlist *uidlist,
368 				       const struct stat *st)
369 {
370 	struct maildir_index_header *mhdr = uidlist->mhdr;
371 
372 	if (mhdr->uidlist_mtime == 0 && uidlist->version != UIDLIST_VERSION) {
373 		/* upgrading from older version. don't update the
374 		   uidlist times until it uses the new format */
375 		uidlist->recreate = TRUE;
376 		return;
377 	}
378 	mhdr->uidlist_mtime = st->st_mtime;
379 	mhdr->uidlist_mtime_nsecs = ST_MTIME_NSEC(*st);
380 	mhdr->uidlist_size = st->st_size;
381 }
382 
383 static unsigned int
maildir_uidlist_records_array_delete(struct maildir_uidlist * uidlist,struct maildir_uidlist_rec * rec)384 maildir_uidlist_records_array_delete(struct maildir_uidlist *uidlist,
385 				     struct maildir_uidlist_rec *rec)
386 {
387 	struct maildir_uidlist_rec *const *recs, *const *pos;
388 	unsigned int idx, count;
389 
390 	recs = array_get(&uidlist->records, &count);
391 	if (!uidlist->unsorted) {
392 		pos = array_bsearch(&uidlist->records, &rec, maildir_uid_cmp);
393 		i_assert(pos != NULL);
394 		idx = pos - recs;
395 	} else {
396 		for (idx = 0; idx < count; idx++) {
397 			if (recs[idx]->uid == rec->uid)
398 				break;
399 		}
400 		i_assert(idx != count);
401 	}
402 	array_delete(&uidlist->records, idx, 1);
403 	return idx;
404 }
405 
406 static bool
maildir_uidlist_read_extended(struct maildir_uidlist * uidlist,const char ** line_p,struct maildir_uidlist_rec * rec)407 maildir_uidlist_read_extended(struct maildir_uidlist *uidlist,
408 			      const char **line_p,
409 			      struct maildir_uidlist_rec *rec)
410 {
411 	const char *start, *line = *line_p;
412 	buffer_t *buf;
413 
414 	buf = t_buffer_create(128);
415 	while (*line != '\0' && *line != ':') {
416 		/* skip over an extension field */
417 		start = line;
418 		while (*line != ' ' && *line != '\0') line++;
419 		if (MAILDIR_UIDLIST_REC_EXT_KEY_IS_VALID(*start)) {
420 			buffer_append(buf, start, line - start);
421 			buffer_append_c(buf, '\0');
422 		} else {
423 			maildir_uidlist_set_corrupted(uidlist,
424 				"Invalid extension record, removing: %s",
425 				t_strdup_until(start, line));
426 			uidlist->recreate = TRUE;
427 		}
428 		while (*line == ' ') line++;
429 	}
430 
431 	if (buf->used > 0) {
432 		/* save the extensions */
433 		buffer_append_c(buf, '\0');
434 		rec->extensions = p_malloc(uidlist->record_pool, buf->used);
435 		memcpy(rec->extensions, buf->data, buf->used);
436 	}
437 
438 	if (*line == ':')
439 		line++;
440 	if (*line == '\0')
441 		return FALSE;
442 
443 	*line_p = line;
444 	return TRUE;
445 }
446 
maildir_uidlist_next(struct maildir_uidlist * uidlist,const char * line)447 static bool maildir_uidlist_next(struct maildir_uidlist *uidlist,
448 				 const char *line)
449 {
450 	struct maildir_uidlist_rec *rec, *old_rec, *const *recs;
451 	unsigned int count;
452 	uint32_t uid;
453 
454 	uid = 0;
455 	while (*line >= '0' && *line <= '9') {
456 		uid = uid*10 + (*line - '0');
457 		line++;
458 	}
459 
460 	if (uid == 0 || *line != ' ') {
461 		/* invalid file */
462 		maildir_uidlist_set_corrupted(uidlist, "Invalid data: %s",
463 					      line);
464 		return FALSE;
465 	}
466 	if (uid <= uidlist->prev_read_uid) {
467 		maildir_uidlist_set_corrupted(uidlist,
468 					      "UIDs not ordered (%u >= %u)",
469 					      uid, uidlist->prev_read_uid);
470 		return FALSE;
471 	}
472 	if (uid >= (uint32_t)-1) {
473 		maildir_uidlist_set_corrupted(uidlist,
474 					      "UID too high (%u)", uid);
475 		return FALSE;
476 	}
477 	uidlist->prev_read_uid = uid;
478 
479 	if (uid <= uidlist->last_seen_uid) {
480 		/* we already have this */
481 		return TRUE;
482 	}
483         uidlist->last_seen_uid = uid;
484 
485 	if (uid >= uidlist->next_uid && uidlist->version == 1) {
486 		maildir_uidlist_set_corrupted(uidlist,
487 			"UID larger than next_uid (%u >= %u)",
488 			uid, uidlist->next_uid);
489 		return FALSE;
490 	}
491 
492 	rec = p_new(uidlist->record_pool, struct maildir_uidlist_rec, 1);
493 	rec->uid = uid;
494 	rec->flags = MAILDIR_UIDLIST_REC_FLAG_NONSYNCED;
495 
496 	while (*line == ' ') line++;
497 
498 	if (uidlist->version == UIDLIST_VERSION) {
499 		/* read extended fields */
500 		bool ret;
501 
502 		T_BEGIN {
503 			ret = maildir_uidlist_read_extended(uidlist, &line,
504 							    rec);
505 		} T_END;
506 		if (!ret) {
507 			maildir_uidlist_set_corrupted(uidlist,
508 				"Invalid extended fields: %s", line);
509 			return FALSE;
510 		}
511 	}
512 
513 	if (strchr(line, '/') != NULL) {
514 		maildir_uidlist_set_corrupted(uidlist,
515 			"%s: Broken filename at line %u: %s",
516 			uidlist->path, uidlist->read_line_count, line);
517 		return FALSE;
518 	}
519 
520 	old_rec = hash_table_lookup(uidlist->files, line);
521 	if (old_rec == NULL) {
522 		/* no conflicts */
523 	} else if (old_rec->uid == uid) {
524 		/* most likely this is a record we saved ourself, but couldn't
525 		   update last_seen_uid because uidlist wasn't refreshed while
526 		   it was locked.
527 
528 		   another possibility is a duplicate file record. currently
529 		   it would be a bug, but not that big of a deal. also perhaps
530 		   in future such duplicate lines could be used to update
531 		   extended fields. so just let it through anyway.
532 
533 		   we'll waste a bit of memory here by allocating the record
534 		   twice, but that's not really a problem.  */
535 		rec->filename = old_rec->filename;
536 		hash_table_update(uidlist->files, rec->filename, rec);
537 		uidlist->unsorted = TRUE;
538 		return TRUE;
539 	} else {
540 		/* This can happen if expunged file is moved back and the file
541 		   was appended to uidlist. */
542 		i_warning("%s: Duplicate file entry at line %u: "
543 			  "%s (uid %u -> %u)%s",
544 			  uidlist->path, uidlist->read_line_count, line,
545 			  old_rec->uid, uid, uidlist->retry_rewind ?
546 			  " - retrying by re-reading from beginning" : "");
547 		if (uidlist->retry_rewind)
548 			return FALSE;
549 		/* Delete the old UID */
550 		(void)maildir_uidlist_records_array_delete(uidlist, old_rec);
551 		/* Replace the old record with this new one */
552 		*old_rec = *rec;
553 		rec = old_rec;
554 		uidlist->recreate = TRUE;
555 	}
556 
557 	recs = array_get(&uidlist->records, &count);
558 	if (count > 0 && recs[count-1]->uid > uid) {
559 		/* we most likely have some records in the array that we saved
560 		   ourself without refreshing uidlist */
561 		uidlist->unsorted = TRUE;
562 	}
563 
564 	rec->filename = p_strdup(uidlist->record_pool, line);
565 	hash_table_update(uidlist->files, rec->filename, rec);
566 	array_push_back(&uidlist->records, &rec);
567 	return TRUE;
568 }
569 
570 static int
maildir_uidlist_read_v3_header(struct maildir_uidlist * uidlist,const char * line,unsigned int * uid_validity_r,unsigned int * next_uid_r)571 maildir_uidlist_read_v3_header(struct maildir_uidlist *uidlist,
572 			       const char *line,
573 			       unsigned int *uid_validity_r,
574 			       unsigned int *next_uid_r)
575 {
576 	char key;
577 
578 	str_truncate(uidlist->hdr_extensions, 0);
579 	while (*line != '\0') {
580 		const char *value;
581 
582 		key = *line;
583 		value = ++line;
584 		while (*line != '\0' && *line != ' ') line++;
585 		value = t_strdup_until(value, line);
586 
587 		switch (key) {
588 		case MAILDIR_UIDLIST_HDR_EXT_UID_VALIDITY:
589 			if (str_to_uint(value, uid_validity_r) < 0) {
590 				maildir_uidlist_set_corrupted(uidlist,
591 					"Invalid mailbox UID_VALIDITY: %s", value);
592 				return -1;
593 			}
594 			break;
595 		case MAILDIR_UIDLIST_HDR_EXT_NEXT_UID:
596 			if (str_to_uint(value, next_uid_r) < 0) {
597 				maildir_uidlist_set_corrupted(uidlist,
598 					"Invalid mailbox NEXT_UID: %s", value);
599 				return -1;
600 			}
601 			break;
602 		case MAILDIR_UIDLIST_HDR_EXT_GUID:
603 			if (guid_128_from_string(value,
604 						 uidlist->mailbox_guid) < 0) {
605 				maildir_uidlist_set_corrupted(uidlist,
606 					"Invalid mailbox GUID: %s", value);
607 				return -1;
608 			}
609 			uidlist->have_mailbox_guid = TRUE;
610 			break;
611 		default:
612 			if (str_len(uidlist->hdr_extensions) > 0)
613 				str_append_c(uidlist->hdr_extensions, ' ');
614 			str_printfa(uidlist->hdr_extensions,
615 				    "%c%s", key, value);
616 			break;
617 		}
618 
619 		while (*line == ' ') line++;
620 	}
621 	return 0;
622 }
623 
maildir_uidlist_read_header(struct maildir_uidlist * uidlist,struct istream * input)624 static int maildir_uidlist_read_header(struct maildir_uidlist *uidlist,
625 				       struct istream *input)
626 {
627 	unsigned int uid_validity = 0, next_uid = 0;
628 	const char *line;
629 	int ret;
630 
631 	line = i_stream_read_next_line(input);
632         if (line == NULL) {
633                 /* I/O error / empty file */
634                 return input->stream_errno == 0 ? 0 : -1;
635 	}
636 	uidlist->read_line_count = 1;
637 
638 	if (*line < '0' || *line > '9' || line[1] != ' ') {
639 		maildir_uidlist_set_corrupted(uidlist,
640 			"Corrupted header (invalid version number)");
641 		return 0;
642 	}
643 
644 	uidlist->version = *line - '0';
645 	line += 2;
646 
647 	switch (uidlist->version) {
648 	case 1:
649 		if (sscanf(line, "%u %u", &uid_validity, &next_uid) != 2) {
650 			maildir_uidlist_set_corrupted(uidlist,
651 				"Corrupted header (version 1)");
652 			return 0;
653 		}
654 		break;
655 	case UIDLIST_VERSION:
656 		T_BEGIN {
657 			ret = maildir_uidlist_read_v3_header(uidlist, line,
658 							     &uid_validity,
659 							     &next_uid);
660 		} T_END;
661 		if (ret < 0)
662 			return 0;
663 		break;
664 	default:
665 		maildir_uidlist_set_corrupted(uidlist, "Unsupported version %u",
666 					      uidlist->version);
667 		return 0;
668 	}
669 
670 	if (uid_validity == 0 || next_uid == 0) {
671 		maildir_uidlist_set_corrupted(uidlist,
672 			"Broken header (uidvalidity = %u, next_uid=%u)",
673 			uid_validity, next_uid);
674 		return 0;
675 	}
676 
677 	if (uid_validity == uidlist->uid_validity &&
678 	    next_uid < uidlist->hdr_next_uid) {
679 		maildir_uidlist_set_corrupted(uidlist,
680 			"next_uid header was lowered (%u -> %u)",
681 			uidlist->hdr_next_uid, next_uid);
682 		return 0;
683 	}
684 
685 	uidlist->uid_validity = uid_validity;
686 	uidlist->next_uid = next_uid;
687 	uidlist->hdr_next_uid = next_uid;
688 	return 1;
689 }
690 
maildir_uidlist_records_sort_by_uid(struct maildir_uidlist * uidlist)691 static void maildir_uidlist_records_sort_by_uid(struct maildir_uidlist *uidlist)
692 {
693 	array_sort(&uidlist->records, maildir_uid_cmp);
694 	uidlist->unsorted = FALSE;
695 }
696 
697 static int
maildir_uidlist_update_read(struct maildir_uidlist * uidlist,bool * retry_r,bool try_retry)698 maildir_uidlist_update_read(struct maildir_uidlist *uidlist,
699 			    bool *retry_r, bool try_retry)
700 {
701 	const char *line;
702 	uint32_t orig_next_uid, orig_uid_validity;
703 	struct istream *input;
704 	struct stat st;
705 	uoff_t last_read_offset;
706 	int fd, ret;
707 	bool readonly = FALSE;
708 
709 	*retry_r = FALSE;
710 
711 	if (uidlist->fd == -1) {
712 		fd = nfs_safe_open(uidlist->path, O_RDWR);
713 		if (fd == -1 && errno == EACCES) {
714 			fd = nfs_safe_open(uidlist->path, O_RDONLY);
715 			readonly = TRUE;
716 		}
717 		if (fd == -1) {
718 			if (errno != ENOENT) {
719 				mailbox_set_critical(uidlist->box,
720 					"open(%s) failed: %m", uidlist->path);
721 				return -1;
722 			}
723 			return 0;
724 		}
725 		last_read_offset = 0;
726 	} else {
727 		/* the file was updated */
728 		fd = uidlist->fd;
729 		if (lseek(fd, 0, SEEK_SET) < 0) {
730 			if (errno == ESTALE && try_retry) {
731 				*retry_r = TRUE;
732 				return -1;
733 			}
734 			mailbox_set_critical(uidlist->box,
735 				"lseek(%s) failed: %m", uidlist->path);
736 			return -1;
737 		}
738 		uidlist->fd = -1;
739 		uidlist->fd_ino = 0;
740 		last_read_offset = uidlist->last_read_offset;
741 		uidlist->last_read_offset = 0;
742 	}
743 
744 	if (fstat(fd, &st) < 0) {
745                 i_close_fd(&fd);
746                 if (errno == ESTALE && try_retry) {
747                         *retry_r = TRUE;
748                         return -1;
749                 }
750                 mailbox_set_critical(uidlist->box,
751 			"fstat(%s) failed: %m", uidlist->path);
752 		return -1;
753 	}
754 
755 	if (uidlist->record_pool == NULL) {
756 		uidlist->record_pool =
757 			pool_alloconly_create(MEMPOOL_GROWING
758 					      "uidlist record_pool",
759 					      nearest_power(st.st_size -
760 							    st.st_size/8));
761 	}
762 
763 	input = i_stream_create_fd(fd, SIZE_MAX);
764 	i_stream_seek(input, last_read_offset);
765 
766 	orig_uid_validity = uidlist->uid_validity;
767 	orig_next_uid = uidlist->next_uid;
768 	ret = input->v_offset != 0 ? 1 :
769 		maildir_uidlist_read_header(uidlist, input);
770 	if (ret > 0) {
771 		uidlist->prev_read_uid = 0;
772 		uidlist->change_counter++;
773 		uidlist->retry_rewind = last_read_offset != 0 && try_retry;
774 
775 		ret = 1;
776 		while ((line = i_stream_read_next_line(input)) != NULL) {
777 			uidlist->read_records_count++;
778 			uidlist->read_line_count++;
779 			if (!maildir_uidlist_next(uidlist, line)) {
780 				if (!uidlist->retry_rewind)
781 					ret = 0;
782 				else {
783 					ret = -1;
784 					*retry_r = TRUE;
785 				}
786 				break;
787 			}
788                 }
789 		uidlist->retry_rewind = FALSE;
790 		if (input->stream_errno != 0)
791                         ret = -1;
792 
793 		if (uidlist->unsorted) {
794 			uidlist->recreate_on_change = TRUE;
795 			maildir_uidlist_records_sort_by_uid(uidlist);
796 		}
797 		if (uidlist->next_uid <= uidlist->prev_read_uid)
798 			uidlist->next_uid = uidlist->prev_read_uid + 1;
799 		if (ret > 0 && uidlist->uid_validity != orig_uid_validity &&
800 		    orig_uid_validity != 0) {
801 			uidlist->recreate = TRUE;
802 		} else if (ret > 0 && uidlist->next_uid < orig_next_uid) {
803 			mailbox_set_critical(uidlist->box,
804 				"%s: next_uid was lowered (%u -> %u, hdr=%u)",
805 				uidlist->path, orig_next_uid,
806 				uidlist->next_uid, uidlist->hdr_next_uid);
807 			uidlist->recreate = TRUE;
808 			uidlist->next_uid = orig_next_uid;
809 		}
810 	}
811 
812         if (ret == 0) {
813                 /* file is broken */
814                 i_unlink(uidlist->path);
815         } else if (ret > 0) {
816                 /* success */
817 		if (readonly)
818 			uidlist->recreate_on_change = TRUE;
819 		uidlist->fd = fd;
820 		uidlist->fd_dev = st.st_dev;
821 		uidlist->fd_ino = st.st_ino;
822 		uidlist->fd_size = st.st_size;
823 		uidlist->last_read_offset = input->v_offset;
824 		maildir_uidlist_update_hdr(uidlist, &st);
825         } else if (!*retry_r) {
826                 /* I/O error */
827                 if (input->stream_errno == ESTALE && try_retry)
828 			*retry_r = TRUE;
829 		else {
830 			mailbox_set_critical(uidlist->box,
831 				"read(%s) failed: %s", uidlist->path,
832 				i_stream_get_error(input));
833 		}
834 		uidlist->last_read_offset = 0;
835 	}
836 
837 	i_stream_destroy(&input);
838 	if (ret <= 0) {
839 		if (close(fd) < 0) {
840 			mailbox_set_critical(uidlist->box,
841 				"close(%s) failed: %m", uidlist->path);
842 		}
843 	}
844 	return ret;
845 }
846 
847 static int
maildir_uidlist_stat(struct maildir_uidlist * uidlist,struct stat * st_r)848 maildir_uidlist_stat(struct maildir_uidlist *uidlist, struct stat *st_r)
849 {
850 	struct mail_storage *storage = uidlist->box->storage;
851 
852 	if (storage->set->mail_nfs_storage) {
853 		nfs_flush_file_handle_cache(uidlist->path);
854 		nfs_flush_attr_cache_unlocked(uidlist->path);
855 	}
856 	if (nfs_safe_stat(uidlist->path, st_r) < 0) {
857 		if (errno != ENOENT) {
858 			mailbox_set_critical(uidlist->box,
859 				"stat(%s) failed: %m", uidlist->path);
860 			return -1;
861 		}
862 		return 0;
863 	}
864 	return 1;
865 }
866 
867 static int
maildir_uidlist_has_changed(struct maildir_uidlist * uidlist,bool * recreated_r)868 maildir_uidlist_has_changed(struct maildir_uidlist *uidlist, bool *recreated_r)
869 {
870 	struct mail_storage *storage = uidlist->box->storage;
871         struct stat st;
872 	int ret;
873 
874 	*recreated_r = FALSE;
875 
876 	if ((ret = maildir_uidlist_stat(uidlist, &st)) < 0)
877 		return -1;
878 	if (ret == 0) {
879 		*recreated_r = TRUE;
880 		return 1;
881 	}
882 
883 	if (st.st_ino != uidlist->fd_ino ||
884 	    !CMP_DEV_T(st.st_dev, uidlist->fd_dev)) {
885 		/* file recreated */
886 		*recreated_r = TRUE;
887 		return 1;
888 	}
889 
890 	if (storage->set->mail_nfs_storage) {
891 		/* NFS: either the file hasn't been changed, or it has already
892 		   been deleted and the inodes just happen to be the same.
893 		   check if the fd is still valid. */
894 		if (fstat(uidlist->fd, &st) < 0) {
895 			if (errno == ESTALE) {
896 				*recreated_r = TRUE;
897 				return 1;
898 			}
899 			mailbox_set_critical(uidlist->box,
900 				"fstat(%s) failed: %m", uidlist->path);
901 			return -1;
902 		}
903 	}
904 
905 	if (st.st_size != uidlist->fd_size) {
906 		/* file modified but not recreated */
907 		return 1;
908 	} else {
909 		/* unchanged */
910 		return 0;
911 	}
912 }
913 
maildir_uidlist_open_latest(struct maildir_uidlist * uidlist)914 static int maildir_uidlist_open_latest(struct maildir_uidlist *uidlist)
915 {
916 	bool recreated;
917 	int ret;
918 
919 	if (uidlist->fd != -1) {
920 		ret = maildir_uidlist_has_changed(uidlist, &recreated);
921 		if (ret <= 0) {
922 			if (UIDLIST_IS_LOCKED(uidlist))
923 				uidlist->locked_refresh = TRUE;
924 			return ret < 0 ? -1 : 1;
925 		}
926 
927 		if (!recreated)
928 			return 0;
929 		maildir_uidlist_reset(uidlist);
930 	}
931 
932 	uidlist->fd = nfs_safe_open(uidlist->path, O_RDWR);
933 	if (uidlist->fd == -1 && errno == EACCES) {
934 		uidlist->fd = nfs_safe_open(uidlist->path, O_RDONLY);
935 		uidlist->recreate_on_change = TRUE;
936 	}
937 	if (uidlist->fd == -1 && errno != ENOENT) {
938 		mailbox_set_critical(uidlist->box,
939 			"open(%s) failed: %m", uidlist->path);
940 		return -1;
941 	}
942 	return 0;
943 }
944 
maildir_uidlist_refresh(struct maildir_uidlist * uidlist)945 int maildir_uidlist_refresh(struct maildir_uidlist *uidlist)
946 {
947         unsigned int i;
948         bool retry;
949         int ret;
950 
951 	if (maildir_uidlist_open_latest(uidlist) < 0)
952 		return -1;
953 
954         for (i = 0; ; i++) {
955 		ret = maildir_uidlist_update_read(uidlist, &retry,
956 						i < UIDLIST_ESTALE_RETRY_COUNT);
957 		if (!retry)
958 			break;
959 		/* ESTALE - try reopening and rereading */
960 		maildir_uidlist_close(uidlist);
961         }
962 	if (ret >= 0) {
963 		uidlist->initial_read = TRUE;
964 		uidlist->initial_hdr_read = TRUE;
965 		if (UIDLIST_IS_LOCKED(uidlist))
966 			uidlist->locked_refresh = TRUE;
967 		if (!uidlist->have_mailbox_guid) {
968 			uidlist->recreate = TRUE;
969 			(void)maildir_uidlist_update(uidlist);
970 		}
971 	}
972         return ret;
973 }
974 
maildir_uidlist_refresh_fast_init(struct maildir_uidlist * uidlist)975 int maildir_uidlist_refresh_fast_init(struct maildir_uidlist *uidlist)
976 {
977 	const struct maildir_index_header *mhdr = uidlist->mhdr;
978 	struct mail_index *index = uidlist->box->index;
979 	struct mail_index_view *view;
980 	const struct mail_index_header *hdr;
981 	struct stat st;
982 	int ret;
983 
984 	i_assert(UIDLIST_IS_LOCKED(uidlist));
985 
986 	if (uidlist->fd != -1)
987 		return maildir_uidlist_refresh(uidlist);
988 
989 	if ((ret = maildir_uidlist_stat(uidlist, &st)) < 0)
990 		return ret;
991 
992 	if (ret > 0 && st.st_size == mhdr->uidlist_size &&
993 	    st.st_mtime == (time_t)mhdr->uidlist_mtime &&
994 	    ST_NTIMES_EQUAL(ST_MTIME_NSEC(st), mhdr->uidlist_mtime_nsecs) &&
995 	    (!mail_index_is_in_memory(index) || st.st_mtime < ioloop_time-1)) {
996 		/* index is up-to-date. look up the uidvalidity and next-uid
997 		   from it. we'll need to create a new view temporarily to
998 		   make sure we get the latest values. */
999 		view = mail_index_view_open(index);
1000 		hdr = mail_index_get_header(view);
1001 		uidlist->uid_validity = hdr->uid_validity;
1002 		uidlist->next_uid = hdr->next_uid;
1003 		uidlist->initial_hdr_read = TRUE;
1004 		mail_index_view_close(&view);
1005 
1006 		if (UIDLIST_IS_LOCKED(uidlist))
1007 			uidlist->locked_refresh = TRUE;
1008 		return 1;
1009 	} else {
1010 		return maildir_uidlist_refresh(uidlist);
1011 	}
1012 }
1013 
1014 static int
maildir_uid_bsearch_cmp(const uint32_t * uidp,struct maildir_uidlist_rec * const * recp)1015 maildir_uid_bsearch_cmp(const uint32_t *uidp,
1016 			struct maildir_uidlist_rec *const *recp)
1017 {
1018 	return *uidp < (*recp)->uid ? -1 :
1019 		*uidp > (*recp)->uid ? 1 : 0;
1020 }
1021 
1022 static int
maildir_uidlist_lookup_rec(struct maildir_uidlist * uidlist,uint32_t uid,struct maildir_uidlist_rec ** rec_r)1023 maildir_uidlist_lookup_rec(struct maildir_uidlist *uidlist, uint32_t uid,
1024 			   struct maildir_uidlist_rec **rec_r)
1025 {
1026 	struct maildir_uidlist_rec *const *pos;
1027 
1028 	if (!uidlist->initial_read) {
1029 		/* first time we need to read uidlist */
1030 		if (maildir_uidlist_refresh(uidlist) < 0)
1031 			return -1;
1032 	}
1033 
1034 	pos = array_bsearch(&uidlist->records, &uid,
1035 			    maildir_uid_bsearch_cmp);
1036 	if (pos == NULL) {
1037 		*rec_r = NULL;
1038 		return 0;
1039 	}
1040 	*rec_r = *pos;
1041 	return 1;
1042 }
1043 
maildir_uidlist_lookup(struct maildir_uidlist * uidlist,uint32_t uid,enum maildir_uidlist_rec_flag * flags_r,const char ** fname_r)1044 int maildir_uidlist_lookup(struct maildir_uidlist *uidlist, uint32_t uid,
1045 			   enum maildir_uidlist_rec_flag *flags_r,
1046 			   const char **fname_r)
1047 {
1048 	struct maildir_uidlist_rec *rec;
1049 	int ret;
1050 
1051 	if ((ret = maildir_uidlist_lookup_rec(uidlist, uid, &rec)) <= 0)
1052 		return ret;
1053 
1054 	*flags_r = rec->flags;
1055 	*fname_r = rec->filename;
1056 	return 1;
1057 }
1058 
1059 const char *
maildir_uidlist_lookup_ext(struct maildir_uidlist * uidlist,uint32_t uid,enum maildir_uidlist_rec_ext_key key)1060 maildir_uidlist_lookup_ext(struct maildir_uidlist *uidlist, uint32_t uid,
1061 			   enum maildir_uidlist_rec_ext_key key)
1062 {
1063 	struct maildir_uidlist_rec *rec;
1064 	const unsigned char *p;
1065 	int ret;
1066 
1067 	ret = maildir_uidlist_lookup_rec(uidlist, uid, &rec);
1068 	if (ret <= 0 || rec->extensions == NULL)
1069 		return NULL;
1070 
1071 	p = rec->extensions;
1072 	while (*p != '\0') {
1073 		/* <key><value>\0 */
1074 		if (*p == (unsigned char)key)
1075 			return (const char *)p + 1;
1076 
1077 		p += strlen((const char *)p) + 1;
1078 	}
1079 	return NULL;
1080 }
1081 
maildir_uidlist_get_uid_validity(struct maildir_uidlist * uidlist)1082 uint32_t maildir_uidlist_get_uid_validity(struct maildir_uidlist *uidlist)
1083 {
1084 	return uidlist->uid_validity;
1085 }
1086 
maildir_uidlist_get_next_uid(struct maildir_uidlist * uidlist)1087 uint32_t maildir_uidlist_get_next_uid(struct maildir_uidlist *uidlist)
1088 {
1089 	return !uidlist->initial_hdr_read ? 0 : uidlist->next_uid;
1090 }
1091 
maildir_uidlist_get_mailbox_guid(struct maildir_uidlist * uidlist,guid_128_t mailbox_guid)1092 int maildir_uidlist_get_mailbox_guid(struct maildir_uidlist *uidlist,
1093 				     guid_128_t mailbox_guid)
1094 {
1095 	if (!uidlist->initial_hdr_read) {
1096 		if (maildir_uidlist_refresh(uidlist) < 0)
1097 			return -1;
1098 	}
1099 	if (!uidlist->have_mailbox_guid) {
1100 		uidlist->recreate = TRUE;
1101 		if (maildir_uidlist_update(uidlist) < 0)
1102 			return -1;
1103 	}
1104 	memcpy(mailbox_guid, uidlist->mailbox_guid, GUID_128_SIZE);
1105 	return 0;
1106 }
1107 
maildir_uidlist_set_mailbox_guid(struct maildir_uidlist * uidlist,const guid_128_t mailbox_guid)1108 void maildir_uidlist_set_mailbox_guid(struct maildir_uidlist *uidlist,
1109 				      const guid_128_t mailbox_guid)
1110 {
1111 	if (memcmp(uidlist->mailbox_guid, mailbox_guid,
1112 		   sizeof(uidlist->mailbox_guid)) != 0) {
1113 		memcpy(uidlist->mailbox_guid, mailbox_guid,
1114 		       sizeof(uidlist->mailbox_guid));
1115 		uidlist->recreate = TRUE;
1116 	}
1117 }
1118 
maildir_uidlist_set_uid_validity(struct maildir_uidlist * uidlist,uint32_t uid_validity)1119 void maildir_uidlist_set_uid_validity(struct maildir_uidlist *uidlist,
1120 				      uint32_t uid_validity)
1121 {
1122 	i_assert(uid_validity != 0);
1123 
1124 	if (uid_validity != uidlist->uid_validity) {
1125 		uidlist->uid_validity = uid_validity;
1126 		uidlist->recreate = TRUE;
1127 	}
1128 }
1129 
maildir_uidlist_set_next_uid(struct maildir_uidlist * uidlist,uint32_t next_uid,bool force)1130 void maildir_uidlist_set_next_uid(struct maildir_uidlist *uidlist,
1131 				  uint32_t next_uid, bool force)
1132 {
1133 	if (uidlist->next_uid < next_uid || force) {
1134 		i_assert(next_uid != 0);
1135 		uidlist->next_uid = next_uid;
1136 		uidlist->recreate = TRUE;
1137 	}
1138 }
1139 
1140 static void
maildir_uidlist_rec_set_ext(struct maildir_uidlist_rec * rec,pool_t pool,enum maildir_uidlist_rec_ext_key key,const char * value)1141 maildir_uidlist_rec_set_ext(struct maildir_uidlist_rec *rec, pool_t pool,
1142 			    enum maildir_uidlist_rec_ext_key key,
1143 			    const char *value)
1144 {
1145 	const unsigned char *p;
1146 	buffer_t *buf;
1147 	size_t len;
1148 
1149 	/* copy existing extensions, except for the one we're updating */
1150 	buf = t_buffer_create(128);
1151 	if (rec->extensions != NULL) {
1152 		p = rec->extensions;
1153 		while (*p != '\0') {
1154 			/* <key><value>\0 */
1155 			i_assert(MAILDIR_UIDLIST_REC_EXT_KEY_IS_VALID(*p));
1156 
1157 			len = strlen((const char *)p) + 1;
1158 			if (*p != (unsigned char)key)
1159 				buffer_append(buf, p, len);
1160 			p += len;
1161 		}
1162 	}
1163 	if (value != NULL) {
1164 		buffer_append_c(buf, key);
1165 		buffer_append(buf, value, strlen(value) + 1);
1166 	}
1167 	buffer_append_c(buf, '\0');
1168 
1169 	rec->extensions = p_malloc(pool, buf->used);
1170 	memcpy(rec->extensions, buf->data, buf->used);
1171 }
1172 
1173 static void ATTR_NULL(4)
maildir_uidlist_set_ext_internal(struct maildir_uidlist * uidlist,uint32_t uid,enum maildir_uidlist_rec_ext_key key,const char * value)1174 maildir_uidlist_set_ext_internal(struct maildir_uidlist *uidlist, uint32_t uid,
1175 				 enum maildir_uidlist_rec_ext_key key,
1176 				 const char *value)
1177 {
1178 	struct maildir_uidlist_rec *rec;
1179 	int ret;
1180 
1181 	i_assert(MAILDIR_UIDLIST_REC_EXT_KEY_IS_VALID(key));
1182 
1183 	ret = maildir_uidlist_lookup_rec(uidlist, uid, &rec);
1184 	if (ret <= 0) {
1185 		if (ret < 0)
1186 			return;
1187 
1188 		/* maybe it's a new message */
1189 		if (maildir_uidlist_refresh(uidlist) < 0)
1190 			return;
1191 		if (maildir_uidlist_lookup_rec(uidlist, uid, &rec) <= 0) {
1192 			/* message is already expunged, ignore */
1193 			return;
1194 		}
1195 	}
1196 
1197 	T_BEGIN {
1198 		maildir_uidlist_rec_set_ext(rec, uidlist->record_pool,
1199 					    key, value);
1200 	} T_END;
1201 
1202 	if (rec->uid != (uint32_t)-1) {
1203 		/* message already exists in uidlist, need to recreate it */
1204 		uidlist->recreate = TRUE;
1205 	}
1206 }
1207 
maildir_uidlist_set_ext(struct maildir_uidlist * uidlist,uint32_t uid,enum maildir_uidlist_rec_ext_key key,const char * value)1208 void maildir_uidlist_set_ext(struct maildir_uidlist *uidlist, uint32_t uid,
1209 			     enum maildir_uidlist_rec_ext_key key,
1210 			     const char *value)
1211 {
1212 	maildir_uidlist_set_ext_internal(uidlist, uid, key, value);
1213 }
1214 
maildir_uidlist_unset_ext(struct maildir_uidlist * uidlist,uint32_t uid,enum maildir_uidlist_rec_ext_key key)1215 void maildir_uidlist_unset_ext(struct maildir_uidlist *uidlist, uint32_t uid,
1216 			       enum maildir_uidlist_rec_ext_key key)
1217 {
1218 	maildir_uidlist_set_ext_internal(uidlist, uid, key, NULL);
1219 }
1220 
1221 static void
maildir_uidlist_generate_uid_validity(struct maildir_uidlist * uidlist)1222 maildir_uidlist_generate_uid_validity(struct maildir_uidlist *uidlist)
1223 {
1224 	const struct mail_index_header *hdr;
1225 
1226 	if (uidlist->box->opened) {
1227 		hdr = mail_index_get_header(uidlist->box->view);
1228 		if (hdr->uid_validity != 0) {
1229 			uidlist->uid_validity = hdr->uid_validity;
1230 			return;
1231 		}
1232 	}
1233 	uidlist->uid_validity =
1234 		maildir_get_uidvalidity_next(uidlist->box->list);
1235 }
1236 
maildir_uidlist_write_fd(struct maildir_uidlist * uidlist,int fd,const char * path,unsigned int first_idx,uoff_t * file_size_r)1237 static int maildir_uidlist_write_fd(struct maildir_uidlist *uidlist, int fd,
1238 				    const char *path, unsigned int first_idx,
1239 				    uoff_t *file_size_r)
1240 {
1241 	struct mail_storage *storage = uidlist->box->storage;
1242 	struct maildir_uidlist_iter_ctx *iter;
1243 	struct ostream *output;
1244 	struct maildir_uidlist_rec *rec;
1245 	string_t *str;
1246 	const unsigned char *p;
1247 	const char *strp;
1248 	size_t len;
1249 
1250 	i_assert(fd != -1);
1251 
1252 	output = o_stream_create_fd_file(fd, UOFF_T_MAX, FALSE);
1253 	o_stream_cork(output);
1254 	str = t_str_new(512);
1255 
1256 	if (output->offset == 0) {
1257 		i_assert(first_idx == 0);
1258 		uidlist->version = UIDLIST_VERSION;
1259 
1260 		if (uidlist->uid_validity == 0)
1261 			maildir_uidlist_generate_uid_validity(uidlist);
1262 		if (!uidlist->have_mailbox_guid)
1263 			guid_128_generate(uidlist->mailbox_guid);
1264 
1265 		i_assert(uidlist->next_uid > 0);
1266 		str_printfa(str, "%u %c%u %c%u %c%s", uidlist->version,
1267 			    MAILDIR_UIDLIST_HDR_EXT_UID_VALIDITY,
1268 			    uidlist->uid_validity,
1269 			    MAILDIR_UIDLIST_HDR_EXT_NEXT_UID,
1270 			    uidlist->next_uid,
1271 			    MAILDIR_UIDLIST_HDR_EXT_GUID,
1272 			    guid_128_to_string(uidlist->mailbox_guid));
1273 		if (str_len(uidlist->hdr_extensions) > 0) {
1274 			str_append_c(str, ' ');
1275 			str_append_str(str, uidlist->hdr_extensions);
1276 		}
1277 		str_append_c(str, '\n');
1278 		o_stream_nsend(output, str_data(str), str_len(str));
1279 	}
1280 
1281 	iter = maildir_uidlist_iter_init(uidlist);
1282 	i_assert(first_idx <= array_count(&uidlist->records));
1283 	iter->next += first_idx;
1284 
1285 	while (maildir_uidlist_iter_next_rec(iter, &rec)) {
1286 		uidlist->read_records_count++;
1287 		str_truncate(str, 0);
1288 		str_printfa(str, "%u", rec->uid);
1289 		if (rec->extensions != NULL) {
1290 			for (p = rec->extensions; *p != '\0'; ) {
1291 				i_assert(MAILDIR_UIDLIST_REC_EXT_KEY_IS_VALID(*p));
1292 				len = strlen((const char *)p);
1293 				str_append_c(str, ' ');
1294 				str_append_data(str, p, len);
1295 				p += len + 1;
1296 			}
1297 		}
1298 		str_append(str, " :");
1299 		strp = strchr(rec->filename, *MAILDIR_INFO_SEP_S);
1300 		if (strp == NULL)
1301 			str_append(str, rec->filename);
1302 		else
1303 			str_append_data(str, rec->filename, strp - rec->filename);
1304 		str_append_c(str, '\n');
1305 		o_stream_nsend(output, str_data(str), str_len(str));
1306 	}
1307 	maildir_uidlist_iter_deinit(&iter);
1308 
1309 	if (o_stream_finish(output) < 0) {
1310 		mailbox_set_critical(uidlist->box, "write(%s) failed: %s",
1311 				     path, o_stream_get_error(output));
1312 		o_stream_unref(&output);
1313 		return -1;
1314 	}
1315 
1316 	*file_size_r = output->offset;
1317 	o_stream_unref(&output);
1318 
1319 	if (storage->set->parsed_fsync_mode != FSYNC_MODE_NEVER) {
1320 		if (fdatasync(fd) < 0) {
1321 			mailbox_set_critical(uidlist->box,
1322 				"fdatasync(%s) failed: %m", path);
1323 			return -1;
1324 		}
1325 	}
1326 	return 0;
1327 }
1328 
1329 static void
maildir_uidlist_records_drop_expunges(struct maildir_uidlist * uidlist)1330 maildir_uidlist_records_drop_expunges(struct maildir_uidlist *uidlist)
1331 {
1332 	struct mail_index_view *view;
1333 	struct maildir_uidlist_rec *const *recs;
1334 	ARRAY_TYPE(maildir_uidlist_rec_p) new_records;
1335 	const struct mail_index_header *hdr;
1336 	const struct mail_index_record *rec;
1337 	unsigned int i, count;
1338 	uint32_t seq;
1339 
1340 	/* we could get here when opening and locking mailbox,
1341 	   before index files have been opened. */
1342 	if (!uidlist->box->opened)
1343 		return;
1344 
1345 	mail_index_refresh(uidlist->box->index);
1346 	view = mail_index_view_open(uidlist->box->index);
1347 	count = array_count(&uidlist->records);
1348 	hdr = mail_index_get_header(view);
1349 	if (count * UIDLIST_COMPRESS_PERCENTAGE / 100 <= hdr->messages_count) {
1350 		/* too much trouble to be worth it */
1351 		mail_index_view_close(&view);
1352 		return;
1353 	}
1354 
1355 	i_array_init(&new_records, hdr->messages_count + 64);
1356 	recs = array_get(&uidlist->records, &count);
1357 	for (i = 0, seq = 1; i < count && seq <= hdr->messages_count; ) {
1358 		rec = mail_index_lookup(view, seq);
1359 		if (recs[i]->uid < rec->uid) {
1360 			/* expunged entry */
1361 			hash_table_remove(uidlist->files, recs[i]->filename);
1362 			i++;
1363 		} else if (recs[i]->uid > rec->uid) {
1364 			/* index isn't up to date. we're probably just
1365 			   syncing it here. ignore this entry. */
1366 			seq++;
1367 		} else {
1368 			array_push_back(&new_records, &recs[i]);
1369 			seq++; i++;
1370 		}
1371 	}
1372 
1373 	/* drop messages expunged at the end of index */
1374 	while (i < count && recs[i]->uid < hdr->next_uid) {
1375 		hash_table_remove(uidlist->files, recs[i]->filename);
1376 		i++;
1377 	}
1378 	/* view might not be completely up-to-date, so preserve any
1379 	   messages left */
1380 	for (; i < count; i++)
1381 		array_push_back(&new_records, &recs[i]);
1382 
1383 	array_free(&uidlist->records);
1384 	uidlist->records = new_records;
1385 
1386 	mail_index_view_close(&view);
1387 }
1388 
maildir_uidlist_recreate(struct maildir_uidlist * uidlist)1389 static int maildir_uidlist_recreate(struct maildir_uidlist *uidlist)
1390 {
1391 	struct mailbox *box = uidlist->box;
1392 	const struct mailbox_permissions *perm = mailbox_get_permissions(box);
1393 	const char *control_dir, *temp_path;
1394 	struct stat st;
1395 	mode_t old_mask;
1396 	uoff_t file_size;
1397 	int i, fd, ret;
1398 
1399 	i_assert(uidlist->initial_read);
1400 
1401 	maildir_uidlist_records_drop_expunges(uidlist);
1402 
1403 	if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_CONTROL,
1404 				&control_dir) <= 0)
1405 		i_unreached();
1406 	temp_path = t_strconcat(control_dir,
1407 				"/" MAILDIR_UIDLIST_NAME ".tmp", NULL);
1408 
1409 	for (i = 0;; i++) {
1410 		old_mask = umask(0777 & ~perm->file_create_mode);
1411 		fd = open(temp_path, O_RDWR | O_CREAT | O_TRUNC, 0777);
1412 		umask(old_mask);
1413 		if (fd != -1)
1414 			break;
1415 
1416 		if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT) {
1417 			mailbox_set_critical(box,
1418 				"open(%s, O_CREAT) failed: %m", temp_path);
1419 			return -1;
1420 		}
1421 		/* the control dir doesn't exist. create it unless the whole
1422 		   mailbox was just deleted. */
1423 		if (!maildir_set_deleted(uidlist->box))
1424 			return -1;
1425 	}
1426 
1427 	if (perm->file_create_gid != (gid_t)-1 &&
1428 	    fchown(fd, (uid_t)-1, perm->file_create_gid) < 0) {
1429 		if (errno == EPERM) {
1430 			mailbox_set_critical(box, "%s",
1431 				eperm_error_get_chgrp("fchown", temp_path,
1432 						perm->file_create_gid,
1433 						perm->file_create_gid_origin));
1434 		} else {
1435 			mailbox_set_critical(box,
1436 				"fchown(%s) failed: %m", temp_path);
1437 		}
1438 	}
1439 
1440 	uidlist->read_records_count = 0;
1441 	ret = maildir_uidlist_write_fd(uidlist, fd, temp_path, 0, &file_size);
1442 	if (ret == 0) {
1443 		if (rename(temp_path, uidlist->path) < 0) {
1444 			mailbox_set_critical(box,
1445 				"rename(%s, %s) failed: %m",
1446 				temp_path, uidlist->path);
1447 			ret = -1;
1448 		}
1449 	}
1450 
1451 	if (ret < 0)
1452 		i_unlink(temp_path);
1453 	else if (fstat(fd, &st) < 0) {
1454 		mailbox_set_critical(box,
1455 			"fstat(%s) failed: %m", temp_path);
1456 		ret = -1;
1457 	} else if (file_size != (uoff_t)st.st_size) {
1458 		i_assert(!file_dotlock_is_locked(uidlist->dotlock));
1459 		mailbox_set_critical(box,
1460 			"Maildir uidlist dotlock overridden: %s",
1461 			uidlist->path);
1462 		ret = -1;
1463 	} else {
1464 		maildir_uidlist_close(uidlist);
1465 		uidlist->fd = fd;
1466 		uidlist->fd_dev = st.st_dev;
1467 		uidlist->fd_ino = st.st_ino;
1468 		uidlist->fd_size = st.st_size;
1469 		uidlist->last_read_offset = st.st_size;
1470 		uidlist->recreate = FALSE;
1471 		uidlist->recreate_on_change = FALSE;
1472 		uidlist->have_mailbox_guid = TRUE;
1473 		maildir_uidlist_update_hdr(uidlist, &st);
1474 	}
1475 	if (ret < 0)
1476 		i_close_fd(&fd);
1477 	return ret;
1478 }
1479 
maildir_uidlist_update(struct maildir_uidlist * uidlist)1480 int maildir_uidlist_update(struct maildir_uidlist *uidlist)
1481 {
1482 	int ret;
1483 
1484 	if (!uidlist->recreate)
1485 		return 0;
1486 
1487 	if (maildir_uidlist_lock(uidlist) <= 0)
1488 		return -1;
1489 	ret = maildir_uidlist_recreate(uidlist);
1490 	maildir_uidlist_unlock(uidlist);
1491 	return ret;
1492 }
1493 
maildir_uidlist_want_compress(struct maildir_uidlist_sync_ctx * ctx)1494 static bool maildir_uidlist_want_compress(struct maildir_uidlist_sync_ctx *ctx)
1495 {
1496 	unsigned int min_rewrite_count;
1497 
1498 	if (!ctx->uidlist->locked_refresh)
1499 		return FALSE;
1500 	if (ctx->uidlist->recreate)
1501 		return TRUE;
1502 
1503 	min_rewrite_count =
1504 		(ctx->uidlist->read_records_count + ctx->new_files_count) *
1505 		UIDLIST_COMPRESS_PERCENTAGE / 100;
1506 	return min_rewrite_count >= array_count(&ctx->uidlist->records);
1507 }
1508 
maildir_uidlist_want_recreate(struct maildir_uidlist_sync_ctx * ctx)1509 static bool maildir_uidlist_want_recreate(struct maildir_uidlist_sync_ctx *ctx)
1510 {
1511 	struct maildir_uidlist *uidlist = ctx->uidlist;
1512 
1513 	if (!uidlist->locked_refresh || !uidlist->initial_read)
1514 		return FALSE;
1515 
1516 	if (ctx->finish_change_counter != uidlist->change_counter)
1517 		return TRUE;
1518 	if (uidlist->fd == -1 || uidlist->version != UIDLIST_VERSION ||
1519 	    !uidlist->have_mailbox_guid)
1520 		return TRUE;
1521 	return maildir_uidlist_want_compress(ctx);
1522 }
1523 
maildir_uidlist_sync_update(struct maildir_uidlist_sync_ctx * ctx)1524 static int maildir_uidlist_sync_update(struct maildir_uidlist_sync_ctx *ctx)
1525 {
1526 	struct maildir_uidlist *uidlist = ctx->uidlist;
1527 	struct stat st;
1528 	uoff_t file_size;
1529 
1530 	if (maildir_uidlist_want_recreate(ctx) || uidlist->recreate_on_change)
1531 		return maildir_uidlist_recreate(uidlist);
1532 
1533 	if (!uidlist->locked_refresh || uidlist->fd == -1) {
1534 		/* make sure we have the latest file (e.g. NOREFRESH used) */
1535 		i_assert(uidlist->initial_hdr_read);
1536 		if (maildir_uidlist_open_latest(uidlist) < 0)
1537 			return -1;
1538 		if (uidlist->recreate_on_change)
1539 			return maildir_uidlist_recreate(uidlist);
1540 	}
1541 	i_assert(ctx->first_unwritten_pos != UINT_MAX);
1542 
1543 	if (lseek(uidlist->fd, 0, SEEK_END) < 0) {
1544 		mailbox_set_critical(uidlist->box,
1545 			"lseek(%s) failed: %m", uidlist->path);
1546 		return -1;
1547 	}
1548 
1549 	if (maildir_uidlist_write_fd(uidlist, uidlist->fd, uidlist->path,
1550 				     ctx->first_unwritten_pos, &file_size) < 0)
1551 		return -1;
1552 
1553 	if (fstat(uidlist->fd, &st) < 0) {
1554 		mailbox_set_critical(uidlist->box,
1555 			"fstat(%s) failed: %m", uidlist->path);
1556 		return -1;
1557 	}
1558 	if ((uoff_t)st.st_size != file_size) {
1559 		i_warning("%s: file size changed unexpectedly after write",
1560 			  uidlist->path);
1561 	} else if (uidlist->locked_refresh) {
1562 		uidlist->fd_size = st.st_size;
1563 		uidlist->last_read_offset = st.st_size;
1564 		maildir_uidlist_update_hdr(uidlist, &st);
1565 	}
1566 	return 0;
1567 }
1568 
maildir_uidlist_mark_all(struct maildir_uidlist * uidlist,bool nonsynced)1569 static void maildir_uidlist_mark_all(struct maildir_uidlist *uidlist,
1570 				     bool nonsynced)
1571 {
1572 	struct maildir_uidlist_rec **recs;
1573 	unsigned int i, count;
1574 
1575 	recs = array_get_modifiable(&uidlist->records, &count);
1576 	if (nonsynced) {
1577 		for (i = 0; i < count; i++)
1578 			recs[i]->flags |= MAILDIR_UIDLIST_REC_FLAG_NONSYNCED;
1579 	} else {
1580 		for (i = 0; i < count; i++)
1581 			recs[i]->flags &= ENUM_NEGATE(MAILDIR_UIDLIST_REC_FLAG_NONSYNCED);
1582 	}
1583 }
1584 
maildir_uidlist_sync_lock(struct maildir_uidlist * uidlist,enum maildir_uidlist_sync_flags sync_flags,bool * locked_r)1585 static int maildir_uidlist_sync_lock(struct maildir_uidlist *uidlist,
1586 				     enum maildir_uidlist_sync_flags sync_flags,
1587 				     bool *locked_r)
1588 {
1589 	bool nonblock, refresh;
1590 	int ret;
1591 
1592 	*locked_r = FALSE;
1593 
1594 	if ((sync_flags & MAILDIR_UIDLIST_SYNC_NOLOCK) != 0) {
1595 		if (maildir_uidlist_refresh(uidlist) < 0)
1596 			return -1;
1597 		return 1;
1598 	}
1599 
1600 	nonblock = (sync_flags & MAILDIR_UIDLIST_SYNC_TRYLOCK) != 0;
1601 	refresh = (sync_flags & MAILDIR_UIDLIST_SYNC_NOREFRESH) == 0;
1602 
1603 	ret = maildir_uidlist_lock_timeout(uidlist, nonblock, refresh, refresh);
1604 	if (ret <= 0) {
1605 		if (ret < 0 || !nonblock)
1606 			return ret;
1607 
1608 		/* couldn't lock it */
1609 		if ((sync_flags & MAILDIR_UIDLIST_SYNC_FORCE) == 0)
1610 			return 0;
1611 		/* forcing the sync anyway */
1612 		if (maildir_uidlist_refresh(uidlist) < 0)
1613 			return -1;
1614 	} else {
1615 		*locked_r = TRUE;
1616 	}
1617 	return 1;
1618 }
1619 
maildir_uidlist_set_all_nonsynced(struct maildir_uidlist * uidlist)1620 void maildir_uidlist_set_all_nonsynced(struct maildir_uidlist *uidlist)
1621 {
1622 	maildir_uidlist_mark_all(uidlist, TRUE);
1623 }
1624 
maildir_uidlist_sync_init(struct maildir_uidlist * uidlist,enum maildir_uidlist_sync_flags sync_flags,struct maildir_uidlist_sync_ctx ** sync_ctx_r)1625 int maildir_uidlist_sync_init(struct maildir_uidlist *uidlist,
1626 			      enum maildir_uidlist_sync_flags sync_flags,
1627 			      struct maildir_uidlist_sync_ctx **sync_ctx_r)
1628 {
1629 	struct maildir_uidlist_sync_ctx *ctx;
1630 	bool locked;
1631 	int ret;
1632 
1633 	ret = maildir_uidlist_sync_lock(uidlist, sync_flags, &locked);
1634 	if (ret <= 0)
1635 		return ret;
1636 
1637 	*sync_ctx_r = ctx = i_new(struct maildir_uidlist_sync_ctx, 1);
1638 	ctx->uidlist = uidlist;
1639 	ctx->sync_flags = sync_flags;
1640 	ctx->partial = !locked ||
1641 		(sync_flags & MAILDIR_UIDLIST_SYNC_PARTIAL) != 0;
1642 	ctx->locked = locked;
1643 	ctx->first_unwritten_pos = UINT_MAX;
1644 	ctx->first_new_pos = UINT_MAX;
1645 
1646 	if (ctx->partial) {
1647 		if ((sync_flags & MAILDIR_UIDLIST_SYNC_KEEP_STATE) == 0) {
1648 			/* initially mark all nonsynced */
1649 			maildir_uidlist_mark_all(uidlist, TRUE);
1650 		}
1651 		return 1;
1652 	}
1653 	i_assert(uidlist->locked_refresh);
1654 
1655 	ctx->record_pool = pool_alloconly_create(MEMPOOL_GROWING
1656 						 "maildir_uidlist_sync", 16384);
1657 	hash_table_create(&ctx->files, ctx->record_pool, 4096,
1658 			  maildir_filename_base_hash,
1659 			  maildir_filename_base_cmp);
1660 
1661 	i_array_init(&ctx->records, array_count(&uidlist->records));
1662 	return 1;
1663 }
1664 
1665 static int
maildir_uidlist_sync_next_partial(struct maildir_uidlist_sync_ctx * ctx,const char * filename,uint32_t uid,enum maildir_uidlist_rec_flag flags,struct maildir_uidlist_rec ** rec_r)1666 maildir_uidlist_sync_next_partial(struct maildir_uidlist_sync_ctx *ctx,
1667 				  const char *filename, uint32_t uid,
1668 				  enum maildir_uidlist_rec_flag flags,
1669 				  struct maildir_uidlist_rec **rec_r)
1670 {
1671 	struct maildir_uidlist *uidlist = ctx->uidlist;
1672 	struct maildir_uidlist_rec *rec, *const *recs;
1673 	unsigned int count;
1674 
1675 	/* we'll update uidlist directly */
1676 	rec = hash_table_lookup(uidlist->files, filename);
1677 	if (rec == NULL) {
1678 		/* doesn't exist in uidlist */
1679 		if (!ctx->locked) {
1680 			/* we can't add it, so just ignore it */
1681 			return 1;
1682 		}
1683 		if (ctx->first_new_pos == UINT_MAX)
1684 			ctx->first_new_pos = array_count(&uidlist->records);
1685 		ctx->new_files_count++;
1686 		ctx->changed = TRUE;
1687 
1688 		if (uidlist->record_pool == NULL) {
1689 			uidlist->record_pool =
1690 				pool_alloconly_create(MEMPOOL_GROWING
1691 						      "uidlist record_pool",
1692 						      1024);
1693 		}
1694 
1695 		rec = p_new(uidlist->record_pool,
1696 			    struct maildir_uidlist_rec, 1);
1697 		rec->uid = (uint32_t)-1;
1698 		rec->filename = p_strdup(uidlist->record_pool, filename);
1699 		array_push_back(&uidlist->records, &rec);
1700 		uidlist->change_counter++;
1701 
1702 		hash_table_insert(uidlist->files, rec->filename, rec);
1703 	} else if (strcmp(rec->filename, filename) != 0) {
1704 		rec->filename = p_strdup(uidlist->record_pool, filename);
1705 	}
1706 	if (uid != 0) {
1707 		if (rec->uid != uid && rec->uid != (uint32_t)-1) {
1708 			mailbox_set_critical(uidlist->box,
1709 				"Maildir: %s changed UID %u -> %u",
1710 				filename, rec->uid, uid);
1711 			return -1;
1712 		}
1713 		rec->uid = uid;
1714 		if (uidlist->next_uid <= uid)
1715 			uidlist->next_uid = uid + 1;
1716 		else {
1717 			recs = array_get(&uidlist->records, &count);
1718 			if (count > 1 && uid < recs[count-1]->uid)
1719 				uidlist->unsorted = TRUE;
1720 		}
1721 	}
1722 
1723 	rec->flags &= ENUM_NEGATE(MAILDIR_UIDLIST_REC_FLAG_NEW_DIR);
1724 	rec->flags = (rec->flags | flags) &
1725 		ENUM_NEGATE(MAILDIR_UIDLIST_REC_FLAG_NONSYNCED);
1726 
1727 	ctx->finished = FALSE;
1728 	*rec_r = rec;
1729 	return 1;
1730 }
1731 
ext_dup(pool_t pool,const unsigned char * extensions)1732 static unsigned char *ext_dup(pool_t pool, const unsigned char *extensions)
1733 {
1734 	unsigned char *ret;
1735 
1736 	if (extensions == NULL)
1737 		return NULL;
1738 
1739 	T_BEGIN {
1740 		unsigned int len;
1741 
1742 		for (len = 0; extensions[len] != '\0'; len++) {
1743 			while (extensions[len] != '\0') len++;
1744 		}
1745 		ret = p_malloc(pool, len + 1);
1746 		memcpy(ret, extensions, len);
1747 	} T_END;
1748 	return ret;
1749 }
1750 
maildir_uidlist_sync_next(struct maildir_uidlist_sync_ctx * ctx,const char * filename,enum maildir_uidlist_rec_flag flags)1751 int maildir_uidlist_sync_next(struct maildir_uidlist_sync_ctx *ctx,
1752 			      const char *filename,
1753 			      enum maildir_uidlist_rec_flag flags)
1754 {
1755 	struct maildir_uidlist_rec *rec;
1756 
1757 	return maildir_uidlist_sync_next_uid(ctx, filename, 0, flags, &rec);
1758 }
1759 
maildir_uidlist_sync_next_uid(struct maildir_uidlist_sync_ctx * ctx,const char * filename,uint32_t uid,enum maildir_uidlist_rec_flag flags,struct maildir_uidlist_rec ** rec_r)1760 int maildir_uidlist_sync_next_uid(struct maildir_uidlist_sync_ctx *ctx,
1761 				  const char *filename, uint32_t uid,
1762 				  enum maildir_uidlist_rec_flag flags,
1763 				  struct maildir_uidlist_rec **rec_r)
1764 {
1765 	struct maildir_uidlist *uidlist = ctx->uidlist;
1766 	struct maildir_uidlist_rec *rec, *old_rec;
1767 	const char *p;
1768 
1769 	*rec_r = NULL;
1770 
1771 	if (ctx->failed)
1772 		return -1;
1773 	for (p = filename; *p != '\0'; p++) {
1774 		if (*p == 13 || *p == 10) {
1775 			i_warning("Maildir %s: Ignoring a file with #0x%x: %s",
1776 				  mailbox_get_path(uidlist->box), *p, filename);
1777 			return 1;
1778 		}
1779 	}
1780 
1781 	if (ctx->partial) {
1782 		return maildir_uidlist_sync_next_partial(ctx, filename,
1783 							 uid, flags, rec_r);
1784 	}
1785 
1786 	rec = hash_table_lookup(ctx->files, filename);
1787 	if (rec != NULL) {
1788 		if ((rec->flags & (MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
1789 				   MAILDIR_UIDLIST_REC_FLAG_MOVED)) == 0) {
1790 			/* possibly duplicate */
1791 			return 0;
1792 		}
1793 
1794 		/* probably was in new/ and now we're seeing it in cur/.
1795 		   remove new/moved flags so if this happens again we'll know
1796 		   to check for duplicates. */
1797 		rec->flags &= ENUM_NEGATE(MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
1798 					  MAILDIR_UIDLIST_REC_FLAG_MOVED);
1799 		if (strcmp(rec->filename, filename) != 0)
1800 			rec->filename = p_strdup(ctx->record_pool, filename);
1801 	} else {
1802 		old_rec = hash_table_lookup(uidlist->files, filename);
1803 		i_assert(old_rec != NULL || UIDLIST_IS_LOCKED(uidlist));
1804 
1805 		rec = p_new(ctx->record_pool, struct maildir_uidlist_rec, 1);
1806 
1807 		if (old_rec != NULL) {
1808 			*rec = *old_rec;
1809 			rec->extensions =
1810 				ext_dup(ctx->record_pool, rec->extensions);
1811 		} else {
1812 			rec->uid = (uint32_t)-1;
1813 			ctx->new_files_count++;
1814 			ctx->changed = TRUE;
1815 			/* didn't exist in uidlist, it's recent */
1816 			flags |= MAILDIR_UIDLIST_REC_FLAG_RECENT;
1817 		}
1818 		rec->filename = p_strdup(ctx->record_pool, filename);
1819 		hash_table_insert(ctx->files, rec->filename, rec);
1820 
1821 		array_push_back(&ctx->records, &rec);
1822 	}
1823 	if (uid != 0) {
1824 		rec->uid = uid;
1825 		if (uidlist->next_uid <= uid)
1826 			uidlist->next_uid = uid + 1;
1827 	}
1828 
1829 	rec->flags = (rec->flags | flags) & ENUM_NEGATE(MAILDIR_UIDLIST_REC_FLAG_NONSYNCED);
1830 	*rec_r = rec;
1831 	return 1;
1832 }
1833 
maildir_uidlist_sync_remove(struct maildir_uidlist_sync_ctx * ctx,const char * filename)1834 void maildir_uidlist_sync_remove(struct maildir_uidlist_sync_ctx *ctx,
1835 				 const char *filename)
1836 {
1837 	struct maildir_uidlist_rec *rec;
1838 	unsigned int idx;
1839 
1840 	i_assert(ctx->partial);
1841 	i_assert(ctx->uidlist->locked_refresh);
1842 
1843 	rec = hash_table_lookup(ctx->uidlist->files, filename);
1844 	i_assert(rec != NULL);
1845 	i_assert(rec->uid != (uint32_t)-1);
1846 
1847 	hash_table_remove(ctx->uidlist->files, filename);
1848 	idx = maildir_uidlist_records_array_delete(ctx->uidlist, rec);
1849 
1850 	if (ctx->first_unwritten_pos != UINT_MAX) {
1851 		i_assert(ctx->first_unwritten_pos > idx);
1852 		ctx->first_unwritten_pos--;
1853 	}
1854 	if (ctx->first_new_pos != UINT_MAX) {
1855 		i_assert(ctx->first_new_pos > idx);
1856 		ctx->first_new_pos--;
1857 	}
1858 
1859 	ctx->changed = TRUE;
1860 	ctx->uidlist->recreate = TRUE;
1861 }
1862 
maildir_uidlist_sync_set_ext(struct maildir_uidlist_sync_ctx * ctx,struct maildir_uidlist_rec * rec,enum maildir_uidlist_rec_ext_key key,const char * value)1863 void maildir_uidlist_sync_set_ext(struct maildir_uidlist_sync_ctx *ctx,
1864 				  struct maildir_uidlist_rec *rec,
1865 				  enum maildir_uidlist_rec_ext_key key,
1866 				  const char *value)
1867 {
1868 	pool_t pool = ctx->partial ?
1869 		ctx->uidlist->record_pool : ctx->record_pool;
1870 
1871 	i_assert(MAILDIR_UIDLIST_REC_EXT_KEY_IS_VALID(key));
1872 
1873 	maildir_uidlist_rec_set_ext(rec, pool, key, value);
1874 }
1875 
1876 const char *
maildir_uidlist_sync_get_full_filename(struct maildir_uidlist_sync_ctx * ctx,const char * filename)1877 maildir_uidlist_sync_get_full_filename(struct maildir_uidlist_sync_ctx *ctx,
1878 				       const char *filename)
1879 {
1880 	struct maildir_uidlist_rec *rec;
1881 
1882 	rec = hash_table_lookup(ctx->files, filename);
1883 	return rec == NULL ? NULL : rec->filename;
1884 }
1885 
maildir_uidlist_get_uid(struct maildir_uidlist * uidlist,const char * filename,uint32_t * uid_r)1886 bool maildir_uidlist_get_uid(struct maildir_uidlist *uidlist,
1887 			     const char *filename, uint32_t *uid_r)
1888 {
1889 	struct maildir_uidlist_rec *rec;
1890 
1891 	rec = hash_table_lookup(uidlist->files, filename);
1892 	if (rec == NULL)
1893 		return FALSE;
1894 
1895 	*uid_r = rec->uid;
1896 	return TRUE;
1897 }
1898 
maildir_uidlist_update_fname(struct maildir_uidlist * uidlist,const char * filename)1899 void maildir_uidlist_update_fname(struct maildir_uidlist *uidlist,
1900 				  const char *filename)
1901 {
1902 	struct maildir_uidlist_rec *rec;
1903 
1904 	rec = hash_table_lookup(uidlist->files, filename);
1905 	if (rec == NULL)
1906 		return;
1907 
1908 	rec->flags &= ENUM_NEGATE(MAILDIR_UIDLIST_REC_FLAG_NONSYNCED);
1909 	if (strcmp(rec->filename, filename) != 0)
1910 		rec->filename = p_strdup(uidlist->record_pool, filename);
1911 }
1912 
1913 const char *
maildir_uidlist_get_full_filename(struct maildir_uidlist * uidlist,const char * filename)1914 maildir_uidlist_get_full_filename(struct maildir_uidlist *uidlist,
1915 				  const char *filename)
1916 {
1917 	struct maildir_uidlist_rec *rec;
1918 
1919 	rec = hash_table_lookup(uidlist->files, filename);
1920 	return rec == NULL ? NULL : rec->filename;
1921 }
1922 
maildir_assign_uid_cmp(const void * p1,const void * p2)1923 static int maildir_assign_uid_cmp(const void *p1, const void *p2)
1924 {
1925 	const struct maildir_uidlist_rec *const *rec1 = p1, *const *rec2 = p2;
1926 
1927 	if ((*rec1)->uid != (*rec2)->uid) {
1928 		if ((*rec1)->uid < (*rec2)->uid)
1929 			return -1;
1930 		else
1931 			return 1;
1932 	}
1933 	return maildir_filename_sort_cmp((*rec1)->filename, (*rec2)->filename);
1934 }
1935 
maildir_uidlist_assign_uids(struct maildir_uidlist_sync_ctx * ctx)1936 static void maildir_uidlist_assign_uids(struct maildir_uidlist_sync_ctx *ctx)
1937 {
1938 	struct maildir_uidlist_rec **recs;
1939 	unsigned int dest, count;
1940 
1941 	i_assert(UIDLIST_IS_LOCKED(ctx->uidlist));
1942 	i_assert(ctx->first_new_pos != UINT_MAX);
1943 
1944 	if (ctx->first_unwritten_pos == UINT_MAX)
1945 		ctx->first_unwritten_pos = ctx->first_new_pos;
1946 
1947 	/* sort new files and assign UIDs for them */
1948 	recs = array_get_modifiable(&ctx->uidlist->records, &count);
1949 	qsort(recs + ctx->first_new_pos, count - ctx->first_new_pos,
1950 	      sizeof(*recs), maildir_assign_uid_cmp);
1951 
1952 	for (dest = ctx->first_new_pos; dest < count; dest++) {
1953 		if (recs[dest]->uid == (uint32_t)-1)
1954 			break;
1955 	}
1956 
1957 	for (; dest < count; dest++) {
1958 		i_assert(recs[dest]->uid == (uint32_t)-1);
1959 		i_assert(ctx->uidlist->next_uid < (uint32_t)-1);
1960 		recs[dest]->uid = ctx->uidlist->next_uid++;
1961 		recs[dest]->flags &= ENUM_NEGATE(MAILDIR_UIDLIST_REC_FLAG_MOVED);
1962 	}
1963 
1964 	if (ctx->uidlist->locked_refresh && ctx->uidlist->initial_read)
1965 		ctx->uidlist->last_seen_uid = ctx->uidlist->next_uid-1;
1966 
1967 	ctx->new_files_count = 0;
1968 	ctx->first_new_pos = UINT_MAX;
1969 	ctx->uidlist->change_counter++;
1970 	ctx->finish_change_counter = ctx->uidlist->change_counter;
1971 }
1972 
maildir_uidlist_swap(struct maildir_uidlist_sync_ctx * ctx)1973 static void maildir_uidlist_swap(struct maildir_uidlist_sync_ctx *ctx)
1974 {
1975 	struct maildir_uidlist *uidlist = ctx->uidlist;
1976 
1977 	/* buffer is unsorted, sort it by UID */
1978 	array_sort(&ctx->records, maildir_uid_cmp);
1979 
1980 	array_free(&uidlist->records);
1981 	uidlist->records = ctx->records;
1982 	ctx->records.arr.buffer = NULL;
1983 	i_assert(array_is_created(&uidlist->records));
1984 
1985 	hash_table_destroy(&uidlist->files);
1986 	uidlist->files = ctx->files;
1987 	i_zero(&ctx->files);
1988 
1989 	pool_unref(&uidlist->record_pool);
1990 	uidlist->record_pool = ctx->record_pool;
1991 	ctx->record_pool = NULL;
1992 
1993 	if (ctx->new_files_count != 0) {
1994 		ctx->first_new_pos = array_count(&uidlist->records) -
1995 			ctx->new_files_count;
1996 		maildir_uidlist_assign_uids(ctx);
1997 	} else {
1998 		ctx->uidlist->change_counter++;
1999 	}
2000 }
2001 
maildir_uidlist_sync_recreate(struct maildir_uidlist_sync_ctx * ctx)2002 void maildir_uidlist_sync_recreate(struct maildir_uidlist_sync_ctx *ctx)
2003 {
2004 	ctx->uidlist->recreate = TRUE;
2005 }
2006 
maildir_uidlist_sync_finish(struct maildir_uidlist_sync_ctx * ctx)2007 void maildir_uidlist_sync_finish(struct maildir_uidlist_sync_ctx *ctx)
2008 {
2009 	if (!ctx->partial) {
2010 		if (!ctx->failed)
2011 			maildir_uidlist_swap(ctx);
2012 	} else {
2013 		if (ctx->new_files_count != 0 && !ctx->failed) {
2014 			i_assert(ctx->changed);
2015 			i_assert(ctx->locked);
2016 			maildir_uidlist_assign_uids(ctx);
2017 		}
2018 	}
2019 
2020 	ctx->finished = TRUE;
2021 
2022 	/* mbox=NULL means we're coming from dbox rebuilding code.
2023 	   the dbox is already locked, so allow uidlist recreation */
2024 	i_assert(ctx->locked || !ctx->changed);
2025 	if ((ctx->changed || maildir_uidlist_want_compress(ctx)) &&
2026 	    !ctx->failed && ctx->locked) {
2027 		T_BEGIN {
2028 			if (maildir_uidlist_sync_update(ctx) < 0) {
2029 				/* we couldn't write everything we wanted. make
2030 				   sure we don't continue using those UIDs */
2031 				maildir_uidlist_reset(ctx->uidlist);
2032 				ctx->failed = TRUE;
2033 			}
2034 		} T_END;
2035 	}
2036 }
2037 
maildir_uidlist_sync_deinit(struct maildir_uidlist_sync_ctx ** _ctx,bool success)2038 int maildir_uidlist_sync_deinit(struct maildir_uidlist_sync_ctx **_ctx,
2039 				bool success)
2040 {
2041 	struct maildir_uidlist_sync_ctx *ctx = *_ctx;
2042 	int ret;
2043 
2044 	*_ctx = NULL;
2045 
2046 	if (!success)
2047 		ctx->failed = TRUE;
2048 	ret = ctx->failed ? -1 : 0;
2049 
2050 	if (!ctx->finished)
2051 		maildir_uidlist_sync_finish(ctx);
2052 	if (ctx->partial)
2053 		maildir_uidlist_mark_all(ctx->uidlist, FALSE);
2054 	if (ctx->locked)
2055 		maildir_uidlist_unlock(ctx->uidlist);
2056 
2057 	hash_table_destroy(&ctx->files);
2058 	pool_unref(&ctx->record_pool);
2059 	if (array_is_created(&ctx->records))
2060 		array_free(&ctx->records);
2061 	i_free(ctx);
2062 	return ret;
2063 }
2064 
maildir_uidlist_add_flags(struct maildir_uidlist * uidlist,const char * filename,enum maildir_uidlist_rec_flag flags)2065 void maildir_uidlist_add_flags(struct maildir_uidlist *uidlist,
2066 			       const char *filename,
2067 			       enum maildir_uidlist_rec_flag flags)
2068 {
2069 	struct maildir_uidlist_rec *rec;
2070 
2071 	rec = hash_table_lookup(uidlist->files, filename);
2072 	i_assert(rec != NULL);
2073 
2074 	rec->flags |= flags;
2075 }
2076 
2077 struct maildir_uidlist_iter_ctx *
maildir_uidlist_iter_init(struct maildir_uidlist * uidlist)2078 maildir_uidlist_iter_init(struct maildir_uidlist *uidlist)
2079 {
2080 	struct maildir_uidlist_iter_ctx *ctx;
2081 	unsigned int count;
2082 
2083 	ctx = i_new(struct maildir_uidlist_iter_ctx, 1);
2084 	ctx->uidlist = uidlist;
2085 	ctx->next = array_get(&uidlist->records, &count);
2086 	ctx->end = ctx->next + count;
2087 	ctx->change_counter = ctx->uidlist->change_counter;
2088 	return ctx;
2089 }
2090 
2091 static void
maildir_uidlist_iter_update_idx(struct maildir_uidlist_iter_ctx * ctx)2092 maildir_uidlist_iter_update_idx(struct maildir_uidlist_iter_ctx *ctx)
2093 {
2094 	unsigned int old_rev_idx, idx, count;
2095 
2096 	old_rev_idx = ctx->end - ctx->next;
2097 	ctx->next = array_get(&ctx->uidlist->records, &count);
2098 	ctx->end = ctx->next + count;
2099 
2100 	idx = old_rev_idx >= count ? 0 :
2101 		count - old_rev_idx;
2102 	while (idx < count && ctx->next[idx]->uid <= ctx->prev_uid)
2103 		idx++;
2104 	while (idx > 0 && ctx->next[idx-1]->uid > ctx->prev_uid)
2105 		idx--;
2106 
2107 	ctx->next += idx;
2108 }
2109 
maildir_uidlist_iter_next_rec(struct maildir_uidlist_iter_ctx * ctx,struct maildir_uidlist_rec ** rec_r)2110 static bool maildir_uidlist_iter_next_rec(struct maildir_uidlist_iter_ctx *ctx,
2111 					  struct maildir_uidlist_rec **rec_r)
2112 {
2113 	struct maildir_uidlist_rec *rec;
2114 
2115 	if (ctx->change_counter != ctx->uidlist->change_counter)
2116 		maildir_uidlist_iter_update_idx(ctx);
2117 
2118 	if (ctx->next == ctx->end)
2119 		return FALSE;
2120 
2121 	rec = *ctx->next;
2122 	i_assert(rec->uid != (uint32_t)-1);
2123 
2124 	ctx->prev_uid = rec->uid;
2125 	ctx->next++;
2126 
2127 	*rec_r = rec;
2128 	return TRUE;
2129 }
2130 
maildir_uidlist_iter_next(struct maildir_uidlist_iter_ctx * ctx,uint32_t * uid_r,enum maildir_uidlist_rec_flag * flags_r,const char ** filename_r)2131 bool maildir_uidlist_iter_next(struct maildir_uidlist_iter_ctx *ctx,
2132 			       uint32_t *uid_r,
2133 			       enum maildir_uidlist_rec_flag *flags_r,
2134 			       const char **filename_r)
2135 {
2136 	struct maildir_uidlist_rec *rec;
2137 
2138 	if (!maildir_uidlist_iter_next_rec(ctx, &rec))
2139 		return FALSE;
2140 
2141 	*uid_r = rec->uid;
2142 	*flags_r = rec->flags;
2143 	*filename_r = rec->filename;
2144 	return TRUE;
2145 }
2146 
maildir_uidlist_iter_deinit(struct maildir_uidlist_iter_ctx ** _ctx)2147 void maildir_uidlist_iter_deinit(struct maildir_uidlist_iter_ctx **_ctx)
2148 {
2149 	i_free(*_ctx);
2150 	*_ctx = NULL;
2151 }
2152