1 /* $Id$ */
2
3 /*
4 * Copyright (c) 2006 Nicholas Marriott <nicholas.marriott@gmail.com>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/types.h>
20 #include <netinet/in.h>
21 #include <arpa/nameser.h>
22
23 #include <ctype.h>
24 #include <resolv.h>
25 #include <string.h>
26
27 #include <openssl/bio.h>
28 #include <openssl/buffer.h>
29 #include <openssl/evp.h>
30 #include <openssl/hmac.h>
31
32 #include "fdm.h"
33 #include "fetch.h"
34
35 void imap_free(void *);
36
37 int imap_parse(struct account *, int, char *);
38
39 char *imap_base64_encode(char *);
40 char *imap_base64_decode(char *);
41
42 int imap_pick_auth(struct account *, struct fetch_ctx *);
43
44 int imap_state_connect(struct account *, struct fetch_ctx *);
45 int imap_state_capability1(struct account *, struct fetch_ctx *);
46 int imap_state_capability2(struct account *, struct fetch_ctx *);
47 int imap_state_starttls(struct account *, struct fetch_ctx *);
48 int imap_state_cram_md5_auth(struct account *, struct fetch_ctx *);
49 int imap_state_login(struct account *, struct fetch_ctx *);
50 int imap_state_user(struct account *, struct fetch_ctx *);
51 int imap_state_pass(struct account *, struct fetch_ctx *);
52 int imap_state_select2(struct account *, struct fetch_ctx *);
53 int imap_state_select3(struct account *, struct fetch_ctx *);
54 int imap_state_select4(struct account *, struct fetch_ctx *);
55 int imap_state_search1(struct account *, struct fetch_ctx *);
56 int imap_state_search2(struct account *, struct fetch_ctx *);
57 int imap_state_search3(struct account *, struct fetch_ctx *);
58 int imap_state_next(struct account *, struct fetch_ctx *);
59 int imap_state_body(struct account *, struct fetch_ctx *);
60 int imap_state_line(struct account *, struct fetch_ctx *);
61 int imap_state_mail(struct account *, struct fetch_ctx *);
62 int imap_state_gmext_start(struct account *, struct fetch_ctx *);
63 int imap_state_gmext_body(struct account *, struct fetch_ctx *);
64 int imap_state_gmext_done(struct account *, struct fetch_ctx *);
65 int imap_state_commit(struct account *, struct fetch_ctx *);
66 int imap_state_expunge(struct account *, struct fetch_ctx *);
67 int imap_state_close(struct account *, struct fetch_ctx *);
68 int imap_state_quit(struct account *, struct fetch_ctx *);
69
70 /* Put line to server. */
71 int
imap_putln(struct account * a,const char * fmt,...)72 imap_putln(struct account *a, const char *fmt, ...)
73 {
74 struct fetch_imap_data *data = a->data;
75 va_list ap;
76 int n;
77
78 va_start(ap, fmt);
79 n = data->putln(a, fmt, ap);
80 va_end(ap);
81
82 return (n);
83 }
84
85 /*
86 * Get line from server. Returns -1 on error, 0 on success, a NULL line when
87 * out of data.
88 */
89 int
imap_getln(struct account * a,struct fetch_ctx * fctx,int type,char ** line)90 imap_getln(struct account *a, struct fetch_ctx *fctx, int type, char **line)
91 {
92 struct fetch_imap_data *data = a->data;
93 int n;
94
95 do {
96 if (data->getln(a, fctx, line) != 0)
97 return (-1);
98 if (*line == NULL)
99 return (0);
100 } while ((n = imap_parse(a, type, *line)) == 1);
101 return (n);
102 }
103
104 /* Free auxiliary data. */
105 void
imap_free(void * ptr)106 imap_free(void *ptr)
107 {
108 xfree(ptr);
109 }
110
111 /* Check for okay from server. */
112 int
imap_okay(char * line)113 imap_okay(char *line)
114 {
115 char *ptr;
116
117 ptr = strchr(line, ' ');
118 if (ptr == NULL)
119 return (0);
120 if (ptr[1] != 'O' || ptr[2] != 'K' || (ptr[3] != ' ' && ptr[3] != '\0'))
121 return (0);
122 return (1);
123 }
124
125 /* Check for no from server. */
126 int
imap_no(char * line)127 imap_no(char *line)
128 {
129 char *ptr;
130
131 ptr = strchr(line, ' ');
132 if (ptr == NULL)
133 return (0);
134 if (ptr[1] != 'N' || ptr[2] != 'O' || (ptr[3] != ' ' && ptr[3] != '\0'))
135 return (0);
136 return (1);
137 }
138
139 /*
140 * Parse line based on type. Returns -1 on error, 0 on success, 1 to ignore
141 * this line.
142 */
143 int
imap_parse(struct account * a,int type,char * line)144 imap_parse(struct account *a, int type, char *line)
145 {
146 struct fetch_imap_data *data = a->data;
147 int tag;
148
149 if (type == IMAP_RAW)
150 return (0);
151
152 tag = imap_tag(line);
153 switch (type) {
154 case IMAP_TAGGED:
155 if (tag == IMAP_TAG_NONE)
156 return (1);
157 if (tag == IMAP_TAG_CONTINUE)
158 goto invalid;
159 if (tag != data->tag)
160 goto invalid;
161 break;
162 case IMAP_UNTAGGED:
163 if (tag != IMAP_TAG_NONE)
164 goto invalid;
165 break;
166 case IMAP_CONTINUE:
167 if (tag == IMAP_TAG_NONE)
168 return (1);
169 if (tag != IMAP_TAG_CONTINUE)
170 goto invalid;
171 break;
172 }
173
174 return (0);
175
176 invalid:
177 imap_bad(a, line);
178 return (-1);
179 }
180
181 /* Parse IMAP tag. */
182 int
imap_tag(char * line)183 imap_tag(char *line)
184 {
185 int tag;
186 const char *errstr;
187 char *ptr;
188
189 if (line[0] == '*' && line[1] == ' ')
190 return (IMAP_TAG_NONE);
191 if (line[0] == '+')
192 return (IMAP_TAG_CONTINUE);
193
194 if ((ptr = strchr(line, ' ')) == NULL)
195 ptr = strchr(line, '\0');
196 *ptr = '\0';
197
198 tag = strtonum(line, 0, INT_MAX, &errstr);
199 *ptr = ' ';
200 if (errstr != NULL)
201 return (IMAP_TAG_ERROR);
202
203 return (tag);
204 }
205
206 /* Base64 encode string. */
207 char *
imap_base64_encode(char * in)208 imap_base64_encode(char *in)
209 {
210 char *out;
211 size_t size;
212
213 size = (strlen(in) * 2) + 1;
214 out = xcalloc(1, size);
215 if (b64_ntop(in, strlen(in), out, size) < 0) {
216 xfree(out);
217 return (NULL);
218 }
219 return (out);
220 }
221
222 /* Base64 decode string. */
223 char *
imap_base64_decode(char * in)224 imap_base64_decode(char *in)
225 {
226 char *out;
227 size_t size;
228
229 size = (strlen(in) * 4) + 1;
230 out = xcalloc(1, size);
231 if (b64_pton(in, out, size) < 0) {
232 xfree(out);
233 return (NULL);
234 }
235 return (out);
236 }
237
238 int
imap_bad(struct account * a,const char * line)239 imap_bad(struct account *a, const char *line)
240 {
241 log_warnx("%s: unexpected data: %s", a->name, line);
242 return (FETCH_ERROR);
243 }
244
245 int
imap_invalid(struct account * a,const char * line)246 imap_invalid(struct account *a, const char *line)
247 {
248 log_warnx("%s: invalid response: %s", a->name, line);
249 return (FETCH_ERROR);
250 }
251
252 /* Commit mail. */
253 int
imap_commit(struct account * a,struct mail * m)254 imap_commit(struct account *a, struct mail *m)
255 {
256 struct fetch_imap_data *data = a->data;
257 struct fetch_imap_mail *aux = m->auxdata;
258
259 if (m->decision == DECISION_DROP)
260 ARRAY_ADD(&data->dropped, aux->uid);
261 else
262 ARRAY_ADD(&data->kept, aux->uid);
263
264 xfree(aux);
265 m->auxdata = m->auxfree = NULL;
266
267 return (FETCH_AGAIN);
268 }
269
270 /* Abort fetch. */
271 void
imap_abort(struct account * a)272 imap_abort(struct account *a)
273 {
274 struct fetch_imap_data *data = a->data;
275
276 ARRAY_FREE(&data->dropped);
277 ARRAY_FREE(&data->kept);
278 ARRAY_FREE(&data->wanted);
279
280 data->disconnect(a);
281 }
282
283 /* Return total mails available. */
284 u_int
imap_total(struct account * a)285 imap_total(struct account *a)
286 {
287 struct fetch_imap_data *data = a->data;
288
289 return (data->folders_total);
290 }
291
292 /* Try an authentication method. */
293 int
imap_pick_auth(struct account * a,struct fetch_ctx * fctx)294 imap_pick_auth(struct account *a, struct fetch_ctx *fctx)
295 {
296 struct fetch_imap_data *data = a->data;
297
298 /* Try CRAM-MD5, if server supports it and user allows it. */
299 if (!data->nocrammd5 && (data->capa & IMAP_CAPA_AUTH_CRAM_MD5)) {
300 if (imap_putln(a,
301 "%u AUTHENTICATE CRAM-MD5", ++data->tag) != 0)
302 return (FETCH_ERROR);
303 fctx->state = imap_state_cram_md5_auth;
304 return (FETCH_BLOCK);
305 }
306
307 /* Fall back to LOGIN, unless config disallows it. */
308 if (!data->nologin) {
309 if (imap_putln(a,
310 "%u LOGIN {%zu}", ++data->tag, strlen(data->user)) != 0)
311 return (FETCH_ERROR);
312 fctx->state = imap_state_login;
313 return (FETCH_BLOCK);
314 }
315
316 log_warnx("%s: no authentication methods", a->name);
317 return (FETCH_ERROR);
318 }
319
320 /* Common initialisation state. */
321 int
imap_state_init(struct account * a,struct fetch_ctx * fctx)322 imap_state_init(struct account *a, struct fetch_ctx *fctx)
323 {
324 struct fetch_imap_data *data = a->data;
325
326 ARRAY_INIT(&data->dropped);
327 ARRAY_INIT(&data->kept);
328 ARRAY_INIT(&data->wanted);
329
330 data->tag = 0;
331
332 data->folder = 0;
333 data->folders_total = 0;
334
335 fctx->state = imap_state_connect;
336 return (FETCH_AGAIN);
337 }
338
339 /* Connect state. */
340 int
imap_state_connect(struct account * a,struct fetch_ctx * fctx)341 imap_state_connect(struct account *a, struct fetch_ctx *fctx)
342 {
343 struct fetch_imap_data *data = a->data;
344
345 if (data->connect(a) != 0)
346 return (FETCH_ERROR);
347
348 fctx->state = imap_state_connected;
349 return (FETCH_BLOCK);
350 }
351
352 /* Connected state: wait for initial line from server. */
353 int
imap_state_connected(struct account * a,struct fetch_ctx * fctx)354 imap_state_connected(struct account *a, struct fetch_ctx *fctx)
355 {
356 struct fetch_imap_data *data = a->data;
357 char *line;
358
359 if (imap_getln(a, fctx, IMAP_UNTAGGED, &line) != 0)
360 return (FETCH_ERROR);
361 if (line == NULL)
362 return (FETCH_BLOCK);
363
364 if (strncmp(line, "* PREAUTH", 9) == 0) {
365 fctx->state = imap_state_select1;
366 return (FETCH_AGAIN);
367 }
368 if (data->user == NULL || data->pass == NULL) {
369 log_warnx("%s: not PREAUTH and no user or password", a->name);
370 return (FETCH_ERROR);
371 }
372
373 if (imap_putln(a, "%u CAPABILITY", ++data->tag) != 0)
374 return (FETCH_ERROR);
375 fctx->state = imap_state_capability1;
376 return (FETCH_BLOCK);
377 }
378
379 /* Capability state 1. Parse capabilities and set flags. */
380 int
imap_state_capability1(struct account * a,struct fetch_ctx * fctx)381 imap_state_capability1(struct account *a, struct fetch_ctx *fctx)
382 {
383 struct fetch_imap_data *data = a->data;
384 char *line, *ptr;
385
386 if (imap_getln(a, fctx, IMAP_UNTAGGED, &line) != 0)
387 return (FETCH_ERROR);
388 if (line == NULL)
389 return (FETCH_BLOCK);
390
391 /* Convert to uppercase. */
392 for (ptr = line; *ptr != '\0'; ptr++)
393 *ptr = toupper((u_char) *ptr);
394
395 if (strstr(line, "IMAP4REV1") == NULL) {
396 log_warnx("%s: no IMAP4rev1 capability: %s", a->name, line);
397 return (FETCH_ERROR);
398 }
399
400 data->capa = 0;
401 if (strstr(line, "AUTH=CRAM-MD5") != NULL)
402 data->capa |= IMAP_CAPA_AUTH_CRAM_MD5;
403
404 /* Use XYZZY to detect Google brokenness. */
405 if (strstr(line, "XYZZY") != NULL)
406 data->capa |= IMAP_CAPA_XYZZY;
407
408 /* GMail IMAP extensions. */
409 if (strstr(line, "X-GM-EXT-1") != NULL)
410 data->capa |= IMAP_CAPA_GMEXT;
411
412 if (strstr(line, "STARTTLS") != NULL)
413 data->capa |= IMAP_CAPA_STARTTLS;
414
415 fctx->state = imap_state_capability2;
416 return (FETCH_AGAIN);
417 }
418
419 /* Capability state 2. Check capabilities and choose login type. */
420 int
imap_state_capability2(struct account * a,struct fetch_ctx * fctx)421 imap_state_capability2(struct account *a, struct fetch_ctx *fctx)
422 {
423 struct fetch_imap_data *data = a->data;
424 char *line;
425
426 if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0)
427 return (FETCH_ERROR);
428 if (line == NULL)
429 return (FETCH_BLOCK);
430 if (!imap_okay(line))
431 return (imap_bad(a, line));
432
433 if (data->starttls) {
434 if (!(data->capa & IMAP_CAPA_STARTTLS)) {
435 log_warnx("%s: server doesn't support STARTTLS",
436 a->name);
437 return (FETCH_ERROR);
438 }
439 if (imap_putln(a, "%u STARTTLS", ++data->tag) != 0)
440 return (FETCH_ERROR);
441 fctx->state = imap_state_starttls;
442 return (FETCH_BLOCK);
443 }
444
445 return (imap_pick_auth(a, fctx));
446 }
447
448 /* STARTTLS state. */
449 int
imap_state_starttls(struct account * a,struct fetch_ctx * fctx)450 imap_state_starttls(struct account *a, struct fetch_ctx *fctx)
451 {
452 struct fetch_imap_data *data = a->data;
453 char *line, *cause;
454
455 if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0)
456 return (FETCH_ERROR);
457 if (line == NULL)
458 return (FETCH_BLOCK);
459 if (!imap_okay(line))
460 return (imap_bad(a, line));
461
462 data->io->ssl = makessl(&data->server, data->io->fd,
463 conf.verify_certs && data->server.verify, conf.timeout, &cause);
464 if (data->io->ssl == NULL) {
465 log_warnx("%s: STARTTLS failed: %s", a->name, cause);
466 xfree(cause);
467 return (FETCH_ERROR);
468 }
469
470 return (imap_pick_auth(a, fctx));
471 }
472
473 /* CRAM-MD5 auth state. */
474 int
imap_state_cram_md5_auth(struct account * a,struct fetch_ctx * fctx)475 imap_state_cram_md5_auth(struct account *a, struct fetch_ctx *fctx)
476 {
477 struct fetch_imap_data *data = a->data;
478 char *line, *ptr, *src, *b64;
479 char out[EVP_MAX_MD_SIZE * 2 + 1];
480 u_char digest[EVP_MAX_MD_SIZE];
481 u_int i, n;
482
483 if (imap_getln(a, fctx, IMAP_CONTINUE, &line) != 0)
484 return (FETCH_ERROR);
485 if (line == NULL)
486 return (FETCH_BLOCK);
487
488 ptr = line + 1;
489 while (isspace((u_char) *ptr))
490 ptr++;
491 if (*ptr == '\0')
492 return (imap_invalid(a, line));
493
494 b64 = imap_base64_decode(ptr);
495 HMAC(EVP_md5(),
496 data->pass, strlen(data->pass), b64, strlen(b64), digest, &n);
497 xfree(b64);
498
499 for (i = 0; i < n; i++)
500 xsnprintf(out + i * 2, 3, "%02hhx", digest[i]);
501 xasprintf(&src, "%s %s", data->user, out);
502 b64 = imap_base64_encode(src);
503 xfree(src);
504
505 if (imap_putln(a, "%s", b64) != 0) {
506 xfree(b64);
507 return (FETCH_ERROR);
508 }
509 xfree(b64);
510
511 fctx->state = imap_state_pass;
512 return (FETCH_BLOCK);
513 }
514
515 /* Login state. */
516 int
imap_state_login(struct account * a,struct fetch_ctx * fctx)517 imap_state_login(struct account *a, struct fetch_ctx *fctx)
518 {
519 struct fetch_imap_data *data = a->data;
520 char *line;
521 size_t passlen;
522
523 if (imap_getln(a, fctx, IMAP_CONTINUE, &line) != 0)
524 return (FETCH_ERROR);
525 if (line == NULL)
526 return (FETCH_BLOCK);
527
528 passlen = strlen(data->pass);
529 if (data->capa & IMAP_CAPA_NOSPACE) {
530 if (imap_putln(a, "%s{%zu}", data->user, passlen) != 0)
531 return (FETCH_ERROR);
532 } else {
533 if (imap_putln(a, "%s {%zu}", data->user, passlen) != 0)
534 return (FETCH_ERROR);
535 }
536 fctx->state = imap_state_user;
537 return (FETCH_BLOCK);
538 }
539
540 /* User state. */
541 int
imap_state_user(struct account * a,struct fetch_ctx * fctx)542 imap_state_user(struct account *a, struct fetch_ctx *fctx)
543 {
544 struct fetch_imap_data *data = a->data;
545 char *line;
546 int tag;
547
548 if (data->capa & IMAP_CAPA_NOSPACE) {
549 if (imap_getln(a, fctx, IMAP_CONTINUE, &line) != 0)
550 return (FETCH_ERROR);
551 if (line == NULL)
552 return (FETCH_BLOCK);
553 } else {
554 for (;;) {
555 if (imap_getln(a, fctx, IMAP_RAW, &line) != 0)
556 return (FETCH_ERROR);
557 if (line == NULL)
558 return (FETCH_BLOCK);
559 tag = imap_tag(line);
560 if (tag == IMAP_TAG_NONE)
561 continue;
562 if (tag == IMAP_TAG_CONTINUE || tag == data->tag)
563 break;
564 return (FETCH_ERROR);
565 }
566 if (tag != IMAP_TAG_CONTINUE) {
567 log_debug("%s: didn't accept user (%s); "
568 "trying without space", a->name, line);
569 data->capa |= IMAP_CAPA_NOSPACE;
570 return (imap_pick_auth(a, fctx));
571 }
572 }
573
574 if (imap_putln(a, "%s", data->pass) != 0)
575 return (FETCH_ERROR);
576 fctx->state = imap_state_pass;
577 return (FETCH_BLOCK);
578 }
579
580 /* Pass state. */
581 int
imap_state_pass(struct account * a,struct fetch_ctx * fctx)582 imap_state_pass(struct account *a, struct fetch_ctx *fctx)
583 {
584 char *line;
585
586 if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0)
587 return (FETCH_ERROR);
588 if (line == NULL)
589 return (FETCH_BLOCK);
590 if (!imap_okay(line))
591 return (imap_bad(a, line));
592
593 fctx->state = imap_state_select1;
594 return (FETCH_AGAIN);
595 }
596
597 /* Select state 1. */
598 int
imap_state_select1(struct account * a,struct fetch_ctx * fctx)599 imap_state_select1(struct account *a, struct fetch_ctx *fctx)
600 {
601 struct fetch_imap_data *data = a->data;
602
603 if (imap_putln(a, "%u SELECT {%zu}",
604 ++data->tag, strlen(ARRAY_ITEM(data->folders, data->folder))) != 0)
605 return (FETCH_ERROR);
606 fctx->state = imap_state_select2;
607 return (FETCH_BLOCK);
608 }
609
610 /* Select state 2. Wait for continuation and send folder name. */
611 int
imap_state_select2(struct account * a,struct fetch_ctx * fctx)612 imap_state_select2(struct account *a, struct fetch_ctx *fctx)
613 {
614 struct fetch_imap_data *data = a->data;
615 char *line;
616
617 if (imap_getln(a, fctx, IMAP_CONTINUE, &line) != 0)
618 return (FETCH_ERROR);
619 if (line == NULL)
620 return (FETCH_BLOCK);
621
622 if (imap_putln(a, "%s", ARRAY_ITEM(data->folders, data->folder)) != 0)
623 return (FETCH_ERROR);
624 fctx->state = imap_state_select3;
625 return (FETCH_BLOCK);
626 }
627
628 /* Select state 3. Hold until select returns message count. */
629 int
imap_state_select3(struct account * a,struct fetch_ctx * fctx)630 imap_state_select3(struct account *a, struct fetch_ctx *fctx)
631 {
632 struct fetch_imap_data *data = a->data;
633 char *line;
634
635 for (;;) {
636 if (imap_getln(a, fctx, IMAP_UNTAGGED, &line) != 0)
637 return (FETCH_ERROR);
638 if (line == NULL)
639 return (FETCH_BLOCK);
640
641 if (sscanf(line, "* %u EXISTS", &data->total) == 1)
642 break;
643 }
644
645 fctx->state = imap_state_select4;
646 return (FETCH_AGAIN);
647 }
648
649 /* Select state 4. Hold until select completes. */
650 int
imap_state_select4(struct account * a,struct fetch_ctx * fctx)651 imap_state_select4(struct account *a, struct fetch_ctx *fctx)
652 {
653 struct fetch_imap_data *data = a->data;
654 char *line;
655
656 if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0)
657 return (FETCH_ERROR);
658 if (line == NULL)
659 return (FETCH_BLOCK);
660 if (!imap_okay(line))
661 return (imap_bad(a, line));
662
663 /* If no mails, stop early. */
664 if (data->total == 0) {
665 if (imap_putln(a, "%u CLOSE", ++data->tag) != 0)
666 return (FETCH_ERROR);
667 fctx->state = imap_state_close;
668 return (FETCH_BLOCK);
669 }
670
671 fctx->state = imap_state_search1;
672 return (FETCH_AGAIN);
673 }
674
675 /* Search state 1. Request list of mail required. */
676 int
imap_state_search1(struct account * a,struct fetch_ctx * fctx)677 imap_state_search1(struct account *a, struct fetch_ctx *fctx)
678 {
679 struct fetch_imap_data *data = a->data;
680
681 /* Search for a list of the mail UIDs to fetch. */
682 switch (data->only) {
683 case FETCH_ONLY_NEW:
684 if (imap_putln(a, "%u UID SEARCH UNSEEN", ++data->tag) != 0)
685 return (FETCH_ERROR);
686 break;
687 case FETCH_ONLY_OLD:
688 if (imap_putln(a, "%u UID SEARCH SEEN", ++data->tag) != 0)
689 return (FETCH_ERROR);
690 break;
691 default:
692 if (imap_putln(a, "%u UID SEARCH ALL", ++data->tag) != 0)
693 return (FETCH_ERROR);
694 break;
695 }
696
697 fctx->state = imap_state_search2;
698 return (FETCH_BLOCK);
699 }
700
701 /* Search state 2. */
702 int
imap_state_search2(struct account * a,struct fetch_ctx * fctx)703 imap_state_search2(struct account *a, struct fetch_ctx *fctx)
704 {
705 struct fetch_imap_data *data = a->data;
706 char *line, *ptr;
707 u_int uid;
708
709 if (imap_getln(a, fctx, IMAP_UNTAGGED, &line) != 0)
710 return (FETCH_ERROR);
711 if (line == NULL)
712 return (FETCH_BLOCK);
713
714 /* Skip the header. */
715 if (strncasecmp(line, "* SEARCH", 8) != 0)
716 return (imap_bad(a, line));
717 line += 8;
718
719 /* Read each UID and save it. */
720 do {
721 while (isspace((u_char) *line))
722 line++;
723 ptr = strchr(line, ' ');
724 if (ptr == NULL)
725 ptr = strchr(line, '\0');
726 if (ptr == line)
727 break;
728
729 if (sscanf(line, "%u", &uid) != 1)
730 return (imap_bad(a, line));
731 ARRAY_ADD(&data->wanted, uid);
732 log_debug3("%s: fetching UID: %u", a->name, uid);
733
734 line = ptr;
735 } while (*line == ' ');
736
737 fctx->state = imap_state_search3;
738 return (FETCH_AGAIN);
739 }
740
741 /* Search state 3. */
742 int
imap_state_search3(struct account * a,struct fetch_ctx * fctx)743 imap_state_search3(struct account *a, struct fetch_ctx *fctx)
744 {
745 struct fetch_imap_data *data = a->data;
746 char *line;
747
748 if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0)
749 return (FETCH_ERROR);
750 if (line == NULL)
751 return (FETCH_BLOCK);
752 if (!imap_okay(line))
753 return (imap_bad(a, line));
754
755 /* Save the total. */
756 data->total = ARRAY_LENGTH(&data->wanted);
757
758 /* Update grand total. */
759 data->folders_total += data->total;
760
761 /* If no mails, or polling, stop here. */
762 if (data->total == 0 || fctx->flags & FETCH_POLL) {
763 if (imap_putln(a, "%u CLOSE", ++data->tag) != 0)
764 return (FETCH_ERROR);
765 fctx->state = imap_state_close;
766 return (FETCH_BLOCK);
767 }
768
769 fctx->state = imap_state_next;
770 return (FETCH_AGAIN);
771 }
772
773 /*
774 * Next state. Get next mail. This is also the idle state when completed, so
775 * check for finished mail, exiting, and so on.
776 */
777 int
imap_state_next(struct account * a,struct fetch_ctx * fctx)778 imap_state_next(struct account *a, struct fetch_ctx *fctx)
779 {
780 struct fetch_imap_data *data = a->data;
781
782 /* Handle dropped and kept mail. */
783 if (!ARRAY_EMPTY(&data->dropped)) {
784 if (imap_putln(a, "%u UID STORE %u +FLAGS.SILENT (\\Deleted)",
785 ++data->tag, ARRAY_FIRST(&data->dropped)) != 0)
786 return (FETCH_ERROR);
787 ARRAY_REMOVE(&data->dropped, 0);
788 fctx->state = imap_state_commit;
789 return (FETCH_BLOCK);
790 }
791 if (!ARRAY_EMPTY(&data->kept)) {
792 /*
793 * GMail is broken and does not set the \Seen flag after mail
794 * is fetched, so set it explicitly for kept mail.
795 */
796 if (imap_putln(a, "%u UID STORE %u +FLAGS.SILENT (\\Seen)",
797 ++data->tag, ARRAY_FIRST(&data->kept)) != 0)
798 return (FETCH_ERROR);
799 ARRAY_REMOVE(&data->kept, 0);
800 fctx->state = imap_state_commit;
801 return (FETCH_BLOCK);
802 }
803
804 /* Need to purge, switch to purge state. */
805 if (fctx->flags & FETCH_PURGE) {
806 /*
807 * If can't purge now, loop through this state until there is
808 * no mail on the dropped queue and FETCH_EMPTY is set. Can't
809 * have a seperate state to loop through without returning
810 * here: mail could potentially be added to the dropped list
811 * while in that state.
812 */
813 if (fctx->flags & FETCH_EMPTY) {
814 fctx->flags &= ~FETCH_PURGE;
815
816 if (imap_putln(a, "%u EXPUNGE", ++data->tag) != 0)
817 return (FETCH_ERROR);
818 fctx->state = imap_state_expunge;
819 return (FETCH_BLOCK);
820 }
821
822 /*
823 * Must be waiting for delivery, so permit blocking even though
824 * we (fetch) aren't waiting for any data.
825 */
826 return (FETCH_BLOCK);
827 }
828
829 /* If last mail, wait for everything to be committed then close down. */
830 if (ARRAY_EMPTY(&data->wanted)) {
831 if (data->committed != data->total)
832 return (FETCH_BLOCK);
833 if (imap_putln(a, "%u CLOSE", ++data->tag) != 0)
834 return (FETCH_ERROR);
835 fctx->state = imap_state_close;
836 return (FETCH_BLOCK);
837 }
838
839 /* Fetch the next mail. */
840 if (imap_putln(a, "%u "
841 "UID FETCH %u BODY[]",++data->tag, ARRAY_FIRST(&data->wanted)) != 0)
842 return (FETCH_ERROR);
843 fctx->state = imap_state_body;
844 return (FETCH_BLOCK);
845 }
846
847 /* Body state. */
848 int
imap_state_body(struct account * a,struct fetch_ctx * fctx)849 imap_state_body(struct account *a, struct fetch_ctx *fctx)
850 {
851 struct fetch_imap_data *data = a->data;
852 struct mail *m = fctx->mail;
853 struct fetch_imap_mail *aux;
854 char *line, *ptr;
855 u_int n;
856
857 if (imap_getln(a, fctx, IMAP_UNTAGGED, &line) != 0)
858 return (FETCH_ERROR);
859 if (line == NULL)
860 return (FETCH_BLOCK);
861
862 if (sscanf(line, "* %u FETCH (", &n) != 1)
863 return (imap_invalid(a, line));
864 if ((ptr = strstr(line, "BODY[] {")) == NULL)
865 return (imap_invalid(a, line));
866
867 if (sscanf(ptr, "BODY[] {%zu}", &data->size) != 1)
868 return (imap_invalid(a, line));
869 data->lines = 0;
870
871 /* Fill in local data. */
872 aux = xcalloc(1, sizeof *aux);
873 aux->uid = ARRAY_FIRST(&data->wanted);
874 m->auxdata = aux;
875 m->auxfree = imap_free;
876 ARRAY_REMOVE(&data->wanted, 0);
877
878 /* Open the mail. */
879 if (mail_open(m, data->size) != 0) {
880 log_warnx("%s: failed to create mail", a->name);
881 return (FETCH_ERROR);
882 }
883 m->size = 0;
884
885 /* Tag mail. */
886 default_tags(&m->tags, data->src);
887 if (data->server.host != NULL) {
888 add_tag(&m->tags, "server", "%s", data->server.host);
889 add_tag(&m->tags, "port", "%s", data->server.port);
890 }
891 add_tag(&m->tags, "server_uid", "%u", aux->uid);
892 add_tag(&m->tags,
893 "folder", "%s", ARRAY_ITEM(data->folders, data->folder));
894
895 /* If we already know the mail is oversize, start off flushing it. */
896 data->flushing = data->size > conf.max_size;
897
898 fctx->state = imap_state_line;
899 return (FETCH_AGAIN);
900 }
901
902 /* Line state. */
903 int
imap_state_line(struct account * a,struct fetch_ctx * fctx)904 imap_state_line(struct account *a, struct fetch_ctx *fctx)
905 {
906 struct fetch_imap_data *data = a->data;
907 struct mail *m = fctx->mail;
908 char *line;
909 size_t used, size, left;
910
911 for (;;) {
912 if (imap_getln(a, fctx, IMAP_RAW, &line) != 0)
913 return (FETCH_ERROR);
914 if (line == NULL)
915 return (FETCH_BLOCK);
916
917 if (data->flushing)
918 continue;
919
920 /* Check if this line would exceed the expected size. */
921 used = m->size + data->lines;
922 size = strlen(line);
923 if (used + size + 2 > data->size)
924 break;
925
926 if (append_line(m, line, size) != 0) {
927 log_warnx("%s: failed to resize mail", a->name);
928 return (FETCH_ERROR);
929 }
930 data->lines++;
931 }
932
933 /*
934 * Calculate the number of bytes still needed. The current line must
935 * include at least that much data. Some servers include UID or FLAGS
936 * after the message: we don't care about these so just ignore them and
937 * make sure there is a terminating ).
938 */
939 left = data->size - used;
940 if (line[size - 1] != ')' && size <= left)
941 return (imap_invalid(a, line));
942
943 /* If there was data left, add it as a new line without trailing \n. */
944 if (left > 0) {
945 if (append_line(m, line, left) != 0) {
946 log_warnx("%s: failed to resize mail", a->name);
947 return (FETCH_ERROR);
948 }
949 data->lines++;
950
951 /* Wipe out the trailing \n. */
952 m->size--;
953 }
954
955 fctx->state = imap_state_mail;
956 return (FETCH_AGAIN);
957 }
958
959 /* Mail state. */
960 int
imap_state_mail(struct account * a,struct fetch_ctx * fctx)961 imap_state_mail(struct account *a, struct fetch_ctx *fctx)
962 {
963 struct fetch_imap_data *data = a->data;
964 char *line;
965
966 if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0)
967 return (FETCH_ERROR);
968 if (line == NULL)
969 return (FETCH_BLOCK);
970 if (!imap_okay(line))
971 return (imap_bad(a, line));
972
973 if (data->capa & IMAP_CAPA_GMEXT) {
974 fctx->state = imap_state_gmext_start;
975 return (FETCH_AGAIN);
976 }
977
978 fctx->state = imap_state_next;
979 return (FETCH_MAIL);
980 }
981
982 /* GMail extensions start state. */
983 int
imap_state_gmext_start(struct account * a,struct fetch_ctx * fctx)984 imap_state_gmext_start(struct account *a, struct fetch_ctx *fctx)
985 {
986 struct fetch_imap_data *data = a->data;
987 struct mail *m = fctx->mail;
988 struct fetch_imap_mail *aux = m->auxdata;
989
990 if (imap_putln(a, "%u FETCH %u (X-GM-MSGID X-GM-THRID X-GM-LABELS)",
991 ++data->tag, aux->uid) != 0)
992 return (FETCH_ERROR);
993
994 fctx->state = imap_state_gmext_body;
995 return (FETCH_AGAIN);
996 }
997
998 /* GMail extensions body state. */
999 int
imap_state_gmext_body(struct account * a,struct fetch_ctx * fctx)1000 imap_state_gmext_body(struct account *a, struct fetch_ctx *fctx)
1001 {
1002 struct fetch_imap_data *data = a->data;
1003 struct mail *m = fctx->mail;
1004 char *line, *lb;
1005 int tag;
1006 u_int n;
1007 uint64_t thrid, msgid;
1008 size_t lblen;
1009
1010 for (;;) {
1011 if (imap_getln(a, fctx, IMAP_RAW, &line) != 0)
1012 return (FETCH_ERROR);
1013 if (line == NULL)
1014 return (FETCH_BLOCK);
1015 tag = imap_tag(line);
1016 if (tag == IMAP_TAG_NONE)
1017 break;
1018 if (tag == data->tag) {
1019 fctx->state = imap_state_next;
1020 return (FETCH_MAIL);
1021 }
1022 return (FETCH_ERROR);
1023 }
1024
1025 if (sscanf(line, "* %u FETCH (X-GM-THRID %llu X-GM-MSGID %llu ", &n,
1026 &thrid, &msgid) != 3)
1027 return (imap_invalid(a, line));
1028 if ((lb = strstr(line, "X-GM-LABELS")) == NULL)
1029 return (imap_invalid(a, line));
1030 if ((lb = strchr(lb, '(')) == NULL)
1031 return (imap_invalid(a, line));
1032 lb++; /* drop '(' */
1033 lblen = strlen(lb);
1034 if (lblen < 2 || lb[lblen - 1] != ')' || lb[lblen - 2] != ')')
1035 return (imap_invalid(a, line));
1036 lblen -= 2; /* drop '))' from the end */
1037
1038 add_tag(&m->tags, "gmail_msgid", "%llu", msgid);
1039 add_tag(&m->tags, "gmail_thrid", "%llu", thrid);
1040 add_tag(&m->tags, "gmail_labels", "%.*s", (int)lblen, lb);
1041
1042 fctx->state = imap_state_gmext_done;
1043 return (FETCH_AGAIN);
1044 }
1045
1046 /* GMail extensions done state. */
1047 int
imap_state_gmext_done(struct account * a,struct fetch_ctx * fctx)1048 imap_state_gmext_done(struct account *a, struct fetch_ctx *fctx)
1049 {
1050 char *line;
1051
1052 if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0)
1053 return (FETCH_ERROR);
1054 if (line == NULL)
1055 return (FETCH_BLOCK);
1056 if (!imap_okay(line))
1057 return (imap_bad(a, line));
1058
1059 fctx->state = imap_state_next;
1060 return (FETCH_MAIL);
1061 }
1062
1063 /* Commit state. */
1064 int
imap_state_commit(struct account * a,struct fetch_ctx * fctx)1065 imap_state_commit(struct account *a, struct fetch_ctx *fctx)
1066 {
1067 struct fetch_imap_data *data = a->data;
1068 char *line;
1069
1070 if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0)
1071 return (FETCH_ERROR);
1072 if (line == NULL)
1073 return (FETCH_BLOCK);
1074 if (!imap_okay(line))
1075 return (imap_bad(a, line));
1076
1077 data->committed++;
1078
1079 fctx->state = imap_state_next;
1080 return (FETCH_AGAIN);
1081 }
1082
1083 /* Expunge state. */
1084 int
imap_state_expunge(struct account * a,struct fetch_ctx * fctx)1085 imap_state_expunge(struct account *a, struct fetch_ctx *fctx)
1086 {
1087 char *line;
1088
1089 if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0)
1090 return (FETCH_ERROR);
1091 if (line == NULL)
1092 return (FETCH_BLOCK);
1093 if (!imap_okay(line))
1094 return (imap_bad(a, line));
1095
1096 fctx->state = imap_state_next;
1097 return (FETCH_AGAIN);
1098 }
1099
1100 /* Close state. */
1101 int
imap_state_close(struct account * a,struct fetch_ctx * fctx)1102 imap_state_close(struct account *a, struct fetch_ctx *fctx)
1103 {
1104 struct fetch_imap_data *data = a->data;
1105 char *line;
1106
1107 if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0)
1108 return (FETCH_ERROR);
1109 if (line == NULL)
1110 return (FETCH_BLOCK);
1111 if (!imap_okay(line))
1112 return (imap_bad(a, line));
1113
1114 data->folder++;
1115 if (data->folder != ARRAY_LENGTH(data->folders)) {
1116 ARRAY_FREE(&data->wanted);
1117 data->committed = 0;
1118
1119 fctx->state = imap_state_select1;
1120 return (FETCH_AGAIN);
1121 }
1122
1123 if (imap_putln(a, "%u LOGOUT", ++data->tag) != 0)
1124 return (FETCH_ERROR);
1125 fctx->state = imap_state_quit;
1126 return (FETCH_BLOCK);
1127 }
1128
1129 /* Quit state. */
1130 int
imap_state_quit(struct account * a,struct fetch_ctx * fctx)1131 imap_state_quit(struct account *a, struct fetch_ctx *fctx)
1132 {
1133 char *line;
1134
1135 if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0)
1136 return (FETCH_ERROR);
1137 if (line == NULL)
1138 return (FETCH_BLOCK);
1139 if (!imap_okay(line))
1140 return (imap_bad(a, line));
1141
1142 imap_abort(a);
1143 return (FETCH_EXIT);
1144 }
1145