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