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