1 /*
2  * mbsync - mailbox synchronizer
3  * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 2002-2006,2008,2010-2013 Oswald Buddenhagen <ossi@users.sf.net>
5  * Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu>
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  * As a special exception, mbsync may be linked with the OpenSSL library,
21  * despite that library's more restrictive license.
22  */
23 
24 #include "driver.h"
25 
26 #include "socket.h"
27 
28 #include <assert.h>
29 #include <unistd.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <stddef.h>
33 #include <limits.h>
34 #include <string.h>
35 #include <ctype.h>
36 #include <time.h>
37 #include <sys/wait.h>
38 
39 #ifdef HAVE_LIBSASL
40 # include <sasl/sasl.h>
41 # include <sasl/saslutil.h>
42 #endif
43 
44 #ifdef HAVE_MACOS_KEYCHAIN
45 # include <Security/Security.h>
46 #endif
47 
48 #ifdef HAVE_LIBSSL
49 enum { SSL_None, SSL_STARTTLS, SSL_IMAPS };
50 #endif
51 
52 typedef struct imap_server_conf {
53 	struct imap_server_conf *next;
54 	char *name;
55 	server_conf_t sconf;
56 	char *user;
57 	char *user_cmd;
58 	char *pass;
59 	char *pass_cmd;
60 	int max_in_progress;
61 	uint cap_mask;
62 	string_list_t *auth_mechs;
63 #ifdef HAVE_LIBSSL
64 	char ssl_type;
65 #endif
66 #ifdef HAVE_MACOS_KEYCHAIN
67 	char use_keychain;
68 #endif
69 	char failed;
70 } imap_server_conf_t;
71 
72 typedef union imap_store_conf {
73 	store_conf_t gen;
74 	struct {
75 		STORE_CONF
76 		imap_server_conf_t *server;
77 		char *path;  // Note: this may be modified after the delimiter is determined.
78 		char delimiter;
79 		char use_namespace;
80 		char use_lsub;
81 	};
82 } imap_store_conf_t;
83 
84 typedef union imap_message {
85 	message_t gen;
86 	struct {
87 		MESSAGE(union imap_message)
88 		// uint seq; will be needed when expunges are tracked
89 	};
90 } imap_message_t;
91 
92 #define NIL	(void*)0x1
93 #define LIST	(void*)0x2
94 
95 typedef struct _list {
96 	struct _list *next, *child;
97 	char *val;
98 	uint len;
99 } list_t;
100 
101 #define MAX_LIST_DEPTH 5
102 
103 typedef union imap_store imap_store_t;
104 
105 typedef struct {
106 	list_t *head, **stack[MAX_LIST_DEPTH];
107 	int (*callback)( imap_store_t *ctx, list_t *list, char *cmd );
108 	int level, need_bytes;
109 } parse_list_state_t;
110 
111 typedef struct imap_cmd imap_cmd_t;
112 
113 union imap_store {
114 	store_t gen;
115 	struct {
116 		STORE(union imap_store)
117 		const char *label;  // foreign
118 		const char *name;
119 		char *prefix;
120 		uint ref_count;
121 		uint opts;
122 		enum { SST_BAD, SST_HALF, SST_GOOD } state;
123 		// The trash folder's existence is not confirmed yet
124 		enum { TrashUnknown, TrashChecking, TrashKnown } trashnc;
125 		// What kind of BODY-less FETCH response we're expecting
126 		enum { FetchNone, FetchMsgs, FetchUidNext } fetch_sts;
127 		uint got_namespace:1;
128 		uint has_forwarded:1;
129 		char delimiter[2];  // Hierarchy delimiter
130 		char *ns_prefix, ns_delimiter;  // NAMESPACE info
131 		string_list_t *boxes;  // _list results
132 		char listed;  // was _list already run with these flags?
133 		// note that the message counts do _not_ reflect stats from msgs,
134 		// but mailbox totals.
135 		int total_msgs, recent_msgs;
136 		uint uidvalidity, uidnext;
137 		imap_message_t **msgapp, *msgs;  // FETCH results
138 		uint caps;  // CAPABILITY results
139 		string_list_t *auth_mechs;
140 		parse_list_state_t parse_list_sts;
141 		// Command queue
142 		imap_cmd_t *pending, **pending_append;
143 		imap_cmd_t *in_progress, **in_progress_append;
144 		imap_cmd_t *wait_check, **wait_check_append;
145 		int nexttag, num_in_progress, num_wait_check;
146 		uint buffer_mem;  // Memory currently occupied by buffers in the queue
147 
148 		// Used during sequential operations like connect
149 		enum { GreetingPending = 0, GreetingBad, GreetingOk, GreetingPreauth } greeting;
150 		int expectBYE;  // LOGOUT is in progress
151 		int expectEOF;  // received LOGOUT's OK or unsolicited BYE
152 		int canceling;  // imap_cancel() is in progress
153 		union {
154 			void (*imap_open)( int sts, void *aux );
155 			void (*imap_cancel)( void *aux );
156 		} callbacks;
157 		void *callback_aux;
158 #ifdef HAVE_LIBSASL
159 		sasl_conn_t *sasl;
160 		int sasl_cont;
161 #endif
162 
163 		void (*bad_callback)( void *aux );
164 		void *bad_callback_aux;
165 
166 		conn_t conn;  // This is BIG, so put it last
167 	};
168 };
169 
170 #define IMAP_CMD \
171 	struct imap_cmd *next; \
172 	char *cmd; \
173 	int tag; \
174 	\
175 	struct { \
176 		/* Will be called on each continuation request until it resets this pointer. \
177 		 * Needs to invoke bad_callback and return -1 on error, otherwise return 0. */ \
178 		int (*cont)( imap_store_t *ctx, imap_cmd_t *cmd, const char *prompt ); \
179 		void (*done)( imap_store_t *ctx, imap_cmd_t *cmd, int response ); \
180 		char *data; \
181 		uint data_len; \
182 		uint uid;  /* to identify fetch responses */ \
183 		char high_prio;  /* if command is queued, put it at the front of the queue. */ \
184 		char wait_check;  /* Don't report success until subsequent CHECK success. */ \
185 		char to_trash;  /* we are storing to trash, not current. */ \
186 		char create;  /* create the mailbox if we get an error which suggests so. */ \
187 		char failok;  /* Don't complain about NO response. */ \
188 	} param;
189 
190 struct imap_cmd {
191 	IMAP_CMD
192 };
193 
194 #define IMAP_CMD_SIMPLE \
195 	IMAP_CMD \
196 	void (*callback)( int sts, void *aux ); \
197 	void *callback_aux;
198 
199 typedef union {
200 	imap_cmd_t gen;
201 	struct {
202 		IMAP_CMD_SIMPLE
203 	};
204 } imap_cmd_simple_t;
205 
206 typedef union {
207 	imap_cmd_simple_t gen;
208 	struct {
209 		IMAP_CMD_SIMPLE
210 		msg_data_t *msg_data;
211 	};
212 } imap_cmd_fetch_msg_t;
213 
214 typedef union {
215 	imap_cmd_t gen;
216 	struct {
217 		IMAP_CMD
218 		void (*callback)( int sts, uint uid, void *aux );
219 		void *callback_aux;
220 	};
221 } imap_cmd_out_uid_t;
222 
223 typedef union {
224 	imap_cmd_t gen;
225 	struct {
226 		IMAP_CMD
227 		void (*callback)( int sts, message_t *msgs, void *aux );
228 		void *callback_aux;
229 		imap_message_t **out_msgs;
230 		uint uid;
231 	};
232 } imap_cmd_find_new_t;
233 
234 #define IMAP_CMD_REFCOUNTED_STATE \
235 	uint ref_count; \
236 	int ret_val;
237 
238 typedef struct {
239 	IMAP_CMD_REFCOUNTED_STATE
240 } imap_cmd_refcounted_state_t;
241 
242 typedef union {
243 	imap_cmd_t gen;
244 	struct {
245 		IMAP_CMD
246 		imap_cmd_refcounted_state_t *state;
247 	};
248 } imap_cmd_refcounted_t;
249 
250 #define CAP(cap) (ctx->caps & (1 << (cap)))
251 
252 enum CAPABILITY {
253 	NOLOGIN = 0,
254 #ifdef HAVE_LIBSASL
255 	SASLIR,
256 #endif
257 #ifdef HAVE_LIBSSL
258 	STARTTLS,
259 #endif
260 	UIDPLUS,
261 	LITERALPLUS,
262 	MOVE,
263 	NAMESPACE,
264 	COMPRESS_DEFLATE
265 };
266 
267 static const char *cap_list[] = {
268 	"LOGINDISABLED",
269 #ifdef HAVE_LIBSASL
270 	"SASL-IR",
271 #endif
272 #ifdef HAVE_LIBSSL
273 	"STARTTLS",
274 #endif
275 	"UIDPLUS",
276 	"LITERAL+",
277 	"MOVE",
278 	"NAMESPACE",
279 	"COMPRESS=DEFLATE"
280 };
281 
282 #define RESP_OK       0
283 #define RESP_NO       1
284 #define RESP_CANCEL   2
285 
imap_ref(imap_store_t * ctx)286 static INLINE void imap_ref( imap_store_t *ctx ) { ++ctx->ref_count; }
287 static int imap_deref( imap_store_t *ctx );
288 
289 static void imap_invoke_bad_callback( imap_store_t *ctx );
290 
291 /* Keep the mailbox driver flag definitions in sync: */
292 /* grep for MAILBOX_DRIVER_FLAG */
293 /* The order is according to alphabetical maildir flag sort */
294 static const char *Flags[] = {
295 	"\\Draft",	/* 'D' */
296 	"\\Flagged",	/* 'F' */
297 	"$Forwarded",	/* 'P' */
298 	"\\Answered",	/* 'R' */
299 	"\\Seen",	/* 'S' */
300 	"\\Deleted",	/* 'T' */
301 };
302 
303 static imap_cmd_t *
new_imap_cmd(uint size)304 new_imap_cmd( uint size )
305 {
306 	imap_cmd_t *cmd = nfmalloc( size );
307 	memset( &cmd->param, 0, sizeof(cmd->param) );
308 	return cmd;
309 }
310 
311 #define INIT_IMAP_CMD(type, cmdp, cb, aux) \
312 	cmdp = (type *)new_imap_cmd( sizeof(*cmdp) ); \
313 	cmdp->callback = cb; \
314 	cmdp->callback_aux = aux;
315 
316 #define INIT_IMAP_CMD_X(type, cmdp, cb, aux) \
317 	cmdp = (type *)new_imap_cmd( sizeof(*cmdp) ); \
318 	cmdp->callback = cb; \
319 	cmdp->callback_aux = aux;
320 
321 static void
done_imap_cmd(imap_store_t * ctx,imap_cmd_t * cmd,int response)322 done_imap_cmd( imap_store_t *ctx, imap_cmd_t *cmd, int response )
323 {
324 	if (cmd->param.wait_check)
325 		ctx->num_wait_check--;
326 	cmd->param.done( ctx, cmd, response );
327 	if (cmd->param.data) {
328 		free( cmd->param.data );
329 		ctx->buffer_mem -= cmd->param.data_len;
330 	}
331 	free( cmd->cmd );
332 	free( cmd );
333 }
334 
335 static void
send_imap_cmd(imap_store_t * ctx,imap_cmd_t * cmd)336 send_imap_cmd( imap_store_t *ctx, imap_cmd_t *cmd )
337 {
338 	int litplus, iovcnt = 3;
339 	uint tbufl, lbufl;
340 	conn_iovec_t iov[5];
341 	char tagbuf[16];
342 	char lenbuf[16];
343 
344 	cmd->tag = ++ctx->nexttag;
345 	tbufl = nfsnprintf( tagbuf, sizeof(tagbuf), "%d ", cmd->tag );
346 	if (!cmd->param.data) {
347 		memcpy( lenbuf, "\r\n", 3 );
348 		lbufl = 2;
349 		litplus = 0;
350 	} else if ((cmd->param.to_trash && ctx->trashnc == TrashUnknown) || !CAP(LITERALPLUS) || cmd->param.data_len >= 100*1024) {
351 		lbufl = nfsnprintf( lenbuf, sizeof(lenbuf), "{%u}\r\n", cmd->param.data_len );
352 		litplus = 0;
353 	} else {
354 		lbufl = nfsnprintf( lenbuf, sizeof(lenbuf), "{%u+}\r\n", cmd->param.data_len );
355 		litplus = 1;
356 	}
357 	if (DFlags & DEBUG_NET) {
358 		if (ctx->num_in_progress)
359 			printf( "(%d in progress) ", ctx->num_in_progress );
360 		if (starts_with( cmd->cmd, -1, "LOGIN", 5 ))
361 			printf( "%s>>> %sLOGIN <user> <pass>\r\n", ctx->label, tagbuf );
362 		else if (starts_with( cmd->cmd, -1, "AUTHENTICATE PLAIN", 18 ))
363 			printf( "%s>>> %sAUTHENTICATE PLAIN <authdata>\r\n", ctx->label, tagbuf );
364 		else
365 			printf( "%s>>> %s%s%s", ctx->label, tagbuf, cmd->cmd, lenbuf );
366 		fflush( stdout );
367 	}
368 	iov[0].buf = tagbuf;
369 	iov[0].len = tbufl;
370 	iov[0].takeOwn = KeepOwn;
371 	iov[1].buf = cmd->cmd;
372 	iov[1].len = strlen( cmd->cmd );
373 	iov[1].takeOwn = KeepOwn;
374 	iov[2].buf = lenbuf;
375 	iov[2].len = lbufl;
376 	iov[2].takeOwn = KeepOwn;
377 	if (litplus) {
378 		if (DFlags & DEBUG_NET_ALL) {
379 			printf( "%s>>>>>>>>>\n", ctx->label );
380 			fwrite( cmd->param.data, cmd->param.data_len, 1, stdout );
381 			printf( "%s>>>>>>>>>\n", ctx->label );
382 			fflush( stdout );
383 		}
384 		iov[3].buf = cmd->param.data;
385 		iov[3].len = cmd->param.data_len;
386 		iov[3].takeOwn = GiveOwn;
387 		cmd->param.data = NULL;
388 		ctx->buffer_mem -= cmd->param.data_len;
389 		iov[4].buf = "\r\n";
390 		iov[4].len = 2;
391 		iov[4].takeOwn = KeepOwn;
392 		iovcnt = 5;
393 	}
394 	socket_write( &ctx->conn, iov, iovcnt );
395 	if (cmd->param.to_trash && ctx->trashnc == TrashUnknown)
396 		ctx->trashnc = TrashChecking;
397 	cmd->next = NULL;
398 	*ctx->in_progress_append = cmd;
399 	ctx->in_progress_append = &cmd->next;
400 	ctx->num_in_progress++;
401 	socket_expect_activity( &ctx->conn, 1 );
402 }
403 
404 static int
cmd_sendable(imap_store_t * ctx,imap_cmd_t * cmd)405 cmd_sendable( imap_store_t *ctx, imap_cmd_t *cmd )
406 {
407 	if (ctx->conn.write_buf) {
408 		/* Don't build up a long queue in the socket, so we can
409 		 * control when the commands are actually sent.
410 		 * This allows reliable cancelation of pending commands,
411 		 * injecting commands in front of other pending commands,
412 		 * and keeping num_in_progress accurate. */
413 		return 0;
414 	}
415 	if (ctx->in_progress) {
416 		/* If the last command in flight ... */
417 		imap_cmd_t *cmdp = (imap_cmd_t *)((char *)ctx->in_progress_append -
418 		                                  offsetof(imap_cmd_t, next));
419 		if (cmdp->param.cont || cmdp->param.data) {
420 			/* ... is expected to trigger a continuation request, we need to
421 			 * wait for that round-trip before sending the next command. */
422 			return 0;
423 		}
424 	}
425 	if (cmd->param.to_trash && ctx->trashnc == TrashChecking) {
426 		/* Don't build a queue of MOVE/COPY/APPEND commands that may all fail. */
427 		return 0;
428 	}
429 	if (ctx->num_in_progress >= ctx->conf->server->max_in_progress) {
430 		/* Too many commands in flight. */
431 		return 0;
432 	}
433 	return 1;
434 }
435 
436 static void
flush_imap_cmds(imap_store_t * ctx)437 flush_imap_cmds( imap_store_t *ctx )
438 {
439 	imap_cmd_t *cmd;
440 
441 	if ((cmd = ctx->pending) && cmd_sendable( ctx, cmd )) {
442 		if (!(ctx->pending = cmd->next))
443 			ctx->pending_append = &ctx->pending;
444 		send_imap_cmd( ctx, cmd );
445 	}
446 }
447 
448 static void
finalize_checked_imap_cmds(imap_store_t * ctx,int resp)449 finalize_checked_imap_cmds( imap_store_t *ctx, int resp )
450 {
451 	imap_cmd_t *cmd;
452 
453 	while ((cmd = ctx->wait_check)) {
454 		if (!(ctx->wait_check = cmd->next))
455 			ctx->wait_check_append = &ctx->wait_check;
456 		done_imap_cmd( ctx, cmd, resp );
457 	}
458 }
459 
460 static void
cancel_pending_imap_cmds(imap_store_t * ctx)461 cancel_pending_imap_cmds( imap_store_t *ctx )
462 {
463 	imap_cmd_t *cmd;
464 
465 	while ((cmd = ctx->pending)) {
466 		if (!(ctx->pending = cmd->next))
467 			ctx->pending_append = &ctx->pending;
468 		done_imap_cmd( ctx, cmd, RESP_CANCEL );
469 	}
470 }
471 
472 static void
cancel_sent_imap_cmds(imap_store_t * ctx)473 cancel_sent_imap_cmds( imap_store_t *ctx )
474 {
475 	imap_cmd_t *cmd;
476 
477 	socket_expect_activity( &ctx->conn, 0 );
478 	while ((cmd = ctx->in_progress)) {
479 		ctx->in_progress = cmd->next;
480 		/* don't update num_in_progress and in_progress_append - store is dead */
481 		done_imap_cmd( ctx, cmd, RESP_CANCEL );
482 	}
483 }
484 
485 static void
submit_imap_cmd(imap_store_t * ctx,imap_cmd_t * cmd)486 submit_imap_cmd( imap_store_t *ctx, imap_cmd_t *cmd )
487 {
488 	assert( ctx );
489 	assert( ctx->bad_callback );
490 	assert( cmd );
491 	assert( cmd->param.done );
492 
493 	if (cmd->param.wait_check)
494 		ctx->num_wait_check++;
495 	if ((ctx->pending && !cmd->param.high_prio) || !cmd_sendable( ctx, cmd )) {
496 		if (ctx->pending && cmd->param.high_prio) {
497 			cmd->next = ctx->pending;
498 			ctx->pending = cmd;
499 		} else {
500 			cmd->next = NULL;
501 			*ctx->pending_append = cmd;
502 			ctx->pending_append = &cmd->next;
503 		}
504 	} else {
505 		send_imap_cmd( ctx, cmd );
506 	}
507 }
508 
509 /* Minimal printf() replacement that supports an %\s format sequence to print backslash-escaped
510  * string literals. Note that this does not automatically add quotes around the printed string,
511  * so it is possible to concatenate multiple segments. */
512 static char *
imap_vprintf(const char * fmt,va_list ap)513 imap_vprintf( const char *fmt, va_list ap )
514 {
515 	const char *s;
516 	char *d, *ed;
517 	char c;
518 #define MAX_SEGS 16
519 #define add_seg(s, l) \
520 		do { \
521 			if (nsegs == MAX_SEGS) \
522 				oob(); \
523 			segs[nsegs] = s; \
524 			segls[nsegs++] = l; \
525 			totlen += l; \
526 		} while (0)
527 	int nsegs = 0;
528 	uint totlen = 0;
529 	const char *segs[MAX_SEGS];
530 	uint segls[MAX_SEGS];
531 	char buf[1000];
532 
533 	d = buf;
534 	ed = d + sizeof(buf);
535 	s = fmt;
536 	for (;;) {
537 		c = *fmt;
538 		if (!c || c == '%') {
539 			uint l = fmt - s;
540 			if (l)
541 				add_seg( s, l );
542 			if (!c)
543 				break;
544 			uint maxlen = UINT_MAX;
545 			c = *++fmt;
546 			if (c == '\\') {
547 				c = *++fmt;
548 				if (c != 's') {
549 					fputs( "Fatal: unsupported escaped format specifier. Please report a bug.\n", stderr );
550 					abort();
551 				}
552 				char *bd = d;
553 				s = va_arg( ap, const char * );
554 				while ((c = *s++)) {
555 					if (d + 2 > ed)
556 						oob();
557 					if (c == '\\' || c == '"')
558 						*d++ = '\\';
559 					*d++ = c;
560 				}
561 				l = d - bd;
562 				if (l)
563 					add_seg( bd, l );
564 			} else { /* \\ cannot be combined with anything else. */
565 				if (c == '.') {
566 					c = *++fmt;
567 					if (c != '*') {
568 						fputs( "Fatal: unsupported string length specification. Please report a bug.\n", stderr );
569 						abort();
570 					}
571 					maxlen = va_arg( ap, uint );
572 					c = *++fmt;
573 				}
574 				if (c == 'c') {
575 					if (d + 1 > ed)
576 						oob();
577 					add_seg( d, 1 );
578 					*d++ = (char)va_arg( ap , int );
579 				} else if (c == 's') {
580 					s = va_arg( ap, const char * );
581 					l = strnlen( s, maxlen );
582 					if (l)
583 						add_seg( s, l );
584 				} else if (c == 'd') {
585 					l = nfsnprintf( d, ed - d, "%d", va_arg( ap, int ) );
586 					add_seg( d, l );
587 					d += l;
588 				} else if (c == 'u') {
589 					l = nfsnprintf( d, ed - d, "%u", va_arg( ap, uint ) );
590 					add_seg( d, l );
591 					d += l;
592 				} else {
593 					fputs( "Fatal: unsupported format specifier. Please report a bug.\n", stderr );
594 					abort();
595 				}
596 			}
597 			s = ++fmt;
598 		} else {
599 			fmt++;
600 		}
601 	}
602 	char *out = d = nfmalloc( totlen + 1 );
603 	for (int i = 0; i < nsegs; i++) {
604 		memcpy( d, segs[i], segls[i] );
605 		d += segls[i];
606 	}
607 	*d = 0;
608 	return out;
609 }
610 
611 static void
imap_exec(imap_store_t * ctx,imap_cmd_t * cmdp,void (* done)(imap_store_t * ctx,imap_cmd_t * cmd,int response),const char * fmt,...)612 imap_exec( imap_store_t *ctx, imap_cmd_t *cmdp,
613            void (*done)( imap_store_t *ctx, imap_cmd_t *cmd, int response ),
614            const char *fmt, ... )
615 {
616 	va_list ap;
617 
618 	if (!cmdp)
619 		cmdp = new_imap_cmd( sizeof(*cmdp) );
620 	cmdp->param.done = done;
621 	va_start( ap, fmt );
622 	cmdp->cmd = imap_vprintf( fmt, ap );
623 	va_end( ap );
624 	submit_imap_cmd( ctx, cmdp );
625 }
626 
627 static void
transform_box_response(int * response)628 transform_box_response( int *response )
629 {
630 	switch (*response) {
631 	case RESP_CANCEL: *response = DRV_CANCELED; break;
632 	case RESP_NO: *response = DRV_BOX_BAD; break;
633 	default: *response = DRV_OK; break;
634 	}
635 }
636 
637 static void
imap_done_simple_box(imap_store_t * ctx ATTR_UNUSED,imap_cmd_t * cmd,int response)638 imap_done_simple_box( imap_store_t *ctx ATTR_UNUSED,
639                       imap_cmd_t *cmd, int response )
640 {
641 	imap_cmd_simple_t *cmdp = (imap_cmd_simple_t *)cmd;
642 
643 	transform_box_response( &response );
644 	cmdp->callback( response, cmdp->callback_aux );
645 }
646 
647 static void
transform_msg_response(int * response)648 transform_msg_response( int *response )
649 {
650 	switch (*response) {
651 	case RESP_CANCEL: *response = DRV_CANCELED; break;
652 	case RESP_NO: *response = DRV_MSG_BAD; break;
653 	default: *response = DRV_OK; break;
654 	}
655 }
656 
657 static void
imap_done_simple_msg(imap_store_t * ctx ATTR_UNUSED,imap_cmd_t * cmd,int response)658 imap_done_simple_msg( imap_store_t *ctx ATTR_UNUSED,
659                       imap_cmd_t *cmd, int response )
660 {
661 	imap_cmd_simple_t *cmdp = (imap_cmd_simple_t *)cmd;
662 
663 	transform_msg_response( &response );
664 	cmdp->callback( response, cmdp->callback_aux );
665 }
666 
667 static imap_cmd_refcounted_state_t *
imap_refcounted_new_state(uint sz)668 imap_refcounted_new_state( uint sz )
669 {
670 	imap_cmd_refcounted_state_t *sts = nfmalloc( sz );
671 	sts->ref_count = 1; /* so forced sync does not cause an early exit */
672 	sts->ret_val = DRV_OK;
673 	return sts;
674 }
675 
676 #define INIT_REFCOUNTED_STATE(type, sts, cb, aux) \
677 	type *sts = (type *)imap_refcounted_new_state( sizeof(type) ); \
678 	sts->callback = cb; \
679 	sts->callback_aux = aux;
680 
681 static imap_cmd_t *
imap_refcounted_new_cmd(imap_cmd_refcounted_state_t * sts)682 imap_refcounted_new_cmd( imap_cmd_refcounted_state_t *sts )
683 {
684 	imap_cmd_refcounted_t *cmd = (imap_cmd_refcounted_t *)new_imap_cmd( sizeof(*cmd) );
685 	cmd->state = sts;
686 	sts->ref_count++;
687 	return &cmd->gen;
688 }
689 
690 #define DONE_REFCOUNTED_STATE(sts) \
691 	if (!--sts->ref_count) { \
692 		sts->callback( sts->ret_val, sts->callback_aux ); \
693 		free( sts ); \
694 	}
695 
696 #define DONE_REFCOUNTED_STATE_ARGS(sts, finalize, ...) \
697 	if (!--sts->ref_count) { \
698 		finalize \
699 		sts->callback( sts->ret_val, __VA_ARGS__, sts->callback_aux ); \
700 		free( sts ); \
701 	}
702 
703 static void
transform_refcounted_box_response(imap_cmd_refcounted_state_t * sts,int response)704 transform_refcounted_box_response( imap_cmd_refcounted_state_t *sts, int response )
705 {
706 	switch (response) {
707 	case RESP_CANCEL:
708 		sts->ret_val = DRV_CANCELED;
709 		break;
710 	case RESP_NO:
711 		if (sts->ret_val == DRV_OK) /* Don't override cancelation. */
712 			sts->ret_val = DRV_BOX_BAD;
713 		break;
714 	}
715 }
716 
717 static void
transform_refcounted_msg_response(imap_cmd_refcounted_state_t * sts,int response)718 transform_refcounted_msg_response( imap_cmd_refcounted_state_t *sts, int response )
719 {
720 	switch (response) {
721 	case RESP_CANCEL:
722 		sts->ret_val = DRV_CANCELED;
723 		break;
724 	case RESP_NO:
725 		if (sts->ret_val == DRV_OK) /* Don't override cancelation. */
726 			sts->ret_val = DRV_MSG_BAD;
727 		break;
728 	}
729 }
730 
731 static const char *
imap_strchr(const char * s,char tc)732 imap_strchr( const char *s, char tc )
733 {
734 	for (;; s++) {
735 		char c = *s;
736 		if (c == '\\')
737 			c = *++s;
738 		if (!c)
739 			return NULL;
740 		if (c == tc)
741 			return s;
742 	}
743 }
744 
745 static char *
next_arg(char ** ps)746 next_arg( char **ps )
747 {
748 	char *ret, *s, *d;
749 	char c;
750 
751 	assert( ps );
752 	s = *ps;
753 	if (!s)
754 		return NULL;
755 	while (isspace( (uchar)*s ))
756 		s++;
757 	if (!*s) {
758 		*ps = NULL;
759 		return NULL;
760 	}
761 	if (*s == '"') {
762 		s++;
763 		ret = d = s;
764 		while ((c = *s++) != '"') {
765 			if (c == '\\')
766 				c = *s++;
767 			if (!c) {
768 				*ps = NULL;
769 				return NULL;
770 			}
771 			*d++ = c;
772 		}
773 		*d = 0;
774 	} else {
775 		ret = s;
776 		while ((c = *s)) {
777 			if (isspace( (uchar)c )) {
778 				*s++ = 0;
779 				break;
780 			}
781 			s++;
782 		}
783 	}
784 	if (!*s)
785 		s = NULL;
786 
787 	*ps = s;
788 	return ret;
789 }
790 
791 static int
is_opt_atom(list_t * list)792 is_opt_atom( list_t *list )
793 {
794 	return list && list->val && list->val != LIST;
795 }
796 
797 static int
is_atom(list_t * list)798 is_atom( list_t *list )
799 {
800 	return list && list->val && list->val != NIL && list->val != LIST;
801 }
802 
803 static int
is_list(list_t * list)804 is_list( list_t *list )
805 {
806 	return list && list->val == LIST;
807 }
808 
809 static void
free_list(list_t * list)810 free_list( list_t *list )
811 {
812 	list_t *tmp;
813 
814 	for (; list; list = tmp) {
815 		tmp = list->next;
816 		if (is_list( list ))
817 			free_list( list->child );
818 		else if (is_atom( list ))
819 			free( list->val );
820 		free( list );
821 	}
822 }
823 
824 enum {
825 	LIST_OK,
826 	LIST_PARTIAL,
827 	LIST_BAD
828 };
829 
830 static int
parse_imap_list(imap_store_t * ctx,char ** sp,parse_list_state_t * sts)831 parse_imap_list( imap_store_t *ctx, char **sp, parse_list_state_t *sts )
832 {
833 	list_t *cur, **curp;
834 	char *s = *sp, *d, *p;
835 	int n, bytes;
836 	char c;
837 
838 	assert( sts );
839 	assert( sts->level > 0 );
840 	curp = sts->stack[--sts->level];
841 	bytes = sts->need_bytes;
842 	if (bytes >= 0) {
843 		sts->need_bytes = -1;
844 		if (!bytes)
845 			goto getline;
846 		cur = (list_t *)((char *)curp - offsetof(list_t, next));
847 		s = cur->val + cur->len - bytes;
848 		goto getbytes;
849 	}
850 
851 	if (!s)
852 		return LIST_BAD;
853 	for (;;) {
854 		while (isspace( (uchar)*s ))
855 			s++;
856 		if (sts->level && *s == ')') {
857 			s++;
858 			curp = sts->stack[--sts->level];
859 			goto next;
860 		}
861 		*curp = cur = nfmalloc( sizeof(*cur) );
862 		cur->val = NULL; /* for clean bail */
863 		curp = &cur->next;
864 		*curp = NULL; /* ditto */
865 		if (*s == '(') {
866 			/* sublist */
867 			if (sts->level == MAX_LIST_DEPTH)
868 				goto bail;
869 			s++;
870 			cur->val = LIST;
871 			sts->stack[sts->level++] = curp;
872 			curp = &cur->child;
873 			*curp = NULL; /* for clean bail */
874 			goto next2;
875 		} else if (ctx && *s == '{') {
876 			/* literal */
877 			bytes = (int)(cur->len = strtoul( s + 1, &s, 10 ));
878 			if (*s != '}' || *++s)
879 				goto bail;
880 			if ((uint)bytes >= INT_MAX) {
881 				error( "IMAP error: excessively large literal from %s "
882 				       "- THIS MIGHT BE AN ATTEMPT TO HACK YOU!\n", ctx->conn.name );
883 				goto bail;
884 			}
885 
886 			s = cur->val = nfmalloc( cur->len + 1 );
887 			s[cur->len] = 0;
888 
889 		  getbytes:
890 			n = socket_read( &ctx->conn, s, (uint)bytes );
891 			if (n < 0) {
892 			  badeof:
893 				error( "IMAP error: unexpected EOF from %s\n", ctx->conn.name );
894 				goto bail;
895 			}
896 			bytes -= n;
897 			if (bytes > 0)
898 				goto postpone;
899 
900 			if (DFlags & DEBUG_NET_ALL) {
901 				printf( "%s=========\n", ctx->label );
902 				fwrite( cur->val, cur->len, 1, stdout );
903 				printf( "%s=========\n", ctx->label );
904 				fflush( stdout );
905 			}
906 
907 		  getline:
908 			if (!(s = socket_read_line( &ctx->conn )))
909 				goto postpone;
910 			if (s == (void *)~0)
911 				goto badeof;
912 			if (DFlags & DEBUG_NET) {
913 				printf( "%s%s\n", ctx->label, s );
914 				fflush( stdout );
915 			}
916 		} else if (*s == '"') {
917 			/* quoted string */
918 			s++;
919 			p = d = s;
920 			while ((c = *s++) != '"') {
921 				if (c == '\\')
922 					c = *s++;
923 				if (!c)
924 					goto bail;
925 				*d++ = c;
926 			}
927 			cur->len = (uint)(d - p);
928 			cur->val = nfstrndup( p, cur->len );
929 		} else {
930 			/* atom */
931 			p = s;
932 			for (; *s && !isspace( (uchar)*s ); s++)
933 				if (sts->level && *s == ')')
934 					break;
935 			cur->len = (uint)(s - p);
936 			if (equals( p, (int)cur->len, "NIL", 3 ))
937 				cur->val = NIL;
938 			else
939 				cur->val = nfstrndup( p, cur->len );
940 		}
941 
942 	  next:
943 		if (!sts->level)
944 			break;
945 	  next2:
946 		if (!*s)
947 			goto bail;
948 	}
949 	*sp = s;
950 	return LIST_OK;
951 
952   postpone:
953 	if (sts->level < MAX_LIST_DEPTH) {
954 		sts->stack[sts->level++] = curp;
955 		sts->need_bytes = bytes;
956 		return LIST_PARTIAL;
957 	}
958   bail:
959 	free_list( sts->head );
960 	sts->level = 0;
961 	return LIST_BAD;
962 }
963 
964 static void
parse_list_init(parse_list_state_t * sts)965 parse_list_init( parse_list_state_t *sts )
966 {
967 	sts->need_bytes = -1;
968 	sts->level = 1;
969 	sts->head = NULL;
970 	sts->stack[0] = &sts->head;
971 }
972 
973 static int
parse_list_continue(imap_store_t * ctx,char * s)974 parse_list_continue( imap_store_t *ctx, char *s )
975 {
976 	list_t *list;
977 	int resp;
978 	if ((resp = parse_imap_list( ctx, &s, &ctx->parse_list_sts )) != LIST_PARTIAL) {
979 		list = (resp == LIST_BAD) ? NULL : ctx->parse_list_sts.head;
980 		ctx->parse_list_sts.head = NULL;
981 		resp = ctx->parse_list_sts.callback( ctx, list, s );
982 		free_list( list );
983 	}
984 	return resp;
985 }
986 
987 static int
parse_list(imap_store_t * ctx,char * s,int (* cb)(imap_store_t * ctx,list_t * list,char * s))988 parse_list( imap_store_t *ctx, char *s, int (*cb)( imap_store_t *ctx, list_t *list, char *s ) )
989 {
990 	parse_list_init( &ctx->parse_list_sts );
991 	ctx->parse_list_sts.callback = cb;
992 	return parse_list_continue( ctx, s );
993 }
994 
995 static int parse_namespace_rsp_p2( imap_store_t *, list_t *, char * );
996 static int parse_namespace_rsp_p3( imap_store_t *, list_t *, char * );
997 
998 static int
parse_namespace_rsp(imap_store_t * ctx,list_t * list,char * s)999 parse_namespace_rsp( imap_store_t *ctx, list_t *list, char *s )
1000 {
1001 	// We use only the 1st personal namespace. Making this configurable
1002 	// would not add value over just specifying Path.
1003 
1004 	if (!list) {
1005 	  bad:
1006 		error( "IMAP error: malformed NAMESPACE response\n" );
1007 		return LIST_BAD;
1008 	}
1009 	if (list->val != NIL) {
1010 		if (list->val != LIST)
1011 			goto bad;
1012 		list_t *nsp_1st = list->child;
1013 		if (nsp_1st->val != LIST)
1014 			goto bad;
1015 		list_t *nsp_1st_ns = nsp_1st->child;
1016 		if (!is_atom( nsp_1st_ns ))
1017 			goto bad;
1018 		ctx->ns_prefix = nsp_1st_ns->val;
1019 		nsp_1st_ns->val = NULL;
1020 		list_t *nsp_1st_dl = nsp_1st_ns->next;
1021 		if (!is_opt_atom( nsp_1st_dl ))
1022 			goto bad;
1023 		if (is_atom( nsp_1st_dl ))
1024 			ctx->ns_delimiter = nsp_1st_dl->val[0];
1025 		// Namespace response extensions may follow here; we don't care.
1026 	}
1027 
1028 	return parse_list( ctx, s, parse_namespace_rsp_p2 );
1029 }
1030 
1031 static int
parse_namespace_rsp_p2(imap_store_t * ctx,list_t * list ATTR_UNUSED,char * s)1032 parse_namespace_rsp_p2( imap_store_t *ctx, list_t *list ATTR_UNUSED, char *s )
1033 {
1034 	return parse_list( ctx, s, parse_namespace_rsp_p3 );
1035 }
1036 
1037 static int
parse_namespace_rsp_p3(imap_store_t * ctx ATTR_UNUSED,list_t * list ATTR_UNUSED,char * s ATTR_UNUSED)1038 parse_namespace_rsp_p3( imap_store_t *ctx ATTR_UNUSED, list_t *list ATTR_UNUSED, char *s ATTR_UNUSED )
1039 {
1040 	return LIST_OK;
1041 }
1042 
1043 static time_t
parse_date(const char * str)1044 parse_date( const char *str )
1045 {
1046 	char *end;
1047 	time_t date;
1048 	int hours, mins;
1049 	struct tm datetime;
1050 
1051 	memset( &datetime, 0, sizeof(datetime) );
1052 	if (!(end = strptime( str, "%e-%b-%Y %H:%M:%S ", &datetime )))
1053 		return -1;
1054 	if ((date = timegm( &datetime )) == -1)
1055 		return -1;
1056 	if (sscanf( end, "%3d%2d", &hours, &mins ) != 2)
1057 		return -1;
1058 	return date - (hours * 60 + mins) * 60;
1059 }
1060 
1061 static int
parse_fetched_flags(list_t * list,uchar * flags,uchar * status)1062 parse_fetched_flags( list_t *list, uchar *flags, uchar *status )
1063 {
1064 	for (; list; list = list->next) {
1065 		if (!is_atom( list )) {
1066 			error( "IMAP error: unable to parse FLAGS list\n" );
1067 			return 0;
1068 		}
1069 		if (list->val[0] != '\\' && list->val[0] != '$')
1070 			continue;
1071 		if (!strcmp( "\\Recent", list->val )) {
1072 			*status |= M_RECENT;
1073 			goto flagok;
1074 		}
1075 		for (uint i = 0; i < as(Flags); i++) {
1076 			if (!strcmp( Flags[i], list->val )) {
1077 				*flags |= 1 << i;
1078 				goto flagok;
1079 			}
1080 		}
1081 		if (list->val[0] == '$')
1082 			goto flagok; // Ignore unknown user-defined flags (keywords)
1083 		if (list->val[1] == 'X' && list->val[2] == '-')
1084 			goto flagok; // Ignore system flag extensions
1085 		warn( "IMAP warning: unknown system flag %s\n", list->val );
1086 	  flagok: ;
1087 	}
1088 	return 1;
1089 }
1090 
1091 static void
parse_fetched_header(char * val,uint uid,char ** tuid,char ** msgid,uint * msgid_len)1092 parse_fetched_header( char *val, uint uid, char **tuid, char **msgid, uint *msgid_len )
1093 {
1094 	char *end;
1095 	int off, in_msgid = 0;
1096 	for (; (end = strchr( val, '\n' )); val = end + 1) {
1097 		int len = (int)(end - val);
1098 		if (len && end[-1] == '\r')
1099 			len--;
1100 		if (!len)
1101 			break;
1102 		if (starts_with_upper( val, len, "X-TUID: ", 8 )) {
1103 			if (len < 8 + TUIDL) {
1104 				warn( "IMAP warning: malformed X-TUID header (UID %u)\n", uid );
1105 				continue;
1106 			}
1107 			*tuid = val + 8;
1108 			in_msgid = 0;
1109 			continue;
1110 		}
1111 		if (starts_with_upper( val, len, "MESSAGE-ID:", 11 )) {
1112 			off = 11;
1113 		} else if (in_msgid) {
1114 			if (!isspace( val[0] )) {
1115 				in_msgid = 0;
1116 				continue;
1117 			}
1118 			off = 1;
1119 		} else {
1120 			continue;
1121 		}
1122 		while (off < len && isspace( val[off] ))
1123 			off++;
1124 		if (off == len) {
1125 			in_msgid = 1;
1126 			continue;
1127 		}
1128 		*msgid = val + off;
1129 		*msgid_len = (uint)(len - off);
1130 		in_msgid = 0;
1131 	}
1132 }
1133 
1134 static int
parse_fetch_rsp(imap_store_t * ctx,list_t * list,char * s ATTR_UNUSED)1135 parse_fetch_rsp( imap_store_t *ctx, list_t *list, char *s ATTR_UNUSED )
1136 {
1137 	list_t *body = NULL, *tmp;
1138 	char *tuid = NULL, *msgid = NULL, *ep;
1139 	imap_message_t *cur;
1140 	msg_data_t *msgdata;
1141 	imap_cmd_t *cmdp;
1142 	uchar mask = 0, status = 0;
1143 	uint uid = 0, size = 0, msgid_len = 0;
1144 	time_t date = 0;
1145 
1146 	if (!is_list( list )) {
1147 		error( "IMAP error: bogus FETCH response\n" );
1148 		return LIST_BAD;
1149 	}
1150 
1151 	for (tmp = list->child; tmp; tmp = tmp->next) {
1152 		if (!is_atom( tmp )) {
1153 			error( "IMAP error: bogus item name in FETCH response\n" );
1154 			return LIST_BAD;
1155 		}
1156 		const char *name = tmp->val;
1157 		tmp = tmp->next;
1158 		if (!strcmp( "UID", name )) {
1159 			if (!is_atom( tmp ) || (uid = strtoul( tmp->val, &ep, 10 ), *ep)) {
1160 				error( "IMAP error: unable to parse UID\n" );
1161 				return LIST_BAD;
1162 			}
1163 		} else if (!strcmp( "FLAGS", name )) {
1164 			if (!is_list( tmp )) {
1165 				error( "IMAP error: unable to parse FLAGS\n" );
1166 				return LIST_BAD;
1167 			}
1168 			if (!parse_fetched_flags( tmp->child, &mask, &status ))
1169 				return LIST_BAD;
1170 			status |= M_FLAGS;
1171 		} else if (!strcmp( "INTERNALDATE", name )) {
1172 			if (!is_atom( tmp )) {
1173 				error( "IMAP error: unable to parse INTERNALDATE\n" );
1174 				return LIST_BAD;
1175 			}
1176 			if ((date = parse_date( tmp->val )) == -1) {
1177 				error( "IMAP error: unable to parse INTERNALDATE format\n" );
1178 				return LIST_BAD;
1179 			}
1180 			status |= M_DATE;
1181 		} else if (!strcmp( "RFC822.SIZE", name )) {
1182 			if (!is_atom( tmp ) || (size = strtoul( tmp->val, &ep, 10 ), *ep)) {
1183 				error( "IMAP error: unable to parse RFC822.SIZE\n" );
1184 				return LIST_BAD;
1185 			}
1186 			status |= M_SIZE;
1187 		} else if (!strcmp( "BODY[]", name ) || !strcmp( "BODY[HEADER]", name )) {
1188 			if (!is_atom( tmp )) {
1189 				error( "IMAP error: unable to parse BODY[]\n" );
1190 				return LIST_BAD;
1191 			}
1192 			body = tmp;
1193 			status |= M_BODY;
1194 		} else if (!strcmp( "BODY[HEADER.FIELDS", name )) {
1195 			if (!is_list( tmp )) {
1196 			  bfail:
1197 				error( "IMAP error: unable to parse BODY[HEADER.FIELDS ...]\n" );
1198 				return LIST_BAD;
1199 			}
1200 			tmp = tmp->next;
1201 			if (!is_atom( tmp ) || strcmp( tmp->val, "]" ))
1202 				goto bfail;
1203 			tmp = tmp->next;
1204 			if (!is_atom( tmp ))
1205 				goto bfail;
1206 			parse_fetched_header( tmp->val, uid, &tuid, &msgid, &msgid_len );
1207 			status |= M_HEADER;
1208 		}
1209 	}
1210 
1211 	if (!uid) {
1212 		// Ignore async flag updates for now.
1213 		status &= ~(M_FLAGS | M_RECENT);
1214 	} else if (status & M_BODY) {
1215 		for (cmdp = ctx->in_progress; cmdp; cmdp = cmdp->next)
1216 			if (cmdp->param.uid == uid)
1217 				goto gotuid;
1218 		error( "IMAP error: unexpected FETCH response with BODY (UID %u)\n", uid );
1219 		return LIST_BAD;
1220 	  gotuid:
1221 		msgdata = ((imap_cmd_fetch_msg_t *)cmdp)->msg_data;
1222 		msgdata->data = body->val;
1223 		body->val = NULL;       // Don't free together with list.
1224 		msgdata->len = body->len;
1225 		msgdata->date = date;
1226 		if (status & M_FLAGS)
1227 			msgdata->flags = mask;
1228 		status &= ~(M_FLAGS | M_RECENT | M_BODY | M_DATE);
1229 	} else if (ctx->fetch_sts == FetchUidNext) {
1230 		// Workaround for server not sending UIDNEXT and/or APPENDUID.
1231 		ctx->uidnext = uid + 1;
1232 	} else if (ctx->fetch_sts == FetchMsgs) {
1233 		cur = nfcalloc( sizeof(*cur) );
1234 		*ctx->msgapp = cur;
1235 		ctx->msgapp = &cur->next;
1236 		cur->uid = uid;
1237 		cur->flags = mask;
1238 		cur->status = status;
1239 		cur->size = size;
1240 		if (msgid)
1241 			cur->msgid = nfstrndup( msgid, msgid_len );
1242 		if (tuid)
1243 			memcpy( cur->tuid, tuid, TUIDL );
1244 		status &= ~(M_FLAGS | M_RECENT | M_SIZE | M_HEADER);
1245 	} else {
1246 		// These may come in as a result of STORE FLAGS despite .SILENT.
1247 		status &= ~(M_FLAGS | M_RECENT);
1248 	}
1249 
1250 	if (status) {
1251 		error( "IMAP error: received extraneous data in FETCH response\n" );
1252 		return LIST_BAD;
1253 	}
1254 
1255 	return LIST_OK;
1256 }
1257 
1258 static void
parse_capability(imap_store_t * ctx,char * cmd)1259 parse_capability( imap_store_t *ctx, char *cmd )
1260 {
1261 	char *arg;
1262 	uint i;
1263 
1264 	free_string_list( ctx->auth_mechs );
1265 	ctx->auth_mechs = NULL;
1266 	ctx->caps = 0x80000000;
1267 	while ((arg = next_arg( &cmd ))) {
1268 		if (starts_with( arg, -1, "AUTH=", 5 )) {
1269 			add_string_list( &ctx->auth_mechs, arg + 5 );
1270 		} else {
1271 			for (i = 0; i < as(cap_list); i++)
1272 				if (!strcmp( cap_list[i], arg ))
1273 					ctx->caps |= 1 << i;
1274 		}
1275 	}
1276 	ctx->caps &= ~ctx->conf->server->cap_mask;
1277 	if (!CAP(NOLOGIN))
1278 		add_string_list( &ctx->auth_mechs, "LOGIN" );
1279 }
1280 
1281 static int
parse_response_code(imap_store_t * ctx,imap_cmd_t * cmd,char * s)1282 parse_response_code( imap_store_t *ctx, imap_cmd_t *cmd, char *s )
1283 {
1284 	char *arg, *earg, *p;
1285 
1286 	if (!s || *s != '[')
1287 		return RESP_OK;		/* no response code */
1288 	s++;
1289 	if (!(arg = next_arg( &s ))) {
1290 		error( "IMAP error: malformed response code\n" );
1291 		return RESP_CANCEL;
1292 	}
1293 	if (!strcmp( "UIDVALIDITY", arg )) {
1294 		if (!(arg = next_arg( &s )) ||
1295 		    (ctx->uidvalidity = strtoul( arg, &earg, 10 ), *earg != ']'))
1296 		{
1297 			error( "IMAP error: malformed UIDVALIDITY status\n" );
1298 			return RESP_CANCEL;
1299 		}
1300 	} else if (!strcmp( "UIDNEXT", arg )) {
1301 		if (!(arg = next_arg( &s )) ||
1302 		    (ctx->uidnext = strtoul( arg, &earg, 10 ), *earg != ']'))
1303 		{
1304 			error( "IMAP error: malformed UIDNEXT status\n" );
1305 			return RESP_CANCEL;
1306 		}
1307 	} else if (!strcmp( "CAPABILITY", arg )) {
1308 		if (!s || !(p = strchr( s, ']' ))) {
1309 			error( "IMAP error: malformed CAPABILITY status\n" );
1310 			return RESP_CANCEL;
1311 		}
1312 		*p = 0;
1313 		parse_capability( ctx, s );
1314 	} else if (!strcmp( "ALERT]", arg )) {
1315 		/* RFC2060 says that these messages MUST be displayed
1316 		 * to the user
1317 		 */
1318 		if (!s) {
1319 			error( "IMAP error: malformed ALERT status\n" );
1320 			return RESP_CANCEL;
1321 		}
1322 		for (; isspace( (uchar)*s ); s++);
1323 		error( "*** IMAP ALERT *** %s\n", s );
1324 	} else if (!strcmp( "APPENDUID", arg )) {
1325 		// The checks ensure that:
1326 		// - cmd => this is the final tagged response of a command, at which
1327 		//   point cmd was already removed from ctx->in_progress, so param.uid
1328 		//   is available for reuse.
1329 		// - !param.uid => the command isn't actually a FETCH. This doesn't
1330 		//   really matter, as the field is safe to overwrite given the
1331 		//   previous condition; it just has no effect for non-APPENDs.
1332 		if (!cmd || cmd->param.uid) {
1333 			error( "IMAP error: unexpected APPENDUID status\n" );
1334 			return RESP_CANCEL;
1335 		}
1336 		if (!(arg = next_arg( &s )) ||
1337 		    (ctx->uidvalidity = strtoul( arg, &earg, 10 ), *earg) ||
1338 		    !(arg = next_arg( &s )) ||
1339 		    (cmd->param.uid = strtoul( arg, &earg, 10 ), *earg != ']'))
1340 		{
1341 			error( "IMAP error: malformed APPENDUID status\n" );
1342 			return RESP_CANCEL;
1343 		}
1344 	} else if (!strcmp( "PERMANENTFLAGS", arg )) {
1345 		parse_list_init( &ctx->parse_list_sts );
1346 		if (parse_imap_list( NULL, &s, &ctx->parse_list_sts ) != LIST_OK || *s != ']') {
1347 			error( "IMAP error: malformed PERMANENTFLAGS status\n" );
1348 			return RESP_CANCEL;
1349 		}
1350 		int ret = RESP_OK;
1351 		for (list_t *tmp = ctx->parse_list_sts.head->child; tmp; tmp = tmp->next) {
1352 			if (!is_atom( tmp )) {
1353 				error( "IMAP error: malformed PERMANENTFLAGS status item\n" );
1354 				ret = RESP_CANCEL;
1355 				break;
1356 			}
1357 			if (!strcmp( tmp->val, "\\*" ) || !strcmp( tmp->val, "$Forwarded" )) {
1358 				ctx->has_forwarded = 1;
1359 				break;
1360 			}
1361 		}
1362 		free_list( ctx->parse_list_sts.head );
1363 		ctx->parse_list_sts.head = NULL;
1364 		return ret;
1365 	}
1366 	return RESP_OK;
1367 }
1368 
1369 static int parse_list_rsp_p1( imap_store_t *, list_t *, char * );
1370 static int parse_list_rsp_p2( imap_store_t *, list_t *, char * );
1371 
1372 static int
parse_list_rsp(imap_store_t * ctx,list_t * list,char * cmd)1373 parse_list_rsp( imap_store_t *ctx, list_t *list, char *cmd )
1374 {
1375 	list_t *lp;
1376 
1377 	if (!is_list( list )) {
1378 		error( "IMAP error: malformed LIST response\n" );
1379 		return LIST_BAD;
1380 	}
1381 	for (lp = list->child; lp; lp = lp->next)
1382 		if (is_atom( lp ) && !strcasecmp( lp->val, "\\NoSelect" ))
1383 			return LIST_OK;
1384 	return parse_list( ctx, cmd, parse_list_rsp_p1 );
1385 }
1386 
1387 static int
parse_list_rsp_p1(imap_store_t * ctx,list_t * list,char * cmd ATTR_UNUSED)1388 parse_list_rsp_p1( imap_store_t *ctx, list_t *list, char *cmd ATTR_UNUSED )
1389 {
1390 	if (!is_opt_atom( list )) {
1391 		error( "IMAP error: malformed LIST response\n" );
1392 		return LIST_BAD;
1393 	}
1394 	if (!ctx->delimiter[0] && is_atom( list ))
1395 		ctx->delimiter[0] = list->val[0];
1396 	return parse_list( ctx, cmd, parse_list_rsp_p2 );
1397 }
1398 
1399 // Use this to check whether a full path refers to the actual IMAP INBOX.
1400 static int
is_inbox(imap_store_t * ctx,const char * arg,int argl)1401 is_inbox( imap_store_t *ctx, const char *arg, int argl )
1402 {
1403 	if (!starts_with_upper( arg, argl, "INBOX", 5 ))
1404 		return 0;
1405 	if (arg[5] && arg[5] != ctx->delimiter[0])
1406 		return 0;
1407 	return 1;
1408 }
1409 
1410 // Use this to check whether a path fragment collides with the canonical INBOX.
1411 static int
is_INBOX(imap_store_t * ctx,const char * arg,int argl)1412 is_INBOX( imap_store_t *ctx, const char *arg, int argl )
1413 {
1414 	if (!starts_with( arg, argl, "INBOX", 5 ))
1415 		return 0;
1416 	if (arg[5] && arg[5] != ctx->delimiter[0])
1417 		return 0;
1418 	return 1;
1419 }
1420 
1421 static void
normalize_INBOX(imap_store_t * ctx,char * arg,int argl)1422 normalize_INBOX( imap_store_t *ctx, char *arg, int argl )
1423 {
1424 	if (is_inbox( ctx, arg, argl ))
1425 		memcpy( arg, "INBOX", 5 );
1426 }
1427 
1428 static int
parse_list_rsp_p2(imap_store_t * ctx,list_t * list,char * cmd ATTR_UNUSED)1429 parse_list_rsp_p2( imap_store_t *ctx, list_t *list, char *cmd ATTR_UNUSED )
1430 {
1431 	string_list_t *narg;
1432 	char *arg, c;
1433 	int argl;
1434 	uint l;
1435 
1436 	if (!is_atom( list )) {
1437 		error( "IMAP error: malformed LIST response\n" );
1438 		return LIST_BAD;
1439 	}
1440 	arg = list->val;
1441 	argl = (int)list->len;
1442 	if (argl > 1000) {
1443 		warn( "IMAP warning: ignoring unreasonably long mailbox name '%.100s[...]'\n", arg );
1444 		return LIST_OK;
1445 	}
1446 	// The server might be weird and have a non-uppercase INBOX. It
1447 	// may legitimately do so, but we need the canonical spelling.
1448 	normalize_INBOX( ctx, arg, argl );
1449 	if ((l = strlen( ctx->prefix ))) {
1450 		if (!starts_with( arg, argl, ctx->prefix, l )) {
1451 			if (!is_INBOX( ctx, arg, argl ))
1452 				return LIST_OK;
1453 			// INBOX and its subfolders bypass the namespace.
1454 		} else {
1455 			arg += l;
1456 			argl -= l;
1457 			// A folder named "INBOX" would be indistinguishable from the
1458 			// actual INBOX after prefix stripping, so drop it. This applies
1459 			// only to the fully uppercased spelling, as our canonical box
1460 			// names are case-sensitive (unlike IMAP's INBOX).
1461 			if (is_INBOX( ctx, arg, argl )) {
1462 				if (!arg[5])  // No need to complain about subfolders as well.
1463 					warn( "IMAP warning: ignoring INBOX in %s\n", ctx->prefix );
1464 				return LIST_OK;
1465 			}
1466 		}
1467 	}
1468 	if (argl >= 5 && !memcmp( arg + argl - 5, ".lock", 5 )) /* workaround broken servers */
1469 		return LIST_OK;
1470 	if (map_name( arg, (char **)&narg, offsetof(string_list_t, string), ctx->delimiter, "/") < 0) {
1471 		warn( "IMAP warning: ignoring mailbox %s (reserved character '/' in name)\n", arg );
1472 		return LIST_OK;
1473 	}
1474 	// Validate the normalized name. Technically speaking, we could tolerate
1475 	// '//' and '/./', and '/../' being forbidden is a limitation of the Maildir
1476 	// driver, but there isn't really a legitimate reason for these being present.
1477 	for (const char *p = narg->string, *sp = p;;) {
1478 		if (!(c = *p) || c == '/') {
1479 			uint pcl = (uint)(p - sp);
1480 			if (!pcl) {
1481 				error( "IMAP warning: ignoring mailbox '%s' due to empty name component\n", narg->string );
1482 				free( narg );
1483 				return LIST_OK;
1484 			}
1485 			if (pcl == 1 && sp[0] == '.') {
1486 				error( "IMAP warning: ignoring mailbox '%s' due to '.' component\n", narg->string );
1487 				free( narg );
1488 				return LIST_OK;
1489 			}
1490 			if (pcl == 2 && sp[0] == '.' && sp[1] == '.') {
1491 				error( "IMAP error: LIST'd mailbox name '%s' contains '..' component - THIS MIGHT BE AN ATTEMPT TO HACK YOU!\n", narg->string );
1492 				free( narg );
1493 				return LIST_BAD;
1494 			}
1495 			if (!c)
1496 				break;
1497 			sp = ++p;
1498 		} else {
1499 			++p;
1500 		}
1501 	}
1502 	narg->next = ctx->boxes;
1503 	ctx->boxes = narg;
1504 	return LIST_OK;
1505 }
1506 
1507 static int
prepare_name(char ** buf,const imap_store_t * ctx,const char * prefix,const char * name)1508 prepare_name( char **buf, const imap_store_t *ctx, const char *prefix, const char *name )
1509 {
1510 	uint pl = strlen( prefix );
1511 
1512 	switch (map_name( name, buf, pl, "/", ctx->delimiter )) {
1513 	case -1:
1514 		error( "IMAP error: mailbox name %s contains server's hierarchy delimiter\n", name );
1515 		return -1;
1516 	case -2:
1517 		error( "IMAP error: server's hierarchy delimiter not known\n" );
1518 		return -1;
1519 	default:
1520 		memcpy( *buf, prefix, pl );
1521 		return 0;
1522 	}
1523 }
1524 
1525 static int
prepare_box(char ** buf,const imap_store_t * ctx)1526 prepare_box( char **buf, const imap_store_t *ctx )
1527 {
1528 	const char *name = ctx->name;
1529 	const char *pfx = ctx->prefix;
1530 
1531 	if (starts_with_upper( name, -1, "INBOX", 5 ) && (!name[5] || name[5] == '/')) {
1532 		if (!memcmp( name, "INBOX", 5 )) {
1533 			pfx = "";
1534 		} else if (!*pfx) {
1535 			error( "IMAP error: cannot use unqualified '%s'. Did you mean INBOX?", name );
1536 			return -1;
1537 		}
1538 	}
1539 	return prepare_name( buf, ctx, pfx, name );
1540 }
1541 
1542 static int
prepare_trash(char ** buf,const imap_store_t * ctx)1543 prepare_trash( char **buf, const imap_store_t *ctx )
1544 {
1545 	return prepare_name( buf, ctx, ctx->prefix, ctx->conf->trash );
1546 }
1547 
1548 typedef union {
1549 	imap_cmd_t gen;
1550 	struct {
1551 		IMAP_CMD
1552 		imap_cmd_t *orig_cmd;
1553 	};
1554 } imap_cmd_trycreate_t;
1555 
1556 static void imap_open_store_greeted( imap_store_t * );
1557 static void get_cmd_result_p2( imap_store_t *, imap_cmd_t *, int );
1558 
1559 static void
imap_socket_read(void * aux)1560 imap_socket_read( void *aux )
1561 {
1562 	imap_store_t *ctx = (imap_store_t *)aux;
1563 	imap_cmd_t *cmdp, **pcmdp;
1564 	char *cmd, *arg, *arg1, *p;
1565 	int resp, resp2, tag;
1566 	conn_iovec_t iov[2];
1567 
1568 	for (;;) {
1569 		if (ctx->parse_list_sts.level) {
1570 			resp = parse_list_continue( ctx, NULL );
1571 		  listret:
1572 			if (resp == LIST_PARTIAL)
1573 				return;
1574 			if (resp == LIST_BAD)
1575 				break;
1576 			continue;
1577 		}
1578 		if (!(cmd = socket_read_line( &ctx->conn )))
1579 			return;
1580 		if (cmd == (void *)~0) {
1581 			if (!ctx->expectEOF)
1582 				error( "IMAP error: unexpected EOF from %s\n", ctx->conn.name );
1583 			/* A clean shutdown sequence ends with bad_callback as well (see imap_cleanup()). */
1584 			break;
1585 		}
1586 		if (DFlags & DEBUG_NET) {
1587 			printf( "%s%s\n", ctx->label, cmd );
1588 			fflush( stdout );
1589 		}
1590 
1591 		arg = next_arg( &cmd );
1592 		if (!arg) {
1593 			error( "IMAP error: empty response\n" );
1594 			break;
1595 		}
1596 		if (*arg == '*') {
1597 			arg = next_arg( &cmd );
1598 			if (!arg) {
1599 				error( "IMAP error: malformed untagged response\n" );
1600 				break;
1601 			}
1602 
1603 			if (ctx->greeting == GreetingPending && !strcmp( "PREAUTH", arg )) {
1604 				parse_response_code( ctx, NULL, cmd );
1605 				ctx->greeting = GreetingPreauth;
1606 			  dogreet:
1607 				imap_ref( ctx );
1608 				imap_open_store_greeted( ctx );
1609 				if (imap_deref( ctx ))
1610 					return;
1611 			} else if (!strcmp( "OK", arg )) {
1612 				parse_response_code( ctx, NULL, cmd );
1613 				if (ctx->greeting == GreetingPending) {
1614 					ctx->greeting = GreetingOk;
1615 					goto dogreet;
1616 				}
1617 			} else if (!strcmp( "BYE", arg )) {
1618 				if (!ctx->expectBYE) {
1619 					ctx->greeting = GreetingBad;
1620 					error( "IMAP error: unexpected BYE response: %s\n", cmd );
1621 					/* We just wait for the server to close the connection now. */
1622 					ctx->expectEOF = 1;
1623 				} else {
1624 					/* We still need to wait for the LOGOUT's tagged OK. */
1625 				}
1626 			} else if (ctx->greeting == GreetingPending) {
1627 				error( "IMAP error: bogus greeting response %s\n", arg );
1628 				break;
1629 			} else if (!strcmp( "NO", arg )) {
1630 				warn( "Warning from IMAP server: %s\n", cmd );
1631 			} else if (!strcmp( "BAD", arg )) {
1632 				error( "Error from IMAP server: %s\n", cmd );
1633 			} else if (!strcmp( "CAPABILITY", arg )) {
1634 				parse_capability( ctx, cmd );
1635 			} else if (!strcmp( "LIST", arg ) || !strcmp( "LSUB", arg )) {
1636 				resp = parse_list( ctx, cmd, parse_list_rsp );
1637 				goto listret;
1638 			} else if (!strcmp( "NAMESPACE", arg )) {
1639 				resp = parse_list( ctx, cmd, parse_namespace_rsp );
1640 				goto listret;
1641 			} else if ((arg1 = next_arg( &cmd ))) {
1642 				if (!strcmp( "EXISTS", arg1 ))
1643 					ctx->total_msgs = atoi( arg );
1644 				else if (!strcmp( "EXPUNGE", arg1 ))
1645 					ctx->total_msgs--;
1646 				else if (!strcmp( "RECENT", arg1 ))
1647 					ctx->recent_msgs = atoi( arg );
1648 				else if(!strcmp ( "FETCH", arg1 )) {
1649 					resp = parse_list( ctx, cmd, parse_fetch_rsp );
1650 					goto listret;
1651 				}
1652 			} else {
1653 				error( "IMAP error: unrecognized untagged response '%s'\n", arg );
1654 				break; /* this may mean anything, so prefer not to spam the log */
1655 			}
1656 			continue;
1657 		} else if (!ctx->in_progress) {
1658 			error( "IMAP error: unexpected reply: %s %s\n", arg, cmd ? cmd : "" );
1659 			break; /* this may mean anything, so prefer not to spam the log */
1660 		} else if (*arg == '+') {
1661 			socket_expect_activity( &ctx->conn, 0 );
1662 			/* There can be any number of commands in flight, but only the last
1663 			 * one can require a continuation, as it enforces a round-trip. */
1664 			cmdp = (imap_cmd_t *)((char *)ctx->in_progress_append -
1665 			                      offsetof(imap_cmd_t, next));
1666 			if (cmdp->param.data) {
1667 				if (cmdp->param.to_trash)
1668 					ctx->trashnc = TrashKnown; /* Can't get NO [TRYCREATE] any more. */
1669 				if (DFlags & DEBUG_NET_ALL) {
1670 					printf( "%s>>>>>>>>>\n", ctx->label );
1671 					fwrite( cmdp->param.data, cmdp->param.data_len, 1, stdout );
1672 					printf( "%s>>>>>>>>>\n", ctx->label );
1673 					fflush( stdout );
1674 				}
1675 				iov[0].buf = cmdp->param.data;
1676 				iov[0].len = cmdp->param.data_len;
1677 				iov[0].takeOwn = GiveOwn;
1678 				cmdp->param.data = NULL;
1679 				ctx->buffer_mem -= cmdp->param.data_len;
1680 				iov[1].buf = "\r\n";
1681 				iov[1].len = 2;
1682 				iov[1].takeOwn = KeepOwn;
1683 				socket_write( &ctx->conn, iov, 2 );
1684 			} else if (cmdp->param.cont) {
1685 				if (cmdp->param.cont( ctx, cmdp, cmd ))
1686 					return;
1687 			} else {
1688 				error( "IMAP error: unexpected command continuation request\n" );
1689 				break;
1690 			}
1691 			socket_expect_activity( &ctx->conn, 1 );
1692 		} else {
1693 			tag = atoi( arg );
1694 			for (pcmdp = &ctx->in_progress; (cmdp = *pcmdp); pcmdp = &cmdp->next)
1695 				if (cmdp->tag == tag)
1696 					goto gottag;
1697 			error( "IMAP error: unexpected tag %s\n", arg );
1698 			break;
1699 		  gottag:
1700 			if (!(*pcmdp = cmdp->next))
1701 				ctx->in_progress_append = pcmdp;
1702 			if (!--ctx->num_in_progress)
1703 				socket_expect_activity( &ctx->conn, 0 );
1704 			arg = next_arg( &cmd );
1705 			if (!arg) {
1706 				error( "IMAP error: malformed tagged response\n" );
1707 				break;
1708 			}
1709 			if (!strcmp( "OK", arg )) {
1710 				if (cmdp->param.to_trash)
1711 					ctx->trashnc = TrashKnown; /* Can't get NO [TRYCREATE] any more. */
1712 				resp = RESP_OK;
1713 			} else {
1714 				if (!strcmp( "NO", arg )) {
1715 					if (cmdp->param.create && cmd && starts_with( cmd, -1, "[TRYCREATE]", 11 )) { /* APPEND or UID COPY */
1716 						imap_cmd_trycreate_t *cmd2 =
1717 							(imap_cmd_trycreate_t *)new_imap_cmd( sizeof(*cmd2) );
1718 						cmd2->orig_cmd = cmdp;
1719 						cmd2->param.high_prio = 1;
1720 						p = strchr( cmdp->cmd, '"' );
1721 						imap_exec( ctx, &cmd2->gen, get_cmd_result_p2,
1722 						           "CREATE %.*s", imap_strchr( p + 1, '"' ) - p + 1, p );
1723 						continue;
1724 					}
1725 					resp = RESP_NO;
1726 					if (cmdp->param.failok)
1727 						goto doresp;
1728 				} else /*if (!strcmp( "BAD", arg ))*/
1729 					resp = RESP_CANCEL;
1730 				error( "IMAP command '%s' returned an error: %s %s\n",
1731 				       starts_with( cmdp->cmd, -1, "LOGIN", 5 ) ?
1732 				           "LOGIN <user> <pass>" :
1733 				           starts_with( cmdp->cmd, -1, "AUTHENTICATE PLAIN", 18 ) ?
1734 				               "AUTHENTICATE PLAIN <authdata>" :
1735 				                cmdp->cmd,
1736 				       arg, cmd ? cmd : "" );
1737 			}
1738 		  doresp:
1739 			if ((resp2 = parse_response_code( ctx, cmdp, cmd )) > resp)
1740 				resp = resp2;
1741 			imap_ref( ctx );
1742 			if (resp == RESP_CANCEL)
1743 				imap_invoke_bad_callback( ctx );
1744 			if (resp == RESP_OK && cmdp->param.wait_check) {
1745 				cmdp->next = NULL;
1746 				*ctx->wait_check_append = cmdp;
1747 				ctx->wait_check_append = &cmdp->next;
1748 			} else {
1749 				done_imap_cmd( ctx, cmdp, resp );
1750 			}
1751 			if (imap_deref( ctx ))
1752 				return;
1753 			if (ctx->canceling && !ctx->in_progress) {
1754 				ctx->canceling = 0;
1755 				ctx->callbacks.imap_cancel( ctx->callback_aux );
1756 				return;
1757 			}
1758 		}
1759 		flush_imap_cmds( ctx );
1760 	}
1761 	imap_invoke_bad_callback( ctx );
1762 }
1763 
1764 static void
get_cmd_result_p2(imap_store_t * ctx,imap_cmd_t * cmd,int response)1765 get_cmd_result_p2( imap_store_t *ctx, imap_cmd_t *cmd, int response )
1766 {
1767 	imap_cmd_trycreate_t *cmdp = (imap_cmd_trycreate_t *)cmd;
1768 	imap_cmd_t *ocmd = cmdp->orig_cmd;
1769 
1770 	if (response != RESP_OK) {
1771 		done_imap_cmd( ctx, ocmd, response );
1772 	} else {
1773 		assert( !ocmd->param.wait_check );
1774 		ctx->uidnext = 1;
1775 		if (ocmd->param.to_trash)
1776 			ctx->trashnc = TrashKnown;
1777 		ocmd->param.create = 0;
1778 		ocmd->param.high_prio = 1;
1779 		submit_imap_cmd( ctx, ocmd );
1780 	}
1781 }
1782 
1783 /******************* imap_cancel_store *******************/
1784 
1785 static void
imap_cancel_store(store_t * gctx)1786 imap_cancel_store( store_t *gctx )
1787 {
1788 	imap_store_t *ctx = (imap_store_t *)gctx;
1789 
1790 #ifdef HAVE_LIBSASL
1791 	sasl_dispose( &ctx->sasl );
1792 #endif
1793 	socket_close( &ctx->conn );
1794 	finalize_checked_imap_cmds( ctx, RESP_CANCEL );
1795 	cancel_sent_imap_cmds( ctx );
1796 	cancel_pending_imap_cmds( ctx );
1797 	free( ctx->ns_prefix );
1798 	free_string_list( ctx->auth_mechs );
1799 	free_generic_messages( &ctx->msgs->gen );
1800 	free_string_list( ctx->boxes );
1801 	imap_deref( ctx );
1802 }
1803 
1804 static int
imap_deref(imap_store_t * ctx)1805 imap_deref( imap_store_t *ctx )
1806 {
1807 	if (!--ctx->ref_count) {
1808 		free( ctx );
1809 		return -1;
1810 	}
1811 	return 0;
1812 }
1813 
1814 static void
imap_set_bad_callback(store_t * gctx,void (* cb)(void * aux),void * aux)1815 imap_set_bad_callback( store_t *gctx, void (*cb)( void *aux ), void *aux )
1816 {
1817 	imap_store_t *ctx = (imap_store_t *)gctx;
1818 
1819 	ctx->bad_callback = cb;
1820 	ctx->bad_callback_aux = aux;
1821 }
1822 
1823 static void
imap_invoke_bad_callback(imap_store_t * ctx)1824 imap_invoke_bad_callback( imap_store_t *ctx )
1825 {
1826 	ctx->bad_callback( ctx->bad_callback_aux );
1827 }
1828 
1829 /******************* imap_free_store *******************/
1830 
1831 static imap_store_t *unowned;
1832 
1833 static void
imap_cancel_unowned(void * gctx)1834 imap_cancel_unowned( void *gctx )
1835 {
1836 	imap_store_t *store, **storep;
1837 
1838 	for (storep = &unowned; (store = *storep); storep = &store->next)
1839 		if (store == gctx) {
1840 			*storep = store->next;
1841 			break;
1842 		}
1843 	imap_cancel_store( gctx );
1844 }
1845 
1846 static void
imap_free_store(store_t * gctx)1847 imap_free_store( store_t *gctx )
1848 {
1849 	imap_store_t *ctx = (imap_store_t *)gctx;
1850 
1851 	assert( !ctx->pending && !ctx->in_progress && !ctx->wait_check );
1852 
1853 	free_generic_messages( &ctx->msgs->gen );
1854 	ctx->msgs = NULL;
1855 	imap_set_bad_callback( gctx, imap_cancel_unowned, gctx );
1856 	ctx->next = unowned;
1857 	unowned = ctx;
1858 }
1859 
1860 /******************* imap_cleanup *******************/
1861 
1862 static void imap_cleanup_p2( imap_store_t *, imap_cmd_t *, int );
1863 
1864 static void
imap_cleanup(void)1865 imap_cleanup( void )
1866 {
1867 	imap_store_t *ctx, *nctx;
1868 
1869 	for (ctx = unowned; ctx; ctx = nctx) {
1870 		nctx = ctx->next;
1871 		imap_set_bad_callback( &ctx->gen, (void (*)(void *))imap_cancel_store, ctx );
1872 		if (((imap_store_t *)ctx)->state != SST_BAD) {
1873 			((imap_store_t *)ctx)->expectBYE = 1;
1874 			imap_exec( (imap_store_t *)ctx, NULL, imap_cleanup_p2, "LOGOUT" );
1875 		} else {
1876 			imap_cancel_store( &ctx->gen );
1877 		}
1878 	}
1879 }
1880 
1881 static void
imap_cleanup_p2(imap_store_t * ctx,imap_cmd_t * cmd ATTR_UNUSED,int response)1882 imap_cleanup_p2( imap_store_t *ctx,
1883                  imap_cmd_t *cmd ATTR_UNUSED, int response )
1884 {
1885 	if (response == RESP_NO)
1886 		imap_cancel_store( &ctx->gen );
1887 	else if (response == RESP_OK)
1888 		ctx->expectEOF = 1;
1889 }
1890 
1891 /******************* imap_open_store *******************/
1892 
1893 static void imap_open_store_connected( int, void * );
1894 #ifdef HAVE_LIBSSL
1895 static void imap_open_store_tlsstarted1( int, void * );
1896 #endif
1897 static void imap_open_store_p2( imap_store_t *, imap_cmd_t *, int );
1898 static void imap_open_store_authenticate( imap_store_t * );
1899 #ifdef HAVE_LIBSSL
1900 static void imap_open_store_authenticate_p2( imap_store_t *, imap_cmd_t *, int );
1901 static void imap_open_store_tlsstarted2( int, void * );
1902 static void imap_open_store_authenticate_p3( imap_store_t *, imap_cmd_t *, int );
1903 #endif
1904 static void imap_open_store_authenticate2( imap_store_t * );
1905 static void imap_open_store_authenticate2_p2( imap_store_t *, imap_cmd_t *, int );
1906 static void imap_open_store_compress( imap_store_t * );
1907 #ifdef HAVE_LIBZ
1908 static void imap_open_store_compress_p2( imap_store_t *, imap_cmd_t *, int );
1909 #endif
1910 static void imap_open_store_namespace( imap_store_t * );
1911 static void imap_open_store_namespace_p2( imap_store_t *, imap_cmd_t *, int );
1912 static void imap_open_store_namespace2( imap_store_t * );
1913 static void imap_open_store_finalize( imap_store_t * );
1914 #ifdef HAVE_LIBSSL
1915 static void imap_open_store_ssl_bail( imap_store_t * );
1916 #endif
1917 static void imap_open_store_bail( imap_store_t *, int );
1918 
1919 static store_t *
imap_alloc_store(store_conf_t * conf,const char * label)1920 imap_alloc_store( store_conf_t *conf, const char *label )
1921 {
1922 	imap_store_conf_t *cfg = (imap_store_conf_t *)conf;
1923 	imap_server_conf_t *srvc = cfg->server;
1924 	imap_store_t *ctx, **ctxp;
1925 
1926 	/* First try to recycle a whole store. */
1927 	for (ctxp = &unowned; (ctx = *ctxp); ctxp = &ctx->next)
1928 		if (ctx->state == SST_GOOD && ctx->conf == cfg) {
1929 			*ctxp = ctx->next;
1930 			goto gotstore;
1931 		}
1932 
1933 	/* Then try to recycle a server connection. */
1934 	for (ctxp = &unowned; (ctx = *ctxp); ctxp = &ctx->next)
1935 		if (ctx->state != SST_BAD && ctx->conf->server == srvc) {
1936 			*ctxp = ctx->next;
1937 			free_string_list( ctx->boxes );
1938 			ctx->boxes = NULL;
1939 			ctx->listed = 0;
1940 			/* One could ping the server here, but given that the idle timeout
1941 			 * is at least 30 minutes, this sounds pretty pointless. */
1942 			ctx->state = SST_HALF;
1943 			goto gotsrv;
1944 		}
1945 
1946 	/* Finally, schedule opening a new server connection. */
1947 	ctx = nfcalloc( sizeof(*ctx) );
1948 	ctx->driver = &imap_driver;
1949 	ctx->ref_count = 1;
1950 	socket_init( &ctx->conn, &srvc->sconf,
1951 	             (void (*)( void * ))imap_invoke_bad_callback,
1952 	             imap_socket_read, (void (*)(void *))flush_imap_cmds, ctx );
1953 	ctx->in_progress_append = &ctx->in_progress;
1954 	ctx->pending_append = &ctx->pending;
1955 	ctx->wait_check_append = &ctx->wait_check;
1956 
1957   gotsrv:
1958 	ctx->conf = cfg;
1959   gotstore:
1960 	ctx->label = label;
1961 	return &ctx->gen;
1962 }
1963 
1964 static void
imap_connect_store(store_t * gctx,void (* cb)(int sts,void * aux),void * aux)1965 imap_connect_store( store_t *gctx,
1966                     void (*cb)( int sts, void *aux ), void *aux )
1967 {
1968 	imap_store_t *ctx = (imap_store_t *)gctx;
1969 
1970 	if (ctx->state == SST_GOOD) {
1971 		cb( DRV_OK, aux );
1972 	} else {
1973 		ctx->callbacks.imap_open = cb;
1974 		ctx->callback_aux = aux;
1975 		if (ctx->state == SST_HALF)
1976 			imap_open_store_namespace( ctx );
1977 		else
1978 			socket_connect( &ctx->conn, imap_open_store_connected );
1979 	}
1980 }
1981 
1982 static void
imap_open_store_connected(int ok,void * aux)1983 imap_open_store_connected( int ok, void *aux )
1984 {
1985 	imap_store_t *ctx = (imap_store_t *)aux;
1986 
1987 	if (!ok)
1988 		imap_open_store_bail( ctx, FAIL_WAIT );
1989 #ifdef HAVE_LIBSSL
1990 	else if (ctx->conf->server->ssl_type == SSL_IMAPS)
1991 		socket_start_tls( &ctx->conn, imap_open_store_tlsstarted1 );
1992 #endif
1993 	else
1994 		socket_expect_activity( &ctx->conn, 1 );
1995 }
1996 
1997 #ifdef HAVE_LIBSSL
1998 static void
imap_open_store_tlsstarted1(int ok,void * aux)1999 imap_open_store_tlsstarted1( int ok, void *aux )
2000 {
2001 	imap_store_t *ctx = (imap_store_t *)aux;
2002 
2003 	if (!ok)
2004 		imap_open_store_ssl_bail( ctx );
2005 	else
2006 		socket_expect_activity( &ctx->conn, 1 );
2007 }
2008 #endif
2009 
2010 static void
imap_open_store_greeted(imap_store_t * ctx)2011 imap_open_store_greeted( imap_store_t *ctx )
2012 {
2013 	socket_expect_activity( &ctx->conn, 0 );
2014 	if (!ctx->caps)
2015 		imap_exec( ctx, NULL, imap_open_store_p2, "CAPABILITY" );
2016 	else
2017 		imap_open_store_authenticate( ctx );
2018 }
2019 
2020 static void
imap_open_store_p2(imap_store_t * ctx,imap_cmd_t * cmd ATTR_UNUSED,int response)2021 imap_open_store_p2( imap_store_t *ctx, imap_cmd_t *cmd ATTR_UNUSED, int response )
2022 {
2023 	if (response == RESP_NO)
2024 		imap_open_store_bail( ctx, FAIL_FINAL );
2025 	else if (response == RESP_OK)
2026 		imap_open_store_authenticate( ctx );
2027 }
2028 
2029 static void
imap_open_store_authenticate(imap_store_t * ctx)2030 imap_open_store_authenticate( imap_store_t *ctx )
2031 {
2032 #ifdef HAVE_LIBSSL
2033 	imap_server_conf_t *srvc = ctx->conf->server;
2034 #endif
2035 
2036 	if (ctx->greeting != GreetingPreauth) {
2037 #ifdef HAVE_LIBSSL
2038 		if (srvc->ssl_type == SSL_STARTTLS) {
2039 			if (CAP(STARTTLS)) {
2040 				imap_exec( ctx, NULL, imap_open_store_authenticate_p2, "STARTTLS" );
2041 				return;
2042 			} else {
2043 				error( "IMAP error: SSL support not available\n" );
2044 				imap_open_store_bail( ctx, FAIL_FINAL );
2045 				return;
2046 			}
2047 		}
2048 #endif
2049 		imap_open_store_authenticate2( ctx );
2050 	} else {
2051 #ifdef HAVE_LIBSSL
2052 		if (srvc->ssl_type == SSL_STARTTLS) {
2053 			error( "IMAP error: SSL support not available\n" );
2054 			imap_open_store_bail( ctx, FAIL_FINAL );
2055 			return;
2056 		}
2057 #endif
2058 		imap_open_store_compress( ctx );
2059 	}
2060 }
2061 
2062 #ifdef HAVE_LIBSSL
2063 static void
imap_open_store_authenticate_p2(imap_store_t * ctx,imap_cmd_t * cmd ATTR_UNUSED,int response)2064 imap_open_store_authenticate_p2( imap_store_t *ctx, imap_cmd_t *cmd ATTR_UNUSED, int response )
2065 {
2066 	if (response == RESP_NO)
2067 		imap_open_store_bail( ctx, FAIL_FINAL );
2068 	else if (response == RESP_OK)
2069 		socket_start_tls( &ctx->conn, imap_open_store_tlsstarted2 );
2070 }
2071 
2072 static void
imap_open_store_tlsstarted2(int ok,void * aux)2073 imap_open_store_tlsstarted2( int ok, void *aux )
2074 {
2075 	imap_store_t *ctx = (imap_store_t *)aux;
2076 
2077 	if (!ok)
2078 		imap_open_store_ssl_bail( ctx );
2079 	else
2080 		imap_exec( ctx, NULL, imap_open_store_authenticate_p3, "CAPABILITY" );
2081 }
2082 
2083 static void
imap_open_store_authenticate_p3(imap_store_t * ctx,imap_cmd_t * cmd ATTR_UNUSED,int response)2084 imap_open_store_authenticate_p3( imap_store_t *ctx, imap_cmd_t *cmd ATTR_UNUSED, int response )
2085 {
2086 	if (response == RESP_NO)
2087 		imap_open_store_bail( ctx, FAIL_FINAL );
2088 	else if (response == RESP_OK)
2089 		imap_open_store_authenticate2( ctx );
2090 }
2091 #endif
2092 
2093 static char *
cred_from_cmd(const char * cred,const char * cmd,const char * srv_name)2094 cred_from_cmd( const char *cred, const char *cmd, const char *srv_name )
2095 {
2096 	FILE *fp;
2097 	int ret;
2098 	char buffer[8192];  // Hopefully more than enough room for XOAUTH2, etc. tokens
2099 
2100 	if (*cmd == '+') {
2101 		flushn();
2102 		cmd++;
2103 	}
2104 	if (!(fp = popen( cmd, "r" ))) {
2105 	  pipeerr:
2106 		sys_error( "Skipping account %s, %s failed", srv_name, cred );
2107 		return NULL;
2108 	}
2109 	if (!fgets( buffer, sizeof(buffer), fp ))
2110 		buffer[0] = 0;
2111 	if ((ret = pclose( fp )) < 0)
2112 		goto pipeerr;
2113 	if (ret) {
2114 		if (WIFSIGNALED( ret ))
2115 			error( "Skipping account %s, %s crashed\n", srv_name, cred );
2116 		else
2117 			error( "Skipping account %s, %s exited with status %d\n", srv_name, cred, WEXITSTATUS( ret ) );
2118 		return NULL;
2119 	}
2120 	if (!buffer[0]) {
2121 		error( "Skipping account %s, %s produced no output\n", srv_name, cred );
2122 		return NULL;
2123 	}
2124 	buffer[strcspn( buffer, "\n" )] = 0; /* Strip trailing newline */
2125 	return nfstrdup( buffer );
2126 }
2127 
2128 static const char *
ensure_user(imap_server_conf_t * srvc)2129 ensure_user( imap_server_conf_t *srvc )
2130 {
2131 	if (!srvc->user) {
2132 		if (srvc->user_cmd) {
2133 			srvc->user = cred_from_cmd( "UserCmd", srvc->user_cmd, srvc->name );
2134 		} else {
2135 			error( "Skipping account %s, no user\n", srvc->name );
2136 		}
2137 	}
2138 	return srvc->user;
2139 }
2140 
2141 static const char *
ensure_password(imap_server_conf_t * srvc)2142 ensure_password( imap_server_conf_t *srvc )
2143 {
2144 	if (!srvc->pass) {
2145 		if (srvc->pass_cmd) {
2146 			srvc->pass = cred_from_cmd( "PassCmd", srvc->pass_cmd, srvc->name );
2147 #ifdef HAVE_MACOS_KEYCHAIN
2148 		} else if (srvc->use_keychain) {
2149 			void *password_data;
2150 			UInt32 password_length;
2151 			OSStatus ret = SecKeychainFindInternetPassword(
2152 					NULL,  // keychainOrArray
2153 					strlen( srvc->sconf.host ), srvc->sconf.host,
2154 					0, NULL,  // securityDomain
2155 					strlen( srvc->user ), srvc->user,
2156 					0, NULL,  // path
2157 					0,  // port - we could use it, but it seems pointless
2158 					kSecProtocolTypeIMAP,
2159 					kSecAuthenticationTypeDefault,
2160 					&password_length, &password_data,
2161 					NULL );  // itemRef
2162 			if (ret != errSecSuccess) {
2163 				CFStringRef errmsg = SecCopyErrorMessageString( ret, NULL );
2164 				error( "Looking up Keychain failed: %s\n",
2165 				       CFStringGetCStringPtr( errmsg, kCFStringEncodingUTF8 ) );
2166 				CFRelease( errmsg );
2167 				return NULL;
2168 			}
2169 			srvc->pass = nfstrndup( password_data, password_length );
2170 			SecKeychainItemFreeContent( NULL, password_data );
2171 #endif /* HAVE_MACOS_KEYCHAIN */
2172 		} else {
2173 			flushn();
2174 			char prompt[80];
2175 			sprintf( prompt, "Password (%s): ", srvc->name );
2176 			char *pass = getpass( prompt );
2177 			if (!pass) {
2178 				perror( "getpass" );
2179 				exit( 1 );
2180 			}
2181 			if (!*pass) {
2182 				error( "Skipping account %s, no password\n", srvc->name );
2183 				return NULL;
2184 			}
2185 			/* getpass() returns a pointer to a static buffer. Make a copy for long term storage. */
2186 			srvc->pass = nfstrdup( pass );
2187 		}
2188 	}
2189 	return srvc->pass;
2190 }
2191 
2192 #ifdef HAVE_LIBSASL
2193 
2194 static sasl_callback_t sasl_callbacks[] = {
2195 	{ SASL_CB_USER,     NULL, NULL },
2196 	{ SASL_CB_AUTHNAME, NULL, NULL },
2197 	{ SASL_CB_PASS,     NULL, NULL },
2198 	{ SASL_CB_LIST_END, NULL, NULL }
2199 };
2200 
2201 static int
process_sasl_interact(sasl_interact_t * interact,imap_server_conf_t * srvc)2202 process_sasl_interact( sasl_interact_t *interact, imap_server_conf_t *srvc )
2203 {
2204 	const char *val;
2205 
2206 	for (;; ++interact) {
2207 		switch (interact->id) {
2208 		case SASL_CB_LIST_END:
2209 			return 0;
2210 		case SASL_CB_USER:  // aka authorization id - who to act as
2211 		case SASL_CB_AUTHNAME:  // who is really logging in
2212 			val = ensure_user( srvc );
2213 			break;
2214 		case SASL_CB_PASS:
2215 			val = ensure_password( srvc );
2216 			break;
2217 		default:
2218 			error( "Error: Unknown SASL interaction ID\n" );
2219 			return -1;
2220 		}
2221 		if (!val)
2222 			return -1;
2223 		interact->result = val;
2224 		interact->len = strlen( val );
2225 	}
2226 }
2227 
2228 static int
process_sasl_step(imap_store_t * ctx,int rc,const char * in,uint in_len,sasl_interact_t * interact,const char ** out,uint * out_len)2229 process_sasl_step( imap_store_t *ctx, int rc, const char *in, uint in_len,
2230                    sasl_interact_t *interact, const char **out, uint *out_len )
2231 {
2232 	imap_server_conf_t *srvc = ctx->conf->server;
2233 
2234 	while (rc == SASL_INTERACT) {
2235 		if (process_sasl_interact( interact, srvc ) < 0)
2236 			return -1;
2237 		rc = sasl_client_step( ctx->sasl, in, in_len, &interact, out, out_len );
2238 	}
2239 	if (rc == SASL_CONTINUE) {
2240 		ctx->sasl_cont = 1;
2241 	} else if (rc == SASL_OK) {
2242 		ctx->sasl_cont = 0;
2243 	} else {
2244 		error( "Error performing SASL authentication step: %s\n", sasl_errdetail( ctx->sasl ) );
2245 		return -1;
2246 	}
2247 	return 0;
2248 }
2249 
2250 static int
decode_sasl_data(const char * prompt,char ** in,uint * in_len)2251 decode_sasl_data( const char *prompt, char **in, uint *in_len )
2252 {
2253 	if (prompt) {
2254 		int rc;
2255 		uint prompt_len = strlen( prompt );
2256 		/* We're decoding, the output will be shorter than prompt_len. */
2257 		*in = nfmalloc( prompt_len );
2258 		rc = sasl_decode64( prompt, prompt_len, *in, prompt_len, in_len );
2259 		if (rc != SASL_OK) {
2260 			free( *in );
2261 			error( "Error decoding SASL prompt: %s\n", sasl_errstring( rc, NULL, NULL ) );
2262 			return -1;
2263 		}
2264 	} else {
2265 		*in = NULL;
2266 		*in_len = 0;
2267 	}
2268 	return 0;
2269 }
2270 
2271 static int
encode_sasl_data(const char * out,uint out_len,char ** enc,uint * enc_len)2272 encode_sasl_data( const char *out, uint out_len, char **enc, uint *enc_len )
2273 {
2274 	int rc;
2275 	uint enc_len_max = ((out_len + 2) / 3) * 4 + 1;
2276 	*enc = nfmalloc( enc_len_max );
2277 	rc = sasl_encode64( out, out_len, *enc, enc_len_max, enc_len );
2278 	if (rc != SASL_OK) {
2279 		free( *enc );
2280 		error( "Error encoding SASL response: %s\n", sasl_errstring( rc, NULL, NULL ) );
2281 		return -1;
2282 	}
2283 	return 0;
2284 }
2285 
2286 static int
do_sasl_auth(imap_store_t * ctx,imap_cmd_t * cmdp ATTR_UNUSED,const char * prompt)2287 do_sasl_auth( imap_store_t *ctx, imap_cmd_t *cmdp ATTR_UNUSED, const char *prompt )
2288 {
2289 	int rc, ret, iovcnt = 0;
2290 	uint in_len, out_len, enc_len;
2291 	const char *out;
2292 	char *in, *enc;
2293 	sasl_interact_t *interact = NULL;
2294 	conn_iovec_t iov[2];
2295 
2296 	if (!ctx->sasl_cont) {
2297 		error( "Error: IMAP wants more steps despite successful SASL authentication.\n" );
2298 		goto bail;
2299 	}
2300 	if (decode_sasl_data( prompt, &in, &in_len ) < 0)
2301 		goto bail;
2302 	rc = sasl_client_step( ctx->sasl, in, in_len, &interact, &out, &out_len );
2303 	ret = process_sasl_step( ctx, rc, in, in_len, interact, &out, &out_len );
2304 	free( in );
2305 	if (ret < 0)
2306 		goto bail;
2307 
2308 	if (out) {
2309 		if (encode_sasl_data( out, out_len, &enc, &enc_len ) < 0)
2310 			goto bail;
2311 
2312 		iov[0].buf = enc;
2313 		iov[0].len = enc_len;
2314 		iov[0].takeOwn = GiveOwn;
2315 		iovcnt = 1;
2316 
2317 		if (DFlags & DEBUG_NET) {
2318 			printf( "%s>+> %s\n", ctx->label, enc );
2319 			fflush( stdout );
2320 		}
2321 	} else {
2322 		if (DFlags & DEBUG_NET) {
2323 			printf( "%s>+>\n", ctx->label );
2324 			fflush( stdout );
2325 		}
2326 	}
2327 	iov[iovcnt].buf = "\r\n";
2328 	iov[iovcnt].len = 2;
2329 	iov[iovcnt].takeOwn = KeepOwn;
2330 	iovcnt++;
2331 	socket_write( &ctx->conn, iov, iovcnt );
2332 	return 0;
2333 
2334   bail:
2335 	imap_open_store_bail( ctx, FAIL_FINAL );
2336 	return -1;
2337 }
2338 
2339 static void
done_sasl_auth(imap_store_t * ctx,imap_cmd_t * cmd ATTR_UNUSED,int response)2340 done_sasl_auth( imap_store_t *ctx, imap_cmd_t *cmd ATTR_UNUSED, int response )
2341 {
2342 	if (response == RESP_OK && ctx->sasl_cont) {
2343 		sasl_interact_t *interact = NULL;
2344 		const char *out;
2345 		uint out_len;
2346 		int rc = sasl_client_step( ctx->sasl, NULL, 0, &interact, &out, &out_len );
2347 		if (process_sasl_step( ctx, rc, NULL, 0, interact, &out, &out_len ) < 0)
2348 			warn( "Warning: SASL reported failure despite successful IMAP authentication. Ignoring...\n" );
2349 		else if (out_len > 0)
2350 			warn( "Warning: SASL wants more steps despite successful IMAP authentication. Ignoring...\n" );
2351 	}
2352 
2353 	imap_open_store_authenticate2_p2( ctx, NULL, response );
2354 }
2355 
2356 #endif
2357 
2358 static void
imap_open_store_authenticate2(imap_store_t * ctx)2359 imap_open_store_authenticate2( imap_store_t *ctx )
2360 {
2361 	imap_server_conf_t *srvc = ctx->conf->server;
2362 	string_list_t *mech, *cmech;
2363 	int auth_login = 0;
2364 	int skipped_login = 0;
2365 #ifdef HAVE_LIBSASL
2366 	const char *saslavail;
2367 	char saslmechs[1024], *saslend = saslmechs;
2368 	int want_external = 0;
2369 #endif
2370 
2371 	// Ensure that there are no leftovers from previous runs. This is needed in case
2372 	// the credentials have a timing dependency or otherwise lose validity after use.
2373 	if (srvc->user_cmd) {
2374 		free( srvc->user );
2375 		srvc->user = NULL;
2376 	}
2377 	if (srvc->pass_cmd) {
2378 		free( srvc->pass );
2379 		srvc->pass = NULL;
2380 	}
2381 
2382 	info( "Logging in...\n" );
2383 	for (mech = srvc->auth_mechs; mech; mech = mech->next) {
2384 		int any = !strcmp( mech->string, "*" );
2385 		for (cmech = ctx->auth_mechs; cmech; cmech = cmech->next) {
2386 			if (any || !strcasecmp( mech->string, cmech->string )) {
2387 				if (!strcasecmp( cmech->string, "LOGIN" )) {
2388 #ifdef HAVE_LIBSSL
2389 					if (ctx->conn.ssl || !any)
2390 #else
2391 					if (!any)
2392 #endif
2393 						auth_login = 1;
2394 					else
2395 						skipped_login = 1;
2396 #ifdef HAVE_LIBSASL
2397 				} else {
2398 					uint len = strlen( cmech->string );
2399 					if (saslend + len + 2 > saslmechs + sizeof(saslmechs))
2400 						oob();
2401 					*saslend++ = ' ';
2402 					memcpy( saslend, cmech->string, len + 1 );
2403 					saslend += len;
2404 
2405 					if (!strcasecmp( cmech->string, "EXTERNAL" ))
2406 						want_external = 1;
2407 #endif
2408 				}
2409 			}
2410 		}
2411 	}
2412 #ifdef HAVE_LIBSASL
2413 	if (saslend != saslmechs) {
2414 		int rc;
2415 		uint out_len = 0;
2416 		char *enc = NULL;
2417 		const char *gotmech = NULL, *out = NULL;
2418 		sasl_interact_t *interact = NULL;
2419 		imap_cmd_t *cmd;
2420 		static int sasl_inited;
2421 
2422 		if (!sasl_inited) {
2423 			rc = sasl_client_init( sasl_callbacks );
2424 			if (rc != SASL_OK) {
2425 			  saslbail:
2426 				error( "Error initializing SASL client: %s\n", sasl_errstring( rc, NULL, NULL ) );
2427 				goto bail;
2428 			}
2429 			sasl_inited = 1;
2430 		}
2431 
2432 		rc = sasl_client_new( "imap", srvc->sconf.host, NULL, NULL, NULL, 0, &ctx->sasl );
2433 		if (rc != SASL_OK) {
2434 			if (rc == SASL_NOMECH)
2435 				goto notsasl;
2436 			if (!ctx->sasl)
2437 				goto saslbail;
2438 			error( "Error initializing SASL context: %s\n", sasl_errdetail( ctx->sasl ) );
2439 			goto bail;
2440 		}
2441 
2442 		// The built-in EXTERNAL mechanism wants the authentication id to be set
2443 		// even before instantiation; consequently it won't prompt for it, either.
2444 		// While this clearly makes sense on the server side, it arguably does not
2445 		// on the client side. Ah, well ...
2446 		if (want_external && ensure_user( srvc )) {
2447 			rc = sasl_setprop( ctx->sasl, SASL_AUTH_EXTERNAL, srvc->user );
2448 			if (rc != SASL_OK ) {
2449 				error( "Error setting SASL authentication id: %s\n", sasl_errdetail( ctx->sasl ) );
2450 				goto bail;
2451 			}
2452 		}
2453 
2454 		rc = sasl_client_start( ctx->sasl, saslmechs + 1, &interact, CAP(SASLIR) ? &out : NULL, &out_len, &gotmech );
2455 		if (rc == SASL_NOMECH)
2456 			goto notsasl;
2457 		if (gotmech)
2458 			info( "Authenticating with SASL mechanism %s...\n", gotmech );
2459 		/* Technically, we are supposed to loop over sasl_client_start(),
2460 		 * but it just calls sasl_client_step() anyway. */
2461 		if (process_sasl_step( ctx, rc, NULL, 0, interact, CAP(SASLIR) ? &out : NULL, &out_len ) < 0)
2462 			goto bail;
2463 		if (out) {
2464 			if (!out_len)
2465 				enc = nfstrdup( "=" ); /* A zero-length initial response is encoded as padding. */
2466 			else if (encode_sasl_data( out, out_len, &enc, NULL ) < 0)
2467 				goto bail;
2468 		}
2469 
2470 		cmd = new_imap_cmd( sizeof(*cmd) );
2471 		cmd->param.cont = do_sasl_auth;
2472 		imap_exec( ctx, cmd, done_sasl_auth, enc ? "AUTHENTICATE %s %s" : "AUTHENTICATE %s", gotmech, enc );
2473 		free( enc );
2474 		return;
2475 	  notsasl:
2476 		if (!ctx->sasl || sasl_listmech( ctx->sasl, NULL, "", " ", "", &saslavail, NULL, NULL ) != SASL_OK)
2477 			saslavail = "(none)";
2478 		if (!auth_login) {
2479 			error( "IMAP error: selected SASL mechanism(s) not available;\n"
2480 			       "   selected:%s\n   available: %s\n", saslmechs, saslavail );
2481 			goto skipnote;
2482 		}
2483 		info( "NOT using available SASL mechanism(s): %s\n", saslavail );
2484 		sasl_dispose( &ctx->sasl );
2485 	}
2486 #endif
2487 	if (auth_login) {
2488 		if (!ensure_user( srvc ) || !ensure_password( srvc ))
2489 			goto bail;
2490 #ifdef HAVE_LIBSSL
2491 		if (!ctx->conn.ssl)
2492 #endif
2493 			warn( "*** IMAP Warning *** Password is being sent in the clear\n" );
2494 		imap_exec( ctx, NULL, imap_open_store_authenticate2_p2,
2495 		           "LOGIN \"%\\s\" \"%\\s\"", srvc->user, srvc->pass );
2496 		return;
2497 	}
2498 	error( "IMAP error: server supports no acceptable authentication mechanism\n" );
2499 #ifdef HAVE_LIBSASL
2500   skipnote:
2501 #endif
2502 	if (skipped_login)
2503 		error( "Note: not using LOGIN because connection is not encrypted;\n"
2504 		       "      use 'AuthMechs LOGIN' explicitly to force it.\n" );
2505 
2506   bail:
2507 	imap_open_store_bail( ctx, FAIL_FINAL );
2508 }
2509 
2510 static void
imap_open_store_authenticate2_p2(imap_store_t * ctx,imap_cmd_t * cmd ATTR_UNUSED,int response)2511 imap_open_store_authenticate2_p2( imap_store_t *ctx, imap_cmd_t *cmd ATTR_UNUSED, int response )
2512 {
2513 	if (response == RESP_NO)
2514 		imap_open_store_bail( ctx, FAIL_FINAL );
2515 	else if (response == RESP_OK)
2516 		imap_open_store_compress( ctx );
2517 }
2518 
2519 static void
imap_open_store_compress(imap_store_t * ctx)2520 imap_open_store_compress( imap_store_t *ctx )
2521 {
2522 #ifdef HAVE_LIBZ
2523 	if (CAP(COMPRESS_DEFLATE)) {
2524 		imap_exec( ctx, NULL, imap_open_store_compress_p2, "COMPRESS DEFLATE" );
2525 		return;
2526 	}
2527 #endif
2528 	imap_open_store_namespace( ctx );
2529 }
2530 
2531 #ifdef HAVE_LIBZ
2532 static void
imap_open_store_compress_p2(imap_store_t * ctx,imap_cmd_t * cmd ATTR_UNUSED,int response)2533 imap_open_store_compress_p2( imap_store_t *ctx, imap_cmd_t *cmd ATTR_UNUSED, int response )
2534 {
2535 	if (response == RESP_NO) {
2536 		/* We already reported an error, but it's not fatal to us. */
2537 		imap_open_store_namespace( ctx );
2538 	} else if (response == RESP_OK) {
2539 		socket_start_deflate( &ctx->conn );
2540 		imap_open_store_namespace( ctx );
2541 	}
2542 }
2543 #endif
2544 
2545 static void
imap_open_store_namespace(imap_store_t * ctx)2546 imap_open_store_namespace( imap_store_t *ctx )
2547 {
2548 	imap_store_conf_t *cfg = ctx->conf;
2549 
2550 	ctx->state = SST_HALF;
2551 	ctx->prefix = cfg->path;
2552 	ctx->delimiter[0] = cfg->delimiter;
2553 	if (((!ctx->prefix && cfg->use_namespace) || !cfg->delimiter) && CAP(NAMESPACE)) {
2554 		/* get NAMESPACE info */
2555 		if (!ctx->got_namespace)
2556 			imap_exec( ctx, NULL, imap_open_store_namespace_p2, "NAMESPACE" );
2557 		else
2558 			imap_open_store_namespace2( ctx );
2559 		return;
2560 	}
2561 	imap_open_store_finalize( ctx );
2562 }
2563 
2564 static void
imap_open_store_namespace_p2(imap_store_t * ctx,imap_cmd_t * cmd ATTR_UNUSED,int response)2565 imap_open_store_namespace_p2( imap_store_t *ctx, imap_cmd_t *cmd ATTR_UNUSED, int response )
2566 {
2567 	if (response == RESP_NO) {
2568 		imap_open_store_bail( ctx, FAIL_FINAL );
2569 	} else if (response == RESP_OK) {
2570 		ctx->got_namespace = 1;
2571 		imap_open_store_namespace2( ctx );
2572 	}
2573 }
2574 
2575 static void
imap_open_store_namespace2(imap_store_t * ctx)2576 imap_open_store_namespace2( imap_store_t *ctx )
2577 {
2578 	if (!ctx->prefix && ctx->conf->use_namespace)
2579 		ctx->prefix = ctx->ns_prefix;
2580 	if (!ctx->delimiter[0])
2581 		ctx->delimiter[0] = ctx->ns_delimiter;
2582 	imap_open_store_finalize( ctx );
2583 }
2584 
2585 static void
imap_open_store_finalize(imap_store_t * ctx)2586 imap_open_store_finalize( imap_store_t *ctx )
2587 {
2588 	ctx->state = SST_GOOD;
2589 	if (!ctx->prefix)
2590 		ctx->prefix = "";
2591 	else
2592 		normalize_INBOX( ctx, ctx->prefix, -1 );
2593 	ctx->trashnc = TrashUnknown;
2594 	ctx->callbacks.imap_open( DRV_OK, ctx->callback_aux );
2595 }
2596 
2597 #ifdef HAVE_LIBSSL
2598 static void
imap_open_store_ssl_bail(imap_store_t * ctx)2599 imap_open_store_ssl_bail( imap_store_t *ctx )
2600 {
2601 	/* This avoids that we try to send LOGOUT to an unusable socket. */
2602 	socket_close( &ctx->conn );
2603 	imap_open_store_bail( ctx, FAIL_FINAL );
2604 }
2605 #endif
2606 
2607 static void
imap_open_store_bail(imap_store_t * ctx,int failed)2608 imap_open_store_bail( imap_store_t *ctx, int failed )
2609 {
2610 	ctx->conf->server->failed = (char)failed;
2611 	ctx->callbacks.imap_open( DRV_STORE_BAD, ctx->callback_aux );
2612 }
2613 
2614 /******************* imap_open_box *******************/
2615 
2616 static int
imap_select_box(store_t * gctx,const char * name)2617 imap_select_box( store_t *gctx, const char *name )
2618 {
2619 	imap_store_t *ctx = (imap_store_t *)gctx;
2620 
2621 	assert( !ctx->pending && !ctx->in_progress && !ctx->wait_check );
2622 
2623 	free_generic_messages( &ctx->msgs->gen );
2624 	ctx->msgs = NULL;
2625 	ctx->msgapp = &ctx->msgs;
2626 
2627 	ctx->name = name;
2628 	return DRV_OK;
2629 }
2630 
2631 static const char *
imap_get_box_path(store_t * gctx ATTR_UNUSED)2632 imap_get_box_path( store_t *gctx ATTR_UNUSED )
2633 {
2634 	return NULL;
2635 }
2636 
2637 typedef union {
2638 	imap_cmd_t gen;
2639 	struct {
2640 		IMAP_CMD
2641 		void (*callback)( int sts, uint uidvalidity, void *aux );
2642 		void *callback_aux;
2643 	};
2644 } imap_cmd_open_box_t;
2645 
2646 static void imap_open_box_p2( imap_store_t *, imap_cmd_t *, int );
2647 static void imap_open_box_p3( imap_store_t *, imap_cmd_t *, int );
2648 static void imap_open_box_p4( imap_store_t *, imap_cmd_open_box_t *, int );
2649 
2650 static void
imap_open_box(store_t * gctx,void (* cb)(int sts,uint uidvalidity,void * aux),void * aux)2651 imap_open_box( store_t *gctx,
2652                void (*cb)( int sts, uint uidvalidity, void *aux ), void *aux )
2653 {
2654 	imap_store_t *ctx = (imap_store_t *)gctx;
2655 	imap_cmd_open_box_t *cmd;
2656 	char *buf;
2657 
2658 	if (prepare_box( &buf, ctx ) < 0) {
2659 		cb( DRV_BOX_BAD, UIDVAL_BAD, aux );
2660 		return;
2661 	}
2662 
2663 	ctx->uidvalidity = UIDVAL_BAD;
2664 	ctx->uidnext = 0;
2665 
2666 	INIT_IMAP_CMD(imap_cmd_open_box_t, cmd, cb, aux)
2667 	cmd->param.failok = 1;
2668 	imap_exec( ctx, &cmd->gen, imap_open_box_p2,
2669 	           "SELECT \"%\\s\"", buf );
2670 	free( buf );
2671 }
2672 
2673 static void
imap_open_box_p2(imap_store_t * ctx,imap_cmd_t * gcmd,int response)2674 imap_open_box_p2( imap_store_t *ctx, imap_cmd_t *gcmd, int response )
2675 {
2676 	imap_cmd_open_box_t *cmdp = (imap_cmd_open_box_t *)gcmd;
2677 	imap_cmd_open_box_t *cmd;
2678 
2679 	if (response != RESP_OK || ctx->uidnext) {
2680 		imap_open_box_p4( ctx, cmdp, response );
2681 		return;
2682 	}
2683 
2684 	assert( ctx->fetch_sts == FetchNone );
2685 	ctx->fetch_sts = FetchUidNext;
2686 	INIT_IMAP_CMD(imap_cmd_open_box_t, cmd, cmdp->callback, cmdp->callback_aux)
2687 	imap_exec( ctx, &cmd->gen, imap_open_box_p3,
2688 	           "UID FETCH * (UID)" );
2689 }
2690 
2691 static void
imap_open_box_p3(imap_store_t * ctx,imap_cmd_t * gcmd,int response)2692 imap_open_box_p3( imap_store_t *ctx, imap_cmd_t *gcmd, int response )
2693 {
2694 	imap_cmd_open_box_t *cmdp = (imap_cmd_open_box_t *)gcmd;
2695 
2696 	ctx->fetch_sts = FetchNone;
2697 	if (!ctx->uidnext) {
2698 		if (ctx->total_msgs) {
2699 			error( "IMAP error: querying server for highest UID failed\n" );
2700 			imap_open_box_p4( ctx, cmdp, RESP_NO );
2701 			return;
2702 		}
2703 		// This is ok, the box is simply empty.
2704 		ctx->uidnext = 1;
2705 	}
2706 
2707 	imap_open_box_p4( ctx, cmdp, response );
2708 }
2709 
2710 static void
imap_open_box_p4(imap_store_t * ctx,imap_cmd_open_box_t * cmdp,int response)2711 imap_open_box_p4( imap_store_t *ctx, imap_cmd_open_box_t *cmdp, int response )
2712 {
2713 	transform_box_response( &response );
2714 	cmdp->callback( response, ctx->uidvalidity, cmdp->callback_aux );
2715 }
2716 
2717 static uint
imap_get_uidnext(store_t * gctx)2718 imap_get_uidnext( store_t *gctx )
2719 {
2720 	imap_store_t *ctx = (imap_store_t *)gctx;
2721 
2722 	return ctx->uidnext;
2723 }
2724 
2725 static xint
imap_get_supported_flags(store_t * gctx)2726 imap_get_supported_flags( store_t *gctx )
2727 {
2728 	imap_store_t *ctx = (imap_store_t *)gctx;
2729 
2730 	return ctx->has_forwarded ? 255 : (255 & ~F_FORWARDED);
2731 }
2732 
2733 /******************* imap_create_box *******************/
2734 
2735 static void
imap_create_box(store_t * gctx,void (* cb)(int sts,void * aux),void * aux)2736 imap_create_box( store_t *gctx,
2737                  void (*cb)( int sts, void *aux ), void *aux )
2738 {
2739 	imap_store_t *ctx = (imap_store_t *)gctx;
2740 	imap_cmd_simple_t *cmd;
2741 	char *buf;
2742 
2743 	if (prepare_box( &buf, ctx ) < 0) {
2744 		cb( DRV_BOX_BAD, aux );
2745 		return;
2746 	}
2747 
2748 	INIT_IMAP_CMD(imap_cmd_simple_t, cmd, cb, aux)
2749 	imap_exec( ctx, &cmd->gen, imap_done_simple_box,
2750 	           "CREATE \"%\\s\"", buf );
2751 	free( buf );
2752 }
2753 
2754 /******************* imap_delete_box *******************/
2755 
2756 static int
imap_confirm_box_empty(store_t * gctx)2757 imap_confirm_box_empty( store_t *gctx )
2758 {
2759 	imap_store_t *ctx = (imap_store_t *)gctx;
2760 
2761 	return ctx->total_msgs ? DRV_BOX_BAD : DRV_OK;
2762 }
2763 
2764 static void imap_delete_box_p2( imap_store_t *, imap_cmd_t *, int );
2765 
2766 static void
imap_delete_box(store_t * gctx,void (* cb)(int sts,void * aux),void * aux)2767 imap_delete_box( store_t *gctx,
2768                  void (*cb)( int sts, void *aux ), void *aux )
2769 {
2770 	imap_store_t *ctx = (imap_store_t *)gctx;
2771 	imap_cmd_simple_t *cmd;
2772 
2773 	INIT_IMAP_CMD(imap_cmd_simple_t, cmd, cb, aux)
2774 	imap_exec( ctx, &cmd->gen, imap_delete_box_p2, "CLOSE" );
2775 }
2776 
2777 static void
imap_delete_box_p2(imap_store_t * ctx,imap_cmd_t * gcmd,int response)2778 imap_delete_box_p2( imap_store_t *ctx, imap_cmd_t *gcmd, int response )
2779 {
2780 	imap_cmd_simple_t *cmdp = (imap_cmd_simple_t *)gcmd;
2781 	imap_cmd_simple_t *cmd;
2782 	char *buf;
2783 
2784 	if (response != RESP_OK) {
2785 		imap_done_simple_box( ctx, &cmdp->gen, response );
2786 		return;
2787 	}
2788 
2789 	if (prepare_box( &buf, ctx ) < 0) {
2790 		imap_done_simple_box( ctx, &cmdp->gen, RESP_NO );
2791 		return;
2792 	}
2793 	INIT_IMAP_CMD(imap_cmd_simple_t, cmd, cmdp->callback, cmdp->callback_aux)
2794 	imap_exec( ctx, &cmd->gen, imap_done_simple_box,
2795 	           "DELETE \"%\\s\"", buf );
2796 	free( buf );
2797 }
2798 
2799 static int
imap_finish_delete_box(store_t * gctx ATTR_UNUSED)2800 imap_finish_delete_box( store_t *gctx ATTR_UNUSED )
2801 {
2802 	return DRV_OK;
2803 }
2804 
2805 /******************* imap_load_box *******************/
2806 
2807 static uint
imap_prepare_load_box(store_t * gctx,uint opts)2808 imap_prepare_load_box( store_t *gctx, uint opts )
2809 {
2810 	imap_store_t *ctx = (imap_store_t *)gctx;
2811 
2812 	ctx->opts = opts;
2813 	return opts;
2814 }
2815 
2816 enum { WantSize = 1, WantTuids = 2, WantMsgids = 4 };
2817 typedef struct {
2818 	uint first, last;
2819 	int flags;
2820 } imap_range_t;
2821 
2822 static void
imap_set_range(imap_range_t * ranges,uint * nranges,int low_flags,int high_flags,uint maxlow)2823 imap_set_range( imap_range_t *ranges, uint *nranges, int low_flags, int high_flags, uint maxlow )
2824 {
2825 	if (low_flags != high_flags) {
2826 		for (uint r = 0; r < *nranges; r++) {
2827 			if (ranges[r].first > maxlow)
2828 				break; /* Range starts above split point; so do all subsequent ranges. */
2829 			if (ranges[r].last < maxlow)
2830 				continue; /* Range ends below split point; try next one. */
2831 			if (ranges[r].last != maxlow) {
2832 				/* Range does not end exactly at split point; need to split. */
2833 				memmove( &ranges[r + 1], &ranges[r], ((*nranges)++ - r) * sizeof(*ranges) );
2834 				ranges[r].last = maxlow;
2835 				ranges[r + 1].first = maxlow + 1;
2836 			}
2837 			break;
2838 		}
2839 	}
2840 	for (uint r = 0; r < *nranges; r++)
2841 		ranges[r].flags |= (ranges[r].last <= maxlow) ? low_flags : high_flags;
2842 }
2843 
2844 typedef union {
2845 	imap_cmd_refcounted_state_t gen;
2846 	struct {
2847 		IMAP_CMD_REFCOUNTED_STATE
2848 		void (*callback)( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux );
2849 		void *callback_aux;
2850 	};
2851 } imap_load_box_state_t;
2852 
2853 static void imap_submit_load( imap_store_t *, const char *, int, imap_load_box_state_t * );
2854 static void imap_submit_load_p3( imap_store_t *ctx, imap_load_box_state_t * );
2855 
2856 static void
imap_load_box(store_t * gctx,uint minuid,uint maxuid,uint finduid,uint pairuid,uint newuid,uint_array_t excs,void (* cb)(int sts,message_t * msgs,int total_msgs,int recent_msgs,void * aux),void * aux)2857 imap_load_box( store_t *gctx, uint minuid, uint maxuid, uint finduid, uint pairuid, uint newuid, uint_array_t excs,
2858                void (*cb)( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux ), void *aux )
2859 {
2860 	imap_store_t *ctx = (imap_store_t *)gctx;
2861 	char buf[1000];
2862 
2863 	if (!ctx->total_msgs) {
2864 		free( excs.data );
2865 		cb( DRV_OK, NULL, 0, 0, aux );
2866 	} else {
2867 		assert( ctx->fetch_sts == FetchNone );
2868 		ctx->fetch_sts = FetchMsgs;
2869 
2870 		INIT_REFCOUNTED_STATE(imap_load_box_state_t, sts, cb, aux)
2871 		for (uint i = 0; i < excs.size; ) {
2872 			for (int bl = 0; i < excs.size && bl < 960; i++) {
2873 				if (bl)
2874 					buf[bl++] = ',';
2875 				bl += sprintf( buf + bl, "%u", excs.data[i] );
2876 				uint j = i;
2877 				for (; i + 1 < excs.size && excs.data[i + 1] == excs.data[i] + 1; i++) {}
2878 				if (i != j)
2879 					bl += sprintf( buf + bl, ":%u", excs.data[i] );
2880 			}
2881 			imap_submit_load( ctx, buf, shifted_bit( ctx->opts, OPEN_OLD_IDS, WantMsgids ), sts );
2882 		}
2883 		if (maxuid == UINT_MAX)
2884 			maxuid = ctx->uidnext - 1;
2885 		if (maxuid >= minuid) {
2886 			imap_range_t ranges[3];
2887 			ranges[0].first = minuid;
2888 			ranges[0].last = maxuid;
2889 			ranges[0].flags = 0;
2890 			uint nranges = 1;
2891 			if (ctx->opts & OPEN_NEW_SIZE)
2892 				imap_set_range( ranges, &nranges, 0, WantSize, newuid );
2893 			if (ctx->opts & OPEN_FIND)
2894 				imap_set_range( ranges, &nranges, 0, WantTuids, finduid - 1 );
2895 			if (ctx->opts & OPEN_OLD_IDS)
2896 				imap_set_range( ranges, &nranges, WantMsgids, 0, pairuid );
2897 			for (uint r = 0; r < nranges; r++) {
2898 				sprintf( buf, "%u:%u", ranges[r].first, ranges[r].last );
2899 				imap_submit_load( ctx, buf, ranges[r].flags, sts );
2900 			}
2901 		}
2902 		free( excs.data );
2903 		imap_submit_load_p3( ctx, sts );
2904 	}
2905 }
2906 
2907 static int
imap_sort_msgs_comp(const void * a_,const void * b_)2908 imap_sort_msgs_comp( const void *a_, const void *b_ )
2909 {
2910 	const message_t *a = *(const message_t * const *)a_;
2911 	const message_t *b = *(const message_t * const *)b_;
2912 
2913 	if (a->uid < b->uid)
2914 		return -1;
2915 	if (a->uid > b->uid)
2916 		return 1;
2917 	return 0;
2918 }
2919 
2920 static void
imap_sort_msgs(imap_store_t * ctx)2921 imap_sort_msgs( imap_store_t *ctx )
2922 {
2923 	uint count = count_generic_messages( &ctx->msgs->gen );
2924 	if (count <= 1)
2925 		return;
2926 
2927 	imap_message_t **t = nfmalloc( sizeof(*t) * count );
2928 
2929 	imap_message_t *m = ctx->msgs;
2930 	for (uint i = 0; i < count; i++) {
2931 		t[i] = m;
2932 		m = m->next;
2933 	}
2934 
2935 	qsort( t, count, sizeof(*t), imap_sort_msgs_comp );
2936 
2937 	ctx->msgs = t[0];
2938 
2939 	uint j;
2940 	for (j = 0; j < count - 1; j++)
2941 		t[j]->next = t[j + 1];
2942 	ctx->msgapp = &t[j]->next;
2943 	*ctx->msgapp = NULL;
2944 
2945 	free( t );
2946 }
2947 
2948 static void imap_submit_load_p2( imap_store_t *, imap_cmd_t *, int );
2949 
2950 static void
imap_submit_load(imap_store_t * ctx,const char * buf,int flags,imap_load_box_state_t * sts)2951 imap_submit_load( imap_store_t *ctx, const char *buf, int flags, imap_load_box_state_t *sts )
2952 {
2953 	imap_exec( ctx, imap_refcounted_new_cmd( &sts->gen ), imap_submit_load_p2,
2954 	           "UID FETCH %s (UID%s%s%s%s%s%s%s)", buf,
2955 	           (ctx->opts & OPEN_FLAGS) ? " FLAGS" : "",
2956 	           (flags & WantSize) ? " RFC822.SIZE" : "",
2957 	           (flags & (WantTuids | WantMsgids)) ? " BODY.PEEK[HEADER.FIELDS (" : "",
2958 	           (flags & WantTuids) ? "X-TUID" : "",
2959 	           !(~flags & (WantTuids | WantMsgids)) ? " " : "",
2960 	           (flags & WantMsgids) ? "MESSAGE-ID" : "",
2961 	           (flags & (WantTuids | WantMsgids)) ? ")]" : "");
2962 }
2963 
2964 static void
imap_submit_load_p2(imap_store_t * ctx,imap_cmd_t * cmd,int response)2965 imap_submit_load_p2( imap_store_t *ctx, imap_cmd_t *cmd, int response )
2966 {
2967 	imap_load_box_state_t *sts = (imap_load_box_state_t *)((imap_cmd_refcounted_t *)cmd)->state;
2968 
2969 	transform_refcounted_box_response( &sts->gen, response );
2970 	imap_submit_load_p3( ctx, sts );
2971 }
2972 
2973 static void
imap_submit_load_p3(imap_store_t * ctx,imap_load_box_state_t * sts)2974 imap_submit_load_p3( imap_store_t *ctx, imap_load_box_state_t *sts )
2975 {
2976 	DONE_REFCOUNTED_STATE_ARGS(sts, {
2977 		ctx->fetch_sts = FetchNone;
2978 		if (sts->ret_val == DRV_OK)
2979 			imap_sort_msgs( ctx );
2980 	}, &ctx->msgs->gen, ctx->total_msgs, ctx->recent_msgs)
2981 }
2982 
2983 /******************* imap_fetch_msg *******************/
2984 
2985 static void imap_fetch_msg_p2( imap_store_t *, imap_cmd_t *, int );
2986 
2987 static void
imap_fetch_msg(store_t * ctx,message_t * msg,msg_data_t * data,int minimal,void (* cb)(int sts,void * aux),void * aux)2988 imap_fetch_msg( store_t *ctx, message_t *msg, msg_data_t *data, int minimal,
2989                 void (*cb)( int sts, void *aux ), void *aux )
2990 {
2991 	imap_cmd_fetch_msg_t *cmd;
2992 
2993 	INIT_IMAP_CMD_X(imap_cmd_fetch_msg_t, cmd, cb, aux)
2994 	cmd->param.uid = msg->uid;
2995 	cmd->msg_data = data;
2996 	data->data = NULL;
2997 	imap_exec( (imap_store_t *)ctx, &cmd->gen.gen, imap_fetch_msg_p2,
2998 	           "UID FETCH %u (%s%sBODY.PEEK[%s])", msg->uid,
2999 	           !(msg->status & M_FLAGS) ? "FLAGS " : "",
3000 	           (data->date== -1) ? "INTERNALDATE " : "",
3001 	           minimal ? "HEADER" : "" );
3002 }
3003 
3004 static void
imap_fetch_msg_p2(imap_store_t * ctx,imap_cmd_t * gcmd,int response)3005 imap_fetch_msg_p2( imap_store_t *ctx, imap_cmd_t *gcmd, int response )
3006 {
3007 	imap_cmd_fetch_msg_t *cmd = (imap_cmd_fetch_msg_t *)gcmd;
3008 
3009 	if (response == RESP_OK && !cmd->msg_data->data) {
3010 		/* The FETCH succeeded, but there is no message with this UID. */
3011 		response = RESP_NO;
3012 	}
3013 	imap_done_simple_msg( ctx, gcmd, response );
3014 }
3015 
3016 /******************* imap_set_msg_flags *******************/
3017 
3018 static uint
imap_make_flags(int flags,char * buf)3019 imap_make_flags( int flags, char *buf )
3020 {
3021 	const char *s;
3022 	uint i, d;
3023 
3024 	for (i = d = 0; i < as(Flags); i++)
3025 		if (flags & (1 << i)) {
3026 			buf[d++] = ' ';
3027 			for (s = Flags[i]; *s; s++)
3028 				buf[d++] = *s;
3029 		}
3030 	buf[0] = '(';
3031 	buf[d++] = ')';
3032 	return d;
3033 }
3034 
3035 typedef union {
3036 	imap_cmd_refcounted_state_t gen;
3037 	struct {
3038 		IMAP_CMD_REFCOUNTED_STATE
3039 		void (*callback)( int sts, void *aux );
3040 		void *callback_aux;
3041 	};
3042 } imap_set_msg_flags_state_t;
3043 
3044 static void imap_set_flags_p2( imap_store_t *, imap_cmd_t *, int );
3045 static void imap_set_flags_p3( imap_set_msg_flags_state_t * );
3046 
3047 static void
imap_flags_helper(imap_store_t * ctx,uint uid,char what,int flags,imap_set_msg_flags_state_t * sts)3048 imap_flags_helper( imap_store_t *ctx, uint uid, char what, int flags,
3049                    imap_set_msg_flags_state_t *sts )
3050 {
3051 	char buf[256];
3052 
3053 	buf[imap_make_flags( flags, buf )] = 0;
3054 	imap_cmd_t *cmd = imap_refcounted_new_cmd( &sts->gen );
3055 	cmd->param.wait_check = 1;
3056 	imap_exec( ctx, cmd, imap_set_flags_p2,
3057 	           "UID STORE %u %cFLAGS.SILENT %s", uid, what, buf );
3058 }
3059 
3060 static void
imap_set_msg_flags(store_t * gctx,message_t * msg,uint uid,int add,int del,void (* cb)(int sts,void * aux),void * aux)3061 imap_set_msg_flags( store_t *gctx, message_t *msg, uint uid, int add, int del,
3062                     void (*cb)( int sts, void *aux ), void *aux )
3063 {
3064 	imap_store_t *ctx = (imap_store_t *)gctx;
3065 
3066 	if (msg) {
3067 		uid = msg->uid;
3068 		add &= ~msg->flags;
3069 		del &= msg->flags;
3070 		msg->flags |= add;
3071 		msg->flags &= ~del;
3072 	}
3073 	if (add || del) {
3074 		INIT_REFCOUNTED_STATE(imap_set_msg_flags_state_t, sts, cb, aux)
3075 		if (add)
3076 			imap_flags_helper( ctx, uid, '+', add, sts );
3077 		if (del)
3078 			imap_flags_helper( ctx, uid, '-', del, sts );
3079 		imap_set_flags_p3( sts );
3080 	} else {
3081 		cb( DRV_OK, aux );
3082 	}
3083 }
3084 
3085 static void
imap_set_flags_p2(imap_store_t * ctx ATTR_UNUSED,imap_cmd_t * cmd,int response)3086 imap_set_flags_p2( imap_store_t *ctx ATTR_UNUSED, imap_cmd_t *cmd, int response )
3087 {
3088 	imap_set_msg_flags_state_t *sts = (imap_set_msg_flags_state_t *)((imap_cmd_refcounted_t *)cmd)->state;
3089 
3090 	transform_refcounted_msg_response( &sts->gen, response);
3091 	imap_set_flags_p3( sts );
3092 }
3093 
3094 static void
imap_set_flags_p3(imap_set_msg_flags_state_t * sts)3095 imap_set_flags_p3( imap_set_msg_flags_state_t *sts )
3096 {
3097 	DONE_REFCOUNTED_STATE(sts)
3098 }
3099 
3100 /******************* imap_close_box *******************/
3101 
3102 typedef union {
3103 	imap_cmd_refcounted_state_t gen;
3104 	struct {
3105 		IMAP_CMD_REFCOUNTED_STATE
3106 		void (*callback)( int sts, void *aux );
3107 		void *callback_aux;
3108 	};
3109 } imap_expunge_state_t;
3110 
3111 static void imap_close_box_p2( imap_store_t *, imap_cmd_t *, int );
3112 static void imap_close_box_p3( imap_expunge_state_t * );
3113 
3114 static void
imap_close_box(store_t * gctx,void (* cb)(int sts,void * aux),void * aux)3115 imap_close_box( store_t *gctx,
3116                 void (*cb)( int sts, void *aux ), void *aux )
3117 {
3118 	imap_store_t *ctx = (imap_store_t *)gctx;
3119 
3120 	assert( !ctx->num_wait_check );
3121 
3122 	if (ctx->conf->trash && CAP(UIDPLUS)) {
3123 		INIT_REFCOUNTED_STATE(imap_expunge_state_t, sts, cb, aux)
3124 		imap_message_t *msg, *fmsg, *nmsg;
3125 		int bl;
3126 		char buf[1000];
3127 
3128 		for (msg = ctx->msgs; ; ) {
3129 			for (bl = 0; msg && bl < 960; msg = msg->next) {
3130 				if (!(msg->flags & F_DELETED))
3131 					continue;
3132 				if (bl)
3133 					buf[bl++] = ',';
3134 				bl += sprintf( buf + bl, "%u", msg->uid );
3135 				fmsg = msg;
3136 				for (; (nmsg = msg->next) && (nmsg->flags & F_DELETED); msg = nmsg) {}
3137 				if (msg != fmsg)
3138 					bl += sprintf( buf + bl, ":%u", msg->uid );
3139 			}
3140 			if (!bl)
3141 				break;
3142 			imap_exec( ctx, imap_refcounted_new_cmd( &sts->gen ), imap_close_box_p2,
3143 			           "UID EXPUNGE %s", buf );
3144 		}
3145 		imap_close_box_p3( sts );
3146 	} else {
3147 		/* This is inherently racy: it may cause messages which other clients
3148 		 * marked as deleted to be expunged without being trashed. */
3149 		imap_cmd_simple_t *cmd;
3150 		INIT_IMAP_CMD(imap_cmd_simple_t, cmd, cb, aux)
3151 		imap_exec( ctx, &cmd->gen, imap_done_simple_box, "CLOSE" );
3152 	}
3153 }
3154 
3155 static void
imap_close_box_p2(imap_store_t * ctx ATTR_UNUSED,imap_cmd_t * cmd,int response)3156 imap_close_box_p2( imap_store_t *ctx ATTR_UNUSED, imap_cmd_t *cmd, int response )
3157 {
3158 	imap_expunge_state_t *sts = (imap_expunge_state_t *)((imap_cmd_refcounted_t *)cmd)->state;
3159 
3160 	transform_refcounted_box_response( &sts->gen, response );
3161 	imap_close_box_p3( sts );
3162 }
3163 
3164 static void
imap_close_box_p3(imap_expunge_state_t * sts)3165 imap_close_box_p3( imap_expunge_state_t *sts )
3166 {
3167 	DONE_REFCOUNTED_STATE(sts)
3168 }
3169 
3170 /******************* imap_trash_msg *******************/
3171 
3172 static void
imap_trash_msg(store_t * gctx,message_t * msg,void (* cb)(int sts,void * aux),void * aux)3173 imap_trash_msg( store_t *gctx, message_t *msg,
3174                 void (*cb)( int sts, void *aux ), void *aux )
3175 {
3176 	imap_store_t *ctx = (imap_store_t *)gctx;
3177 	imap_cmd_simple_t *cmd;
3178 	char *buf;
3179 
3180 	INIT_IMAP_CMD(imap_cmd_simple_t, cmd, cb, aux)
3181 	cmd->param.create = 1;
3182 	cmd->param.to_trash = 1;
3183 	if (prepare_trash( &buf, ctx ) < 0) {
3184 		cb( DRV_BOX_BAD, aux );
3185 		return;
3186 	}
3187 	imap_exec( ctx, &cmd->gen, imap_done_simple_msg,
3188 	           CAP(MOVE) ? "UID MOVE %u \"%\\s\"" : "UID COPY %u \"%\\s\"", msg->uid, buf );
3189 	free( buf );
3190 }
3191 
3192 /******************* imap_store_msg *******************/
3193 
3194 static void imap_store_msg_p2( imap_store_t *, imap_cmd_t *, int );
3195 
3196 static void
imap_store_msg(store_t * gctx,msg_data_t * data,int to_trash,void (* cb)(int sts,uint uid,void * aux),void * aux)3197 imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
3198                 void (*cb)( int sts, uint uid, void *aux ), void *aux )
3199 {
3200 	imap_store_t *ctx = (imap_store_t *)gctx;
3201 	imap_cmd_out_uid_t *cmd;
3202 	char *buf;
3203 	uint d;
3204 	char flagstr[128], datestr[64];
3205 
3206 	d = 0;
3207 	if (data->flags) {
3208 		d = imap_make_flags( data->flags, flagstr );
3209 		flagstr[d++] = ' ';
3210 	}
3211 	flagstr[d] = 0;
3212 
3213 	INIT_IMAP_CMD(imap_cmd_out_uid_t, cmd, cb, aux)
3214 	ctx->buffer_mem += data->len;
3215 	cmd->param.data_len = data->len;
3216 	cmd->param.data = data->data;
3217 
3218 	if (to_trash) {
3219 		cmd->param.create = 1;
3220 		cmd->param.to_trash = 1;
3221 		if (prepare_trash( &buf, ctx ) < 0) {
3222 			cb( DRV_BOX_BAD, 0, aux );
3223 			return;
3224 		}
3225 	} else {
3226 		if (prepare_box( &buf, ctx ) < 0) {
3227 			cb( DRV_BOX_BAD, 0, aux );
3228 			return;
3229 		}
3230 	}
3231 	if (data->date) {
3232 		/* configure ensures that %z actually works. */
3233 DIAG_PUSH
3234 DIAG_DISABLE("-Wformat")
3235 		strftime( datestr, sizeof(datestr), "%d-%b-%Y %H:%M:%S %z", localtime( &data->date ) );
3236 DIAG_POP
3237 		imap_exec( ctx, &cmd->gen, imap_store_msg_p2,
3238 		           "APPEND \"%\\s\" %s\"%\\s\" ", buf, flagstr, datestr );
3239 	} else {
3240 		imap_exec( ctx, &cmd->gen, imap_store_msg_p2,
3241 		           "APPEND \"%\\s\" %s", buf, flagstr );
3242 	}
3243 	free( buf );
3244 }
3245 
3246 static void
imap_store_msg_p2(imap_store_t * ctx ATTR_UNUSED,imap_cmd_t * cmd,int response)3247 imap_store_msg_p2( imap_store_t *ctx ATTR_UNUSED, imap_cmd_t *cmd, int response )
3248 {
3249 	imap_cmd_out_uid_t *cmdp = (imap_cmd_out_uid_t *)cmd;
3250 
3251 	transform_msg_response( &response );
3252 	cmdp->callback( response, cmdp->param.uid, cmdp->callback_aux );
3253 }
3254 
3255 /******************* imap_find_new_msgs *******************/
3256 
3257 static void imap_find_new_msgs_p2( imap_store_t *, imap_cmd_t *, int );
3258 static void imap_find_new_msgs_p3( imap_store_t *, imap_cmd_t *, int );
3259 static void imap_find_new_msgs_p4( imap_store_t *, imap_cmd_t *, int );
3260 
3261 static void
imap_find_new_msgs(store_t * gctx,uint newuid,void (* cb)(int sts,message_t * msgs,void * aux),void * aux)3262 imap_find_new_msgs( store_t *gctx, uint newuid,
3263                     void (*cb)( int sts, message_t *msgs, void *aux ), void *aux )
3264 {
3265 	imap_store_t *ctx = (imap_store_t *)gctx;
3266 	imap_cmd_find_new_t *cmd;
3267 
3268 	INIT_IMAP_CMD(imap_cmd_find_new_t, cmd, cb, aux)
3269 	cmd->out_msgs = ctx->msgapp;
3270 	cmd->uid = newuid;
3271 	// Some servers fail to enumerate recently STOREd messages without syncing first.
3272 	imap_exec( (imap_store_t *)ctx, &cmd->gen, imap_find_new_msgs_p2, "CHECK" );
3273 }
3274 
3275 static void
imap_find_new_msgs_p2(imap_store_t * ctx,imap_cmd_t * gcmd,int response)3276 imap_find_new_msgs_p2( imap_store_t *ctx, imap_cmd_t *gcmd, int response )
3277 {
3278 	imap_cmd_find_new_t *cmdp = (imap_cmd_find_new_t *)gcmd;
3279 	imap_cmd_find_new_t *cmd;
3280 
3281 	if (response != RESP_OK) {
3282 		imap_done_simple_box( ctx, gcmd, response );
3283 		return;
3284 	}
3285 
3286 	// We appended messages, so we need to re-query UIDNEXT.
3287 	ctx->uidnext = 0;
3288 	assert( ctx->fetch_sts == FetchNone );
3289 	ctx->fetch_sts = FetchUidNext;
3290 
3291 	INIT_IMAP_CMD(imap_cmd_find_new_t, cmd, cmdp->callback, cmdp->callback_aux)
3292 	cmd->out_msgs = cmdp->out_msgs;
3293 	cmd->uid = cmdp->uid;
3294 	imap_exec( ctx, &cmd->gen, imap_find_new_msgs_p3,
3295 	           "UID FETCH * (UID)" );
3296 }
3297 
3298 static void
imap_find_new_msgs_p3(imap_store_t * ctx,imap_cmd_t * gcmd,int response)3299 imap_find_new_msgs_p3( imap_store_t *ctx, imap_cmd_t *gcmd, int response )
3300 {
3301 	imap_cmd_find_new_t *cmdp = (imap_cmd_find_new_t *)gcmd;
3302 	imap_cmd_find_new_t *cmd;
3303 
3304 	ctx->fetch_sts = FetchNone;
3305 	if (response != RESP_OK) {
3306 		imap_find_new_msgs_p4( ctx, gcmd, response );
3307 		return;
3308 	}
3309 	if (ctx->uidnext <= cmdp->uid) {
3310 		if (!ctx->uidnext && ctx->total_msgs) {
3311 			error( "IMAP error: re-querying server for highest UID failed\n" );
3312 			imap_find_new_msgs_p4( ctx, gcmd, RESP_NO );
3313 		} else {
3314 			// The messages evaporated, or the server just didn't register them -
3315 			// we'll catch that later (via lost TUIDs).
3316 			// This case is why we do the extra roundtrip instead of simply passing
3317 			// '*' as the end of the range below - IMAP ranges are unordered, so we
3318 			// would potentially re-fetch an already loaded message.
3319 			imap_find_new_msgs_p4( ctx, gcmd, RESP_OK );
3320 		}
3321 		return;
3322 	}
3323 	INIT_IMAP_CMD(imap_cmd_find_new_t, cmd, cmdp->callback, cmdp->callback_aux)
3324 	cmd->out_msgs = cmdp->out_msgs;
3325 	imap_exec( (imap_store_t *)ctx, &cmd->gen, imap_find_new_msgs_p4,
3326 	           "UID FETCH %u:%u (UID BODY.PEEK[HEADER.FIELDS (X-TUID)])", cmdp->uid, ctx->uidnext - 1 );
3327 }
3328 
3329 static void
imap_find_new_msgs_p4(imap_store_t * ctx ATTR_UNUSED,imap_cmd_t * gcmd,int response)3330 imap_find_new_msgs_p4( imap_store_t *ctx ATTR_UNUSED, imap_cmd_t *gcmd, int response )
3331 {
3332 	imap_cmd_find_new_t *cmdp = (imap_cmd_find_new_t *)gcmd;
3333 
3334 	transform_box_response( &response );
3335 	cmdp->callback( response, &(*cmdp->out_msgs)->gen, cmdp->callback_aux );
3336 }
3337 
3338 /******************* imap_list_store *******************/
3339 
3340 typedef union {
3341 	imap_cmd_refcounted_state_t gen;
3342 	struct {
3343 		IMAP_CMD_REFCOUNTED_STATE
3344 		void (*callback)( int sts, string_list_t *, void *aux );
3345 		void *callback_aux;
3346 	};
3347 } imap_list_store_state_t;
3348 
3349 static void imap_list_store_p2( imap_store_t *, imap_cmd_t *, int );
3350 static void imap_list_store_p3( imap_store_t *, imap_list_store_state_t * );
3351 
3352 static void
imap_list_store(store_t * gctx,int flags,void (* cb)(int sts,string_list_t * boxes,void * aux),void * aux)3353 imap_list_store( store_t *gctx, int flags,
3354                  void (*cb)( int sts, string_list_t *boxes, void *aux ), void *aux )
3355 {
3356 	imap_store_t *ctx = (imap_store_t *)gctx;
3357 	imap_store_conf_t *cfg = ctx->conf;
3358 	INIT_REFCOUNTED_STATE(imap_list_store_state_t, sts, cb, aux)
3359 
3360 	// ctx->prefix may be empty, "INBOX.", or something else.
3361 	// 'flags' may be LIST_INBOX, LIST_PATH (or LIST_PATH_MAYBE), or both. 'listed'
3362 	// already containing a particular value effectively removes it from 'flags'.
3363 	// This matrix determines what to query, and what comes out as a side effect.
3364 	// The lowercase letters indicate unnecessary results; the queries are done
3365 	// this way to have non-overlapping result sets, so subsequent calls create
3366 	// no duplicates:
3367 	//
3368 	// qry \ pfx | empty | inbox | other
3369 	// ----------+-------+-------+-------
3370 	// inbox     | p [I] | I [p] | I
3371 	// both      | P [I] | I [P] | I + P
3372 	// path      | P [i] | i [P] | P
3373 	//
3374 	int pfx_is_empty = !*ctx->prefix;
3375 	int pfx_is_inbox = !pfx_is_empty && is_inbox( ctx, ctx->prefix, -1 );
3376 	if (((flags & (LIST_PATH | LIST_PATH_MAYBE)) || pfx_is_empty) && !pfx_is_inbox && !(ctx->listed & LIST_PATH)) {
3377 		ctx->listed |= LIST_PATH;
3378 		if (pfx_is_empty)
3379 			ctx->listed |= LIST_INBOX;
3380 		imap_exec( ctx, imap_refcounted_new_cmd( &sts->gen ), imap_list_store_p2,
3381 		           "%s \"\" \"%\\s*\"", cfg->use_lsub ? "LSUB" : "LIST", ctx->prefix );
3382 	}
3383 	if (((flags & LIST_INBOX) || pfx_is_inbox) && !pfx_is_empty && !(ctx->listed & LIST_INBOX)) {
3384 		ctx->listed |= LIST_INBOX;
3385 		if (pfx_is_inbox)
3386 			ctx->listed |= LIST_PATH;
3387 		imap_exec( ctx, imap_refcounted_new_cmd( &sts->gen ), imap_list_store_p2,
3388 		           "%s \"\" INBOX*", cfg->use_lsub ? "LSUB" : "LIST" );
3389 	}
3390 	imap_list_store_p3( ctx, sts );
3391 }
3392 
3393 static void
imap_list_store_p2(imap_store_t * ctx,imap_cmd_t * cmd,int response)3394 imap_list_store_p2( imap_store_t *ctx, imap_cmd_t *cmd, int response )
3395 {
3396 	imap_list_store_state_t *sts = (imap_list_store_state_t *)((imap_cmd_refcounted_t *)cmd)->state;
3397 
3398 	transform_refcounted_box_response( &sts->gen, response );
3399 	imap_list_store_p3( ctx, sts );
3400 }
3401 
3402 static void
imap_list_store_p3(imap_store_t * ctx,imap_list_store_state_t * sts)3403 imap_list_store_p3( imap_store_t *ctx, imap_list_store_state_t *sts )
3404 {
3405 	DONE_REFCOUNTED_STATE_ARGS(sts, , ctx->boxes)
3406 }
3407 
3408 /******************* imap_cancel_cmds *******************/
3409 
3410 static void
imap_cancel_cmds(store_t * gctx,void (* cb)(void * aux),void * aux)3411 imap_cancel_cmds( store_t *gctx,
3412                   void (*cb)( void *aux ), void *aux )
3413 {
3414 	imap_store_t *ctx = (imap_store_t *)gctx;
3415 
3416 	finalize_checked_imap_cmds( ctx, RESP_CANCEL );
3417 	cancel_pending_imap_cmds( ctx );
3418 	if (ctx->in_progress) {
3419 		ctx->canceling = 1;
3420 		ctx->callbacks.imap_cancel = cb;
3421 		ctx->callback_aux = aux;
3422 	} else {
3423 		cb( aux );
3424 	}
3425 }
3426 
3427 /******************* imap_commit_cmds *******************/
3428 
3429 static void imap_commit_cmds_p2( imap_store_t *, imap_cmd_t *, int );
3430 
3431 static void
imap_commit_cmds(store_t * gctx)3432 imap_commit_cmds( store_t *gctx )
3433 {
3434 	imap_store_t *ctx = (imap_store_t *)gctx;
3435 
3436 	if (ctx->num_wait_check)
3437 		imap_exec( ctx, NULL, imap_commit_cmds_p2, "CHECK" );
3438 }
3439 
3440 static void
imap_commit_cmds_p2(imap_store_t * ctx,imap_cmd_t * cmd ATTR_UNUSED,int response)3441 imap_commit_cmds_p2( imap_store_t *ctx, imap_cmd_t *cmd ATTR_UNUSED, int response )
3442 {
3443 	finalize_checked_imap_cmds( ctx, response );
3444 }
3445 
3446 /******************* imap_get_memory_usage *******************/
3447 
3448 static uint
imap_get_memory_usage(store_t * gctx)3449 imap_get_memory_usage( store_t *gctx )
3450 {
3451 	imap_store_t *ctx = (imap_store_t *)gctx;
3452 
3453 	return ctx->buffer_mem + ctx->conn.buffer_mem;
3454 }
3455 
3456 /******************* imap_get_fail_state *******************/
3457 
3458 static int
imap_get_fail_state(store_conf_t * gconf)3459 imap_get_fail_state( store_conf_t *gconf )
3460 {
3461 	return ((imap_store_conf_t *)gconf)->server->failed;
3462 }
3463 
3464 /******************* imap_parse_store *******************/
3465 
3466 static imap_server_conf_t *servers, **serverapp = &servers;
3467 
3468 static int
imap_parse_store(conffile_t * cfg,store_conf_t ** storep)3469 imap_parse_store( conffile_t *cfg, store_conf_t **storep )
3470 {
3471 	imap_store_conf_t *store;
3472 	imap_server_conf_t *server, *srv, sserver;
3473 	const char *type, *name, *arg;
3474 	unsigned u;
3475 	int acc_opt = 0;
3476 #ifdef HAVE_LIBSSL
3477 	/* Legacy SSL options */
3478 	int require_ssl = -1, use_imaps = -1;
3479 	int use_tlsv1 = -1, use_tlsv11 = -1, use_tlsv12 = -1, use_tlsv13 = -1;
3480 #endif
3481 	/* Legacy SASL option */
3482 	int require_cram = -1;
3483 
3484 	if (!strcasecmp( "IMAPAccount", cfg->cmd )) {
3485 		server = nfcalloc( sizeof(*server) );
3486 		name = server->name = nfstrdup( cfg->val );
3487 		*serverapp = server;
3488 		serverapp = &server->next;
3489 		store = NULL;
3490 		*storep = NULL;
3491 		type = "IMAP account";
3492 	} else if (!strcasecmp( "IMAPStore", cfg->cmd )) {
3493 		store = nfcalloc( sizeof(*store) );
3494 		store->driver = &imap_driver;
3495 		name = store->name = nfstrdup( cfg->val );
3496 		store->use_namespace = 1;
3497 		*storep = &store->gen;
3498 		memset( &sserver, 0, sizeof(sserver) );
3499 		server = &sserver;
3500 		type = "IMAP store";
3501 	} else
3502 		return 0;
3503 
3504 	server->sconf.timeout = 20;
3505 #ifdef HAVE_LIBSSL
3506 	server->ssl_type = -1;
3507 	server->sconf.ssl_versions = -1;
3508 	server->sconf.system_certs = 1;
3509 #endif
3510 	server->max_in_progress = INT_MAX;
3511 
3512 	while (getcline( cfg ) && cfg->cmd) {
3513 		if (!strcasecmp( "Host", cfg->cmd )) {
3514 			/* The imap[s]: syntax is just a backwards compat hack. */
3515 			arg = cfg->val;
3516 #ifdef HAVE_LIBSSL
3517 			if (starts_with( arg, -1, "imaps:", 6 )) {
3518 				arg += 6;
3519 				server->ssl_type = SSL_IMAPS;
3520 				if (server->sconf.ssl_versions == -1)
3521 					server->sconf.ssl_versions = TLSv1 | TLSv1_1 | TLSv1_2 | TLSv1_3;
3522 			} else
3523 #endif
3524 			if (starts_with( arg, -1, "imap:", 5 ))
3525 				arg += 5;
3526 			if (starts_with( arg, -1, "//", 2 ))
3527 				arg += 2;
3528 			if (arg != cfg->val)
3529 				warn( "%s:%d: Notice: URL notation is deprecated; use a plain host name and possibly 'SSLType IMAPS' instead\n", cfg->file, cfg->line );
3530 			server->sconf.host = nfstrdup( arg );
3531 		}
3532 		else if (!strcasecmp( "User", cfg->cmd ))
3533 			server->user = nfstrdup( cfg->val );
3534 		else if (!strcasecmp( "UserCmd", cfg->cmd ))
3535 			server->user_cmd = nfstrdup( cfg->val );
3536 		else if (!strcasecmp( "Pass", cfg->cmd ))
3537 			server->pass = nfstrdup( cfg->val );
3538 		else if (!strcasecmp( "PassCmd", cfg->cmd ))
3539 			server->pass_cmd = nfstrdup( cfg->val );
3540 #ifdef HAVE_MACOS_KEYCHAIN
3541 		else if (!strcasecmp( "UseKeychain", cfg->cmd ))
3542 			server->use_keychain = parse_bool( cfg );
3543 #endif
3544 		else if (!strcasecmp( "Port", cfg->cmd )) {
3545 			int port = parse_int( cfg );
3546 			if ((unsigned)port > 0xffff) {
3547 				error( "%s:%d: Invalid port number\n", cfg->file, cfg->line );
3548 				cfg->err = 1;
3549 			} else {
3550 				server->sconf.port = (ushort)port;
3551 			}
3552 		} else if (!strcasecmp( "Timeout", cfg->cmd ))
3553 			server->sconf.timeout = parse_int( cfg );
3554 		else if (!strcasecmp( "PipelineDepth", cfg->cmd )) {
3555 			if ((server->max_in_progress = parse_int( cfg )) < 1) {
3556 				error( "%s:%d: PipelineDepth must be at least 1\n", cfg->file, cfg->line );
3557 				cfg->err = 1;
3558 			}
3559 		} else if (!strcasecmp( "DisableExtension", cfg->cmd ) ||
3560 		           !strcasecmp( "DisableExtensions", cfg->cmd )) {
3561 			arg = cfg->val;
3562 			do {
3563 				for (u = 0; u < as(cap_list); u++) {
3564 					if (!strcasecmp( cap_list[u], arg )) {
3565 						server->cap_mask |= 1 << u;
3566 						goto gotcap;
3567 					}
3568 				}
3569 				error( "%s:%d: Unrecognized IMAP extension '%s'\n", cfg->file, cfg->line, arg );
3570 				cfg->err = 1;
3571 			  gotcap: ;
3572 			} while ((arg = get_arg( cfg, ARG_OPTIONAL, NULL )));
3573 		}
3574 #ifdef HAVE_LIBSSL
3575 		else if (!strcasecmp( "CertificateFile", cfg->cmd )) {
3576 			server->sconf.cert_file = expand_strdup( cfg->val );
3577 			if (access( server->sconf.cert_file, R_OK )) {
3578 				sys_error( "%s:%d: CertificateFile '%s'",
3579 				           cfg->file, cfg->line, server->sconf.cert_file );
3580 				cfg->err = 1;
3581 			}
3582 		} else if (!strcasecmp( "SystemCertificates", cfg->cmd )) {
3583 			server->sconf.system_certs = parse_bool( cfg );
3584 		} else if (!strcasecmp( "ClientCertificate", cfg->cmd )) {
3585 			server->sconf.client_certfile = expand_strdup( cfg->val );
3586 			if (access( server->sconf.client_certfile, R_OK )) {
3587 				sys_error( "%s:%d: ClientCertificate '%s'",
3588 				           cfg->file, cfg->line, server->sconf.client_certfile );
3589 				cfg->err = 1;
3590 			}
3591 		} else if (!strcasecmp( "ClientKey", cfg->cmd )) {
3592 			server->sconf.client_keyfile = expand_strdup( cfg->val );
3593 			if (access( server->sconf.client_keyfile, R_OK )) {
3594 				sys_error( "%s:%d: ClientKey '%s'",
3595 				           cfg->file, cfg->line, server->sconf.client_keyfile );
3596 				cfg->err = 1;
3597 			}
3598 		} else if (!strcasecmp( "CipherString", cfg->cmd )) {
3599 			server->sconf.cipher_string = nfstrdup( cfg->val );
3600 		} else if (!strcasecmp( "SSLType", cfg->cmd )) {
3601 			if (!strcasecmp( "None", cfg->val )) {
3602 				server->ssl_type = SSL_None;
3603 			} else if (!strcasecmp( "STARTTLS", cfg->val )) {
3604 				server->ssl_type = SSL_STARTTLS;
3605 			} else if (!strcasecmp( "IMAPS", cfg->val )) {
3606 				server->ssl_type = SSL_IMAPS;
3607 			} else {
3608 				error( "%s:%d: Invalid SSL type\n", cfg->file, cfg->line );
3609 				cfg->err = 1;
3610 			}
3611 		} else if (!strcasecmp( "SSLVersion", cfg->cmd ) ||
3612 		           !strcasecmp( "SSLVersions", cfg->cmd )) {
3613 			server->sconf.ssl_versions = 0;
3614 			arg = cfg->val;
3615 			do {
3616 				if (!strcasecmp( "SSLv2", arg )) {
3617 					warn( "Warning: SSLVersion SSLv2 is no longer supported\n" );
3618 				} else if (!strcasecmp( "SSLv3", arg )) {
3619 					warn( "Warning: SSLVersion SSLv3 is no longer supported\n" );
3620 				} else if (!strcasecmp( "TLSv1", arg )) {
3621 					server->sconf.ssl_versions |= TLSv1;
3622 				} else if (!strcasecmp( "TLSv1.1", arg )) {
3623 					server->sconf.ssl_versions |= TLSv1_1;
3624 				} else if (!strcasecmp( "TLSv1.2", arg )) {
3625 					server->sconf.ssl_versions |= TLSv1_2;
3626 				} else if (!strcasecmp( "TLSv1.3", arg )) {
3627 					server->sconf.ssl_versions |= TLSv1_3;
3628 				} else {
3629 					error( "%s:%d: Unrecognized SSL version\n", cfg->file, cfg->line );
3630 					cfg->err = 1;
3631 				}
3632 			} while ((arg = get_arg( cfg, ARG_OPTIONAL, NULL )));
3633 		} else if (!strcasecmp( "RequireSSL", cfg->cmd ))
3634 			require_ssl = parse_bool( cfg );
3635 		else if (!strcasecmp( "UseIMAPS", cfg->cmd ))
3636 			use_imaps = parse_bool( cfg );
3637 		else if (!strcasecmp( "UseSSLv2", cfg->cmd ))
3638 			warn( "Warning: UseSSLv2 is no longer supported\n" );
3639 		else if (!strcasecmp( "UseSSLv3", cfg->cmd ))
3640 			warn( "Warning: UseSSLv3 is no longer supported\n" );
3641 		else if (!strcasecmp( "UseTLSv1", cfg->cmd ))
3642 			use_tlsv1 = parse_bool( cfg );
3643 		else if (!strcasecmp( "UseTLSv1.1", cfg->cmd ))
3644 			use_tlsv11 = parse_bool( cfg );
3645 		else if (!strcasecmp( "UseTLSv1.2", cfg->cmd ))
3646 			use_tlsv12 = parse_bool( cfg );
3647 		else if (!strcasecmp( "UseTLSv1.3", cfg->cmd ))
3648 			use_tlsv13 = parse_bool( cfg );
3649 #endif
3650 		else if (!strcasecmp( "AuthMech", cfg->cmd ) ||
3651 		         !strcasecmp( "AuthMechs", cfg->cmd )) {
3652 			arg = cfg->val;
3653 			do
3654 				add_string_list( &server->auth_mechs, arg );
3655 			while ((arg = get_arg( cfg, ARG_OPTIONAL, NULL )));
3656 		} else if (!strcasecmp( "RequireCRAM", cfg->cmd ))
3657 			require_cram = parse_bool( cfg );
3658 		else if (!strcasecmp( "Tunnel", cfg->cmd ))
3659 			server->sconf.tunnel = nfstrdup( cfg->val );
3660 		else if (store) {
3661 			if (!strcasecmp( "Account", cfg->cmd )) {
3662 				for (srv = servers; srv; srv = srv->next)
3663 					if (srv->name && !strcmp( srv->name, cfg->val ))
3664 						goto gotsrv;
3665 				error( "%s:%d: unknown IMAP account '%s'\n", cfg->file, cfg->line, cfg->val );
3666 				cfg->err = 1;
3667 				continue;
3668 			  gotsrv:
3669 				store->server = srv;
3670 			} else if (!strcasecmp( "UseNamespace", cfg->cmd ))
3671 				store->use_namespace = parse_bool( cfg );
3672 			else if (!strcasecmp( "SubscribedOnly", cfg->cmd ))
3673 				store->use_lsub = parse_bool( cfg );
3674 			else if (!strcasecmp( "Path", cfg->cmd ))
3675 				store->path = nfstrdup( cfg->val );
3676 			else if (!strcasecmp( "PathDelimiter", cfg->cmd )) {
3677 				if (strlen( cfg->val ) != 1) {
3678 					error( "%s:%d: Path delimiter must be exactly one character long\n", cfg->file, cfg->line );
3679 					cfg->err = 1;
3680 					continue;
3681 				}
3682 				store->delimiter = cfg->val[0];
3683 			} else
3684 				parse_generic_store( &store->gen, cfg, "IMAPStore" );
3685 			continue;
3686 		} else {
3687 			error( "%s:%d: keyword '%s' is not recognized in IMAPAccount sections\n",
3688 			       cfg->file, cfg->line, cfg->cmd );
3689 			cfg->err = 1;
3690 			continue;
3691 		}
3692 		acc_opt = 1;
3693 	}
3694 	if (!store || !store->server) {
3695 		if (!server->sconf.tunnel && !server->sconf.host) {
3696 			error( "%s '%s' has neither Tunnel nor Host\n", type, name );
3697 			cfg->err = 1;
3698 			return 1;
3699 		}
3700 		if (server->user && server->user_cmd) {
3701 			error( "%s '%s' has both User and UserCmd\n", type, name );
3702 			cfg->err = 1;
3703 			return 1;
3704 		}
3705 		if (server->pass && server->pass_cmd) {
3706 			error( "%s '%s' has both Pass and PassCmd\n", type, name );
3707 			cfg->err = 1;
3708 			return 1;
3709 		}
3710 #ifdef HAVE_MACOS_KEYCHAIN
3711 		if (server->use_keychain && (server->pass || server->pass_cmd)) {
3712 			error( "%s '%s' has UseKeychain enabled despite specifying Pass/PassCmd\n", type, name );
3713 			cfg->err = 1;
3714 			return 1;
3715 		}
3716 #endif
3717 #ifdef HAVE_LIBSSL
3718 		if ((use_tlsv1 & use_tlsv11 & use_tlsv12 & use_tlsv13) != -1 || use_imaps >= 0 || require_ssl >= 0) {
3719 			if (server->ssl_type >= 0 || server->sconf.ssl_versions >= 0) {
3720 				error( "%s '%s': The deprecated UseSSL*, UseTLS*, UseIMAPS, and RequireSSL options are mutually exclusive with SSLType and SSLVersions.\n", type, name );
3721 				cfg->err = 1;
3722 				return 1;
3723 			}
3724 			warn( "Notice: %s '%s': UseSSL*, UseTLS*, UseIMAPS, and RequireSSL are deprecated. Use SSLType and SSLVersions instead.\n", type, name );
3725 			server->sconf.ssl_versions =
3726 					(use_tlsv1 == 0 ? 0 : TLSv1) |
3727 					(use_tlsv11 != 1 ? 0 : TLSv1_1) |
3728 					(use_tlsv12 != 1 ? 0 : TLSv1_2) |
3729 					(use_tlsv13 != 1 ? 0 : TLSv1_3);
3730 			if (use_imaps == 1) {
3731 				server->ssl_type = SSL_IMAPS;
3732 			} else if (require_ssl) {
3733 				server->ssl_type = SSL_STARTTLS;
3734 			} else if (!server->sconf.ssl_versions) {
3735 				server->ssl_type = SSL_None;
3736 			} else {
3737 				warn( "Notice: %s '%s': 'RequireSSL no' is being ignored\n", type, name );
3738 				server->ssl_type = SSL_STARTTLS;
3739 			}
3740 			if (server->ssl_type != SSL_None && !server->sconf.ssl_versions) {
3741 				error( "%s '%s' requires SSL but no SSL versions enabled\n", type, name );
3742 				cfg->err = 1;
3743 				return 1;
3744 			}
3745 		} else {
3746 			if (server->sconf.ssl_versions < 0)
3747 				server->sconf.ssl_versions = TLSv1 | TLSv1_1 | TLSv1_2 | TLSv1_3;
3748 			if (server->ssl_type < 0)
3749 				server->ssl_type = server->sconf.tunnel ? SSL_None : SSL_STARTTLS;
3750 		}
3751 #endif
3752 		if (require_cram >= 0) {
3753 			if (server->auth_mechs) {
3754 				error( "%s '%s': The deprecated RequireCRAM option is mutually exclusive with AuthMech.\n", type, name );
3755 				cfg->err = 1;
3756 				return 1;
3757 			}
3758 			warn( "Notice: %s '%s': RequireCRAM is deprecated. Use AuthMech instead.\n", type, name );
3759 			if (require_cram)
3760 				add_string_list(&server->auth_mechs, "CRAM-MD5");
3761 		}
3762 		if (!server->auth_mechs)
3763 			add_string_list( &server->auth_mechs, "*" );
3764 		if (!server->sconf.port)
3765 			server->sconf.port =
3766 #ifdef HAVE_LIBSSL
3767 				server->ssl_type == SSL_IMAPS ? 993 :
3768 #endif
3769 				143;
3770 	}
3771 	if (store) {
3772 		if (!store->server) {
3773 			store->server = nfmalloc( sizeof(sserver) );
3774 			memcpy( store->server, &sserver, sizeof(sserver) );
3775 			store->server->name = store->name;
3776 		} else if (acc_opt) {
3777 			error( "%s '%s' has both Account and account-specific options\n", type, name );
3778 			cfg->err = 1;
3779 		}
3780 	}
3781 	return 1;
3782 }
3783 
3784 static uint
imap_get_caps(store_t * gctx ATTR_UNUSED)3785 imap_get_caps( store_t *gctx ATTR_UNUSED )
3786 {
3787 	return DRV_CRLF | DRV_VERBOSE | DRV_ASYNC;
3788 }
3789 
3790 struct driver imap_driver = {
3791 	imap_get_caps,
3792 	imap_parse_store,
3793 	imap_cleanup,
3794 	imap_alloc_store,
3795 	imap_set_bad_callback,
3796 	imap_connect_store,
3797 	imap_free_store,
3798 	imap_cancel_store,
3799 	imap_list_store,
3800 	imap_select_box,
3801 	imap_get_box_path,
3802 	imap_create_box,
3803 	imap_open_box,
3804 	imap_get_uidnext,
3805 	imap_get_supported_flags,
3806 	imap_confirm_box_empty,
3807 	imap_delete_box,
3808 	imap_finish_delete_box,
3809 	imap_prepare_load_box,
3810 	imap_load_box,
3811 	imap_fetch_msg,
3812 	imap_store_msg,
3813 	imap_find_new_msgs,
3814 	imap_set_msg_flags,
3815 	imap_trash_msg,
3816 	imap_close_box,
3817 	imap_cancel_cmds,
3818 	imap_commit_cmds,
3819 	imap_get_memory_usage,
3820 	imap_get_fail_state,
3821 };
3822