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