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-2003 Brendan Cully <brendan@kublai.com>
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.
12 /* Support for IMAP4rev1, with the occasional nod to IMAP 4. */
19 #include "mutt_curses.h"
26 #include "imap_private.h"
27 #if defined(USE_SSL) || defined(USE_GNUTLS)
28 # include "mutt_ssl.h"
35 #include <sys/types.h>
38 /* imap forward declarations */
39 static int imap_get_delim (IMAP_DATA * idata);
40 static char *imap_get_flags (LIST ** hflags, char *s);
41 static int imap_check_acl (IMAP_DATA * idata);
42 static int imap_check_capabilities (IMAP_DATA * idata);
43 static void imap_set_flag (IMAP_DATA * idata, int aclbit, int flag,
44 const char *str, char *flags, size_t flsize);
46 /* imap_access: Check permissions on an IMAP mailbox. */
47 int imap_access (const char *path, int flags)
51 char buf[LONG_STRING];
52 char mailbox[LONG_STRING];
53 char mbox[LONG_STRING];
55 if (imap_parse_path (path, &mx))
58 if (!(idata = imap_conn_find (&mx.account,
59 option (OPTIMAPPASSIVE) ? M_IMAP_CONN_NONEW :
65 imap_fix_path (idata, mx.mbox, mailbox, sizeof (mailbox));
67 imap_munge_mbox_name (mbox, sizeof (mbox), mailbox);
69 /* TODO: ACL checks. Right now we assume if it exists we can mess with it. */
70 if (mutt_bit_isset (idata->capabilities, IMAP4REV1))
71 snprintf (buf, sizeof (buf), "STATUS %s (UIDVALIDITY)", mbox);
72 else if (mutt_bit_isset (idata->capabilities, STATUS))
73 snprintf (buf, sizeof (buf), "STATUS %s (UID-VALIDITY)", mbox);
75 dprint (2, (debugfile, "imap_access: STATUS not supported?\n"));
79 if (imap_exec (idata, buf, IMAP_CMD_FAIL_OK) < 0) {
80 dprint (1, (debugfile, "imap_access: Can't check STATUS of %s\n", mbox));
87 int imap_create_mailbox (IMAP_DATA * idata, char *mailbox)
89 char buf[LONG_STRING], mbox[LONG_STRING];
91 imap_munge_mbox_name (mbox, sizeof (mbox), mailbox);
92 snprintf (buf, sizeof (buf), "CREATE %s", mbox);
94 if (imap_exec (idata, buf, 0) != 0)
100 int imap_rename_mailbox (IMAP_DATA * idata, IMAP_MBOX * mx,
103 char oldmbox[LONG_STRING];
104 char newmbox[LONG_STRING];
105 char buf[LONG_STRING];
107 imap_munge_mbox_name (oldmbox, sizeof (oldmbox), mx->mbox);
108 imap_munge_mbox_name (newmbox, sizeof (newmbox), newname);
110 snprintf (buf, sizeof (buf), "RENAME %s %s", oldmbox, newmbox);
112 if (imap_exec (idata, buf, 0) != 0)
118 int imap_delete_mailbox (CONTEXT * ctx, IMAP_MBOX mx)
120 char buf[LONG_STRING], mbox[LONG_STRING];
123 if (!ctx || !ctx->data) {
124 if (!(idata = imap_conn_find (&mx.account,
125 option (OPTIMAPPASSIVE) ? M_IMAP_CONN_NONEW
135 imap_munge_mbox_name (mbox, sizeof (mbox), mx.mbox);
136 snprintf (buf, sizeof (buf), "DELETE %s", mbox);
138 if (imap_exec ((IMAP_DATA *) idata, buf, 0) != 0)
144 /* imap_logout_all: close all open connections. Quick and dirty until we can
145 * make sure we've got all the context we need. */
146 void imap_logout_all (void)
151 conn = mutt_socket_head ();
156 if (conn->account.type == M_ACCT_TYPE_IMAP && conn->fd >= 0) {
157 mutt_message (_("Closing connection to %s..."), conn->account.host);
158 imap_logout ((IMAP_DATA *) conn->data);
160 mutt_socket_close (conn);
161 mutt_socket_free (conn);
168 /* imap_read_literal: read bytes bytes from server into file. Not explicitly
169 * buffered, relies on FILE buffering. NOTE: strips \r from \r\n.
170 * Apparently even literals use \r\n-terminated strings ?! */
171 int imap_read_literal (FILE * fp, IMAP_DATA * idata, long bytes)
178 dprint (2, (debugfile, "imap_read_literal: reading %ld bytes\n", bytes));
180 for (pos = 0; pos < bytes; pos++) {
181 if (mutt_socket_readchar (idata->conn, &c) != 1) {
184 "imap_read_literal: error during read, %ld bytes read\n",
186 idata->status = IMAP_FATAL;
192 if (r == 1 && c != '\n')
204 if (debuglevel >= IMAP_LOG_LTRL)
205 fputc (c, debugfile);
212 /* imap_expunge_mailbox: Purge IMAP portion of expunged messages from the
213 * context. Must not be done while something has a handle on any headers
214 * (eg inside pager or editor). That is, check IMAP_REOPEN_ALLOW. */
215 void imap_expunge_mailbox (IMAP_DATA * idata)
220 for (i = 0; i < idata->ctx->msgcount; i++) {
221 h = idata->ctx->hdrs[i];
223 if (h->index == -1) {
225 (debugfile, "Expunging message UID %d.\n",
226 HEADER_DATA (h)->uid));
230 /* free cached body from disk, if neccessary */
231 cacheno = HEADER_DATA (h)->uid % IMAP_CACHE_LEN;
232 if (idata->cache[cacheno].uid == HEADER_DATA (h)->uid &&
233 idata->cache[cacheno].path) {
234 unlink (idata->cache[cacheno].path);
235 FREE (&idata->cache[cacheno].path);
238 imap_free_header_data (&h->data);
242 /* We may be called on to expunge at any time. We can't rely on the caller
243 * to always know to rethread */
244 mx_update_tables (idata->ctx, 0);
245 mutt_sort_headers (idata->ctx, 1);
248 static int imap_get_delim (IMAP_DATA * idata)
253 /* assume that the delim is /. If this fails, we're in bigger trouble
254 * than getting the delim wrong */
257 imap_cmd_start (idata, "LIST \"\" \"\"");
260 if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
263 s = imap_next_word (idata->cmd.buf);
264 if (ascii_strncasecmp ("LIST", s, 4) == 0) {
265 s = imap_next_word (s);
266 s = imap_next_word (s);
267 if (s && s[0] == '\"' && s[1] && s[2] == '\"')
269 else if (s && s[0] == '\"' && s[1] && s[1] == '\\' && s[2]
274 while (rc == IMAP_CMD_CONTINUE);
276 if (rc != IMAP_CMD_OK) {
277 dprint (1, (debugfile, "imap_get_delim: failed.\n"));
281 dprint (2, (debugfile, "Delimiter: %c\n", idata->delim));
286 /* get rights for folder, let imap_handle_untagged do the rest */
287 static int imap_check_acl (IMAP_DATA * idata)
289 char buf[LONG_STRING];
290 char mbox[LONG_STRING];
292 imap_munge_mbox_name (mbox, sizeof (mbox), idata->mailbox);
293 snprintf (buf, sizeof (buf), "MYRIGHTS %s", mbox);
294 if (imap_exec (idata, buf, 0) != 0) {
295 imap_error ("imap_check_acl", buf);
301 /* imap_check_capabilities: make sure we can log in to this server. */
302 static int imap_check_capabilities (IMAP_DATA * idata)
304 if (imap_exec (idata, "CAPABILITY", 0) != 0) {
305 imap_error ("imap_check_capabilities", idata->cmd.buf);
309 if (!(mutt_bit_isset (idata->capabilities, IMAP4)
310 || mutt_bit_isset (idata->capabilities, IMAP4REV1))) {
311 mutt_error _("This IMAP server is ancient. Mutt does not work with it.");
313 mutt_sleep (2); /* pause a moment to let the user see the error */
321 /* imap_conn_find: Find an open IMAP connection matching account, or open
322 * a new one if none can be found. */
323 IMAP_DATA *imap_conn_find (const ACCOUNT * account, int flags)
329 if (!(conn = mutt_conn_find (NULL, account)))
332 /* if opening a new UNSELECTED connection, preserve existing creds */
333 creds = &(conn->account);
335 /* make sure this connection is not in SELECTED state, if neccessary */
336 if (flags & M_IMAP_CONN_NOSELECT)
337 while (conn->data && ((IMAP_DATA *) conn->data)->state == IMAP_SELECTED) {
338 if (!(conn = mutt_conn_find (conn, account)))
340 memcpy (&(conn->account), creds, sizeof (ACCOUNT));
343 idata = (IMAP_DATA *) conn->data;
345 /* don't open a new connection if one isn't wanted */
346 if (flags & M_IMAP_CONN_NONEW) {
348 mutt_socket_free (conn);
351 if (idata->state < IMAP_AUTHENTICATED)
356 /* The current connection is a new connection */
357 if (!(idata = imap_new_idata ())) {
358 mutt_socket_free (conn);
366 if (idata->state == IMAP_DISCONNECTED)
367 imap_open_connection (idata);
368 if (idata->state == IMAP_CONNECTED) {
369 if (!imap_authenticate (idata)) {
370 idata->state = IMAP_AUTHENTICATED;
371 if (idata->conn->ssf)
372 dprint (2, (debugfile, "Communication encrypted at %d bits\n",
376 mutt_account_unsetpass (&idata->conn->account);
378 FREE (&idata->capstr);
380 if (idata->state == IMAP_AUTHENTICATED)
381 imap_get_delim (idata);
386 int imap_open_connection (IMAP_DATA * idata)
388 char buf[LONG_STRING];
390 if (mutt_socket_open (idata->conn) < 0)
393 idata->state = IMAP_CONNECTED;
395 if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE) {
396 mutt_error (_("Unexpected response received from server: %s"),
400 mutt_socket_close (idata->conn);
401 idata->state = IMAP_DISCONNECTED;
405 if (ascii_strncasecmp ("* OK", idata->cmd.buf, 4) == 0) {
406 /* TODO: Parse new tagged CAPABILITY data (* OK [CAPABILITY...]) */
407 if (imap_check_capabilities (idata))
409 #if defined(USE_SSL) || defined(USE_GNUTLS)
410 /* Attempt STARTTLS if available and desired. */
411 if (mutt_bit_isset (idata->capabilities, STARTTLS) && !idata->conn->ssf) {
414 if ((rc = query_quadoption (OPT_SSLSTARTTLS,
415 _("Secure connection with TLS?"))) == -1)
418 if ((rc = imap_exec (idata, "STARTTLS", IMAP_CMD_FAIL_OK)) == -1)
422 if (mutt_ssl_starttls (idata->conn))
424 if (mutt_gnutls_starttls (idata->conn))
427 mutt_error (_("Could not negotiate TLS connection"));
432 /* RFC 2595 demands we recheck CAPABILITY after TLS completes. */
433 if (imap_exec (idata, "CAPABILITY", 0))
441 else if (ascii_strncasecmp ("* PREAUTH", idata->cmd.buf, 9) == 0) {
442 idata->state = IMAP_AUTHENTICATED;
443 if (imap_check_capabilities (idata) != 0)
445 FREE (&idata->capstr);
448 imap_error ("imap_open_connection()", buf);
455 mutt_socket_close (idata->conn);
456 idata->state = IMAP_DISCONNECTED;
458 FREE (&idata->capstr);
462 /* imap_get_flags: Make a simple list out of a FLAGS response.
463 * return stream following FLAGS response */
464 static char *imap_get_flags (LIST ** hflags, char *s)
470 /* sanity-check string */
471 if (ascii_strncasecmp ("FLAGS", s, 5) != 0) {
472 dprint (1, (debugfile, "imap_get_flags: not a FLAGS response: %s\n", s));
478 dprint (1, (debugfile, "imap_get_flags: bogus FLAGS response: %s\n", s));
482 /* create list, update caller's flags handle */
483 flags = mutt_new_list ();
486 while (*s && *s != ')') {
490 while (*s && (*s != ')') && !ISSPACE (*s))
495 mutt_add_list (flags, flag_word);
499 /* note bad flags response */
501 dprint (1, (debugfile,
502 "imap_get_flags: Unterminated FLAGS response: %s\n", s));
503 mutt_free_list (hflags);
513 int imap_open_mailbox (CONTEXT * ctx)
517 char buf[LONG_STRING];
518 char bufout[LONG_STRING];
523 if (imap_parse_path (ctx->path, &mx)) {
524 mutt_error (_("%s is an invalid IMAP path"), ctx->path);
528 /* we require a connection which isn't currently in IMAP_SELECTED state */
529 if (!(idata = imap_conn_find (&(mx.account), M_IMAP_CONN_NOSELECT)))
531 if (idata->state < IMAP_AUTHENTICATED)
536 /* once again the context is new */
539 /* Clean up path and replace the one in the ctx */
540 imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
541 FREE (&(idata->mailbox));
542 idata->mailbox = safe_strdup (buf);
543 imap_qualify_path (buf, sizeof (buf), &mx, idata->mailbox);
546 ctx->path = safe_strdup (buf);
550 /* clear mailbox status */
552 memset (idata->rights, 0, (RIGHTSMAX + 7) / 8);
553 idata->newMailCount = 0;
555 mutt_message (_("Selecting %s..."), idata->mailbox);
556 imap_munge_mbox_name (buf, sizeof (buf), idata->mailbox);
557 snprintf (bufout, sizeof (bufout), "%s %s",
558 ctx->readonly ? "EXAMINE" : "SELECT", buf);
560 idata->state = IMAP_SELECTED;
562 imap_cmd_start (idata, bufout);
567 if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
570 pc = idata->cmd.buf + 2;
572 /* Obtain list of available flags here, may be overridden by a
573 * PERMANENTFLAGS tag in the OK response */
574 if (ascii_strncasecmp ("FLAGS", pc, 5) == 0) {
575 /* don't override PERMANENTFLAGS */
577 dprint (2, (debugfile, "Getting mailbox FLAGS\n"));
578 if ((pc = imap_get_flags (&(idata->flags), pc)) == NULL)
582 /* PERMANENTFLAGS are massaged to look like FLAGS, then override FLAGS */
583 else if (ascii_strncasecmp ("OK [PERMANENTFLAGS", pc, 18) == 0) {
584 dprint (2, (debugfile, "Getting mailbox PERMANENTFLAGS\n"));
585 /* safe to call on NULL */
586 mutt_free_list (&(idata->flags));
587 /* skip "OK [PERMANENT" so syntax is the same as FLAGS */
589 if ((pc = imap_get_flags (&(idata->flags), pc)) == NULL)
593 /* save UIDVALIDITY for the header cache */
594 else if (ascii_strncasecmp ("OK [UIDVALIDITY", pc, 14) == 0) {
595 dprint (2, (debugfile, "Getting mailbox UIDVALIDITY\n"));
597 pc = imap_next_word (pc);
599 sscanf (pc, "%u", &(idata->uid_validity));
603 pc = imap_next_word (pc);
604 if (!ascii_strncasecmp ("EXISTS", pc, 6)) {
605 count = idata->newMailCount;
606 idata->newMailCount = 0;
610 while (rc == IMAP_CMD_CONTINUE);
612 if (rc == IMAP_CMD_NO) {
615 s = imap_next_word (idata->cmd.buf); /* skip seq */
616 s = imap_next_word (s); /* Skip response */
617 mutt_error ("%s", s);
622 if (rc != IMAP_CMD_OK)
625 /* check for READ-ONLY notification */
626 if (!ascii_strncasecmp
627 (imap_get_qualifier (idata->cmd.buf), "[READ-ONLY]", 11)
628 && !mutt_bit_isset (idata->capabilities, ACL)) {
629 dprint (2, (debugfile, "Mailbox is read-only.\n"));
634 /* dump the mailbox flags we've found */
635 if (debuglevel > 2) {
637 dprint (3, (debugfile, "No folder flags found\n"));
639 LIST *t = idata->flags;
641 dprint (3, (debugfile, "Mailbox flags: "));
645 dprint (3, (debugfile, "[%s] ", t->data));
648 dprint (3, (debugfile, "\n"));
653 if (mutt_bit_isset (idata->capabilities, ACL)) {
654 if (imap_check_acl (idata))
656 if (!(mutt_bit_isset (idata->rights, IMAP_ACL_DELETE) ||
657 mutt_bit_isset (idata->rights, IMAP_ACL_SEEN) ||
658 mutt_bit_isset (idata->rights, IMAP_ACL_WRITE) ||
659 mutt_bit_isset (idata->rights, IMAP_ACL_INSERT)))
662 /* assume we have all rights if ACL is unavailable */
664 mutt_bit_set (idata->rights, IMAP_ACL_LOOKUP);
665 mutt_bit_set (idata->rights, IMAP_ACL_READ);
666 mutt_bit_set (idata->rights, IMAP_ACL_SEEN);
667 mutt_bit_set (idata->rights, IMAP_ACL_WRITE);
668 mutt_bit_set (idata->rights, IMAP_ACL_INSERT);
669 mutt_bit_set (idata->rights, IMAP_ACL_POST);
670 mutt_bit_set (idata->rights, IMAP_ACL_CREATE);
671 mutt_bit_set (idata->rights, IMAP_ACL_DELETE);
675 ctx->hdrs = safe_calloc (count, sizeof (HEADER *));
676 ctx->v2r = safe_calloc (count, sizeof (int));
678 if (count && (imap_read_headers (idata, 0, count - 1) < 0)) {
679 mutt_error _("Error opening mailbox");
686 (debugfile, "imap_open_mailbox: msgcount is %d\n", ctx->msgcount));
691 if (idata->state == IMAP_SELECTED)
692 idata->state = IMAP_AUTHENTICATED;
698 int imap_open_mailbox_append (CONTEXT * ctx)
702 char buf[LONG_STRING], mbox[LONG_STRING];
703 char mailbox[LONG_STRING];
707 if (imap_parse_path (ctx->path, &mx))
710 /* in APPEND mode, we appear to hijack an existing IMAP connection -
711 * ctx is brand new and mostly empty */
713 if (!(idata = imap_conn_find (&(mx.account), 0)))
720 /* check mailbox existance */
722 imap_fix_path (idata, mx.mbox, mailbox, sizeof (mailbox));
724 imap_munge_mbox_name (mbox, sizeof (mbox), mailbox);
726 if (mutt_bit_isset (idata->capabilities, IMAP4REV1))
727 snprintf (buf, sizeof (buf), "STATUS %s (UIDVALIDITY)", mbox);
728 else if (mutt_bit_isset (idata->capabilities, STATUS))
729 /* We have no idea what the other guy wants. UW imapd 8.3 wants this
730 * (but it does not work if another mailbox is selected) */
731 snprintf (buf, sizeof (buf), "STATUS %s (UID-VALIDITY)", mbox);
733 /* STATUS not supported */
734 mutt_message _("Unable to append to IMAP mailboxes at this server");
739 r = imap_exec (idata, buf, IMAP_CMD_FAIL_OK);
741 /* command failed cause folder doesn't exist */
742 snprintf (buf, sizeof (buf), _("Create %s?"), mailbox);
743 if (option (OPTCONFIRMCREATE) && mutt_yesorno (buf, 1) < 1)
746 if (imap_create_mailbox (idata, mailbox) < 0)
750 /* Hmm, some other failure */
761 /* imap_logout: Gracefully log out of server. */
762 void imap_logout (IMAP_DATA * idata)
764 /* we set status here to let imap_handle_untagged know we _expect_ to
765 * receive a bye response (so it doesn't freak out and close the conn) */
766 idata->status = IMAP_BYE;
767 imap_cmd_start (idata, "LOGOUT");
768 while (imap_cmd_step (idata) == IMAP_CMD_CONTINUE);
769 FREE (&idata->cmd.buf);
774 int imap_close_connection (CONTEXT *ctx)
776 dprint (1, (debugfile, "imap_close_connection(): closing connection\n"));
777 if (CTX_DATA->status != IMAP_BYE)
779 mutt_message _("Closing connection to IMAP server...");
780 imap_logout (CTX_DATA);
783 mutt_socket_close (CTX_DATA->conn);
784 CTX_DATA->state = IMAP_DISCONNECTED;
785 CTX_DATA->conn->data = NULL;
790 /* imap_set_flag: append str to flags if we currently have permission
791 * according to aclbit */
792 static void imap_set_flag (IMAP_DATA * idata, int aclbit, int flag,
793 const char *str, char *flags, size_t flsize)
795 if (mutt_bit_isset (idata->rights, aclbit))
797 safe_strcat (flags, flsize, str);
800 /* imap_make_msg_set: make an IMAP4rev1 UID message set out of a set of
801 * headers, given a flag enum to filter on.
802 * Params: idata: IMAP_DATA containing context containing header set
803 * buf: to write message set into
804 * buflen: length of buffer
805 * flag: enum of flag type on which to filter
806 * changed: include only changed messages in message set
807 * Returns: number of messages in message set (0 if no matches) */
808 int imap_make_msg_set (IMAP_DATA * idata, BUFFER * buf, int flag, int changed)
810 HEADER **hdrs; /* sorted local copy */
811 int count = 0; /* number of messages in message set */
812 int match = 0; /* whether current message matches flag condition */
813 unsigned int setstart = 0; /* start of current message range */
815 short oldsort; /* we clobber reverse, must restore it */
817 /* assuming 32-bit UIDs */
821 /* make copy of header pointers to sort in natural order */
822 hdrs = safe_calloc (idata->ctx->msgcount, sizeof (HEADER *));
823 memcpy (hdrs, idata->ctx->hdrs, idata->ctx->msgcount * sizeof (HEADER *));
825 if (Sort != SORT_ORDER) {
828 qsort ((void *) hdrs, idata->ctx->msgcount, sizeof (HEADER *),
829 mutt_get_sort_func (SORT_ORDER));
833 for (n = 0; n < idata->ctx->msgcount; n++) {
835 /* don't include pending expunged messages */
839 if (hdrs[n]->deleted)
848 if (match && (!changed || hdrs[n]->changed)) {
851 setstart = HEADER_DATA (hdrs[n])->uid;
853 snprintf (uid, sizeof (uid), "%u", HEADER_DATA (hdrs[n])->uid);
854 mutt_buffer_addstr (buf, uid);
858 snprintf (uid, sizeof (uid), ",%u", HEADER_DATA (hdrs[n])->uid);
859 mutt_buffer_addstr (buf, uid);
862 /* tie up if the last message also matches */
863 else if (n == idata->ctx->msgcount - 1) {
864 snprintf (uid, sizeof (uid), ":%u", HEADER_DATA (hdrs[n])->uid);
865 mutt_buffer_addstr (buf, uid);
868 /* this message is not expunged and doesn't match. End current set. */
869 else if (setstart && hdrs[n]->active) {
870 if (HEADER_DATA (hdrs[n - 1])->uid > setstart) {
871 snprintf (uid, sizeof (uid), ":%u", HEADER_DATA (hdrs[n - 1])->uid);
872 mutt_buffer_addstr (buf, uid);
883 /* update the IMAP server to reflect message changes done within mutt.
885 * ctx: the current context
886 * expunge: 0 or 1 - do expunge?
889 int imap_sync_mailbox (CONTEXT * ctx, int expunge, int *index_hint)
892 CONTEXT *appendctx = NULL;
894 char flags[LONG_STRING];
898 int err_continue = M_NO; /* continue on error? */
901 idata = (IMAP_DATA *) ctx->data;
903 if (idata->state != IMAP_SELECTED) {
904 dprint (2, (debugfile, "imap_sync_mailbox: no mailbox selected\n"));
908 /* CLOSE purges deleted messages. If we don't want to purge them, we must
909 * tell imap_close_mailbox not to issue the CLOSE command */
915 /* This function is only called when the calling code expects the context
917 imap_allow_reopen (ctx);
919 if ((rc = imap_check_mailbox (ctx, index_hint, 0)) != 0)
922 memset (&cmd, 0, sizeof (cmd));
924 /* if we are expunging anyway, we can do deleted messages very quickly... */
925 if (expunge && mutt_bit_isset (idata->rights, IMAP_ACL_DELETE)) {
926 mutt_buffer_addstr (&cmd, "UID STORE ");
927 deleted = imap_make_msg_set (idata, &cmd, M_DELETE, 1);
929 /* if we have a message set, then let's delete */
931 mutt_message (_("Marking %d messages deleted..."), deleted);
932 mutt_buffer_addstr (&cmd, " +FLAGS.SILENT (\\Deleted)");
933 /* mark these messages as unchanged so second pass ignores them. Done
934 * here so BOGUS UW-IMAP 4.7 SILENT FLAGS updates are ignored. */
935 for (n = 0; n < ctx->msgcount; n++)
936 if (ctx->hdrs[n]->deleted && ctx->hdrs[n]->changed)
937 ctx->hdrs[n]->active = 0;
938 if (imap_exec (idata, cmd.data, 0) != 0) {
939 mutt_error (_("Expunge failed"));
947 /* save status changes */
948 for (n = 0; n < ctx->msgcount; n++) {
949 if (ctx->hdrs[n]->active && ctx->hdrs[n]->changed) {
950 ctx->hdrs[n]->changed = 0;
952 mutt_message (_("Saving message status flags... [%d/%d]"), n + 1,
955 snprintf (uid, sizeof (uid), "%u", HEADER_DATA (ctx->hdrs[n])->uid);
957 mutt_buffer_addstr (&cmd, "UID STORE ");
958 mutt_buffer_addstr (&cmd, uid);
960 /* if the message has been rethreaded or attachments have been deleted
961 * we delete the message and reupload it.
962 * This works better if we're expunging, of course. */
963 if (ctx->hdrs[n]->refs_changed || ctx->hdrs[n]->irt_changed ||
964 ctx->hdrs[n]->attach_del) {
967 "imap_sync_mailbox: Attachments to be deleted, falling back to _mutt_save_message\n"));
969 appendctx = mx_open_mailbox (ctx->path, M_APPEND | M_QUIET, NULL);
973 "imap_sync_mailbox: Error opening mailbox in append mode\n"));
976 _mutt_save_message (ctx->hdrs[n], appendctx, 1, 0, 0);
980 imap_set_flag (idata, IMAP_ACL_SEEN, ctx->hdrs[n]->read, "\\Seen ",
981 flags, sizeof (flags));
982 imap_set_flag (idata, IMAP_ACL_WRITE, ctx->hdrs[n]->flagged,
983 "\\Flagged ", flags, sizeof (flags));
984 imap_set_flag (idata, IMAP_ACL_WRITE, ctx->hdrs[n]->replied,
985 "\\Answered ", flags, sizeof (flags));
986 imap_set_flag (idata, IMAP_ACL_DELETE, ctx->hdrs[n]->deleted,
987 "\\Deleted ", flags, sizeof (flags));
989 /* now make sure we don't lose custom tags */
990 if (mutt_bit_isset (idata->rights, IMAP_ACL_WRITE))
991 imap_add_keywords (flags, ctx->hdrs[n], idata->flags, sizeof (flags));
993 mutt_remove_trailing_ws (flags);
995 /* UW-IMAP is OK with null flags, Cyrus isn't. The only solution is to
996 * explicitly revoke all system flags (if we have permission) */
998 imap_set_flag (idata, IMAP_ACL_SEEN, 1, "\\Seen ", flags,
1000 imap_set_flag (idata, IMAP_ACL_WRITE, 1, "\\Flagged ", flags,
1002 imap_set_flag (idata, IMAP_ACL_WRITE, 1, "\\Answered ", flags,
1004 imap_set_flag (idata, IMAP_ACL_DELETE, 1, "\\Deleted ", flags,
1007 mutt_remove_trailing_ws (flags);
1009 mutt_buffer_addstr (&cmd, " -FLAGS.SILENT (");
1012 mutt_buffer_addstr (&cmd, " FLAGS.SILENT (");
1014 mutt_buffer_addstr (&cmd, flags);
1015 mutt_buffer_addstr (&cmd, ")");
1017 /* dumb hack for bad UW-IMAP 4.7 servers spurious FLAGS updates */
1018 ctx->hdrs[n]->active = 0;
1020 /* after all this it's still possible to have no flags, if you
1021 * have no ACL rights */
1022 if (*flags && (imap_exec (idata, cmd.data, 0) != 0) &&
1023 (err_continue != M_YES)) {
1024 err_continue = imap_continue ("imap_sync_mailbox: STORE failed",
1026 if (err_continue != M_YES) {
1032 ctx->hdrs[n]->active = 1;
1037 /* We must send an EXPUNGE command if we're not closing. */
1038 if (expunge && !(ctx->closing) &&
1039 mutt_bit_isset (idata->rights, IMAP_ACL_DELETE)) {
1040 mutt_message _("Expunging messages from server...");
1042 /* Set expunge bit so we don't get spurious reopened messages */
1043 idata->reopen |= IMAP_EXPUNGE_EXPECTED;
1044 if (imap_exec (idata, "EXPUNGE", 0) != 0) {
1045 imap_error (_("imap_sync_mailbox: EXPUNGE failed"), idata->cmd.buf);
1056 mx_fastclose_mailbox (appendctx);
1062 /* imap_close_mailbox: issue close command if neccessary, reset IMAP_DATA */
1063 void imap_close_mailbox (CONTEXT * ctx)
1068 idata = (IMAP_DATA *) ctx->data;
1069 /* Check to see if the mailbox is actually open */
1073 if ((idata->status != IMAP_FATAL) &&
1074 (idata->state == IMAP_SELECTED) && (ctx == idata->ctx)) {
1075 if (!(idata->noclose) && imap_exec (idata, "CLOSE", 0))
1076 mutt_error (_("CLOSE failed"));
1078 idata->reopen &= IMAP_REOPEN_ALLOW;
1079 idata->state = IMAP_AUTHENTICATED;
1080 FREE (&(idata->mailbox));
1081 mutt_free_list (&idata->flags);
1085 /* free IMAP part of headers */
1086 for (i = 0; i < ctx->msgcount; i++)
1087 imap_free_header_data (&(ctx->hdrs[i]->data));
1089 for (i = 0; i < IMAP_CACHE_LEN; i++) {
1090 if (idata->cache[i].path) {
1091 unlink (idata->cache[i].path);
1092 FREE (&idata->cache[i].path);
1097 /* use the NOOP command to poll for new mail
1100 * M_REOPENED mailbox has been externally modified
1101 * M_NEW_MAIL new mail has arrived!
1105 int imap_check_mailbox (CONTEXT * ctx, int *index_hint, int force)
1107 /* overload keyboard timeout to avoid many mailbox checks in a row.
1108 * Most users don't like having to wait exactly when they press a key. */
1112 idata = (IMAP_DATA *) ctx->data;
1114 if ((force || time (NULL) > idata->lastread + Timeout)
1115 && imap_exec (idata, "NOOP", 0) != 0)
1118 /* We call this even when we haven't run NOOP in case we have pending
1119 * changes to process, since we can reopen here. */
1120 imap_cmd_finish (idata);
1122 if (idata->check_status & IMAP_EXPUNGE_PENDING)
1123 result = M_REOPENED;
1124 else if (idata->check_status & IMAP_NEWMAIL_PENDING)
1125 result = M_NEW_MAIL;
1126 else if (idata->check_status & IMAP_FLAGS_PENDING)
1129 idata->check_status = 0;
1134 /* returns count of recent messages if new = 1, else count of total messages.
1135 * (useful for at least postponed function)
1136 * Question of taste: use RECENT or UNSEEN for new?
1137 * 0+ number of messages in mailbox
1138 * -1 error while polling mailboxes
1140 int imap_mailbox_check (char *path, int new)
1144 char buf[LONG_STRING];
1145 char mbox[LONG_STRING];
1146 char mbox_unquoted[LONG_STRING];
1153 if (imap_parse_path (path, &mx))
1156 /* If imap_passive is set, don't open a connection to check for new mail */
1157 if (option (OPTIMAPPASSIVE))
1158 connflags = M_IMAP_CONN_NONEW;
1160 if (!(idata = imap_conn_find (&(mx.account), connflags))) {
1166 imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
1169 imap_munge_mbox_name (mbox, sizeof (mbox), buf);
1170 strfcpy (mbox_unquoted, buf, sizeof (mbox_unquoted));
1172 /* The draft IMAP implementor's guide warns againts using the STATUS
1173 * command on a mailbox that you have selected
1176 if (mutt_strcmp (mbox_unquoted, idata->mailbox) == 0
1177 || (ascii_strcasecmp (mbox_unquoted, "INBOX") == 0
1178 && mutt_strcasecmp (mbox_unquoted, idata->mailbox) == 0)) {
1179 strfcpy (buf, "NOOP", sizeof (buf));
1181 else if (mutt_bit_isset (idata->capabilities, IMAP4REV1) ||
1182 mutt_bit_isset (idata->capabilities, STATUS)) {
1183 snprintf (buf, sizeof (buf), "STATUS %s (%s)", mbox,
1184 new ? "RECENT" : "MESSAGES");
1187 /* Server does not support STATUS, and this is not the current mailbox.
1188 * There is no lightweight way to check recent arrivals */
1191 imap_cmd_start (idata, buf);
1194 if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
1197 s = imap_next_word (idata->cmd.buf);
1198 if (ascii_strncasecmp ("STATUS", s, 6) == 0) {
1199 s = imap_next_word (s);
1200 /* The mailbox name may or may not be quoted here. We could try to
1201 * munge the server response and compare with quoted (or vise versa)
1202 * but it is probably more efficient to just strncmp against both. */
1203 if (mutt_strncmp (mbox_unquoted, s, mutt_strlen (mbox_unquoted)) == 0
1204 || mutt_strncmp (mbox, s, mutt_strlen (mbox)) == 0) {
1205 s = imap_next_word (s);
1206 s = imap_next_word (s);
1207 if (isdigit ((unsigned char) *s)) {
1209 msgcount = atoi (s);
1211 (debugfile, "%d new messages in %s\n", msgcount, path));
1218 "imap_mailbox_check: STATUS response doesn't match requested mailbox.\n"));
1221 while (rc == IMAP_CMD_CONTINUE);
1226 /* all this listing/browsing is a mess. I don't like that name is a pointer
1227 * into idata->buf (used to be a pointer into the passed in buffer, just
1228 * as bad), nor do I like the fact that the fetch is done here. This
1229 * code can't possibly handle non-LIST untagged responses properly.
1231 int imap_parse_list_response (IMAP_DATA * idata, char **name, int *noselect,
1232 int *noinferiors, char *delim)
1240 rc = imap_cmd_step (idata);
1241 if (rc == IMAP_CMD_OK)
1243 if (rc != IMAP_CMD_CONTINUE)
1246 s = imap_next_word (idata->cmd.buf);
1247 if ((ascii_strncasecmp ("LIST", s, 4) == 0) ||
1248 (ascii_strncasecmp ("LSUB", s, 4) == 0)) {
1252 s = imap_next_word (s); /* flags */
1258 while (*ep && *ep != ')')
1261 if (!ascii_strncasecmp (s, "\\NoSelect", 9))
1263 if (!ascii_strncasecmp (s, "\\NoInferiors", 12))
1265 /* See draft-gahrns-imap-child-mailbox-?? */
1266 if (!ascii_strncasecmp (s, "\\HasNoChildren", 14))
1270 while (*s && *s != '\\' && *s != ')')
1276 s = imap_next_word (s); /* delim */
1277 /* Reset the delimiter, this can change */
1278 if (ascii_strncasecmp (s, "NIL", 3)) {
1279 if (s && s[0] == '\"' && s[1] && s[2] == '\"')
1281 else if (s && s[0] == '\"' && s[1] && s[1] == '\\' && s[2]
1285 s = imap_next_word (s); /* name */
1286 if (s && *s == '{') { /* Literal */
1287 if (imap_get_literal_count (idata->cmd.buf, &bytes) < 0)
1289 if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE)
1291 *name = idata->cmd.buf;
1300 int imap_subscribe (char *path, int subscribe)
1304 char buf[LONG_STRING];
1305 char mbox[LONG_STRING];
1308 if (!mx_is_imap (path) || imap_parse_path (path, &mx)) {
1309 mutt_error (_("Bad mailbox name"));
1314 if (!(idata = imap_conn_find (&(mx.account), 0)))
1319 imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
1321 mutt_message (_("Subscribing to %s..."), buf);
1323 mutt_message (_("Unsubscribing to %s..."), buf);
1324 imap_munge_mbox_name (mbox, sizeof (mbox), buf);
1326 snprintf (buf, sizeof (buf), "%s %s", subscribe ? "SUBSCRIBE" :
1327 "UNSUBSCRIBE", mbox);
1329 if (imap_exec (idata, buf, 0) < 0)
1340 /* imap_complete: given a partial IMAP folder path, return a string which
1341 * adds as much to the path as is unique */
1342 int imap_complete (char *dest, size_t dlen, char *path)
1346 char list[LONG_STRING];
1347 char buf[LONG_STRING];
1348 char *list_word = NULL;
1349 int noselect, noinferiors;
1351 char completion[LONG_STRING];
1352 int clen, matchlen = 0;
1353 int completions = 0;
1357 /* verify passed in path is an IMAP path */
1358 if (imap_parse_path (path, &mx)) {
1359 dprint (2, (debugfile, "imap_complete: bad path %s\n", path));
1363 /* don't open a new socket just for completion */
1364 if (!(idata = imap_conn_find (&(mx.account), M_IMAP_CONN_NONEW)))
1368 /* reformat path for IMAP list, and append wildcard */
1369 /* don't use INBOX in place of "" */
1370 if (mx.mbox && mx.mbox[0])
1371 imap_fix_path (idata, mx.mbox, list, sizeof (list));
1375 /* fire off command */
1376 snprintf (buf, sizeof (buf), "%s \"\" \"%s%%\"",
1377 option (OPTIMAPLSUB) ? "LSUB" : "LIST", list);
1379 imap_cmd_start (idata, buf);
1381 /* and see what the results are */
1382 strfcpy (completion, NONULL (mx.mbox), sizeof (completion));
1384 if (imap_parse_list_response (idata, &list_word, &noselect, &noinferiors,
1389 /* store unquoted */
1390 imap_unmunge_mbox_name (list_word);
1392 /* if the folder isn't selectable, append delimiter to force browse
1393 * to enter it on second tab. */
1395 clen = mutt_strlen (list_word);
1396 list_word[clen++] = delim;
1397 list_word[clen] = '\0';
1399 /* copy in first word */
1401 strfcpy (completion, list_word, sizeof (completion));
1402 matchlen = mutt_strlen (completion);
1408 while (pos < matchlen && list_word[pos] &&
1409 completion[pos] == list_word[pos])
1411 completion[pos] = '\0';
1417 while (ascii_strncmp (idata->cmd.seq, idata->cmd.buf, SEQLEN));
1420 /* reformat output */
1421 imap_qualify_path (dest, dlen, &mx, completion);
1422 mutt_pretty_mailbox (dest);
1433 /* reconnect if connection was lost */
1434 int imap_reconnect (CONTEXT * ctx)
1436 IMAP_DATA *imap_data = (IMAP_DATA *) ctx->data;
1439 if (imap_data->status == IMAP_CONNECTED)
1443 if (query_quadoption
1445 _("Connection lost. Reconnect to IMAP server?")) != M_YES)
1448 mx_open_mailbox (ctx->path, 0, ctx);