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