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