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