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