1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdarg.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <errno.h>
7 #include <netdb.h>
8 #include <fcntl.h>
9 #include <time.h>
10 #include <poll.h>
11 #include <signal.h>
12 #include <sys/socket.h>
13 #include <sys/wait.h>
14 #ifdef USE_OPENSSL
15 #include <openssl/ssl.h>
16 #include <openssl/err.h>
17 #endif
18 #if defined(__FreeBSD__)
19 #define __BSD_VISIBLE 1
20 #endif
21 #include "imap.h"
22
23 struct imap_ll {
24 int w_socket;
25 int r_socket;
26 time_t timeout_time;
27 char buffer[1024];
28 char *bufferp;
29 size_t buffer_left;
30 int current_tag_number;
31 #ifdef USE_OPENSSL
32 SSL *ssl;
33 #endif
34
35 char *selected_folder;
36 size_t selected_folder_namelen;
37 char *selected_folder_uidvalidity;
38 int selected_folder_writable;
39 long selected_folder_exists;
40 };
41
42 struct imap_ll *
imap_ll_connect(const char * host,const char * port)43 imap_ll_connect(const char *host, const char *port)
44 {
45 struct imap_ll *ll;
46 struct addrinfo hints, *res, *res0;
47 int error;
48 int s;
49
50 memset(&hints, 0, sizeof(hints));
51 hints.ai_family = PF_UNSPEC;
52 hints.ai_socktype = SOCK_STREAM;
53 error = getaddrinfo(host, port, &hints, &res0);
54 if (error) {
55 fprintf(stderr, "getaddrinfo \"%s\":\"%s\": %s\n",
56 host, port, gai_strerror(error)
57 );
58 return NULL;
59 }
60 s = -1;
61 for (res = res0; res; res = res->ai_next) {
62 s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
63 if (s < 0) {
64 fprintf(stderr, "socket \"%s\":\"%s\": %s\n",
65 host, port, strerror(errno)
66 );
67 continue;
68 }
69 if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
70 fprintf(stderr, "connect \"%s\":\"%s\": %s\n",
71 host, port, strerror(errno)
72 );
73 close(s);
74 s = -1;
75 continue;
76 }
77 break;
78 }
79 if (s == -1) {
80 if (!res0) {
81 fprintf(stderr, "server \"%s\":\"%s\": no available addresses\n",
82 host, port
83 );
84 }
85 return NULL;
86 }
87
88 ll = malloc(sizeof(*ll));
89 if (!ll) {
90 fprintf(stderr, "malloc failed\n");
91 close(s);
92 return NULL;
93 }
94 memset(ll, 0, sizeof(*ll));
95 ll->w_socket = ll->r_socket = s;
96 ll->current_tag_number = -1;
97 #ifdef USE_OPENSSL
98 ll->ssl = NULL;
99 #endif
100 fcntl(s, F_SETFL, O_NONBLOCK);
101
102 return ll;
103 }
104
105 struct imap_ll *
imap_ll_pipe_connect(const char * cmd)106 imap_ll_pipe_connect(const char *cmd)
107 {
108 struct imap_ll *ll;
109 pid_t child;
110 int status;
111 int r[2], w[2];
112
113 if (pipe(&(r[0])) < 0) {
114 fprintf(stderr, "pipe1 failed\n");
115 return NULL;
116 }
117 if (pipe(&(w[0])) < 0) {
118 fprintf(stderr, "pipe2 failed\n");
119 close(r[0]);
120 close(r[1]);
121 return NULL;
122 }
123 if ((child = fork()) < 0) {
124 fprintf(stderr, "fork failed\n");
125 return NULL;
126 }
127 if (child == 0) {
128 close(r[0]);
129 close(w[1]);
130 if (r[1] != STDOUT_FILENO) {
131 dup2(r[1], STDOUT_FILENO);
132 close(r[1]);
133 }
134 if (w[0] != STDIN_FILENO) {
135 dup2(w[0], STDIN_FILENO);
136 close(w[0]);
137 }
138 execl("/bin/sh", "sh", "-c", cmd, NULL);
139 _exit(1);
140 }
141 close(r[1]);
142 close(w[0]);
143
144 ll = malloc(sizeof(*ll));
145 if (!ll) {
146 fprintf(stderr, "malloc failed\n");
147 close(r[0]);
148 close(w[1]);
149 kill(child, SIGTERM);
150 waitpid(child, &status, 0);
151 return NULL;
152 }
153 memset(ll, 0, sizeof(*ll));
154 ll->w_socket = w[1];
155 ll->r_socket = r[0];
156 ll->current_tag_number = -1;
157 #ifdef USE_OPENSSL
158 ll->ssl = NULL;
159 #endif
160 fcntl(w[1], F_SETFL, O_NONBLOCK);
161 fcntl(r[0], F_SETFL, O_NONBLOCK);
162
163 return ll;
164 }
165
166 void
imap_ll_timeout(struct imap_ll * ll,int seconds)167 imap_ll_timeout(struct imap_ll *ll, int seconds)
168 {
169 ll->timeout_time = time(NULL) + seconds;
170 }
171
172 static struct imap_ll_tokenlist *
mktoken(int type,char * leaf,size_t len)173 mktoken(int type, char *leaf, size_t len)
174 {
175 struct imap_ll_tokenlist *tl;
176
177 tl = malloc(sizeof(*tl));
178 if (!tl) {
179 fprintf(stderr, "imap_ll mktoken: malloc failed (fatal)\n");
180 sleep(1);
181 _exit(0);
182 }
183 tl->type = type;
184 tl->first = tl->last = NULL;
185 tl->parent = NULL;
186 tl->leaf = leaf;
187 tl->leaflen = len;
188 if (leaf) leaf[len] = 0;
189 return tl;
190 }
191
192 static void
growlist(struct imap_ll_tokenlist ** nodep,struct imap_ll_tokenlist * tl)193 growlist(struct imap_ll_tokenlist **nodep, struct imap_ll_tokenlist *tl)
194 {
195 if ((*nodep)->last) {
196 (*nodep)->last->next = tl;
197 } else {
198 (*nodep)->first = tl;
199 }
200 (*nodep)->last = tl;
201 tl->parent = *nodep;
202 tl->next = NULL;
203 if (!(tl->leaf)) {
204 *nodep = tl;
205 }
206 }
207
208 void
imap_ll_freeline(struct imap_ll_tokenlist * t)209 imap_ll_freeline(struct imap_ll_tokenlist *t)
210 {
211 struct imap_ll_tokenlist *t2;
212
213 if (t) {
214 if (t->leaf) free(t->leaf);
215 for (t2 = t->first; t2; t2 = t2->next) {
216 imap_ll_freeline(t2);
217 }
218 free(t);
219 }
220 }
221
222 #define MKPRETOKEN(alloc_size) { \
223 curtoken = malloc((alloc_size) + 1); \
224 if (!curtoken) { \
225 fprintf(stderr, "imap_ll_waitline(%d): malloc failed (fatal)\n", (alloc_size)); \
226 sleep(1); \
227 _exit(0); \
228 } \
229 curtoken_now = 0; \
230 curtoken_max = (alloc_size); \
231 }
232
233 struct imap_ll_tokenlist *
imap_ll_waitline(struct imap_ll * ll)234 imap_ll_waitline(struct imap_ll *ll)
235 {
236 int timeout, n;
237 #ifdef USE_OPENSSL
238 int ret2;
239 #endif
240 char cur;
241 struct imap_ll_tokenlist *line = NULL;
242 struct imap_ll_tokenlist *node = NULL;
243 int linestate = 0;
244 int recv_tag;
245 char *curtoken = NULL;
246 size_t curtoken_now;
247 size_t curtoken_max;
248 struct pollfd pfd;
249
250 for (;;) {
251 while (ll->buffer_left) {
252 cur = *(ll->bufferp);
253 ll->bufferp++;
254 ll->buffer_left--;
255 if ((cur == 10) && (linestate < 7)) {
256 if (curtoken && (linestate == 4)) {
257 growlist(&node, mktoken(TLTYPE_ATOM, curtoken, curtoken_now));
258 } else if (curtoken) {
259 free(curtoken);
260 }
261 curtoken = NULL;
262 return line;
263 }
264 switch (linestate) {
265 case 0:
266 /* expect start of line */
267 if (cur == '*') {
268 linestate = 1;
269 } else if (cur == 't') {
270 linestate = 2;
271 recv_tag = 0;
272 } else if (cur == '+') {
273 line = mktoken(TLTYPE_CONTINUATION, NULL, 0);
274 linestate = 3;
275 } else {
276 linestate = 3;
277 }
278 break;
279 case 1:
280 /* expect space after untagged prefix */
281 if (cur == ' ') {
282 node = line = mktoken(TLTYPE_UNTAGGED, NULL, 0);
283 linestate = 4;
284 } else {
285 linestate = 3;
286 }
287 break;
288 case 2:
289 /* expect tag */
290 if (cur == ' ') {
291 if (recv_tag == ll->current_tag_number) {
292 node = line = mktoken(TLTYPE_TAGGED, NULL, 0);
293 linestate = 4;
294 } else {
295 /* wrong tag */
296 linestate = 3;
297 }
298 } else if ((cur >= '0') && (cur <= '9')) {
299 recv_tag *= 10;
300 recv_tag += cur - '0';
301 } else {
302 linestate = 3;
303 }
304 break;
305 case 3:
306 /* error -- discard until end of line */
307 break;
308 case 4:
309 /* looking for start of next "thing" */
310 if (cur == 34) {
311 linestate = 5;
312 MKPRETOKEN(50);
313 } else if (cur == 123) {
314 linestate = 6;
315 recv_tag = 0;
316 } else if (
317 (cur == 91) /* square bracket */ &&
318 (line == node) /* top level */ &&
319 (line->first) /* not first in list */ &&
320 /* list has exactly 1 */
321 (line->first == line->last)
322 ) {
323 growlist(&node, mktoken(TLTYPE_SQLIST, NULL, 0));
324 } else if (cur == 40) {
325 growlist(&node, mktoken(TLTYPE_LIST, NULL, 0));
326 } else if ((
327 (node->type == TLTYPE_LIST) &&
328 (cur == 41)
329 ) || (
330 (node->type == TLTYPE_SQLIST) &&
331 (cur == 93)
332 )) {
333 if (curtoken) {
334 growlist(&node, mktoken(TLTYPE_ATOM, curtoken, curtoken_now));
335 curtoken = NULL;
336 }
337 node = node->parent;
338 } else if (
339
340 (cur == ' ') ||
341 (cur == 9) ||
342 (cur == 13)
343 ) {
344 if (curtoken) {
345 growlist(&node, mktoken(TLTYPE_ATOM, curtoken, curtoken_now));
346 curtoken = NULL;
347 }
348 } else {
349 if (!curtoken) {
350 MKPRETOKEN(25);
351 }
352 token_extend:
353 if (curtoken_now >= curtoken_max) {
354 curtoken_max *= 2;
355 curtoken = realloc(curtoken, curtoken_max + 1);
356 if (!curtoken) {
357 fprintf(stderr, "imap_ll_waitline(size=%d): malloc failed (fatal)\n", (int)curtoken_max);
358 sleep(1);
359 _exit(0);
360 }
361 }
362 curtoken[curtoken_now++] = cur;
363 }
364 break;
365 case 5:
366 /* quoted string */
367 if (cur == 34) {
368 growlist(&node, mktoken(TLTYPE_STRING, curtoken, curtoken_now));
369 curtoken = NULL;
370 linestate = 4;
371 } else {
372 goto token_extend;
373 }
374 break;
375 case 6:
376 /* literal count */
377 if (cur == 125) {
378 linestate = 7;
379 } else if ((cur >= '0') && (cur <= '9')) {
380 recv_tag *= 10;
381 recv_tag += cur - '0';
382 } else {
383 linestate = 3;
384 }
385 break;
386 case 7:
387 /* CRLF after {nn} */
388 if (cur == 13) break;
389 if (cur != 10) {
390 linestate = 3;
391 }
392 MKPRETOKEN(recv_tag);
393 if (recv_tag == 0) {
394 growlist(&node, mktoken(TLTYPE_STRING, curtoken, curtoken_now));
395 curtoken = NULL;
396 linestate = 4;
397 } else {
398 linestate = 8;
399 }
400 break;
401 case 8:
402 /* literal contents */
403 curtoken[curtoken_now++] = cur;
404 if (curtoken_now == curtoken_max) {
405 growlist(&node, mktoken(TLTYPE_STRING, curtoken, curtoken_now));
406 curtoken = NULL;
407 linestate = 4;
408 }
409 break;
410 }
411 }
412 #ifdef USE_OPENSSL
413 if (ll->ssl) {
414 while ((n = SSL_read(ll->ssl, &((ll->buffer)[0]), sizeof(ll->buffer))) <= 0) {
415 ret2 = SSL_get_error(ll->ssl, n);
416 if ((ret2 == SSL_ERROR_WANT_READ) || (ret2 == SSL_ERROR_WANT_WRITE)) {
417 if (ret2 == SSL_ERROR_WANT_READ) {
418 pfd.fd = ll->r_socket;
419 pfd.events = POLLIN;
420 } else {
421 pfd.fd = ll->w_socket;
422 pfd.events = POLLOUT;
423 }
424 timeout = ll->timeout_time - time(NULL);
425 if (timeout < 0) timeout = 0;
426 if ((n = poll(&pfd, 1, timeout*1000)) < 0) {
427 if (errno == EINTR) continue;
428 goto pollerr;
429 }
430 if (n == 0) goto polltimeout;
431 } else {
432 ERR_print_errors_fp(stderr);
433 imap_ll_freeline(line);
434 return NULL;
435 }
436 }
437 } else {
438 #endif
439 pfd.fd = ll->r_socket;
440 pfd.events = POLLIN;
441 repoll:
442 timeout = ll->timeout_time - time(NULL);
443 if (timeout < 0) timeout = 0;
444 if ((n = poll(&pfd, 1, timeout*1000)) < 0) {
445 if (errno == EINTR) goto repoll;
446 #ifdef USE_OPENSSL
447 pollerr:
448 #endif
449 fprintf(stderr, "imap_ll_waitline: poll: %s\n", strerror(errno));
450 imap_ll_freeline(line);
451 return NULL;
452 }
453 if (n == 0) {
454 #ifdef USE_OPENSSL
455 polltimeout:
456 #endif
457 fprintf(stderr, "imap_ll_waitline: timeout\n");
458 imap_ll_freeline(line);
459 return NULL;
460 }
461 if ((n = read(ll->r_socket, &((ll->buffer)[0]), sizeof(ll->buffer))) < 0) {
462 if (errno == EAGAIN) continue;
463 if (errno == EINTR) continue;
464 fprintf(stderr, "imap_ll_waitline: read: %s\n", strerror(errno));
465 imap_ll_freeline(line);
466 return NULL;
467 }
468 if (n == 0) {
469 fprintf(stderr, "imap_ll_waitline: read: EOF\n");
470 imap_ll_freeline(line);
471 return NULL;
472 }
473 #ifdef USE_OPENSSL
474 }
475 #endif
476 ll->buffer_left = n;
477 ll->bufferp = &((ll->buffer)[0]);
478 }
479 }
480
481 /* this may overestimate the size a bit */
482 static size_t
string_len(struct imap_ll_tokenlist * t)483 string_len(struct imap_ll_tokenlist *t)
484 {
485 size_t result;
486
487 switch (t->type) {
488 case TLTYPE_UNTAGGED:
489 result = 2;
490 break;
491 case TLTYPE_TAGGED:
492 result = 15;
493 break;
494 case TLTYPE_LIST:
495 case TLTYPE_SQLIST:
496 result = 2;
497 break;
498 case TLTYPE_ATOM:
499 result = t->leaflen;
500 break;
501 case TLTYPE_STRING:
502 /* worst case is if it's sent as a literal */
503 result = 20 + t->leaflen;
504 break;
505 case TLTYPE_CONTINUATION:
506 /* shouldn't be used */
507 result = 2;
508 break;
509 case TLTYPE_POP:
510 case TLTYPE_END:
511 case TLTYPE_SUB:
512 default:
513 result = 0;
514 }
515 for (t = t->first; t; t = t->next) {
516 result++;
517 result += string_len(t);
518 }
519 return result;
520 }
521
522 static char *
format_cmd(char * out,struct imap_ll * ll,struct imap_ll_tokenlist * t)523 format_cmd(char *out, struct imap_ll *ll, struct imap_ll_tokenlist *t)
524 {
525 int i;
526 char close = 0;
527
528 switch (t->type) {
529 case TLTYPE_UNTAGGED:
530 *(out++) = '*';
531 *(out++) = ' ';
532 case TLTYPE_TAGGED:
533 out += sprintf(out, "t%d ", ++(ll->current_tag_number));
534 break;
535 case TLTYPE_LIST:
536 *(out++) = '('; close = ')';
537 break;
538 case TLTYPE_SQLIST:
539 *(out++) = '['; close = ']';
540 break;
541 case TLTYPE_ATOM:
542 memcpy(out, t->leaf, t->leaflen);
543 out += t->leaflen;
544 break;
545 case TLTYPE_STRING:
546 for (i = 0; i < t->leaflen; i++) {
547 if (
548 (t->leaf[i] < 32) ||
549 (t->leaf[i] == 34) ||
550 (t->leaf[i] > 126)
551 ) break;
552 }
553 if (i < t->leaflen) {
554 /* send as litteral */
555 out += sprintf(out, "{%d+}\015\012", (int)(t->leaflen));
556 memcpy(out, t->leaf, t->leaflen);
557 out += t->leaflen;
558 } else {
559 /* send as regular string */
560 *(out++) = 34;
561 memcpy(out, t->leaf, t->leaflen);
562 out += t->leaflen;
563 *(out++) = 34;
564 }
565 break;
566 case TLTYPE_CONTINUATION:
567 *(out++) = '+';
568 *(out++) = ' ';
569 break;
570 case TLTYPE_END:
571 case TLTYPE_POP:
572 case TLTYPE_SUB:
573 break;
574 }
575 i = 0;
576 for (t = t->first; t; t = t->next) {
577 if (i) {
578 *(out++) = ' ';
579 } else {
580 i = 1;
581 }
582 out = format_cmd(out, ll, t);
583 }
584 if (close) {
585 *(out++) = close;
586 }
587 return out;
588 }
589
590 static int
imap_ll_write(struct imap_ll * ll,const char * p,const char * cmdend)591 imap_ll_write(struct imap_ll *ll, const char *p, const char *cmdend)
592 {
593 size_t len;
594 struct pollfd pfd;
595 int ctimeout, n;
596 #ifdef USE_OPENSSL
597 int ret2;
598
599 if (ll->ssl) {
600 while (p < cmdend) {
601 n = SSL_write(ll->ssl, p, cmdend-p);
602 if (n > 0) {
603 p += n;
604 } else {
605 ret2 = SSL_get_error(ll->ssl, n);
606 if ((ret2 == SSL_ERROR_WANT_READ) || (ret2 == SSL_ERROR_WANT_WRITE)) {
607 if (ret2 == SSL_ERROR_WANT_READ) {
608 pfd.fd = ll->r_socket;
609 pfd.events = POLLIN;
610 } else {
611 pfd.fd = ll->w_socket;
612 pfd.events = POLLOUT;
613 }
614 ctimeout = ll->timeout_time - time(NULL);
615 if (ctimeout < 0) ctimeout = 0;
616 if ((n = poll(&pfd, 1, ctimeout*1000)) < 0) {
617 if (errno == EINTR) continue;
618 goto pollerr;
619 }
620 if (n == 0) goto polltimeout;
621 } else {
622 ERR_print_errors_fp(stderr);
623 return 0;
624 }
625 }
626 }
627 } else {
628 #endif
629 pfd.fd = ll->w_socket;
630 pfd.events = POLLOUT;
631 while (p < cmdend) {
632 repoll:
633 ctimeout = ll->timeout_time - time(NULL);
634 if (ctimeout < 0) ctimeout = 0;
635 if ((n = poll(&pfd, 1, ctimeout*1000)) < 0) {
636 if (errno == EINTR) goto repoll;
637 #ifdef USE_OPENSSL
638 pollerr:
639 #endif
640 fprintf(stderr, "imap_ll_command: poll: %s\n", strerror(errno));
641 return 0;
642 }
643 if (n == 0) {
644 #ifdef USE_OPENSSL
645 polltimeout:
646 #endif
647 fprintf(stderr, "imap_ll_command: timeout\n");
648 return 0;
649 }
650 len = write(ll->w_socket, p, cmdend-p);
651 if (len < 0) {
652 if (errno == EAGAIN) continue;
653 if (errno == EINTR) continue;
654 fprintf(stderr, "imap_ll_command: write: %s\n", strerror(errno));
655 return 0;
656 } else if (len == 0) {
657 fprintf(stderr, "imap_ll_command: short write\n");
658 return 0;
659 }
660 p += len;
661 }
662 #ifdef USE_OPENSSL
663 }
664 #endif
665 return 1;
666 }
667
668 struct imap_ll_tokenlist *
imap_ll_command(struct imap_ll * ll,struct imap_ll_tokenlist * cmd,int timeout)669 imap_ll_command(struct imap_ll *ll, struct imap_ll_tokenlist *cmd, int timeout)
670 {
671 size_t len;
672 char *cmds, *cmdend;
673 struct imap_ll_tokenlist *result, *cur;
674
675 len = string_len(cmd);
676 cmds = malloc(len+1);
677 if (!cmds) {
678 fprintf(stderr, "imap_ll_command: malloc failed\n");
679 return NULL;
680 }
681 cmdend = format_cmd(cmds, ll, cmd);
682 *(cmdend++) = 13;
683 *(cmdend++) = 10;
684 imap_ll_timeout(ll, timeout);
685 if (!imap_ll_write(ll, cmds, cmdend)) {
686 free(cmds);
687 return NULL;
688 }
689 free(cmds);
690 result = mktoken(TLTYPE_LIST, NULL, 0);
691 for (;;) {
692 if (!(cur = imap_ll_waitline(ll))) {
693 imap_ll_freeline(result);
694 return NULL;
695 }
696 cur->parent = result;
697 cur->next = NULL;
698 if (result->first) {
699 result->last->next = cur;
700 } else {
701 result->first = cur;
702 }
703 result->last = cur;
704
705 if (cur->type == TLTYPE_TAGGED) {
706 cur->next = NULL;
707 return result;
708 }
709 }
710 }
711
712 void
imap_ll_logout(struct imap_ll * ll)713 imap_ll_logout(struct imap_ll *ll)
714 {
715 size_t len;
716 struct imap_ll_tokenlist *cur;
717 char cmd[25];
718
719 len = sprintf(cmd, "t%d LOGOUT\015\012", ++(ll->current_tag_number));
720 imap_ll_timeout(ll, 5);
721 if (!imap_ll_write(ll, cmd, cmd+len)) return;
722 imap_ll_timeout(ll, 5);
723 for (;;) {
724 if (!(cur = imap_ll_waitline(ll))) {
725 return;
726 }
727 if (cur->type == TLTYPE_TAGGED) {
728 break;
729 }
730 imap_ll_freeline(cur);
731 }
732 imap_ll_freeline(cur);
733 }
734
735 struct imap_ll_tokenlist *
imap_ll_build(enum imap_ll_tltype maintype,...)736 imap_ll_build(enum imap_ll_tltype maintype, ...)
737 {
738 va_list ap;
739 struct imap_ll_tokenlist *top = NULL;
740 struct imap_ll_tokenlist *cur = NULL;
741 struct imap_ll_tokenlist *l;
742 char *s;
743 size_t slen;
744 int descend;
745 enum imap_ll_tltype next_type = maintype;
746
747 va_start(ap, maintype);
748 for (;;) {
749 switch (next_type) {
750 default:
751 case TLTYPE_END:
752 va_end(ap);
753 return top;
754 case TLTYPE_SUB:
755 l = va_arg(ap, struct imap_ll_tokenlist *);
756 descend = 0;
757 break;
758 case TLTYPE_ATOM:
759 case TLTYPE_STRING:
760 s = va_arg(ap, char *);
761 slen = va_arg(ap, size_t);
762 if (slen == -1) slen = strlen(s);
763 l = malloc(sizeof(*l));
764 if (!l) {
765 nomem:
766 fprintf(stderr, "imap_ll_build: malloc failure\n");
767 imap_ll_freeline(top);
768 va_end(ap);
769 return NULL;
770 }
771 l->type = next_type;
772 l->leaf = malloc(slen+1);
773 if (!(l->leaf)) {
774 free(l);
775 goto nomem;
776 }
777 memcpy(l->leaf, s, slen);
778 l->leaf[slen] = 0;
779 l->leaflen = slen;
780 descend = 0;
781 break;
782 case TLTYPE_TAGGED:
783 case TLTYPE_UNTAGGED:
784 case TLTYPE_LIST:
785 case TLTYPE_SQLIST:
786 l = malloc(sizeof(*l));
787 if (!l) goto nomem;
788 l->type = next_type;
789 l->leaf = NULL;
790 descend = 1;
791 break;
792 case TLTYPE_CONTINUATION:
793 l = malloc(sizeof(*l));
794 if (!l) goto nomem;
795 l->type = TLTYPE_CONTINUATION;
796 l->leaf = NULL;
797 descend = 0;
798 break;
799 case TLTYPE_POP:
800 break;
801 }
802 if (next_type == TLTYPE_POP) {
803 cur = cur->parent;
804 } else {
805 if (next_type != TLTYPE_SUB) l->first = l->last = NULL;
806 l->parent = cur;
807 l->next = NULL;
808 if (cur) {
809 if (cur->last) {
810 cur->last->next = l;
811 cur->last = l;
812 } else {
813 cur->first = cur->last = l;
814 }
815 } else {
816 top = cur = l;
817 }
818 if (descend) cur = l;
819 }
820 next_type = va_arg(ap, int);
821 }
822 }
823
824 void
imap_ll_append(struct imap_ll_tokenlist * list,struct imap_ll_tokenlist * item)825 imap_ll_append(struct imap_ll_tokenlist *list, struct imap_ll_tokenlist *item)
826 {
827 if (list->last) {
828 list->last->next = item;
829 } else {
830 list->first = item;
831 }
832 list->last = item;
833 item->parent = list;
834 }
835
836 const char *
imap_ll_status(struct imap_ll_tokenlist * t)837 imap_ll_status(struct imap_ll_tokenlist *t)
838 {
839 t = t->last->first;
840 if (!t) return "BAD";
841 if ((t->type) != TLTYPE_ATOM) return "BAD";
842 return t->leaf;
843 }
844
845 int
imap_ll_is_trycreate(struct imap_ll_tokenlist * t)846 imap_ll_is_trycreate(struct imap_ll_tokenlist *t)
847 {
848 return (
849 (0 == strcmp(imap_ll_status(t), "NO")) &&
850 (t->last->first->next) &&
851 ((t->last->first->next->type) == TLTYPE_SQLIST) &&
852 (t->last->first->next->first) &&
853 ((t->last->first->next->first->type) == TLTYPE_ATOM) &&
854 (0 == strcmp(t->last->first->next->first->leaf, "TRYCREATE"))
855 );
856 }
857
858 #ifdef USE_OPENSSL
859 enum imap_ll_starttls_result
imap_ll_starttls(struct imap_ll * ll,SSL_CTX * sslctx,const char * servername)860 imap_ll_starttls(struct imap_ll *ll, SSL_CTX *sslctx, const char *servername)
861 {
862 struct imap_ll_tokenlist *cmd, *l;
863 const char *status;
864 struct pollfd pfd;
865 int ret, ret2, timeout;
866 SSL *ssl;
867 long vr;
868 X509 *peer;
869 size_t servernamelen, certnamelen;
870 char buf[256];
871 char buf2[256];
872
873 ssl = SSL_new(sslctx);
874 if (!ssl) {
875 ERR_print_errors_fp(stderr);
876 }
877
878 cmd = imap_ll_build(TLTYPE_TAGGED, TLTYPE_ATOM, "STARTTLS", (size_t)-1, TLTYPE_END);
879 if (!cmd) {
880 fprintf(stderr, "could not build STARTTLS command\n");
881 SSL_free(ll->ssl); ll->ssl = NULL;
882 return IMAP_LL_STARTTLS_FAILED_PROCEED;
883 }
884 l = imap_ll_command(ll, cmd, 5);
885 imap_ll_freeline(cmd);
886 status = imap_ll_status(l);
887 if (0 == strcmp(status, "NO")) {
888 fprintf(stderr, "STARTTLS not accepted by server\n");
889 imap_ll_freeline(l);
890 SSL_free(ll->ssl); ll->ssl = NULL;
891 return IMAP_LL_STARTTLS_FAILED_PROCEED;
892 } else if (0 != strcmp(status, "OK")) {
893 fprintf(stderr, "STARTTLS not understood by server\n");
894 imap_ll_freeline(l);
895 SSL_free(ll->ssl); ll->ssl = NULL;
896 return IMAP_LL_STARTTLS_FAILED_PROCEED;
897 }
898 imap_ll_freeline(l);
899
900 SSL_set_rfd(ssl, ll->r_socket);
901 SSL_set_wfd(ssl, ll->w_socket);
902
903 imap_ll_timeout(ll, 10); /* reset clock */
904 for (;;) {
905 ret = SSL_connect(ssl);
906 if (ret == 1) break;
907 ret2 = SSL_get_error(ssl, ret);
908 if ((ret2 == SSL_ERROR_WANT_READ) || (ret2 == SSL_ERROR_WANT_WRITE)) {
909 if (ret2 == SSL_ERROR_WANT_READ) {
910 pfd.fd = ll->r_socket;
911 pfd.events = POLLIN;
912 } else {
913 pfd.fd = ll->w_socket;
914 pfd.events = POLLOUT;
915 }
916 timeout = ll->timeout_time - time(NULL);
917 if (timeout < 0) timeout = 0;
918 if ((ret = poll(&pfd, 1, timeout*1000)) < 0) {
919 if (errno == EINTR) continue;
920 fprintf(stderr, "STARTTLS poll error: %s\n", strerror(errno));
921 SSL_free(ssl);
922 return IMAP_LL_STARTTLS_FAILED;
923 }
924 if (ret == 0) {
925 fprintf(stderr, "STARTTLS timeout\n");
926 SSL_free(ssl);
927 return IMAP_LL_STARTTLS_FAILED;
928 }
929 } else {
930 ERR_error_string(ERR_get_error(), &(buf[0]));
931 fprintf(stderr, "STARTTLS: %s\n", buf);
932 SSL_free(ssl);
933 return IMAP_LL_STARTTLS_FAILED;
934 }
935 }
936 if ((vr = SSL_get_verify_result(ssl)) != X509_V_OK) {
937 fprintf(stderr, "STARTTLS: ceritifcate verification failed (%ld)\n", vr);
938 certfail:
939 SSL_free(ssl);
940 return IMAP_LL_STARTTLS_FAILED_CERT;
941 }
942 if (!(peer = SSL_get_peer_certificate(ssl))) {
943 fprintf(stderr, "STARTTLS: no peer certificate?\n");
944 goto certfail;
945 }
946 if (X509_NAME_get_text_by_NID(X509_get_subject_name(peer), NID_commonName, buf, sizeof(buf)) == -1) {
947 fprintf(stderr, "STARTTLS: no common name in certificate?\n");
948 goto certfail;
949 }
950 if (!servername) goto certok; /* caller does not want us to check */
951 if (0 != strcasecmp(buf, servername)){
952 /* Certificate name does not match exactly.
953 Check for a wildcard certificate name.
954 The only valid form for certificate naming wildcards is
955 an asterisk as the first character followed by any number
956 of non-wild characters */
957 if (buf[0] == '*') {
958 servernamelen = strlen(servername);
959 certnamelen = strlen(buf+1);
960 if (
961 (servernamelen >= certnamelen) &&
962 (0 == strcasecmp(
963 buf+1,
964 servername + (servernamelen-certnamelen))
965 )
966 ) {
967 goto certok;
968 }
969 }
970 snprintf(buf2, sizeof(buf2), "%s", servername);
971 fprintf(stderr, "STARTTLS: server name \"%s\" != certificate common name \"%s\"\n", buf2, buf);
972 goto certfail;
973 }
974
975 certok:
976 ll->ssl = ssl;
977 return IMAP_LL_STARTTLS_SUCCESS;
978 }
979 #endif
980
981 void
imap_ll_pprint(struct imap_ll_tokenlist * t,int indent,FILE * out)982 imap_ll_pprint(struct imap_ll_tokenlist *t, int indent, FILE *out)
983 {
984 int plusindent = 0;
985 int i;
986 const char *close = NULL;
987
988 switch (t->type) {
989 case TLTYPE_UNTAGGED:
990 for (i = 0; i < indent; i++) fputc(9, out);
991 fprintf(out, "*\n");
992 break;
993 case TLTYPE_TAGGED:
994 break;
995 case TLTYPE_LIST:
996 plusindent = 1;
997 for (i = 0; i < indent; i++) fputc(9, out);
998 fprintf(out, "(\n"); close = ")\n";
999 break;
1000 case TLTYPE_SQLIST:
1001 plusindent = 1;
1002 for (i = 0; i < indent; i++) fputc(9, out);
1003 fprintf(out, "[\n"); close = "]\n";
1004 break;
1005 case TLTYPE_ATOM:
1006 case TLTYPE_STRING:
1007 for (i = 0; i < indent; i++) fputc(9, out);
1008 if ((t->type) == TLTYPE_STRING) fputc(34, out);
1009 for (i = 0; i < (t->leaflen); i++) {
1010 if (
1011 (t->leaf[i] >= ' ') &&
1012 (t->leaf[i] < 127) &&
1013 (t->leaf[i] != 34) &&
1014 (t->leaf[i] != 92)
1015 ) {
1016 fputc(t->leaf[i], out);
1017 } else {
1018 fprintf(out, "\\x%02x", t->leaf[i]);
1019 }
1020 }
1021 if ((t->type) == TLTYPE_STRING) fputc(34, out);
1022 fputc('\n', out);
1023 break;
1024 case TLTYPE_CONTINUATION:
1025 for (i = 0; i < indent; i++) fputc(9, out);
1026 fputs("+ \n", out);
1027 break;
1028 case TLTYPE_END:
1029 case TLTYPE_POP:
1030 case TLTYPE_SUB:
1031 break;
1032 }
1033 indent += plusindent;
1034 for (t = t->first; t; t = t->next) {
1035 imap_ll_pprint(t, indent, out);
1036 }
1037 indent -= plusindent;
1038 if (close) {
1039 for (i = 0; i < indent; i++) fputc(9, out);
1040 fputs(close, out);
1041 }
1042 }
1043
1044 static struct imap_ll_tokenlist *
find_capability_list_in_untagged_response(struct imap_ll_tokenlist * l)1045 find_capability_list_in_untagged_response(struct imap_ll_tokenlist *l)
1046 {
1047 for (l = l->first; l; l = l->next) {
1048 if (
1049 ((l->type) == TLTYPE_UNTAGGED) &&
1050 ((l->first->type) == TLTYPE_ATOM) &&
1051 (0 == strcmp(l->first->leaf, "CAPABILITY"))
1052 ) return l->first->next;
1053 }
1054 fprintf(stderr, "IMAP client cannot find capabilities list\n");
1055 return NULL;
1056 }
1057
1058 static struct imap_ll_tokenlist *
ask_for_capabilities(struct imap_ll * ll)1059 ask_for_capabilities(struct imap_ll *ll)
1060 {
1061 struct imap_ll_tokenlist *cmd, *result;
1062
1063 cmd = imap_ll_build(
1064 TLTYPE_TAGGED,
1065 TLTYPE_ATOM, "CAPABILITY", (size_t)-1,
1066 TLTYPE_END
1067 );
1068 result = imap_ll_command(ll, cmd, 10);
1069 imap_ll_freeline(cmd);
1070 return result;
1071 }
1072
1073 /* caller should free whatever this function fills into freeme_p */
1074 static struct imap_ll_tokenlist *
find_capability_list_from_greeting(struct imap_ll * ll,struct imap_ll_tokenlist * l,struct imap_ll_tokenlist ** freeme_p)1075 find_capability_list_from_greeting(struct imap_ll *ll, struct imap_ll_tokenlist *l, struct imap_ll_tokenlist **freeme_p)
1076 {
1077 *freeme_p = NULL;
1078 if (
1079 (!(l->first)) || (!(l->first->next)) ||
1080 (l->first->next->type != TLTYPE_SQLIST) ||
1081 (!(l->first->next->first)) ||
1082 (!(l->first->next->first->leaf)) ||
1083 (0 != strcmp(l->first->next->first->leaf, "CAPABILITY"))
1084 ) {
1085 /* Server did not send capabilities in greeting. */
1086 *freeme_p = l = ask_for_capabilities(ll);
1087 return find_capability_list_in_untagged_response(l);
1088 } else {
1089 return l->first->next->first->next;
1090 }
1091 }
1092
1093 #ifdef USE_OPENSSL
1094 static int
have_capability(struct imap_ll_tokenlist * l,const char * cap)1095 have_capability(struct imap_ll_tokenlist *l, const char *cap)
1096 {
1097 for (; l; l = l->next) {
1098 if ((l->leaf) && (0 == strcmp(l->leaf, cap))) return 1;
1099 }
1100 return 0;
1101 }
1102 #endif
1103
1104 static int
need_capability(struct imap_ll_tokenlist * l,const char * cap)1105 need_capability(struct imap_ll_tokenlist *l, const char *cap)
1106 {
1107 for (; l; l = l->next) {
1108 if ((l->leaf) && (0 == strcmp(l->leaf, cap))) return 1;
1109 }
1110 fprintf(stderr, "IMAP server missing required capability %s\n", cap);
1111 return 0;
1112 }
1113
1114 enum imap_login_result
imap_login(struct imap_ll * ll,const char * username,size_t username_len,const char * password,size_t password_len,SSL_CTX * sslctx,const char * servername)1115 imap_login(
1116 struct imap_ll *ll,
1117 const char *username, size_t username_len,
1118 const char *password, size_t password_len
1119 #ifdef USE_OPENSSL
1120 , SSL_CTX *sslctx, const char *servername
1121 #endif
1122 ) {
1123 struct imap_ll_tokenlist *cmd, *result, *caps, *freeme;
1124 enum imap_login_result ret;
1125 const char *status;
1126
1127 freeme = NULL;
1128 imap_ll_timeout(ll, 60);
1129 if (!(result = imap_ll_waitline(ll))) {
1130 fprintf(stderr, "error reading IMAP server greeting\n");
1131 return imap_login_error;
1132 }
1133
1134 if (
1135 (!(result->first)) ||
1136 ((result->first->type) != TLTYPE_ATOM)
1137 ) {
1138 fprintf(stderr, "cannot parse IMAP server greeting\n");
1139 imap_ll_freeline(result);
1140 return imap_login_error;
1141 }
1142 if (0 == strcmp(result->first->leaf, "BYE")) {
1143 imap_ll_freeline(result);
1144 fprintf(stderr, "IMAP server rejected connection\n");
1145 return imap_login_error;
1146 } else if (0 == strcmp(result->first->leaf, "PREAUTH")) {
1147 if (!(caps = find_capability_list_from_greeting(ll, result, &freeme))) {
1148 imap_ll_freeline(result);
1149 if (freeme) imap_ll_freeline(freeme);
1150 return imap_login_error;
1151 }
1152 goto logged_in;
1153 } else if (0 != strcmp(result->first->leaf, "OK")) {
1154 fprintf(stderr, "IMAP server unacceptable greeting\n");
1155 return imap_login_error;
1156 }
1157
1158 if (!(caps = find_capability_list_from_greeting(ll, result, &freeme))) {
1159 imap_ll_freeline(result);
1160 if (freeme) imap_ll_freeline(freeme);
1161 return imap_login_error;
1162 }
1163
1164 #ifdef USE_OPENSSL
1165 if (have_capability(caps, "STARTTLS")) {
1166 switch (imap_ll_starttls(ll, sslctx, servername)) {
1167 case IMAP_LL_STARTTLS_FAILED_PROCEED:
1168 break;
1169 case IMAP_LL_STARTTLS_FAILED:
1170 case IMAP_LL_STARTTLS_FAILED_CERT:
1171 if (freeme) imap_ll_freeline(freeme);
1172 imap_ll_freeline(result);
1173 return imap_login_error;
1174 case IMAP_LL_STARTTLS_SUCCESS:
1175 if (freeme) imap_ll_freeline(freeme);
1176 freeme = NULL;
1177 imap_ll_freeline(result);
1178 result = ask_for_capabilities(ll);
1179 if (!(caps = find_capability_list_in_untagged_response(result))) {
1180 imap_ll_freeline(result);
1181 return imap_login_error;
1182 }
1183 }
1184 }
1185 #endif
1186
1187 if (!need_capability(caps, "LITERAL+")) {
1188 ret = imap_login_error;
1189 goto exit;
1190 }
1191 if ((!username) || (!password)) {
1192 fprintf(stderr, "IMAP login credentials are needed\n");
1193 ret = imap_login_denied;
1194 goto exit;
1195 }
1196 if (!need_capability(caps, "AUTH=PLAIN")) {
1197 ret = imap_login_error;
1198 goto exit;
1199 }
1200 if (freeme) imap_ll_freeline(freeme);
1201 imap_ll_freeline(result);
1202 cmd = imap_ll_build(
1203 TLTYPE_TAGGED,
1204 TLTYPE_ATOM, "LOGIN", (size_t)-1,
1205 TLTYPE_STRING, username, username_len,
1206 TLTYPE_STRING, password, password_len,
1207 TLTYPE_END
1208 );
1209 result = imap_ll_command(ll, cmd, 40);
1210 imap_ll_freeline(cmd);
1211 status = imap_ll_status(result);
1212 if (0 == strcmp(status, "OK")) {
1213 if (!(caps = find_capability_list_from_greeting(ll, result->last, &freeme))) {
1214 if (freeme) imap_ll_freeline(freeme);
1215 imap_ll_freeline(result);
1216 return imap_login_error;
1217 }
1218 logged_in:
1219 ret = imap_login_ok;
1220 if (!need_capability(caps, "LITERAL+")) ret = imap_login_error;
1221 exit:
1222 imap_ll_freeline(result);
1223 if (freeme) imap_ll_freeline(freeme);
1224 return ret;
1225 } else if (0 == strcmp(status, "NO")) {
1226 fprintf(stderr, "IMAP authentication failed\n");
1227 imap_ll_freeline(result);
1228 return imap_login_denied;
1229 }
1230 fprintf(stderr, "IMAP server did not understand login command\n");
1231 imap_ll_freeline(result);
1232 return imap_login_error;
1233 }
1234
1235 const char *
imap_select(struct imap_ll * ll,const char * foldername,size_t foldername_len,int need_write,long * exists_p)1236 imap_select(
1237 struct imap_ll *ll,
1238 const char *foldername, size_t foldername_len,
1239 int need_write, long *exists_p
1240 ) {
1241 const char *command_name;
1242 struct imap_ll_tokenlist *cmd, *result, *l, *l2;
1243 char *uidvalidity = NULL;
1244 long exists = 0;
1245
1246 if (foldername_len == -1) foldername_len = strlen(foldername);
1247 if (
1248 (ll->selected_folder) &&
1249 ((ll->selected_folder_namelen) == foldername_len) &&
1250 (0 == memcmp(ll->selected_folder, foldername, foldername_len)) &&
1251 ((!need_write) || (ll->selected_folder_writable))
1252 ) {
1253 if (exists_p) *exists_p = ll->selected_folder_exists;
1254 return ll->selected_folder_uidvalidity;
1255 }
1256
1257 command_name = need_write ? "SELECT" : "EXAMINE";
1258 cmd = imap_ll_build(
1259 TLTYPE_TAGGED,
1260 TLTYPE_ATOM, command_name, (size_t)-1,
1261 TLTYPE_STRING, foldername, foldername_len,
1262 TLTYPE_END
1263 );
1264 result = imap_ll_command(ll, cmd, 40);
1265 imap_ll_freeline(cmd);
1266
1267 if (!result) {
1268 fprintf(stderr, "unable to %s folder \"%s\": no response from IMAP server\n", command_name, foldername);
1269 return NULL;
1270 }
1271 if (0 != strcmp(imap_ll_status(result), "OK")) {
1272 fprintf(stderr, "unable to %s folder \"%s\":\n", command_name, foldername);
1273 imap_ll_pprint(result, 0, stderr);
1274 imap_ll_freeline(result);
1275 return NULL;
1276 }
1277
1278 for (l = result->first; l != result->last; l = l->next) {
1279 /* Check if this is UIDVALIDITY */
1280 l2 = l->first;
1281 if (!l2) continue;
1282 if (l2->type != TLTYPE_ATOM) continue;
1283 if (0 == strcmp(l2->leaf, "OK")) {
1284 l2 = l2->next;
1285 if (!l2) continue;
1286 if (l2->type != TLTYPE_SQLIST) continue;
1287 l2 = l2->first;
1288 if (!l2) continue;
1289 if (l2->type != TLTYPE_ATOM) continue;
1290 if (0 != strcmp(l2->leaf, "UIDVALIDITY")) continue;
1291 l2 = l2->next;
1292 if (!l2) continue;
1293 if (l2->type != TLTYPE_ATOM) continue;
1294 uidvalidity = l2->leaf;
1295 } else {
1296 if (!(l2->next)) continue;
1297 if ((l2->next->type) != TLTYPE_ATOM) continue;
1298 if (0 != strcmp(l2->next->leaf, "EXISTS")) continue;
1299 exists = atol(l2->leaf);
1300 }
1301 }
1302 if (!uidvalidity) {
1303 fprintf(stderr, "no uidvalidity in EXAMINE response for folder \"%s\":\n", foldername);
1304 imap_ll_pprint(result, 0, stderr);
1305 imap_ll_freeline(result);
1306 return NULL;
1307 }
1308
1309 if (ll->selected_folder) free(ll->selected_folder);
1310 if (ll->selected_folder_uidvalidity) free(ll->selected_folder_uidvalidity);
1311 ll->selected_folder = malloc(foldername_len);
1312 if (ll->selected_folder) {
1313 memcpy(ll->selected_folder, foldername, foldername_len);
1314 }
1315 ll->selected_folder_namelen = foldername_len;
1316 ll->selected_folder_uidvalidity = strdup(uidvalidity);
1317 ll->selected_folder_writable = need_write;
1318 ll->selected_folder_exists = exists;
1319 if (exists_p) *exists_p = exists;
1320 imap_ll_freeline(result);
1321 return ll->selected_folder_uidvalidity;
1322 }
1323