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