1 /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "pop3-common.h"
4 #include "array.h"
5 #include "istream.h"
6 #include "ostream.h"
7 #include "hash.h"
8 #include "str.h"
9 #include "var-expand.h"
10 #include "message-size.h"
11 #include "mail-storage.h"
12 #include "mail-storage-settings.h"
13 #include "mail-search-build.h"
14 #include "pop3-capability.h"
15 #include "pop3-commands.h"
16 
17 static enum mail_sort_type pop3_sort_program[] = {
18 	MAIL_SORT_POP3_ORDER,
19 	MAIL_SORT_END
20 };
21 
msgnum_to_seq(struct client * client,uint32_t msgnum)22 static uint32_t msgnum_to_seq(struct client *client, uint32_t msgnum)
23 {
24 	return msgnum < client->msgnum_to_seq_map_count ?
25 		client->msgnum_to_seq_map[msgnum] : msgnum+1;
26 }
27 
get_msgnum(struct client * client,const char * args,unsigned int * msgnum,bool thenspace)28 static const char *get_msgnum(struct client *client, const char *args,
29 			      unsigned int *msgnum, bool thenspace)
30 {
31 	unsigned int num;
32 
33 	if (*args < '0' || *args > '9') {
34 		client_send_line(client,
35 				 "-ERR Invalid message number: %s", args);
36 		return NULL;
37 	}
38 	if (str_parse_uint(args, &num, &args) < 0) {
39 		client_send_line(client,
40 				 "-ERR Message number too large: %s", args);
41 		return NULL;
42 	}
43 	if (*args != (thenspace ? ' ' : '\0')) {
44 		client_send_line(client,
45 				 "-ERR Noise after message number: %s", args);
46 		return NULL;
47 	}
48 	if (num == 0 || num > client->messages_count) {
49 		client_send_line(client,
50 				 "-ERR There's no message %u.", num);
51 		return NULL;
52 	}
53 	num--;
54 
55 	if (client->deleted) {
56 		if ((client->deleted_bitmask[num / CHAR_BIT] &
57 		     (1 << (num % CHAR_BIT))) != 0) {
58 			client_send_line(client, "-ERR Message is deleted.");
59 			return NULL;
60 		}
61 	}
62 
63 	while (*args == ' ') args++;
64 
65 	*msgnum = num;
66 	return args;
67 }
68 
get_size(struct client * client,const char * args,uoff_t * size,bool thenspace)69 static const char *get_size(struct client *client, const char *args,
70 			    uoff_t *size, bool thenspace)
71 {
72 	uoff_t num;
73 
74 	if (*args < '0' || *args > '9') {
75 		client_send_line(client, "-ERR Invalid size: %s",
76 				 args);
77 		return NULL;
78 	}
79 	if (str_parse_uoff(args, &num, &args) < 0) {
80 		client_send_line(client, "-ERR Size too large: %s",
81 				 args);
82 		return NULL;
83 	}
84 	if (*args != (thenspace ? ' ' : '\0')) {
85 		client_send_line(client, "-ERR Noise after size: %s", args);
86 		return NULL;
87 	}
88 
89 	while (*args == ' ') args++;
90 
91 	*size = num;
92 	return args;
93 }
94 
cmd_capa(struct client * client,const char * args ATTR_UNUSED)95 static int cmd_capa(struct client *client, const char *args ATTR_UNUSED)
96 {
97 	client_send_line(client, "+OK\r\n"POP3_CAPABILITY_REPLY".");
98 	return 1;
99 }
100 
cmd_dele(struct client * client,const char * args)101 static int cmd_dele(struct client *client, const char *args)
102 {
103 	unsigned int msgnum;
104 
105 	if (get_msgnum(client, args, &msgnum, FALSE) == NULL)
106 		return -1;
107 
108 	if (!client->deleted) {
109 		client->deleted_bitmask = i_malloc(MSGS_BITMASK_SIZE(client));
110 		client->deleted = TRUE;
111 	}
112 
113 	client->deleted_bitmask[msgnum / CHAR_BIT] |= 1 << (msgnum % CHAR_BIT);
114 	client->deleted_count++;
115 	client->deleted_size += client->message_sizes[msgnum];
116 	client_send_line(client, "+OK Marked to be deleted.");
117 	return 1;
118 }
119 
120 struct cmd_list_context {
121 	unsigned int msgnum;
122 };
123 
cmd_list_callback(struct client * client)124 static void cmd_list_callback(struct client *client)
125 {
126 	struct cmd_list_context *ctx = client->cmd_context;
127 
128 	for (; ctx->msgnum != client->messages_count; ctx->msgnum++) {
129 		if (client->output->closed)
130 			break;
131 		if (POP3_CLIENT_OUTPUT_FULL(client)) {
132 			/* buffer full */
133 			return;
134 		}
135 
136 		if (client->deleted) {
137 			if ((client->deleted_bitmask[ctx->msgnum / CHAR_BIT] &
138 			     (1 << (ctx->msgnum % CHAR_BIT))) != 0)
139 				continue;
140 		}
141 
142 		client_send_line(client, "%u %"PRIuUOFF_T, ctx->msgnum+1,
143 				 client->message_sizes[ctx->msgnum]);
144 	}
145 
146 	client_send_line(client, ".");
147 
148 	i_free(ctx);
149 	client->cmd = NULL;
150 }
151 
cmd_list(struct client * client,const char * args)152 static int cmd_list(struct client *client, const char *args)
153 {
154         struct cmd_list_context *ctx;
155 
156 	if (*args == '\0') {
157 		ctx = i_new(struct cmd_list_context, 1);
158 		client_send_line(client, "+OK %u messages:",
159 				 client->messages_count - client->deleted_count);
160 
161 		client->cmd = cmd_list_callback;
162 		client->cmd_context = ctx;
163 		cmd_list_callback(client);
164 	} else {
165 		unsigned int msgnum;
166 
167 		if (get_msgnum(client, args, &msgnum, FALSE) == NULL)
168 			return -1;
169 
170 		client_send_line(client, "+OK %u %"PRIuUOFF_T, msgnum+1,
171 				 client->message_sizes[msgnum]);
172 	}
173 
174 	return 1;
175 }
176 
cmd_last(struct client * client,const char * args ATTR_UNUSED)177 static int cmd_last(struct client *client, const char *args ATTR_UNUSED)
178 {
179 	client_send_line(client, "+OK %u", client->last_seen_pop3_msn);
180 	return 1;
181 }
182 
cmd_noop(struct client * client,const char * args ATTR_UNUSED)183 static int cmd_noop(struct client *client, const char *args ATTR_UNUSED)
184 {
185 	client_send_line(client, "+OK");
186 	return 1;
187 }
188 
189 static struct mail_search_args *
pop3_search_build_seqset(ARRAY_TYPE (seq_range)* seqset)190 pop3_search_build_seqset(ARRAY_TYPE(seq_range) *seqset)
191 {
192 	struct mail_search_args *search_args;
193 	struct mail_search_arg *sarg;
194 
195 	search_args = mail_search_build_init();
196 	sarg = mail_search_build_add(search_args, SEARCH_SEQSET);
197 	sarg->value.seqset = *seqset;
198 	return search_args;
199 }
200 
201 static struct mail_search_args *
pop3_search_build(struct client * client,uint32_t seq)202 pop3_search_build(struct client *client, uint32_t seq)
203 {
204 	struct mail_search_args *search_args;
205 
206 	if (seq == 0)
207 		return pop3_search_build_seqset(&client->all_seqs);
208 
209 	search_args = mail_search_build_init();
210 	mail_search_build_add_seqset(search_args, seq, seq);
211 	return search_args;
212 }
213 
client_verify_ordering(struct client * client,struct mail * mail,uint32_t msgnum)214 static int client_verify_ordering(struct client *client,
215 				  struct mail *mail, uint32_t msgnum)
216 {
217 	uint32_t seq;
218 
219 	seq = msgnum_to_seq(client, msgnum);
220 	if (seq != mail->seq) {
221 		i_error("Message ordering changed unexpectedly "
222 			"(msg #%u: storage seq %u -> %u)",
223 			msgnum+1, seq, mail->seq);
224 		return -1;
225 	}
226 	return 0;
227 }
228 
client_expunge(struct client * client,struct mail * mail)229 static void client_expunge(struct client *client, struct mail *mail)
230 {
231 	switch (client->set->parsed_delete_type) {
232 	case POP3_DELETE_TYPE_EXPUNGE:
233 		mail_expunge(mail);
234 		break;
235 	case POP3_DELETE_TYPE_FLAG:
236 		i_assert(client->deleted_kw != NULL);
237 		mail_update_keywords(mail, MODIFY_ADD, client->deleted_kw);
238 		break;
239 	}
240 }
241 
client_update_mails(struct client * client)242 bool client_update_mails(struct client *client)
243 {
244 	struct mail_search_args *search_args;
245 	struct mail_search_context *ctx;
246 	struct mail *mail;
247 	ARRAY_TYPE(seq_range) deleted_msgs, seen_msgs;
248 	uint32_t msgnum, bit;
249 	bool ret = TRUE;
250 
251 	if (mailbox_is_readonly(client->mailbox)) {
252 		/* silently ignore */
253 		return TRUE;
254 	}
255 
256 	/* translate msgnums to sequences (in case POP3 ordering is
257 	   different) */
258 	t_array_init(&deleted_msgs, 8);
259 	if (client->deleted_bitmask != NULL && client->quit_seen) {
260 		for (msgnum = 0; msgnum < client->messages_count; msgnum++) {
261 			bit = 1 << (msgnum % CHAR_BIT);
262 			if ((client->deleted_bitmask[msgnum / CHAR_BIT] & bit) != 0)
263 				seq_range_array_add(&deleted_msgs, msgnum_to_seq(client, msgnum));
264 		}
265 	}
266 	t_array_init(&seen_msgs, 8);
267 	if (client->seen_bitmask != NULL) {
268 		for (msgnum = 0; msgnum < client->messages_count; msgnum++) {
269 			bit = 1 << (msgnum % CHAR_BIT);
270 			if ((client->seen_bitmask[msgnum / CHAR_BIT] & bit) != 0)
271 				seq_range_array_add(&seen_msgs, msgnum_to_seq(client, msgnum));
272 		}
273 	}
274 
275 	if (array_count(&deleted_msgs) > 0) {
276 		/* expunge DELEted mails */
277 		search_args = pop3_search_build_seqset(&deleted_msgs);
278 		ctx = mailbox_search_init(client->trans, search_args, NULL, 0, NULL);
279 		mail_search_args_unref(&search_args);
280 
281 		while (mailbox_search_next(ctx, &mail))
282 			client_expunge(client, mail);
283 		if (mailbox_search_deinit(&ctx) < 0)
284 			ret = FALSE;
285 		/* don't bother setting \Seen flags for deleted messages */
286 		seq_range_array_invert(&deleted_msgs, 1, client->highest_seq);
287 		seq_range_array_intersect(&seen_msgs, &deleted_msgs);
288 	}
289 
290 	if (array_count(&seen_msgs) > 0) {
291 		/* add \Seen flags for RETRed mails */
292 		search_args = pop3_search_build_seqset(&seen_msgs);
293 		ctx = mailbox_search_init(client->trans, search_args, NULL, 0, NULL);
294 		mail_search_args_unref(&search_args);
295 
296 		while (mailbox_search_next(ctx, &mail))
297 			mail_update_flags(mail, MODIFY_ADD, MAIL_SEEN);
298 		if (mailbox_search_deinit(&ctx) < 0)
299 			ret = FALSE;
300 	}
301 
302 	client->seen_change_count = 0;
303 	return ret;
304 }
305 
cmd_quit(struct client * client,const char * args ATTR_UNUSED)306 static int cmd_quit(struct client *client, const char *args ATTR_UNUSED)
307 {
308 	client->quit_seen = TRUE;
309 	if (client->deleted || client->seen_bitmask != NULL) {
310 		if (!client_update_mails(client)) {
311 			client_send_storage_error(client);
312 			client_disconnect(client,
313 				"Storage error during logout.");
314 			return 1;
315 		}
316 	}
317 
318 	if (mailbox_transaction_commit(&client->trans) < 0 ||
319 	    mailbox_sync(client->mailbox, MAILBOX_SYNC_FLAG_FULL_WRITE) < 0) {
320 		client_send_storage_error(client);
321 		client_disconnect(client, "Storage error during logout.");
322 		return 1;
323 	} else {
324 		client->delete_success = TRUE;
325 	}
326 
327 	if (!client->deleted)
328 		client_send_line(client, "+OK Logging out.");
329 	else
330 		client_send_line(client, "+OK Logging out, messages deleted.");
331 
332 	client_disconnect(client, "Logged out");
333 	return 1;
334 }
335 
336 struct fetch_context {
337 	struct mail *mail;
338 	struct istream *stream;
339 	uoff_t body_lines;
340 
341 	uoff_t *byte_counter;
342 	uoff_t byte_counter_offset;
343 
344 	unsigned char last;
345 	bool cr_skipped, in_body;
346 };
347 
fetch_deinit(struct fetch_context * ctx)348 static void fetch_deinit(struct fetch_context *ctx)
349 {
350 	mail_free(&ctx->mail);
351 	i_free(ctx);
352 }
353 
fetch_callback(struct client * client)354 static void fetch_callback(struct client *client)
355 {
356 	struct fetch_context *ctx = client->cmd_context;
357 	const unsigned char *data;
358 	unsigned char add;
359 	size_t i, size;
360 	int ret;
361 
362 	while ((ctx->body_lines > 0 || !ctx->in_body) &&
363 	       i_stream_read_more(ctx->stream, &data, &size) > 0) {
364 		if (size > 4096)
365 			size = 4096;
366 
367 		add = '\0';
368 		for (i = 0; i < size; i++) {
369 			if ((data[i] == '\r' || data[i] == '\n') &&
370 			    !ctx->in_body) {
371 				if (i == 0 && (ctx->last == '\0' ||
372 					       ctx->last == '\n'))
373 					ctx->in_body = TRUE;
374 				else if (i > 0 && data[i-1] == '\n')
375 					ctx->in_body = TRUE;
376 			}
377 
378 			if (data[i] == '\n') {
379 				if ((i == 0 && ctx->last != '\r') ||
380 				    (i > 0 && data[i-1] != '\r')) {
381 					/* missing CR */
382 					add = '\r';
383 					break;
384 				}
385 
386 				if (ctx->in_body) {
387 					if (--ctx->body_lines == 0) {
388 						i++;
389 						break;
390 					}
391 				}
392 			} else if (data[i] == '.' &&
393 				   ((i == 0 && ctx->last == '\n') ||
394 				    (i > 0 && data[i-1] == '\n'))) {
395 				/* escape the dot */
396 				add = '.';
397 				break;
398 			} else if (data[i] == '\0' &&
399 				   (client->set->parsed_workarounds &
400 				    WORKAROUND_OUTLOOK_NO_NULS) != 0) {
401 				add = 0x80;
402 				break;
403 			}
404 		}
405 
406 		if (i > 0) {
407 			if (o_stream_send(client->output, data, i) < 0)
408 				break;
409 			ctx->last = data[i-1];
410 			i_stream_skip(ctx->stream, i);
411 		}
412 
413 		if (o_stream_get_buffer_used_size(client->output) >= 4096) {
414 			if ((ret = o_stream_flush(client->output)) < 0)
415 				break;
416 			if (ret == 0) {
417 				/* continue later */
418 				return;
419 			}
420 		}
421 
422 		if (add != '\0') {
423 			if (o_stream_send(client->output, &add, 1) < 0)
424 				break;
425 
426 			ctx->last = add;
427 			if (add == 0x80)
428 				i_stream_skip(ctx->stream, 1);
429 		}
430 	}
431 
432 	if (ctx->last != '\n') {
433 		/* didn't end with CRLF */
434 		o_stream_nsend(client->output, "\r\n", 2);
435 	}
436 
437 	if (!ctx->in_body &&
438 	    (client->set->parsed_workarounds & WORKAROUND_OE_NS_EOH) != 0) {
439 		/* Add the missing end of headers line. */
440 		o_stream_nsend(client->output, "\r\n", 2);
441 	}
442 
443 	*ctx->byte_counter +=
444 		client->output->offset - ctx->byte_counter_offset;
445 
446 	client_send_line(client, ".");
447 	fetch_deinit(ctx);
448 	client->cmd = NULL;
449 }
450 
client_reply_msg_expunged(struct client * client,unsigned int msgnum)451 static int client_reply_msg_expunged(struct client *client, unsigned int msgnum)
452 {
453 	client_send_line(client, "-ERR Message %u expunged.", msgnum + 1);
454 	if (msgnum <= client->highest_expunged_fetch_msgnum) {
455 		/* client tried to fetch an expunged message again.
456 		   treat this as error so we'll eventually disconnect the
457 		   client instead of letting it loop forever. */
458 		return -1;
459 	}
460 	client->highest_expunged_fetch_msgnum = msgnum;
461 	return 1;
462 }
463 
fetch(struct client * client,unsigned int msgnum,uoff_t body_lines,const char * reason,uoff_t * byte_counter)464 static int fetch(struct client *client, unsigned int msgnum, uoff_t body_lines,
465 		 const char *reason, uoff_t *byte_counter)
466 {
467         struct fetch_context *ctx;
468 	int ret;
469 
470 	ctx = i_new(struct fetch_context, 1);
471 	ctx->byte_counter = byte_counter;
472 	ctx->byte_counter_offset = client->output->offset;
473 	ctx->mail = mail_alloc(client->trans,
474 			       MAIL_FETCH_STREAM_HEADER |
475 			       MAIL_FETCH_STREAM_BODY, NULL);
476 	mail_set_seq(ctx->mail, msgnum_to_seq(client, msgnum));
477 
478 	if (mail_get_stream_because(ctx->mail, NULL, NULL, reason, &ctx->stream) < 0) {
479 		ret = client_reply_msg_expunged(client, msgnum);
480 		fetch_deinit(ctx);
481 		return ret;
482 	}
483 
484 	if (body_lines == UOFF_T_MAX && client->seen_bitmask != NULL) {
485 		if ((mail_get_flags(ctx->mail) & MAIL_SEEN) == 0) {
486 			/* mark the message seen with RETR command */
487 			client->seen_bitmask[msgnum / CHAR_BIT] |=
488 				1 << (msgnum % CHAR_BIT);
489 			client->seen_change_count++;
490 		}
491 	}
492 
493 	ctx->body_lines = body_lines;
494 	if (body_lines == UOFF_T_MAX) {
495 		client_send_line(client, "+OK %"PRIuUOFF_T" octets",
496 				 client->message_sizes[msgnum]);
497 	} else {
498 		client_send_line(client, "+OK");
499 		ctx->body_lines++; /* internally we count the empty line too */
500 	}
501 
502 	client->cmd = fetch_callback;
503 	client->cmd_context = ctx;
504 	fetch_callback(client);
505 	return 1;
506 }
507 
cmd_retr(struct client * client,const char * args)508 static int cmd_retr(struct client *client, const char *args)
509 {
510 	unsigned int msgnum;
511 
512 	if (get_msgnum(client, args, &msgnum, FALSE) == NULL)
513 		return -1;
514 
515 	if (client->lowest_retr_pop3_msn > msgnum+1 ||
516 	    client->lowest_retr_pop3_msn == 0)
517 		client->lowest_retr_pop3_msn = msgnum+1;
518 	if (client->last_seen_pop3_msn < msgnum+1)
519 		client->last_seen_pop3_msn = msgnum+1;
520 
521 	client->retr_count++;
522 	return fetch(client, msgnum, UOFF_T_MAX, "RETR", &client->retr_bytes);
523 }
524 
cmd_rset(struct client * client,const char * args ATTR_UNUSED)525 static int cmd_rset(struct client *client, const char *args ATTR_UNUSED)
526 {
527 	struct mail_search_context *search_ctx;
528 	struct mail *mail;
529 	struct mail_search_args *search_args;
530 
531 	client->last_seen_pop3_msn = 0;
532 
533 	if (client->deleted) {
534 		client->deleted = FALSE;
535 		memset(client->deleted_bitmask, 0, MSGS_BITMASK_SIZE(client));
536 		client->deleted_count = 0;
537 		client->deleted_size = 0;
538 	}
539 	if (client->seen_change_count > 0) {
540 		memset(client->seen_bitmask, 0, MSGS_BITMASK_SIZE(client));
541 		client->seen_change_count = 0;
542 	}
543 
544 	if (client->set->pop3_enable_last) {
545 		/* remove all \Seen flags (as specified by RFC 1460) */
546 		search_args = pop3_search_build(client, 0);
547 		search_ctx = mailbox_search_init(client->trans,
548 						 search_args, NULL, 0, NULL);
549 		mail_search_args_unref(&search_args);
550 
551 		while (mailbox_search_next(search_ctx, &mail))
552 			mail_update_flags(mail, MODIFY_REMOVE, MAIL_SEEN);
553 		(void)mailbox_search_deinit(&search_ctx);
554 
555 		(void)mailbox_transaction_commit(&client->trans);
556 		client->trans = mailbox_transaction_begin(client->mailbox, 0,
557 							  __func__);
558 	}
559 
560 	client_send_line(client, "+OK");
561 	return 1;
562 }
563 
cmd_stat(struct client * client,const char * args ATTR_UNUSED)564 static int cmd_stat(struct client *client, const char *args ATTR_UNUSED)
565 {
566 	client_send_line(client, "+OK %u %"PRIuUOFF_T,
567 			 client->messages_count - client->deleted_count,
568 			 client->total_size - client->deleted_size);
569 	return 1;
570 }
571 
cmd_top(struct client * client,const char * args)572 static int cmd_top(struct client *client, const char *args)
573 {
574 	unsigned int msgnum;
575 	uoff_t max_lines;
576 
577 	args = get_msgnum(client, args, &msgnum, TRUE);
578 	if (args == NULL)
579 		return -1;
580 	if (get_size(client, args, &max_lines, FALSE) == NULL)
581 		return -1;
582 
583 	client->top_count++;
584 	return fetch(client, msgnum, max_lines, "TOP", &client->top_bytes);
585 }
586 
587 struct cmd_uidl_context {
588 	struct mail_search_context *search_ctx;
589 	struct mail *mail;
590 	uint32_t msgnum;
591 	bool list_all;
592 };
593 
594 static int
pop3_get_uid(struct client * client,struct mail * mail,string_t * str,bool * permanent_uidl_r)595 pop3_get_uid(struct client *client, struct mail *mail, string_t *str,
596 	     bool *permanent_uidl_r)
597 {
598 	char uid_str[MAX_INT_STRLEN] = { 0 };
599 	const char *uidl;
600 	const char *hdr_md5 = NULL, *filename = NULL, *guid = NULL;
601 
602 	if (mail_get_special(mail, MAIL_FETCH_UIDL_BACKEND, &uidl) == 0 &&
603 	    *uidl != '\0') {
604 		str_append(str, uidl);
605 		/* UIDL is already permanent */
606 		*permanent_uidl_r = TRUE;
607 		return 0;
608 	}
609 
610 	*permanent_uidl_r = FALSE;
611 
612 	if (client->set->pop3_reuse_xuidl &&
613 	    mail_get_first_header(mail, "X-UIDL", &uidl) > 0) {
614 		str_append(str, uidl);
615 		return 0;
616 	}
617 
618 	if ((client->uidl_keymask & UIDL_UID) != 0) {
619 		if (i_snprintf(uid_str, sizeof(uid_str), "%u", mail->uid) < 0)
620 			i_unreached();
621 	}
622 	if ((client->uidl_keymask & UIDL_MD5) != 0) {
623 		if (mail_get_special(mail, MAIL_FETCH_HEADER_MD5,
624 				     &hdr_md5) < 0) {
625 			i_error("UIDL: Header MD5 lookup failed: %s",
626 				mailbox_get_last_internal_error(mail->box, NULL));
627 			return -1;
628 		} else if (hdr_md5[0] == '\0') {
629 			i_error("UIDL: Header MD5 not found "
630 				"(pop3_uidl_format=%%m not supported by storage?)");
631 			return -1;
632 		}
633 	}
634 	if ((client->uidl_keymask & UIDL_FILE_NAME) != 0) {
635 		if (mail_get_special(mail, MAIL_FETCH_STORAGE_ID,
636 				     &filename) < 0) {
637 			i_error("UIDL: File name lookup failed: %s",
638 				mailbox_get_last_internal_error(mail->box, NULL));
639 			return -1;
640 		} else if (filename[0] == '\0') {
641 			i_error("UIDL: File name not found "
642 				"(pop3_uidl_format=%%f not supported by storage?)");
643 			return -1;
644 		}
645 	}
646 	if ((client->uidl_keymask & UIDL_GUID) != 0) {
647 		if (mail_get_special(mail, MAIL_FETCH_GUID,
648 				     &guid) < 0) {
649 			i_error("UIDL: Message GUID lookup failed: %s",
650 				mailbox_get_last_internal_error(mail->box, NULL));
651 			return -1;
652 		} else if (guid[0] == '\0') {
653 			i_error("UIDL: Message GUID not found "
654 				"(pop3_uidl_format=%%g not supported by storage?)");
655 			return -1;
656 		}
657 	}
658 
659 	const struct var_expand_table tab[] = {
660 		{ 'v', dec2str(client->uid_validity), "uidvalidity" },
661 		{ 'u', uid_str, "uid" },
662 		{ 'm', hdr_md5, "md5" },
663 		{ 'f', filename, "filename" },
664 		{ 'g', guid, "guid" },
665 		{ '\0', NULL, NULL }
666 	};
667 	const char *error;
668 
669 	if (var_expand(str, client->mail_set->pop3_uidl_format,
670 		       tab, &error) <= 0) {
671 		i_error("UIDL: Failed to expand pop3_uidl_format=%s: %s",
672 			client->mail_set->pop3_uidl_format, error);
673 		return -1;
674 	}
675 	return 0;
676 }
677 
678 static bool
list_uidls_saved_iter(struct client * client,struct cmd_uidl_context * ctx)679 list_uidls_saved_iter(struct client *client, struct cmd_uidl_context *ctx)
680 {
681 	bool found = FALSE;
682 
683 	while (ctx->msgnum < client->messages_count) {
684 		uint32_t msgnum = ctx->msgnum++;
685 
686 		if (client->deleted) {
687 			if ((client->deleted_bitmask[msgnum / CHAR_BIT] &
688 			     (1 << (msgnum % CHAR_BIT))) != 0)
689 				continue;
690 		}
691 		found = TRUE;
692 
693 		client_send_line(client,
694 				 ctx->list_all ? "%u %s" : "+OK %u %s",
695 				 msgnum+1, client->message_uidls[msgnum]);
696 		if (client->output->closed || !ctx->list_all)
697 			break;
698 		if (POP3_CLIENT_OUTPUT_FULL(client)) {
699 			/* output is being buffered, continue when there's
700 			   more space */
701 			return FALSE;
702 		}
703 	}
704 	/* finished */
705 	client->cmd = NULL;
706 
707 	if (ctx->list_all)
708 		client_send_line(client, ".");
709 	i_free(ctx);
710 	return found;
711 }
712 
list_uids_iter(struct client * client,struct cmd_uidl_context * ctx)713 static bool list_uids_iter(struct client *client, struct cmd_uidl_context *ctx)
714 {
715 	string_t *str;
716 	bool permanent_uidl, found = FALSE;
717 	bool failed = FALSE;
718 
719 	if (client->message_uidls != NULL)
720 		return list_uidls_saved_iter(client, ctx);
721 
722 	str = t_str_new(128);
723 	while (mailbox_search_next(ctx->search_ctx, &ctx->mail)) {
724 		uint32_t msgnum = ctx->msgnum++;
725 
726 		if (client_verify_ordering(client, ctx->mail, msgnum) < 0) {
727 			failed = TRUE;
728 			break;
729 		}
730 		if (client->deleted) {
731 			if ((client->deleted_bitmask[msgnum / CHAR_BIT] &
732 			     (1 << (msgnum % CHAR_BIT))) != 0)
733 				continue;
734 		}
735 		found = TRUE;
736 
737 		str_truncate(str, 0);
738 		if (pop3_get_uid(client, ctx->mail, str, &permanent_uidl) < 0) {
739 			failed = TRUE;
740 			break;
741 		}
742 		if (client->set->pop3_save_uidl && !permanent_uidl)
743 			mail_update_pop3_uidl(ctx->mail, str_c(str));
744 
745 		client_send_line(client, ctx->list_all ? "%u %s" : "+OK %u %s",
746 				 msgnum+1, str_c(str));
747 		if (client->output->closed)
748 			break;
749 		if (POP3_CLIENT_OUTPUT_FULL(client) && ctx->list_all) {
750 			/* output is being buffered, continue when there's
751 			   more space */
752 			return FALSE;
753 		}
754 	}
755 
756 	/* finished */
757 	(void)mailbox_search_deinit(&ctx->search_ctx);
758 
759 	client->cmd = NULL;
760 
761 	if (ctx->list_all && !failed)
762 		client_send_line(client, ".");
763 	i_free(ctx);
764 	if (failed)
765 		client_disconnect(client, "POP3 UIDLs couldn't be listed");
766 	return found || failed;
767 }
768 
cmd_uidl_callback(struct client * client)769 static void cmd_uidl_callback(struct client *client)
770 {
771 	struct cmd_uidl_context *ctx = client->cmd_context;
772 
773         (void)list_uids_iter(client, ctx);
774 }
775 
776 HASH_TABLE_DEFINE_TYPE(uidl_counter, char *, void *);
777 
778 static void
uidl_rename_duplicate(string_t * uidl,HASH_TABLE_TYPE (uidl_counter)prev_uidls)779 uidl_rename_duplicate(string_t *uidl, HASH_TABLE_TYPE(uidl_counter) prev_uidls)
780 {
781 	char *key;
782 	void *value;
783 	unsigned int counter;
784 
785 	while (hash_table_lookup_full(prev_uidls, str_c(uidl), &key, &value)) {
786 		/* duplicate. the value contains the number of duplicates. */
787 		counter = POINTER_CAST_TO(value, unsigned int) + 1;
788 		hash_table_update(prev_uidls, key, POINTER_CAST(counter));
789 		str_printfa(uidl, "-%u", counter);
790 		/* the second lookup really should return NULL, but just in
791 		   case of some weird UIDLs do this as many times as needed */
792 	}
793 }
794 
client_uidls_save(struct client * client)795 static void client_uidls_save(struct client *client)
796 {
797 	struct mail_search_context *search_ctx;
798 	struct mail_search_args *search_args;
799 	struct mail *mail;
800 	HASH_TABLE_TYPE(uidl_counter) prev_uidls;
801 	const char **seq_uidls;
802 	string_t *str;
803 	char *uidl;
804 	enum mail_fetch_field wanted_fields;
805 	uint32_t msgnum;
806 	bool permanent_uidl, uidl_duplicates_rename, failed = FALSE;
807 
808 	i_assert(client->message_uidls == NULL);
809 
810 	search_args = pop3_search_build(client, 0);
811 	wanted_fields = 0;
812 	if ((client->uidl_keymask & UIDL_MD5) != 0)
813 		wanted_fields |= MAIL_FETCH_HEADER_MD5;
814 
815 	search_ctx = mailbox_search_init(client->trans, search_args,
816 					 NULL, wanted_fields, NULL);
817 	mail_search_args_unref(&search_args);
818 
819 	uidl_duplicates_rename =
820 		strcmp(client->set->pop3_uidl_duplicates, "rename") == 0;
821 	if (uidl_duplicates_rename)
822 		hash_table_create(&prev_uidls, default_pool, 0, str_hash,
823 				  strcmp);
824 	client->uidl_pool = pool_alloconly_create("message uidls", 1024);
825 
826 	/* first read all the UIDLs into a temporary [seq] array */
827 	seq_uidls = i_new(const char *, client->highest_seq);
828 	str = t_str_new(128);
829 	while (mailbox_search_next(search_ctx, &mail)) {
830 		str_truncate(str, 0);
831 		if (pop3_get_uid(client, mail, str, &permanent_uidl) < 0) {
832 			failed = TRUE;
833 			break;
834 		}
835 		if (uidl_duplicates_rename)
836 			uidl_rename_duplicate(str, prev_uidls);
837 
838 		uidl = p_strdup(client->uidl_pool, str_c(str));
839 		if (client->set->pop3_save_uidl && !permanent_uidl)
840 			mail_update_pop3_uidl(mail, uidl);
841 
842 		i_assert(mail->seq <= client->highest_seq);
843 		seq_uidls[mail->seq-1] = uidl;
844 		if (uidl_duplicates_rename)
845 			hash_table_update(prev_uidls, uidl, POINTER_CAST(1));
846 	}
847 	(void)mailbox_search_deinit(&search_ctx);
848 	if (uidl_duplicates_rename)
849 		hash_table_destroy(&prev_uidls);
850 
851 	if (failed) {
852 		pool_unref(&client->uidl_pool);
853 		i_free(seq_uidls);
854 		return;
855 	}
856 	/* map UIDLs to msgnums (in case POP3 sort ordering is different) */
857 	client->message_uidls = p_new(client->uidl_pool, const char *,
858 				      MALLOC_ADD(client->messages_count, 1));
859 	for (msgnum = 0; msgnum < client->messages_count; msgnum++) {
860 		client->message_uidls[msgnum] =
861 			seq_uidls[msgnum_to_seq(client, msgnum) - 1];
862 	}
863 	i_free(seq_uidls);
864 }
865 
866 static struct cmd_uidl_context *
cmd_uidl_init(struct client * client,uint32_t seq)867 cmd_uidl_init(struct client *client, uint32_t seq)
868 {
869         struct cmd_uidl_context *ctx;
870 	struct mail_search_args *search_args;
871 	enum mail_fetch_field wanted_fields;
872 
873 	if (client->message_uidls_save && client->message_uidls == NULL &&
874 	    client->messages_count > 0)
875 		client_uidls_save(client);
876 
877 	ctx = i_new(struct cmd_uidl_context, 1);
878 	ctx->list_all = seq == 0;
879 
880 	if (client->message_uidls == NULL) {
881 		wanted_fields = 0;
882 		if ((client->uidl_keymask & UIDL_MD5) != 0)
883 			wanted_fields |= MAIL_FETCH_HEADER_MD5;
884 
885 		search_args = pop3_search_build(client, seq);
886 		ctx->search_ctx = mailbox_search_init(client->trans, search_args,
887 						      pop3_sort_program,
888 						      wanted_fields, NULL);
889 		mail_search_args_unref(&search_args);
890 	}
891 
892 	if (seq == 0) {
893 		client->cmd = cmd_uidl_callback;
894 		client->cmd_context = ctx;
895 	}
896 	return ctx;
897 }
898 
cmd_uidl(struct client * client,const char * args)899 static int cmd_uidl(struct client *client, const char *args)
900 {
901         struct cmd_uidl_context *ctx;
902 	uint32_t seq;
903 
904 	if (*args == '\0') {
905 		client_send_line(client, "+OK");
906 		ctx = cmd_uidl_init(client, 0);
907 		(void)list_uids_iter(client, ctx);
908 	} else {
909 		unsigned int msgnum;
910 
911 		if (get_msgnum(client, args, &msgnum, FALSE) == NULL)
912 			return -1;
913 
914 		seq = msgnum_to_seq(client, msgnum);
915 		ctx = cmd_uidl_init(client, seq);
916 		ctx->msgnum = msgnum;
917 		if (!list_uids_iter(client, ctx))
918 			return client_reply_msg_expunged(client, msgnum);
919 	}
920 
921 	return 1;
922 }
923 
client_command_execute(struct client * client,const char * name,const char * args)924 int client_command_execute(struct client *client,
925 			   const char *name, const char *args)
926 {
927 	/* keep the command uppercased */
928 	name = t_str_ucase(name);
929 
930 	while (*args == ' ') args++;
931 
932 	switch (*name) {
933 	case 'C':
934 		if (strcmp(name, "CAPA") == 0)
935 			return cmd_capa(client, args);
936 		break;
937 	case 'D':
938 		if (strcmp(name, "DELE") == 0)
939 			return cmd_dele(client, args);
940 		break;
941 	case 'L':
942 		if (strcmp(name, "LIST") == 0)
943 			return cmd_list(client, args);
944 		if (strcmp(name, "LAST") == 0 && client->set->pop3_enable_last)
945 			return cmd_last(client, args);
946 		break;
947 	case 'N':
948 		if (strcmp(name, "NOOP") == 0)
949 			return cmd_noop(client, args);
950 		break;
951 	case 'Q':
952 		if (strcmp(name, "QUIT") == 0)
953 			return cmd_quit(client, args);
954 		break;
955 	case 'R':
956 		if (strcmp(name, "RETR") == 0)
957 			return cmd_retr(client, args);
958 		if (strcmp(name, "RSET") == 0)
959 			return cmd_rset(client, args);
960 		break;
961 	case 'S':
962 		if (strcmp(name, "STAT") == 0)
963 			return cmd_stat(client, args);
964 		break;
965 	case 'T':
966 		if (strcmp(name, "TOP") == 0)
967 			return cmd_top(client, args);
968 		break;
969 	case 'U':
970 		if (strcmp(name, "UIDL") == 0)
971 			return cmd_uidl(client, args);
972 		break;
973 	}
974 
975 	client_send_line(client, "-ERR Unknown command: %s", name);
976 	return -1;
977 }
978