fix stupid typo
[apps/madmutt.git] / imap / imap.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-8 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 1996-9 Brandon Long <blong@fiction.net>
5  * Copyright (C) 1999-2005 Brendan Cully <brendan@kublai.com>
6  *
7  * This file is part of mutt-ng, see http://www.muttng.org/.
8  * It's licensed under the GNU General Public License,
9  * please see the file GPL in the top level source directory.
10  */
11
12 /* Support for IMAP4rev1, with the occasional nod to IMAP 4. */
13
14 #include <lib-lib/lib-lib.h>
15 #include <lib-mx/mx.h>
16
17 #include "mutt.h"
18 #include "globals.h"
19 #include "pattern.h"
20 #include "sort.h"
21 #include "browser.h"
22 #include "message.h"
23 #include "imap_private.h"
24 #include "buffy.h"
25
26 /* imap forward declarations */
27 static int imap_get_delim (IMAP_DATA * idata);
28 static char *imap_get_flags (string_list_t ** hflags, char *s);
29 static int imap_check_acl (IMAP_DATA * idata);
30 static int imap_check_capabilities (IMAP_DATA * idata);
31 static void imap_set_flag (IMAP_DATA * idata, int aclbit, int flag,
32                            const char *str, char *flags, size_t flsize);
33
34 /* imap_access: Check permissions on an IMAP mailbox.
35  * TODO: ACL checks. Right now we assume if it exists we can
36  * mess with it. */
37 static int imap_access (const char *path, int flags __attribute__ ((unused)))
38 {
39   IMAP_DATA *idata;
40   IMAP_MBOX mx;
41   char buf[LONG_STRING];
42   char mailbox[LONG_STRING];
43   char mbox[LONG_STRING];
44
45   if (imap_parse_path (path, &mx))
46     return -1;
47
48   if (!(idata = imap_conn_find (&mx.account,
49                                 option (OPTIMAPPASSIVE) ? M_IMAP_CONN_NONEW :
50                                 0))) {
51     p_delete(&mx.mbox);
52     return -1;
53   }
54
55   imap_fix_path (idata, mx.mbox, mailbox, sizeof (mailbox));
56
57   /* we may already be in the folder we're checking */
58   if (!m_strcmp(idata->mailbox, mx.mbox)) {
59     p_delete(&mx.mbox);
60     return 0;
61   }
62
63   p_delete(&mx.mbox);
64   imap_munge_mbox_name (mbox, sizeof (mbox), mailbox);
65
66   if (mutt_bit_isset (idata->capabilities, IMAP4REV1))
67     snprintf (buf, sizeof (buf), "STATUS %s (UIDVALIDITY)", mbox);
68   else if (mutt_bit_isset (idata->capabilities, STATUS))
69     snprintf (buf, sizeof (buf), "STATUS %s (UID-VALIDITY)", mbox);
70   else {
71     return -1;
72   }
73
74   if (imap_exec (idata, buf, IMAP_CMD_FAIL_OK) < 0) {
75     return -1;
76   }
77
78   return 0;
79 }
80
81 int imap_create_mailbox (IMAP_DATA * idata, char *mailbox)
82 {
83   char buf[LONG_STRING], mbox[LONG_STRING];
84
85   imap_munge_mbox_name (mbox, sizeof (mbox), mailbox);
86   snprintf (buf, sizeof (buf), "CREATE %s", mbox);
87
88   if (imap_exec (idata, buf, 0) != 0)
89     return -1;
90
91   return 0;
92 }
93
94 int imap_rename_mailbox (IMAP_DATA * idata, IMAP_MBOX * mx,
95                          const char *newname)
96 {
97   char oldmbox[LONG_STRING];
98   char newmbox[LONG_STRING];
99   char buf[LONG_STRING];
100
101   imap_munge_mbox_name (oldmbox, sizeof (oldmbox), mx->mbox);
102   imap_munge_mbox_name (newmbox, sizeof (newmbox), newname);
103
104   snprintf (buf, sizeof (buf), "RENAME %s %s", oldmbox, newmbox);
105
106   if (imap_exec (idata, buf, 0) != 0)
107     return -1;
108
109   return 0;
110 }
111
112 int imap_delete_mailbox (CONTEXT * ctx, IMAP_MBOX mx)
113 {
114   char buf[LONG_STRING], mbox[LONG_STRING];
115   IMAP_DATA *idata;
116
117   if (!ctx || !ctx->data) {
118     if (!(idata = imap_conn_find (&mx.account,
119                                   option (OPTIMAPPASSIVE) ? M_IMAP_CONN_NONEW
120                                   : 0))) {
121       p_delete(&mx.mbox);
122       return -1;
123     }
124   }
125   else {
126     idata = ctx->data;
127   }
128
129   imap_munge_mbox_name (mbox, sizeof (mbox), mx.mbox);
130   snprintf (buf, sizeof (buf), "DELETE %s", mbox);
131
132   if (imap_exec ((IMAP_DATA *) idata, buf, 0) != 0)
133     return -1;
134
135   return 0;
136 }
137
138 /* imap_logout_all: close all open connections. Quick and dirty until we can
139  *   make sure we've got all the context we need. */
140 void imap_logout_all (void)
141 {
142   CONNECTION *conn;
143   CONNECTION *tmp;
144
145   conn = mutt_socket_head ();
146
147   while (conn) {
148     tmp = conn->next;
149
150     if (conn->account.type == M_ACCT_TYPE_IMAP && conn->fd >= 0) {
151       mutt_message (_("Closing connection to %s..."), conn->account.host);
152       imap_logout ((IMAP_DATA *) conn->data);
153       mutt_clear_error ();
154       mutt_socket_close (conn);
155       mutt_socket_free (conn);
156     }
157
158     conn = tmp;
159   }
160 }
161
162 /* imap_read_literal: read bytes bytes from server into file. Not explicitly
163  *   buffered, relies on FILE buffering. NOTE: strips \r from \r\n.
164  *   Apparently even literals use \r\n-terminated strings ?! */
165 int imap_read_literal (FILE * fp, IMAP_DATA * idata, long bytes, progress_t* bar)
166 {
167   long pos;
168   char c;
169
170   int r = 0;
171
172   for (pos = 0; pos < bytes; pos++) {
173     if (mutt_socket_readchar (idata->conn, &c) != 1) {
174       idata->status = IMAP_FATAL;
175
176       return -1;
177     }
178
179     if (r == 1 && c != '\n')
180       fputc ('\r', fp);
181
182     if (c == '\r') {
183       r = 1;
184       continue;
185     }
186     else
187       r = 0;
188
189     fputc (c, fp);
190     if (bar && !(pos % 1024))
191       mutt_progress_bar (bar, pos);
192   }
193
194   return 0;
195 }
196
197 /* imap_expunge_mailbox: Purge IMAP portion of expunged messages from the
198  *   context. Must not be done while something has a handle on any headers
199  *   (eg inside pager or editor). That is, check IMAP_REOPEN_ALLOW. */
200 void imap_expunge_mailbox (IMAP_DATA * idata)
201 {
202   HEADER *h;
203   int i, cacheno;
204
205   for (i = 0; i < idata->ctx->msgcount; i++) {
206     h = idata->ctx->hdrs[i];
207
208     if (h->index == -1) {
209       h->active = 0;
210
211       /* free cached body from disk, if neccessary */
212       cacheno = HEADER_DATA (h)->uid % IMAP_CACHE_LEN;
213       if (idata->cache[cacheno].uid == HEADER_DATA (h)->uid &&
214           idata->cache[cacheno].path) {
215         unlink (idata->cache[cacheno].path);
216         p_delete(&idata->cache[cacheno].path);
217       }
218
219       imap_free_header_data (&h->data);
220     }
221   }
222
223   /* We may be called on to expunge at any time. We can't rely on the caller
224    * to always know to rethread */
225   mx_update_tables (idata->ctx, 0);
226   mutt_sort_headers (idata->ctx, 1);
227 }
228
229 static int imap_get_delim (IMAP_DATA * idata)
230 {
231   char *s;
232   int rc;
233
234   /* assume that the delim is /.  If this fails, we're in bigger trouble
235    * than getting the delim wrong */
236   idata->delim = '/';
237
238   imap_cmd_start (idata, "LIST \"\" \"\"");
239
240   do {
241     if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
242       break;
243
244     s = imap_next_word (idata->cmd.buf);
245     if (ascii_strncasecmp ("LIST", s, 4) == 0) {
246       s = imap_next_word (s);
247       s = imap_next_word (s);
248       if (s && s[0] == '\"' && s[1] && s[2] == '\"')
249         idata->delim = s[1];
250       else if (s && s[0] == '\"' && s[1] && s[1] == '\\' && s[2]
251                && s[3] == '\"')
252         idata->delim = s[2];
253     }
254   }
255   while (rc == IMAP_CMD_CONTINUE);
256
257   return -1;
258 }
259
260 /* get rights for folder, let imap_handle_untagged do the rest */
261 static int imap_check_acl (IMAP_DATA * idata)
262 {
263   char buf[LONG_STRING];
264   char mbox[LONG_STRING];
265
266   imap_munge_mbox_name (mbox, sizeof (mbox), idata->mailbox);
267   snprintf (buf, sizeof (buf), "MYRIGHTS %s", mbox);
268   if (imap_exec (idata, buf, 0) != 0) {
269     imap_error ("imap_check_acl", buf);
270     return -1;
271   }
272   return 0;
273 }
274
275 /* imap_check_capabilities: make sure we can log in to this server. */
276 static int imap_check_capabilities (IMAP_DATA * idata)
277 {
278   if (imap_exec (idata, "CAPABILITY", 0) != 0) {
279     imap_error ("imap_check_capabilities", idata->cmd.buf);
280     return -1;
281   }
282
283   if (!(mutt_bit_isset (idata->capabilities, IMAP4)
284         || mutt_bit_isset (idata->capabilities, IMAP4REV1))) {
285     mutt_error _("This IMAP server is ancient. Mutt does not work with it.");
286
287     mutt_sleep (2);             /* pause a moment to let the user see the error */
288
289     return -1;
290   }
291
292   return 0;
293 }
294
295 /* imap_conn_find: Find an open IMAP connection matching account, or open
296  *   a new one if none can be found. */
297 IMAP_DATA *imap_conn_find (const ACCOUNT * account, int flags)
298 {
299   CONNECTION *conn;
300   IMAP_DATA *idata;
301   ACCOUNT *creds;
302   int new = 0;
303
304   if (!(conn = mutt_conn_find (NULL, account)))
305     return NULL;
306
307   /* if opening a new UNSELECTED connection, preserve existing creds */
308   creds = &(conn->account);
309
310   /* make sure this connection is not in SELECTED state, if neccessary */
311   if (flags & M_IMAP_CONN_NOSELECT)
312     while (conn->data && ((IMAP_DATA *) conn->data)->state == IMAP_SELECTED) {
313       if (!(conn = mutt_conn_find (conn, account)))
314         return NULL;
315       memcpy (&(conn->account), creds, sizeof (ACCOUNT));
316     }
317
318   idata = (IMAP_DATA *) conn->data;
319
320   /* don't open a new connection if one isn't wanted */
321   if (flags & M_IMAP_CONN_NONEW) {
322     if (!idata) {
323       mutt_socket_free (conn);
324       return NULL;
325     }
326     if (idata->state < IMAP_AUTHENTICATED)
327       return NULL;
328   }
329
330   if (!idata) {
331     /* The current connection is a new connection */
332     if (!(idata = imap_new_idata ())) {
333       mutt_socket_free (conn);
334       return NULL;
335     }
336
337     conn->data = idata;
338     idata->conn = conn;
339     new = 1;
340   }
341
342   if (idata->state == IMAP_DISCONNECTED)
343     imap_open_connection (idata);
344   if (idata->state == IMAP_CONNECTED) {
345     if (!imap_authenticate (idata)) {
346       idata->state = IMAP_AUTHENTICATED;
347     } else {
348       mutt_account_unsetpass (&idata->conn->account);
349     }
350
351     p_delete(&idata->capstr);
352   }
353   if (new && idata->state == IMAP_AUTHENTICATED) {
354     imap_get_delim (idata);
355     if (option (OPTIMAPCHECKSUBSCRIBED)) {
356       mutt_message _("Checking mailbox subscriptions");
357       imap_exec (idata, "LSUB \"\" \"*\"", 0);
358     }
359   }
360
361   return idata;
362 }
363
364 int imap_open_connection (IMAP_DATA * idata)
365 {
366   char buf[LONG_STRING];
367
368   if (mutt_socket_open (idata->conn) < 0)
369     return -1;
370
371   idata->state = IMAP_CONNECTED;
372
373   if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE) {
374     mutt_socket_close (idata->conn);
375     idata->state = IMAP_DISCONNECTED;
376     return -1;
377   }
378
379   if (ascii_strncasecmp ("* OK", idata->cmd.buf, 4) == 0) {
380     /* TODO: Parse new tagged CAPABILITY data (* OK [CAPABILITY...]) */
381     if (imap_check_capabilities (idata))
382       goto bail;
383     /* Attempt STARTTLS if available and desired. */
384     if (!idata->conn->ssf && (mod_ssl.force_tls ||
385                               mutt_bit_isset (idata->capabilities, STARTTLS))) {
386       int rc;
387
388       if (mod_ssl.force_tls)
389         rc = M_YES;
390       else if (mod_ssl.starttls) {
391         if ((rc = imap_exec (idata, "STARTTLS", IMAP_CMD_FAIL_OK)) == -1)
392           goto bail;
393         if (rc != -2) {
394           if (mutt_ssl_starttls (idata->conn))
395           {
396             mutt_error (_("Could not negotiate TLS connection"));
397             mutt_sleep (1);
398             goto err_close_conn;
399           }
400           else {
401             /* RFC 2595 demands we recheck CAPABILITY after TLS completes. */
402             if (imap_exec (idata, "CAPABILITY", 0))
403               goto bail;
404           }
405         }
406       }
407     }
408
409     if (mod_ssl.force_tls && ! idata->conn->ssf) {
410       mutt_error _("Encrypted connection unavailable");
411       mutt_sleep (1);
412       goto err_close_conn;
413     }
414   }
415   else if (ascii_strncasecmp ("* PREAUTH", idata->cmd.buf, 9) == 0) {
416     idata->state = IMAP_AUTHENTICATED;
417     if (imap_check_capabilities (idata) != 0)
418       goto bail;
419     p_delete(&idata->capstr);
420   }
421   else {
422     imap_error ("imap_open_connection()", buf);
423     goto bail;
424   }
425
426   return 0;
427
428 err_close_conn:
429   mutt_socket_close (idata->conn);
430   idata->state = IMAP_DISCONNECTED;
431 bail:
432   p_delete(&idata->capstr);
433   return -1;
434 }
435
436 /* imap_get_flags: Make a simple list out of a FLAGS response.
437  *   return stream following FLAGS response */
438 static char *imap_get_flags (string_list_t ** hflags, char *s)
439 {
440   string_list_t *flags;
441   char *flag_word;
442   char ctmp;
443
444   /* sanity-check string */
445   if (ascii_strncasecmp ("FLAGS", s, 5) != 0) {
446     return NULL;
447   }
448   s = vskipspaces(s + 5);
449   if (*s != '(') {
450     return NULL;
451   }
452
453   /* create list, update caller's flags handle */
454   flags = string_item_new();
455   *hflags = flags;
456
457   while (*s && *s != ')') {
458     s = vskipspaces(s + 1);
459     flag_word = s;
460     while (*s && (*s != ')') && !ISSPACE (*s))
461       s++;
462     ctmp = *s;
463     *s = '\0';
464     if (*flag_word)
465       mutt_add_list (flags, flag_word);
466     *s = ctmp;
467   }
468
469   /* note bad flags response */
470   if (*s != ')') {
471     string_list_wipe(hflags);
472
473     return NULL;
474   }
475
476   s++;
477
478   return s;
479 }
480
481 static int imap_open_mailbox (CONTEXT * ctx)
482 {
483   CONNECTION *conn;
484   IMAP_DATA *idata;
485   char buf[LONG_STRING];
486   char bufout[LONG_STRING];
487   int count = 0;
488   IMAP_MBOX mx;
489   int rc;
490
491   if (imap_parse_path (ctx->path, &mx)) {
492     mutt_error (_("%s is an invalid IMAP path"), ctx->path);
493     return -1;
494   }
495
496   /* we require a connection which isn't currently in IMAP_SELECTED state */
497   if (!(idata = imap_conn_find (&(mx.account), M_IMAP_CONN_NOSELECT)))
498     goto fail_noidata;
499   if (idata->state < IMAP_AUTHENTICATED)
500     goto fail;
501
502   conn = idata->conn;
503
504   /* once again the context is new */
505   ctx->data = idata;
506
507   /* Clean up path and replace the one in the ctx */
508   imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
509   p_delete(&(idata->mailbox));
510   idata->mailbox = m_strdup(buf);
511   imap_qualify_path (buf, sizeof (buf), &mx, idata->mailbox);
512
513   p_delete(&(ctx->path));
514   ctx->path = m_strdup(buf);
515
516   idata->ctx = ctx;
517
518   /* clear mailbox status */
519   idata->status = 0;
520   memset (idata->rights, 0, (RIGHTSMAX + 7) / 8);
521   idata->newMailCount = 0;
522
523   mutt_message (_("Selecting %s..."), idata->mailbox);
524   imap_munge_mbox_name (buf, sizeof (buf), idata->mailbox);
525   snprintf (bufout, sizeof (bufout), "%s %s",
526             ctx->readonly ? "EXAMINE" : "SELECT", buf);
527
528   idata->state = IMAP_SELECTED;
529
530   imap_cmd_start (idata, bufout);
531
532   do {
533     char *pc;
534
535     if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
536       break;
537
538     pc = idata->cmd.buf + 2;
539
540     /* Obtain list of available flags here, may be overridden by a
541      * PERMANENTFLAGS tag in the OK response */
542     if (ascii_strncasecmp ("FLAGS", pc, 5) == 0) {
543       /* don't override PERMANENTFLAGS */
544       if (!idata->flags) {
545         if ((pc = imap_get_flags (&(idata->flags), pc)) == NULL)
546           goto fail;
547       }
548     }
549     /* PERMANENTFLAGS are massaged to look like FLAGS, then override FLAGS */
550     else if (ascii_strncasecmp ("OK [PERMANENTFLAGS", pc, 18) == 0) {
551       /* safe to call on NULL */
552       string_list_wipe(&(idata->flags));
553       /* skip "OK [PERMANENT" so syntax is the same as FLAGS */
554       pc += 13;
555       if ((pc = imap_get_flags (&(idata->flags), pc)) == NULL)
556         goto fail;
557     }
558 #ifdef USE_HCACHE
559     /* save UIDVALIDITY for the header cache */
560     else if (ascii_strncasecmp ("OK [UIDVALIDITY", pc, 14) == 0) {
561       pc += 3;
562       pc = imap_next_word (pc);
563
564       sscanf (pc, "%lu", &(idata->uid_validity));
565     }
566 #endif
567     else {
568       pc = imap_next_word (pc);
569       if (!ascii_strncasecmp ("EXISTS", pc, 6)) {
570         count = idata->newMailCount;
571         idata->newMailCount = 0;
572       }
573     }
574   }
575   while (rc == IMAP_CMD_CONTINUE);
576
577   if (rc == IMAP_CMD_NO) {
578     char *s;
579
580     s = imap_next_word (idata->cmd.buf);        /* skip seq */
581     s = imap_next_word (s);     /* Skip response */
582     mutt_error ("%s", s);
583     mutt_sleep (2);
584     goto fail;
585   }
586
587   if (rc != IMAP_CMD_OK)
588     goto fail;
589
590   /* check for READ-ONLY notification */
591   if (!ascii_strncasecmp
592       (imap_get_qualifier (idata->cmd.buf), "[READ-ONLY]", 11)
593       && !mutt_bit_isset (idata->capabilities, ACL)) {
594     ctx->readonly = 1;
595   }
596
597   if (mutt_bit_isset (idata->capabilities, ACL)) {
598     if (imap_check_acl (idata))
599       goto fail;
600     if (!(mutt_bit_isset (idata->rights, ACL_DELETE) ||
601           mutt_bit_isset (idata->rights, ACL_SEEN) ||
602           mutt_bit_isset (idata->rights, ACL_WRITE) ||
603           mutt_bit_isset (idata->rights, ACL_INSERT)))
604       ctx->readonly = 1;
605   }
606   /* assume we have all rights if ACL is unavailable */
607   else {
608     mutt_bit_set (idata->rights, ACL_LOOKUP);
609     mutt_bit_set (idata->rights, ACL_READ);
610     mutt_bit_set (idata->rights, ACL_SEEN);
611     mutt_bit_set (idata->rights, ACL_WRITE);
612     mutt_bit_set (idata->rights, ACL_INSERT);
613     mutt_bit_set (idata->rights, ACL_POST);
614     mutt_bit_set (idata->rights, ACL_CREATE);
615     mutt_bit_set (idata->rights, ACL_DELETE);
616   }
617
618   ctx->hdrmax = count;
619   ctx->hdrs = p_new(HEADER *, count);
620   ctx->v2r = p_new(int, count);
621   ctx->msgcount = 0;
622   if (count && (imap_read_headers (idata, 0, count - 1) < 0)) {
623     mutt_error _("Error opening mailbox");
624
625     mutt_sleep (1);
626     goto fail;
627   }
628
629   p_delete(&mx.mbox);
630   return 0;
631
632 fail:
633   if (idata->state == IMAP_SELECTED)
634     idata->state = IMAP_AUTHENTICATED;
635 fail_noidata:
636   p_delete(&mx.mbox);
637   return -1;
638 }
639
640 int imap_open_mailbox_append (CONTEXT * ctx)
641 {
642   CONNECTION *conn;
643   IMAP_DATA *idata;
644   char buf[LONG_STRING];
645   char mailbox[LONG_STRING];
646   IMAP_MBOX mx;
647
648   if (imap_parse_path (ctx->path, &mx))
649     return -1;
650
651   /* in APPEND mode, we appear to hijack an existing IMAP connection -
652    * ctx is brand new and mostly empty */
653
654   if (!(idata = imap_conn_find (&(mx.account), 0))) {
655     p_delete(&mx.mbox);
656     return (-1);
657   }
658   conn = idata->conn;
659
660   ctx->magic = M_IMAP;
661   ctx->data = idata;
662
663   imap_fix_path (idata, mx.mbox, mailbox, sizeof (mailbox));
664
665   p_delete(&mx.mbox);
666
667   /* really we should also check for W_OK */
668   if (!imap_access (ctx->path, F_OK))
669     return 0;
670
671   snprintf (buf, sizeof (buf), _("Create %s?"), mailbox);
672   if (option (OPTCONFIRMCREATE) && mutt_yesorno (buf, 1) < 1)
673     return -1;
674
675   if (imap_create_mailbox (idata, mailbox) < 0)
676     return -1;
677
678   return (0);
679 }
680
681 /* imap_logout: Gracefully log out of server. */
682 void imap_logout (IMAP_DATA * idata)
683 {
684   /* we set status here to let imap_handle_untagged know we _expect_ to
685    * receive a bye response (so it doesn't freak out and close the conn) */
686   idata->status = IMAP_BYE;
687   imap_cmd_start (idata, "LOGOUT");
688   while (imap_cmd_step (idata) == IMAP_CMD_CONTINUE);
689   p_delete(&idata->cmd.buf);
690   p_delete(&idata);
691 }
692
693 /*
694 int imap_close_connection (CONTEXT *ctx)
695 {
696   if (CTX_DATA->status != IMAP_BYE)
697   {
698     mutt_message _("Closing connection to IMAP server...");
699     imap_logout (CTX_DATA);
700     mutt_clear_error ();
701   }
702   mutt_socket_close (CTX_DATA->conn);
703   CTX_DATA->state = IMAP_DISCONNECTED;
704   CTX_DATA->conn->data = NULL;
705   return 0;
706 }
707 */
708
709 /* imap_set_flag: append str to flags if we currently have permission
710  *   according to aclbit */
711 static void imap_set_flag(IMAP_DATA *idata, int aclbit, int flag,
712                           const char *str, char *flags, size_t flsize)
713 {
714     if (mutt_bit_isset(idata->rights, aclbit)) {
715         if (flag)
716             m_strcat(flags, flsize, str);
717     }
718 }
719
720 /* imap_make_msg_set: make an IMAP4rev1 UID message set out of a set of
721  *   headers, given a flag enum to filter on.
722  * Params: idata: IMAP_DATA containing context containing header set
723  *         buf: to write message set into
724  *         buflen: length of buffer
725  *         flag: enum of flag type on which to filter
726  *         changed: include only changed messages in message set
727  * Returns: number of messages in message set (0 if no matches) */
728 int imap_make_msg_set (IMAP_DATA * idata, BUFFER * buf, int flag, int changed)
729 {
730   HEADER **hdrs;                /* sorted local copy */
731   int count = 0;                /* number of messages in message set */
732   int match = 0;                /* whether current message matches flag condition */
733   int setstart = 0;             /* start of current message range */
734   int n;
735   short oldsort;                /* we clobber reverse, must restore it */
736
737   /* assuming 32-bit UIDs */
738   char uid[12];
739   int started = 0;
740
741   /* make copy of header pointers to sort in natural order */
742   hdrs = p_dup(idata->ctx->hdrs, idata->ctx->msgcount);
743
744   if (Sort != SORT_ORDER) {
745     oldsort = Sort;
746     Sort = SORT_ORDER;
747     qsort ((void *) hdrs, idata->ctx->msgcount, sizeof (HEADER *),
748            mutt_get_sort_func (SORT_ORDER));
749     Sort = oldsort;
750   }
751
752   for (n = 0; n < idata->ctx->msgcount; n++) {
753     match = 0;
754     /* don't include pending expunged messages */
755     if (hdrs[n]->active)
756       switch (flag) {
757       case M_DELETE:
758         if (hdrs[n]->deleted)
759           match = 1;
760         break;
761       case M_TAG:
762         if (hdrs[n]->tagged)
763           match = 1;
764         break;
765       }
766
767     if (match && (!changed || hdrs[n]->changed)) {
768       count++;
769       if (setstart == 0) {
770         setstart = HEADER_DATA (hdrs[n])->uid;
771         if (started == 0) {
772           snprintf (uid, sizeof (uid), "%u", HEADER_DATA (hdrs[n])->uid);
773           mutt_buffer_addstr (buf, uid);
774           started = 1;
775         }
776         else {
777           snprintf (uid, sizeof (uid), ",%u", HEADER_DATA (hdrs[n])->uid);
778           mutt_buffer_addstr (buf, uid);
779         }
780       }
781       /* tie up if the last message also matches */
782       else if (n == idata->ctx->msgcount - 1) {
783         snprintf (uid, sizeof (uid), ":%u", HEADER_DATA (hdrs[n])->uid);
784         mutt_buffer_addstr (buf, uid);
785       }
786     }
787     /* this message is not expunged and doesn't match. End current set. */
788     else if (setstart && hdrs[n]->active) {
789       if (HEADER_DATA (hdrs[n - 1])->uid > setstart) {
790         snprintf (uid, sizeof (uid), ":%u", HEADER_DATA (hdrs[n - 1])->uid);
791         mutt_buffer_addstr (buf, uid);
792       }
793       setstart = 0;
794     }
795   }
796
797   p_delete(&hdrs);
798
799   return count;
800 }
801
802 /* Update the IMAP server to reflect the flags a single message.  */
803
804 int imap_sync_message (IMAP_DATA *idata, HEADER *hdr, BUFFER *cmd,
805                        int *err_continue)
806 {
807   char flags[LONG_STRING];
808   char uid[11];
809
810   hdr->changed = 0;
811
812   snprintf (uid, sizeof (uid), "%u", HEADER_DATA(hdr)->uid);
813   cmd->dptr = cmd->data;
814   mutt_buffer_addstr (cmd, "UID STORE ");
815   mutt_buffer_addstr (cmd, uid);
816
817   flags[0] = '\0';
818       
819   imap_set_flag (idata, ACL_SEEN, hdr->read, "\\Seen ",
820                  flags, sizeof (flags));
821   imap_set_flag (idata, ACL_WRITE, hdr->flagged,
822                  "\\Flagged ", flags, sizeof (flags));
823   imap_set_flag (idata, ACL_WRITE, hdr->replied,
824                  "\\Answered ", flags, sizeof (flags));
825   imap_set_flag (idata, ACL_DELETE, hdr->deleted,
826                  "\\Deleted ", flags, sizeof (flags));
827
828   /* now make sure we don't lose custom tags */
829   if (mutt_bit_isset (idata->rights, ACL_WRITE))
830     imap_add_keywords (flags, hdr, idata->flags, sizeof (flags));
831
832   m_strrtrim(flags);
833
834   /* UW-IMAP is OK with null flags, Cyrus isn't. The only solution is to
835    * explicitly revoke all system flags (if we have permission) */
836   if (!*flags)
837   {
838     imap_set_flag (idata, ACL_SEEN, 1, "\\Seen ", flags, sizeof (flags));
839     imap_set_flag (idata, ACL_WRITE, 1, "\\Flagged ", flags, sizeof (flags));
840     imap_set_flag (idata, ACL_WRITE, 1, "\\Answered ", flags, sizeof (flags));
841     imap_set_flag (idata, ACL_DELETE, 1, "\\Deleted ", flags, sizeof (flags));
842
843     m_strrtrim(flags);
844
845     mutt_buffer_addstr (cmd, " -FLAGS.SILENT (");
846   } else
847     mutt_buffer_addstr (cmd, " FLAGS.SILENT (");
848
849   mutt_buffer_addstr (cmd, flags);
850   mutt_buffer_addstr (cmd, ")");
851
852   /* dumb hack for bad UW-IMAP 4.7 servers spurious FLAGS updates */
853   hdr->active = 0;
854
855   /* after all this it's still possible to have no flags, if you
856    * have no ACL rights */
857   if (*flags && (imap_exec (idata, cmd->data, 0) != 0) &&
858       err_continue && (*err_continue != M_YES))
859   {
860     *err_continue = imap_continue ("imap_sync_message: STORE failed",
861                                    idata->cmd.buf);
862     if (*err_continue != M_YES)
863       return -1;
864   }
865
866   hdr->active = 1;
867   idata->ctx->changed--;
868
869   return 0;
870 }
871
872 /* update the IMAP server to reflect message changes done within mutt.
873  * Arguments
874  *   ctx: the current context
875  *   expunge: 0 or 1 - do expunge? 
876  */
877
878 int imap_sync_mailbox (CONTEXT * ctx, int expunge, int *index_hint)
879 {
880   IMAP_DATA *idata;
881   CONTEXT *appendctx = NULL;
882   BUFFER cmd;
883   int deleted;
884   int n;
885   int err_continue = M_NO;      /* continue on error? */
886   int rc;
887
888   idata = (IMAP_DATA *) ctx->data;
889
890   if (idata->state != IMAP_SELECTED) {
891     return -1;
892   }
893
894   /* This function is only called when the calling code expects the context
895    * to be changed. */
896   imap_allow_reopen (ctx);
897
898   if ((rc = imap_check_mailbox (ctx, index_hint, 0)) != 0)
899     return rc;
900
901   p_clear(&cmd, 1);
902
903   /* if we are expunging anyway, we can do deleted messages very quickly... */
904   if (expunge && mutt_bit_isset (idata->rights, ACL_DELETE)) {
905     mutt_buffer_addstr (&cmd, "UID STORE ");
906     deleted = imap_make_msg_set (idata, &cmd, M_DELETE, 1);
907
908     /* if we have a message set, then let's delete */
909     if (deleted) {
910       mutt_message (_("Marking %d messages deleted..."), deleted);
911       mutt_buffer_addstr (&cmd, " +FLAGS.SILENT (\\Deleted)");
912       /* mark these messages as unchanged so second pass ignores them. Done
913        * here so BOGUS UW-IMAP 4.7 SILENT FLAGS updates are ignored. */
914       for (n = 0; n < ctx->msgcount; n++)
915         if (ctx->hdrs[n]->deleted && ctx->hdrs[n]->changed)
916           ctx->hdrs[n]->active = 0;
917       if (imap_exec (idata, cmd.data, 0) != 0) {
918         mutt_error (_("Expunge failed"));
919         mutt_sleep (1);
920         rc = -1;
921         goto out;
922       }
923     }
924   }
925
926   /* save status changes */
927   for (n = 0; n < ctx->msgcount; n++) {
928     if (ctx->hdrs[n]->active && ctx->hdrs[n]->changed) {
929
930       mutt_message (_("Saving message status flags... [%d/%d]"), n + 1,
931                     ctx->msgcount);
932
933       /* if the message has been rethreaded or attachments have been deleted
934        * we delete the message and reupload it.
935        * This works better if we're expunging, of course. */
936       if ((ctx->hdrs[n]->env && (ctx->hdrs[n]->env->refs_changed || ctx->hdrs[n]->env->irt_changed)) ||
937           ctx->hdrs[n]->attach_del) {
938         if (!appendctx)
939           appendctx = mx_open_mailbox (ctx->path, M_APPEND | M_QUIET, NULL);
940         if (appendctx) {
941           _mutt_save_message (ctx->hdrs[n], appendctx, 1, 0, 0);
942         }
943       }
944
945       if (imap_sync_message (idata, ctx->hdrs[n], &cmd, &err_continue) < 0) {
946         rc = -1;
947         goto out;
948       }
949     }
950   }
951   ctx->changed = 0;
952
953   /* We must send an EXPUNGE command if we're not closing. */
954   if (expunge && !(ctx->closing) &&
955       mutt_bit_isset (idata->rights, ACL_DELETE)) {
956     mutt_message _("Expunging messages from server...");
957
958     /* Set expunge bit so we don't get spurious reopened messages */
959     idata->reopen |= IMAP_EXPUNGE_EXPECTED;
960     if (imap_exec (idata, "EXPUNGE", 0) != 0) {
961       imap_error (_("imap_sync_mailbox: EXPUNGE failed"), idata->cmd.buf);
962       rc = imap_reconnect (ctx);
963       goto out;
964     }
965   }
966
967   if (expunge && ctx->closing) {
968     if (imap_exec (idata, "CLOSE", 0))
969       mutt_error (_("CLOSE failed"));
970     idata->state = IMAP_AUTHENTICATED;
971   }
972
973   rc = 0;
974 out:
975   if (cmd.data)
976     p_delete(&cmd.data);
977   if (appendctx) {
978     mx_fastclose_mailbox (appendctx);
979     p_delete(&appendctx);
980   }
981   return rc;
982 }
983
984 /* imap_close_mailbox: clean up IMAP data in CONTEXT */
985 static void imap_close_mailbox (CONTEXT * ctx)
986 {
987   IMAP_DATA *idata;
988   int i;
989
990   idata = (IMAP_DATA *) ctx->data;
991   /* Check to see if the mailbox is actually open */
992   if (!idata)
993     return;
994
995   if (ctx == idata->ctx) {
996     if (idata->state != IMAP_FATAL && idata->state == IMAP_SELECTED) {
997       /* mx_close_mailbox won't sync if there are no deleted messages
998       * and the mailbox is unchanged, so we may have to close here */
999       if (!ctx->deleted && imap_exec (idata, "CLOSE", 0))
1000         mutt_error (_("CLOSE failed"));
1001       idata->state = IMAP_AUTHENTICATED;
1002     }
1003
1004     idata->reopen &= IMAP_REOPEN_ALLOW;
1005     p_delete(&(idata->mailbox));
1006     string_list_wipe(&idata->flags);
1007     idata->ctx = NULL;
1008   }
1009
1010   /* free IMAP part of headers */
1011   for (i = 0; i < ctx->msgcount; i++)
1012     imap_free_header_data (&(ctx->hdrs[i]->data));
1013
1014   for (i = 0; i < IMAP_CACHE_LEN; i++) {
1015     if (idata->cache[i].path) {
1016       unlink (idata->cache[i].path);
1017       p_delete(&idata->cache[i].path);
1018     }
1019   }
1020 }
1021
1022 /* use the NOOP command to poll for new mail
1023  *
1024  * return values:
1025  *      M_REOPENED      mailbox has been externally modified
1026  *      M_NEW_MAIL      new mail has arrived!
1027  *      0               no change
1028  *      -1              error
1029  */
1030 int imap_check_mailbox (CONTEXT * ctx, int *index_hint __attribute__ ((unused)), int force)
1031 {
1032   /* overload keyboard timeout to avoid many mailbox checks in a row.
1033    * Most users don't like having to wait exactly when they press a key. */
1034   IMAP_DATA *idata;
1035   int result = 0;
1036
1037   idata = (IMAP_DATA *) ctx->data;
1038
1039   if ((force || time (NULL) >= idata->lastread + Timeout)
1040       && imap_exec (idata, "NOOP", 0) != 0)
1041     return -1;
1042
1043   /* We call this even when we haven't run NOOP in case we have pending
1044    * changes to process, since we can reopen here. */
1045   imap_cmd_finish (idata);
1046
1047   if (idata->check_status & IMAP_EXPUNGE_PENDING)
1048     result = M_REOPENED;
1049   else if (idata->check_status & IMAP_NEWMAIL_PENDING)
1050     result = M_NEW_MAIL;
1051   else if (idata->check_status & IMAP_FLAGS_PENDING)
1052     result = M_FLAGS;
1053
1054   idata->check_status = 0;
1055
1056   return result;
1057 }
1058
1059 /*
1060  * count messages:
1061  *      new == 1:  recent
1062  *      new == 2:  unseen
1063  *      otherwise: total
1064  * return:
1065  *   0+   number of messages in mailbox
1066  *  -1    error while polling mailboxes
1067  */
1068 int imap_mailbox_check (char *path, int new)
1069 {
1070   CONNECTION *conn;
1071   IMAP_DATA *idata;
1072   char buf[LONG_STRING];
1073   char mbox[LONG_STRING];
1074   char mbox_unquoted[LONG_STRING];
1075   char *s;
1076   int msgcount = 0;
1077   int connflags = 0;
1078   IMAP_MBOX mx;
1079   int rc;
1080
1081   if (imap_parse_path (path, &mx))
1082     return -1;
1083
1084   /* If imap_passive is set, don't open a connection to check for new mail */
1085   if (option (OPTIMAPPASSIVE))
1086     connflags = M_IMAP_CONN_NONEW;
1087
1088   if (!(idata = imap_conn_find (&(mx.account), connflags))) {
1089     p_delete(&mx.mbox);
1090     return -1;
1091   }
1092   conn = idata->conn;
1093
1094   imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
1095   p_delete(&mx.mbox);
1096
1097   imap_munge_mbox_name (mbox, sizeof (mbox), buf);
1098   m_strcpy(mbox_unquoted, sizeof(mbox_unquoted), buf);
1099
1100   /* The draft IMAP implementor's guide warns againts using the STATUS
1101    * command on a mailbox that you have selected 
1102    */
1103
1104   if (m_strcmp(mbox_unquoted, idata->mailbox) == 0
1105       || (ascii_strcasecmp (mbox_unquoted, "INBOX") == 0
1106           && m_strcasecmp(mbox_unquoted, idata->mailbox) == 0)) {
1107     m_strcpy(buf, sizeof(buf), "NOOP");
1108   }
1109   else if (mutt_bit_isset (idata->capabilities, IMAP4REV1) ||
1110            mutt_bit_isset (idata->capabilities, STATUS)) {
1111     snprintf (buf, sizeof (buf), "STATUS %s (%s)", mbox,
1112               new == 1 ? "RECENT" : (new == 2 ? "UNSEEN" : "MESSAGES"));
1113   }
1114   else
1115     /* Server does not support STATUS, and this is not the current mailbox.
1116      * There is no lightweight way to check recent arrivals */
1117     return -1;
1118
1119   imap_cmd_start (idata, buf);
1120
1121   do {
1122     if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
1123       break;
1124
1125     s = imap_next_word (idata->cmd.buf);
1126     if (ascii_strncasecmp ("STATUS", s, 6) == 0) {
1127       s = imap_next_word (s);
1128       /* The mailbox name may or may not be quoted here. We could try to 
1129        * munge the server response and compare with quoted (or vise versa)
1130        * but it is probably more efficient to just strncmp against both. */
1131       if (m_strncmp(mbox_unquoted, s, m_strlen(mbox_unquoted)) == 0
1132           || m_strncmp(mbox, s, m_strlen(mbox)) == 0) {
1133         s = imap_next_word (s);
1134         s = imap_next_word (s);
1135         if (isdigit ((unsigned char) *s)) {
1136           if (*s != '0') {
1137             msgcount = atoi (s);
1138           }
1139         }
1140       }
1141     }
1142   }
1143   while (rc == IMAP_CMD_CONTINUE);
1144
1145   return msgcount;
1146 }
1147
1148 /* returns number of patterns in the search that should be done server-side
1149  * (eg are full-text) */
1150 static int do_search (const pattern_t* search, int allpats)
1151 {
1152   int rc = 0;
1153   const pattern_t* pat;
1154
1155   for (pat = search; pat; pat = pat->next) {
1156     switch (pat->op) {
1157       case M_BODY:
1158       case M_HEADER:
1159       case M_WHOLE_MSG:
1160         if (pat->stringmatch)
1161           rc++;
1162         break;
1163       default:
1164       if (pat->child && do_search (pat->child, 1))
1165         rc++;
1166     }
1167
1168     if (!allpats)
1169       break;
1170   }
1171
1172   return rc;
1173 }
1174
1175 /* convert mutt pattern_t to IMAP SEARCH command containing only elements
1176 * that require full-text search (mutt already has what it needs for most
1177 * match types, and does a better job (eg server doesn't support regexps). */
1178 static int imap_compile_search (const pattern_t* pat, BUFFER* buf)
1179 {
1180   if (! do_search (pat, 0))
1181     return 0;
1182
1183   if (pat->not)
1184     mutt_buffer_addstr (buf, "NOT ");
1185
1186   if (pat->child) {
1187     int clauses;
1188
1189     if ((clauses = do_search (pat->child, 1)) > 0) {
1190       const pattern_t* clause = pat->child;
1191
1192       mutt_buffer_addch (buf, '(');
1193
1194       while (clauses) {
1195         if (do_search (clause, 0)) {
1196           if (pat->op == M_OR && clauses > 1)
1197             mutt_buffer_addstr (buf, "OR ");
1198           clauses--;
1199           if (imap_compile_search (clause, buf) < 0)
1200             return -1;
1201
1202           if (clauses)
1203             mutt_buffer_addch (buf, ' ');
1204           
1205           clause = clause->next;
1206         }
1207       }
1208
1209       mutt_buffer_addch (buf, ')');
1210     }
1211   } else {
1212     char term[STRING];
1213     char *delim;
1214
1215     switch (pat->op) {
1216       case M_HEADER:
1217         mutt_buffer_addstr (buf, "HEADER ");
1218
1219         /* extract header name */
1220         if (! (delim = strchr (pat->str, ':'))) {
1221           mutt_error (_("Header search without header name: %s"), pat->str);
1222           return -1;
1223         }
1224         *delim = '\0';
1225         imap_quote_string (term, sizeof (term), pat->str);
1226         mutt_buffer_addstr (buf, term);
1227         mutt_buffer_addch (buf, ' ');
1228
1229         /* and field */
1230         *delim++ = ':';
1231         delim = vskipspaces(delim);
1232         imap_quote_string (term, sizeof (term), delim);
1233         mutt_buffer_addstr (buf, term);
1234         break;
1235
1236       case M_BODY:
1237         mutt_buffer_addstr (buf, "BODY ");
1238         imap_quote_string (term, sizeof (term), pat->str);
1239         mutt_buffer_addstr (buf, term);
1240         break;
1241
1242       case M_WHOLE_MSG:
1243         mutt_buffer_addstr (buf, "TEXT ");
1244         imap_quote_string (term, sizeof (term), pat->str);
1245         mutt_buffer_addstr (buf, term);
1246       break;
1247     }
1248   }
1249
1250   return 0;
1251 }
1252
1253 int imap_search (CONTEXT* ctx, const pattern_t* pat) {
1254   BUFFER buf;
1255   IMAP_DATA* idata = (IMAP_DATA*)ctx->data;
1256   int i;
1257
1258   for (i = 0; i < ctx->msgcount; i++)
1259     ctx->hdrs[i]->matched = 0;
1260
1261   if (!do_search (pat, 1))
1262     return 0;
1263
1264   p_clear(&buf, 1);
1265   mutt_buffer_addstr (&buf, "UID SEARCH ");
1266   if (imap_compile_search (pat, &buf) < 0) {
1267     p_delete(&buf.data);
1268     return -1;
1269   }
1270   if (imap_exec (idata, buf.data, 0) < 0) {
1271     p_delete(&buf.data);
1272     return -1;
1273   }
1274
1275   p_delete(&buf.data);
1276   return 0;
1277 }
1278
1279 /* all this listing/browsing is a mess. I don't like that name is a pointer
1280  *   into idata->buf (used to be a pointer into the passed in buffer, just
1281  *   as bad), nor do I like the fact that the fetch is done here. This
1282  *   code can't possibly handle non-string_list_t untagged responses properly.
1283  *   FIXME. ?! */
1284 int imap_parse_list_response (IMAP_DATA * idata, char **name, int *noselect,
1285                               int *noinferiors, char *delim)
1286 {
1287   char *s;
1288   long bytes;
1289   int rc;
1290
1291   *name = NULL;
1292
1293   rc = imap_cmd_step (idata);
1294   if (rc == IMAP_CMD_OK)
1295     return 0;
1296   if (rc != IMAP_CMD_CONTINUE)
1297     return -1;
1298
1299   s = imap_next_word (idata->cmd.buf);
1300   if ((ascii_strncasecmp ("LIST", s, 4) == 0) ||
1301       (ascii_strncasecmp ("LSUB", s, 4) == 0)) {
1302     *noselect = 0;
1303     *noinferiors = 0;
1304
1305     s = imap_next_word (s);     /* flags */
1306     if (*s == '(') {
1307       char *ep;
1308
1309       s++;
1310       ep = s;
1311       while (*ep && *ep != ')')
1312         ep++;
1313       do {
1314         if (!ascii_strncasecmp (s, "\\NoSelect", 9))
1315           *noselect = 1;
1316         if (!ascii_strncasecmp (s, "\\NoInferiors", 12))
1317           *noinferiors = 1;
1318         /* See draft-gahrns-imap-child-mailbox-?? */
1319         if (!ascii_strncasecmp (s, "\\HasNoChildren", 14))
1320           *noinferiors = 1;
1321         if (*s != ')')
1322           s++;
1323         while (*s && *s != '\\' && *s != ')')
1324           s++;
1325       } while (s != ep);
1326     }
1327     else
1328       return 0;
1329     s = imap_next_word (s);     /* delim */
1330     /* Reset the delimiter, this can change */
1331     if (ascii_strncasecmp (s, "NIL", 3)) {
1332       if (s && s[0] == '\"' && s[1] && s[2] == '\"')
1333         *delim = s[1];
1334       else if (s && s[0] == '\"' && s[1] && s[1] == '\\' && s[2]
1335                && s[3] == '\"')
1336         *delim = s[2];
1337     }
1338     s = imap_next_word (s);     /* name */
1339     if (s && *s == '{') {       /* Literal */
1340       if (imap_get_literal_count (idata->cmd.buf, &bytes) < 0)
1341         return -1;
1342       if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE)
1343         return -1;
1344       *name = idata->cmd.buf;
1345     }
1346     else
1347       *name = s;
1348   }
1349
1350   return 0;
1351 }
1352
1353 int imap_subscribe (char *path, int subscribe)
1354 {
1355   CONNECTION *conn;
1356   IMAP_DATA *idata;
1357   char buf[LONG_STRING];
1358   char mbox[LONG_STRING];
1359   char errstr[STRING];
1360   BUFFER err, token;
1361   IMAP_MBOX mx;
1362
1363   if (mx_get_magic (path) != M_IMAP || imap_parse_path (path, &mx) < 0) {
1364     mutt_error (_("Bad mailbox name"));
1365     return -1;
1366   }
1367
1368   if (!(idata = imap_conn_find (&(mx.account), 0)))
1369     goto fail;
1370
1371   conn = idata->conn;
1372
1373   imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
1374
1375   if (option (OPTIMAPCHECKSUBSCRIBED)) {
1376     p_clear(&token, 1);
1377     err.data = errstr;
1378     err.dsize = sizeof (errstr);
1379     snprintf (mbox, sizeof (mbox), "%smailboxes \"%s\"",
1380               subscribe ? "" : "un", path);
1381     mutt_parse_rc_line (mbox, &token, &err);
1382     p_delete(&token.data);
1383   }
1384
1385   if (subscribe)
1386     mutt_message (_("Subscribing to %s..."), buf);
1387   else
1388     mutt_message (_("Unsubscribing to %s..."), buf);
1389   imap_munge_mbox_name (mbox, sizeof (mbox), buf);
1390
1391   snprintf (buf, sizeof (buf), "%sSUBSCRIBE %s", subscribe ? "" : "UN", mbox);
1392
1393   if (imap_exec (idata, buf, 0) < 0)
1394     goto fail;
1395
1396   p_delete(&mx.mbox);
1397   return 0;
1398
1399 fail:
1400   p_delete(&mx.mbox);
1401   return -1;
1402 }
1403
1404 /* trim dest to the length of the longest prefix it shares with src,
1405  * returning the length of the trimmed string */
1406 static int longest_common_prefix (char *dest, const char* src,
1407                                   int start, ssize_t dlen) {
1408   int pos = start;
1409
1410   while (pos < dlen && dest[pos] && dest[pos] == src[pos])
1411     pos++;
1412   dest[pos] = '\0';
1413
1414   return pos;
1415 }
1416
1417 /* look for IMAP URLs to complete from defined mailboxes. Could be extended
1418  * to complete over open connections and account/folder hooks too. */
1419 static int imap_complete_hosts (char *dest, ssize_t len) {
1420   BUFFY* mailbox;
1421   CONNECTION* conn;
1422   int rc = -1;
1423   int matchlen;
1424   int i = 0;
1425
1426   matchlen = m_strlen(dest);
1427   if (!Incoming.len)
1428     return (-1);
1429   for (i = 0; i < Incoming.len; i++) {
1430     mailbox = Incoming.arr[i];
1431     if (!m_strncmp(dest, mailbox->path, matchlen)) {
1432       if (rc) {
1433         m_strcpy(dest, len, mailbox->path);
1434         rc = 0;
1435       } else
1436         longest_common_prefix (dest, mailbox->path, matchlen, len);
1437     }
1438   }
1439
1440   for (conn = mutt_socket_head (); conn && conn->next; conn = conn->next) {
1441     ciss_url_t url;
1442     char urlstr[LONG_STRING];
1443
1444     if (conn->account.type != M_ACCT_TYPE_IMAP)
1445       continue;
1446
1447     mutt_account_tourl (&conn->account, &url);
1448     /* FIXME: how to handle multiple users on the same host? */
1449     url.user = NULL;
1450     url.path = NULL;
1451     url_ciss_tostring (&url, urlstr, sizeof (urlstr), 0);
1452     if (!m_strncmp(dest, urlstr, matchlen)) {
1453       if (rc) {
1454         m_strcpy(dest, len, urlstr);
1455         rc = 0;
1456       } else
1457         longest_common_prefix (dest, urlstr, matchlen, len);
1458     }
1459   }
1460
1461   return rc;
1462 }
1463
1464 /* imap_complete: given a partial IMAP folder path, return a string which
1465  *   adds as much to the path as is unique */
1466 int imap_complete (char *dest, size_t dlen, char *path) {
1467   CONNECTION *conn;
1468   IMAP_DATA *idata;
1469   char list[LONG_STRING];
1470   char buf[LONG_STRING];
1471   char *list_word = NULL;
1472   int noselect, noinferiors;
1473   char delim;
1474   char completion[LONG_STRING];
1475   int clen, matchlen = 0;
1476   int completions = 0;
1477   IMAP_MBOX mx;
1478
1479   if (imap_parse_path (path, &mx) || !mx.mbox) {
1480     m_strcpy(dest, dlen, path);
1481     return imap_complete_hosts (dest, dlen);
1482   }
1483
1484   /* don't open a new socket just for completion. Instead complete over
1485    * known mailboxes/hooks/etc */
1486   if (!(idata = imap_conn_find (&(mx.account), M_IMAP_CONN_NONEW))) {
1487     p_delete(&mx.mbox);
1488     m_strcpy(dest, dlen, path);
1489     return imap_complete_hosts (dest, dlen);
1490   }
1491   conn = idata->conn;
1492
1493   /* reformat path for IMAP list, and append wildcard */
1494   /* don't use INBOX in place of "" */
1495   if (mx.mbox && mx.mbox[0])
1496     imap_fix_path (idata, mx.mbox, list, sizeof (list));
1497   else
1498     list[0] = '\0';
1499
1500   /* fire off command */
1501   snprintf (buf, sizeof (buf), "%s \"\" \"%s%%\"",
1502             option (OPTIMAPLSUB) ? "LSUB" : "LIST", list);
1503
1504   imap_cmd_start (idata, buf);
1505
1506   /* and see what the results are */
1507   m_strcpy(completion, sizeof(completion), NONULL(mx.mbox));
1508   do {
1509     if (imap_parse_list_response (idata, &list_word, &noselect, &noinferiors,
1510                                   &delim))
1511       break;
1512
1513     if (list_word) {
1514       /* store unquoted */
1515       imap_unmunge_mbox_name (list_word);
1516
1517       /* if the folder isn't selectable, append delimiter to force browse
1518        * to enter it on second tab. */
1519       if (noselect) {
1520         clen = m_strlen(list_word);
1521         list_word[clen++] = delim;
1522         list_word[clen] = '\0';
1523       }
1524       /* copy in first word */
1525       if (!completions) {
1526         m_strcpy(completion, sizeof(completion), list_word);
1527         matchlen = m_strlen(completion);
1528         completions++;
1529         continue;
1530       }
1531
1532       matchlen = longest_common_prefix (completion, list_word, 0, matchlen);
1533       completions++;
1534     }
1535   }
1536   while (m_strncmp(idata->cmd.seq, idata->cmd.buf, SEQLEN));
1537
1538   if (completions) {
1539     /* reformat output */
1540     imap_qualify_path (dest, dlen, &mx, completion);
1541     mutt_pretty_mailbox (dest);
1542
1543     p_delete(&mx.mbox);
1544     return 0;
1545   }
1546
1547   return -1;
1548 }
1549
1550 /* reconnect if connection was lost */
1551 int imap_reconnect (CONTEXT * ctx)
1552 {
1553   IMAP_DATA *imap_data;
1554
1555   if (!ctx)
1556     return (-1);
1557
1558   imap_data = (IMAP_DATA *) ctx->data;
1559
1560   if (imap_data) {
1561     if (imap_data->status == IMAP_CONNECTED)
1562       return -1;
1563   }
1564
1565   if (query_quadoption
1566       (OPT_IMAPRECONNECT,
1567        _("Connection lost. Reconnect to IMAP server?")) != M_YES)
1568     return -1;
1569
1570   mx_open_mailbox (ctx->path, 0, ctx);
1571   return 0;
1572 }
1573
1574 int imap_is_magic (const char* path, struct stat* st __attribute__ ((unused))) {
1575   url_scheme_t s;
1576   if (!path || !*path)
1577     return (-1);
1578   s = url_check_scheme (NONULL (path));
1579   return ((s == U_IMAP || s == U_IMAPS) ? M_IMAP : -1);
1580 }
1581
1582 static int acl_check_imap (CONTEXT* ctx, int bit) {
1583   return (!mutt_bit_isset (((IMAP_DATA*) ctx->data)->capabilities, ACL) ||
1584           mutt_bit_isset (((IMAP_DATA*) ctx->data)->rights, bit));
1585 }
1586
1587 static int imap_open_new_message (MESSAGE * msg,
1588                                   CONTEXT * dest __attribute__ ((unused)),
1589                                   HEADER * hdr __attribute__ ((unused)))
1590 {
1591     char tmp[_POSIX_PATH_MAX];
1592
1593     msg->fp = m_tempfile(tmp, sizeof(tmp), NONULL(MCore.tmpdir), NULL);
1594     if (!msg->fp) {
1595         mutt_perror(tmp);
1596         return -1;
1597     }
1598
1599     msg->path = m_strdup(tmp);
1600     return 0;
1601 }
1602
1603 /* this ugly kludge is required since the last int to
1604  * imap_check_mailbox() doesn't mean 'lock' but 'force'... */
1605 static int _imap_check_mailbox (CONTEXT* ctx,
1606                                 int* index_hint,
1607                                 int lock __attribute__ ((unused))) {
1608   return (imap_check_mailbox (ctx, index_hint, 0));
1609 }
1610
1611 static int imap_commit_message (MESSAGE* msg, CONTEXT* ctx) {
1612   int r = 0;
1613
1614   if ((r = m_fclose(&msg->fp)) == 0)
1615     r = imap_append_message (ctx, msg);
1616   return (r);
1617 }
1618
1619 mx_t const imap_mx = {
1620     M_IMAP,
1621     0,
1622     imap_is_magic,
1623     NULL,
1624     imap_access,
1625     imap_open_mailbox,
1626     imap_open_new_message,
1627     acl_check_imap,
1628     _imap_check_mailbox,
1629     imap_close_mailbox,
1630     imap_sync_mailbox,
1631     imap_commit_message,
1632 };