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>
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. */
14 #include <lib-lib/lib-lib.h>
15 #include <lib-mx/mx.h>
22 #include "imap_private.h"
23 #if defined(USE_SSL) || defined(USE_GNUTLS)
24 # include <lib-sys/mutt_ssl.h>
28 /* imap forward declarations */
29 static int imap_get_delim (IMAP_DATA * idata);
30 static char *imap_get_flags (string_list_t ** hflags, char *s);
31 static int imap_check_acl (IMAP_DATA * idata);
32 static int imap_check_capabilities (IMAP_DATA * idata);
33 static void imap_set_flag (IMAP_DATA * idata, int aclbit, int flag,
34 const char *str, char *flags, size_t flsize);
36 /* imap_access: Check permissions on an IMAP mailbox.
37 * TODO: ACL checks. Right now we assume if it exists we can
39 static int imap_access (const char *path, int flags __attribute__ ((unused)))
43 char buf[LONG_STRING];
44 char mailbox[LONG_STRING];
45 char mbox[LONG_STRING];
47 if (imap_parse_path (path, &mx))
50 if (!(idata = imap_conn_find (&mx.account,
51 option (OPTIMAPPASSIVE) ? M_IMAP_CONN_NONEW :
57 imap_fix_path (idata, mx.mbox, mailbox, sizeof (mailbox));
59 /* we may already be in the folder we're checking */
60 if (!m_strcmp(idata->mailbox, mx.mbox)) {
66 imap_munge_mbox_name (mbox, sizeof (mbox), mailbox);
68 if (mutt_bit_isset (idata->capabilities, IMAP4REV1))
69 snprintf (buf, sizeof (buf), "STATUS %s (UIDVALIDITY)", mbox);
70 else if (mutt_bit_isset (idata->capabilities, STATUS))
71 snprintf (buf, sizeof (buf), "STATUS %s (UID-VALIDITY)", mbox);
76 if (imap_exec (idata, buf, IMAP_CMD_FAIL_OK) < 0) {
83 int imap_create_mailbox (IMAP_DATA * idata, char *mailbox)
85 char buf[LONG_STRING], mbox[LONG_STRING];
87 imap_munge_mbox_name (mbox, sizeof (mbox), mailbox);
88 snprintf (buf, sizeof (buf), "CREATE %s", mbox);
90 if (imap_exec (idata, buf, 0) != 0)
96 int imap_rename_mailbox (IMAP_DATA * idata, IMAP_MBOX * mx,
99 char oldmbox[LONG_STRING];
100 char newmbox[LONG_STRING];
101 char buf[LONG_STRING];
103 imap_munge_mbox_name (oldmbox, sizeof (oldmbox), mx->mbox);
104 imap_munge_mbox_name (newmbox, sizeof (newmbox), newname);
106 snprintf (buf, sizeof (buf), "RENAME %s %s", oldmbox, newmbox);
108 if (imap_exec (idata, buf, 0) != 0)
114 int imap_delete_mailbox (CONTEXT * ctx, IMAP_MBOX mx)
116 char buf[LONG_STRING], mbox[LONG_STRING];
119 if (!ctx || !ctx->data) {
120 if (!(idata = imap_conn_find (&mx.account,
121 option (OPTIMAPPASSIVE) ? M_IMAP_CONN_NONEW
131 imap_munge_mbox_name (mbox, sizeof (mbox), mx.mbox);
132 snprintf (buf, sizeof (buf), "DELETE %s", mbox);
134 if (imap_exec ((IMAP_DATA *) idata, buf, 0) != 0)
140 /* imap_logout_all: close all open connections. Quick and dirty until we can
141 * make sure we've got all the context we need. */
142 void imap_logout_all (void)
147 conn = mutt_socket_head ();
152 if (conn->account.type == M_ACCT_TYPE_IMAP && conn->fd >= 0) {
153 mutt_message (_("Closing connection to %s..."), conn->account.host);
154 imap_logout ((IMAP_DATA *) conn->data);
156 mutt_socket_close (conn);
157 mutt_socket_free (conn);
164 /* imap_read_literal: read bytes bytes from server into file. Not explicitly
165 * buffered, relies on FILE buffering. NOTE: strips \r from \r\n.
166 * Apparently even literals use \r\n-terminated strings ?! */
167 int imap_read_literal (FILE * fp, IMAP_DATA * idata, long bytes, progress_t* bar)
174 for (pos = 0; pos < bytes; pos++) {
175 if (mutt_socket_readchar (idata->conn, &c) != 1) {
176 idata->status = IMAP_FATAL;
181 if (r == 1 && c != '\n')
192 if (bar && !(pos % 1024))
193 mutt_progress_bar (bar, pos);
199 /* imap_expunge_mailbox: Purge IMAP portion of expunged messages from the
200 * context. Must not be done while something has a handle on any headers
201 * (eg inside pager or editor). That is, check IMAP_REOPEN_ALLOW. */
202 void imap_expunge_mailbox (IMAP_DATA * idata)
207 for (i = 0; i < idata->ctx->msgcount; i++) {
208 h = idata->ctx->hdrs[i];
210 if (h->index == -1) {
213 /* free cached body from disk, if neccessary */
214 cacheno = HEADER_DATA (h)->uid % IMAP_CACHE_LEN;
215 if (idata->cache[cacheno].uid == HEADER_DATA (h)->uid &&
216 idata->cache[cacheno].path) {
217 unlink (idata->cache[cacheno].path);
218 p_delete(&idata->cache[cacheno].path);
221 imap_free_header_data (&h->data);
225 /* We may be called on to expunge at any time. We can't rely on the caller
226 * to always know to rethread */
227 mx_update_tables (idata->ctx, 0);
228 mutt_sort_headers (idata->ctx, 1);
231 static int imap_get_delim (IMAP_DATA * idata)
236 /* assume that the delim is /. If this fails, we're in bigger trouble
237 * than getting the delim wrong */
240 imap_cmd_start (idata, "string_list_t \"\" \"\"");
243 if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
246 s = imap_next_word (idata->cmd.buf);
247 if (ascii_strncasecmp ("string_list_t", s, 4) == 0) {
248 s = imap_next_word (s);
249 s = imap_next_word (s);
250 if (s && s[0] == '\"' && s[1] && s[2] == '\"')
252 else if (s && s[0] == '\"' && s[1] && s[1] == '\\' && s[2]
257 while (rc == IMAP_CMD_CONTINUE);
262 /* get rights for folder, let imap_handle_untagged do the rest */
263 static int imap_check_acl (IMAP_DATA * idata)
265 char buf[LONG_STRING];
266 char mbox[LONG_STRING];
268 imap_munge_mbox_name (mbox, sizeof (mbox), idata->mailbox);
269 snprintf (buf, sizeof (buf), "MYRIGHTS %s", mbox);
270 if (imap_exec (idata, buf, 0) != 0) {
271 imap_error ("imap_check_acl", buf);
277 /* imap_check_capabilities: make sure we can log in to this server. */
278 static int imap_check_capabilities (IMAP_DATA * idata)
280 if (imap_exec (idata, "CAPABILITY", 0) != 0) {
281 imap_error ("imap_check_capabilities", idata->cmd.buf);
285 if (!(mutt_bit_isset (idata->capabilities, IMAP4)
286 || mutt_bit_isset (idata->capabilities, IMAP4REV1))) {
287 mutt_error _("This IMAP server is ancient. Mutt does not work with it.");
289 mutt_sleep (2); /* pause a moment to let the user see the error */
297 /* imap_conn_find: Find an open IMAP connection matching account, or open
298 * a new one if none can be found. */
299 IMAP_DATA *imap_conn_find (const ACCOUNT * account, int flags)
306 if (!(conn = mutt_conn_find (NULL, account)))
309 /* if opening a new UNSELECTED connection, preserve existing creds */
310 creds = &(conn->account);
312 /* make sure this connection is not in SELECTED state, if neccessary */
313 if (flags & M_IMAP_CONN_NOSELECT)
314 while (conn->data && ((IMAP_DATA *) conn->data)->state == IMAP_SELECTED) {
315 if (!(conn = mutt_conn_find (conn, account)))
317 memcpy (&(conn->account), creds, sizeof (ACCOUNT));
320 idata = (IMAP_DATA *) conn->data;
322 /* don't open a new connection if one isn't wanted */
323 if (flags & M_IMAP_CONN_NONEW) {
325 mutt_socket_free (conn);
328 if (idata->state < IMAP_AUTHENTICATED)
333 /* The current connection is a new connection */
334 if (!(idata = imap_new_idata ())) {
335 mutt_socket_free (conn);
344 if (idata->state == IMAP_DISCONNECTED)
345 imap_open_connection (idata);
346 if (idata->state == IMAP_CONNECTED) {
347 if (!imap_authenticate (idata)) {
348 idata->state = IMAP_AUTHENTICATED;
350 mutt_account_unsetpass (&idata->conn->account);
353 p_delete(&idata->capstr);
355 if (new && idata->state == IMAP_AUTHENTICATED) {
356 imap_get_delim (idata);
357 if (option (OPTIMAPCHECKSUBSCRIBED)) {
358 mutt_message _("Checking mailbox subscriptions");
359 imap_exec (idata, "LSUB \"\" \"*\"", 0);
366 int imap_open_connection (IMAP_DATA * idata)
368 char buf[LONG_STRING];
370 if (mutt_socket_open (idata->conn) < 0)
373 idata->state = IMAP_CONNECTED;
375 if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE) {
376 mutt_socket_close (idata->conn);
377 idata->state = IMAP_DISCONNECTED;
381 if (ascii_strncasecmp ("* OK", idata->cmd.buf, 4) == 0) {
382 /* TODO: Parse new tagged CAPABILITY data (* OK [CAPABILITY...]) */
383 if (imap_check_capabilities (idata))
385 #if defined(USE_SSL) || defined(USE_GNUTLS)
386 /* Attempt STARTTLS if available and desired. */
387 if (!idata->conn->ssf && (option(OPTSSLFORCETLS) ||
388 mutt_bit_isset (idata->capabilities, STARTTLS))) {
391 if (option (OPTSSLFORCETLS))
393 else if ((rc = query_quadoption (OPT_SSLSTARTTLS,
394 _("Secure connection with TLS?"))) == -1)
397 if ((rc = imap_exec (idata, "STARTTLS", IMAP_CMD_FAIL_OK)) == -1)
400 #if defined (USE_SSL) || defined (USE_GNUTLS)
401 if (mutt_ssl_starttls (idata->conn))
404 mutt_error (_("Could not negotiate TLS connection"));
409 /* RFC 2595 demands we recheck CAPABILITY after TLS completes. */
410 if (imap_exec (idata, "CAPABILITY", 0))
417 if (option(OPTSSLFORCETLS) && ! idata->conn->ssf) {
418 mutt_error _("Encrypted connection unavailable");
424 else if (ascii_strncasecmp ("* PREAUTH", idata->cmd.buf, 9) == 0) {
425 idata->state = IMAP_AUTHENTICATED;
426 if (imap_check_capabilities (idata) != 0)
428 p_delete(&idata->capstr);
431 imap_error ("imap_open_connection()", buf);
438 mutt_socket_close (idata->conn);
439 idata->state = IMAP_DISCONNECTED;
441 p_delete(&idata->capstr);
445 /* imap_get_flags: Make a simple list out of a FLAGS response.
446 * return stream following FLAGS response */
447 static char *imap_get_flags (string_list_t ** hflags, char *s)
449 string_list_t *flags;
453 /* sanity-check string */
454 if (ascii_strncasecmp ("FLAGS", s, 5) != 0) {
457 s = vskipspaces(s + 5);
462 /* create list, update caller's flags handle */
463 flags = string_item_new();
466 while (*s && *s != ')') {
467 s = vskipspaces(s + 1);
469 while (*s && (*s != ')') && !ISSPACE (*s))
474 mutt_add_list (flags, flag_word);
478 /* note bad flags response */
480 string_list_wipe(hflags);
490 static int imap_open_mailbox (CONTEXT * ctx)
494 char buf[LONG_STRING];
495 char bufout[LONG_STRING];
500 if (imap_parse_path (ctx->path, &mx)) {
501 mutt_error (_("%s is an invalid IMAP path"), ctx->path);
505 /* we require a connection which isn't currently in IMAP_SELECTED state */
506 if (!(idata = imap_conn_find (&(mx.account), M_IMAP_CONN_NOSELECT)))
508 if (idata->state < IMAP_AUTHENTICATED)
513 /* once again the context is new */
516 /* Clean up path and replace the one in the ctx */
517 imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
518 p_delete(&(idata->mailbox));
519 idata->mailbox = m_strdup(buf);
520 imap_qualify_path (buf, sizeof (buf), &mx, idata->mailbox);
522 p_delete(&(ctx->path));
523 ctx->path = m_strdup(buf);
527 /* clear mailbox status */
529 memset (idata->rights, 0, (RIGHTSMAX + 7) / 8);
530 idata->newMailCount = 0;
532 mutt_message (_("Selecting %s..."), idata->mailbox);
533 imap_munge_mbox_name (buf, sizeof (buf), idata->mailbox);
534 snprintf (bufout, sizeof (bufout), "%s %s",
535 ctx->readonly ? "EXAMINE" : "SELECT", buf);
537 idata->state = IMAP_SELECTED;
539 imap_cmd_start (idata, bufout);
544 if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
547 pc = idata->cmd.buf + 2;
549 /* Obtain list of available flags here, may be overridden by a
550 * PERMANENTFLAGS tag in the OK response */
551 if (ascii_strncasecmp ("FLAGS", pc, 5) == 0) {
552 /* don't override PERMANENTFLAGS */
554 if ((pc = imap_get_flags (&(idata->flags), pc)) == NULL)
558 /* PERMANENTFLAGS are massaged to look like FLAGS, then override FLAGS */
559 else if (ascii_strncasecmp ("OK [PERMANENTFLAGS", pc, 18) == 0) {
560 /* safe to call on NULL */
561 string_list_wipe(&(idata->flags));
562 /* skip "OK [PERMANENT" so syntax is the same as FLAGS */
564 if ((pc = imap_get_flags (&(idata->flags), pc)) == NULL)
568 /* save UIDVALIDITY for the header cache */
569 else if (ascii_strncasecmp ("OK [UIDVALIDITY", pc, 14) == 0) {
571 pc = imap_next_word (pc);
573 sscanf (pc, "%lu", &(idata->uid_validity));
577 pc = imap_next_word (pc);
578 if (!ascii_strncasecmp ("EXISTS", pc, 6)) {
579 count = idata->newMailCount;
580 idata->newMailCount = 0;
584 while (rc == IMAP_CMD_CONTINUE);
586 if (rc == IMAP_CMD_NO) {
589 s = imap_next_word (idata->cmd.buf); /* skip seq */
590 s = imap_next_word (s); /* Skip response */
591 mutt_error ("%s", s);
596 if (rc != IMAP_CMD_OK)
599 /* check for READ-ONLY notification */
600 if (!ascii_strncasecmp
601 (imap_get_qualifier (idata->cmd.buf), "[READ-ONLY]", 11)
602 && !mutt_bit_isset (idata->capabilities, ACL)) {
606 if (mutt_bit_isset (idata->capabilities, ACL)) {
607 if (imap_check_acl (idata))
609 if (!(mutt_bit_isset (idata->rights, ACL_DELETE) ||
610 mutt_bit_isset (idata->rights, ACL_SEEN) ||
611 mutt_bit_isset (idata->rights, ACL_WRITE) ||
612 mutt_bit_isset (idata->rights, ACL_INSERT)))
615 /* assume we have all rights if ACL is unavailable */
617 mutt_bit_set (idata->rights, ACL_LOOKUP);
618 mutt_bit_set (idata->rights, ACL_READ);
619 mutt_bit_set (idata->rights, ACL_SEEN);
620 mutt_bit_set (idata->rights, ACL_WRITE);
621 mutt_bit_set (idata->rights, ACL_INSERT);
622 mutt_bit_set (idata->rights, ACL_POST);
623 mutt_bit_set (idata->rights, ACL_CREATE);
624 mutt_bit_set (idata->rights, ACL_DELETE);
628 ctx->hdrs = p_new(HEADER *, count);
629 ctx->v2r = p_new(int, count);
631 if (count && (imap_read_headers (idata, 0, count - 1) < 0)) {
632 mutt_error _("Error opening mailbox");
642 if (idata->state == IMAP_SELECTED)
643 idata->state = IMAP_AUTHENTICATED;
649 int imap_open_mailbox_append (CONTEXT * ctx)
653 char buf[LONG_STRING];
654 char mailbox[LONG_STRING];
657 if (imap_parse_path (ctx->path, &mx))
660 /* in APPEND mode, we appear to hijack an existing IMAP connection -
661 * ctx is brand new and mostly empty */
663 if (!(idata = imap_conn_find (&(mx.account), 0))) {
672 imap_fix_path (idata, mx.mbox, mailbox, sizeof (mailbox));
676 /* really we should also check for W_OK */
677 if (!imap_access (ctx->path, F_OK))
680 snprintf (buf, sizeof (buf), _("Create %s?"), mailbox);
681 if (option (OPTCONFIRMCREATE) && mutt_yesorno (buf, 1) < 1)
684 if (imap_create_mailbox (idata, mailbox) < 0)
690 /* imap_logout: Gracefully log out of server. */
691 void imap_logout (IMAP_DATA * idata)
693 /* we set status here to let imap_handle_untagged know we _expect_ to
694 * receive a bye response (so it doesn't freak out and close the conn) */
695 idata->status = IMAP_BYE;
696 imap_cmd_start (idata, "LOGOUT");
697 while (imap_cmd_step (idata) == IMAP_CMD_CONTINUE);
698 p_delete(&idata->cmd.buf);
703 int imap_close_connection (CONTEXT *ctx)
705 if (CTX_DATA->status != IMAP_BYE)
707 mutt_message _("Closing connection to IMAP server...");
708 imap_logout (CTX_DATA);
711 mutt_socket_close (CTX_DATA->conn);
712 CTX_DATA->state = IMAP_DISCONNECTED;
713 CTX_DATA->conn->data = NULL;
718 /* imap_set_flag: append str to flags if we currently have permission
719 * according to aclbit */
720 static void imap_set_flag(IMAP_DATA *idata, int aclbit, int flag,
721 const char *str, char *flags, size_t flsize)
723 if (mutt_bit_isset(idata->rights, aclbit)) {
725 m_strcat(flags, flsize, str);
729 /* imap_make_msg_set: make an IMAP4rev1 UID message set out of a set of
730 * headers, given a flag enum to filter on.
731 * Params: idata: IMAP_DATA containing context containing header set
732 * buf: to write message set into
733 * buflen: length of buffer
734 * flag: enum of flag type on which to filter
735 * changed: include only changed messages in message set
736 * Returns: number of messages in message set (0 if no matches) */
737 int imap_make_msg_set (IMAP_DATA * idata, BUFFER * buf, int flag, int changed)
739 HEADER **hdrs; /* sorted local copy */
740 int count = 0; /* number of messages in message set */
741 int match = 0; /* whether current message matches flag condition */
742 int setstart = 0; /* start of current message range */
744 short oldsort; /* we clobber reverse, must restore it */
746 /* assuming 32-bit UIDs */
750 /* make copy of header pointers to sort in natural order */
751 hdrs = p_dup(idata->ctx->hdrs, idata->ctx->msgcount);
753 if (Sort != SORT_ORDER) {
756 qsort ((void *) hdrs, idata->ctx->msgcount, sizeof (HEADER *),
757 mutt_get_sort_func (SORT_ORDER));
761 for (n = 0; n < idata->ctx->msgcount; n++) {
763 /* don't include pending expunged messages */
767 if (hdrs[n]->deleted)
776 if (match && (!changed || hdrs[n]->changed)) {
779 setstart = HEADER_DATA (hdrs[n])->uid;
781 snprintf (uid, sizeof (uid), "%u", HEADER_DATA (hdrs[n])->uid);
782 mutt_buffer_addstr (buf, uid);
786 snprintf (uid, sizeof (uid), ",%u", HEADER_DATA (hdrs[n])->uid);
787 mutt_buffer_addstr (buf, uid);
790 /* tie up if the last message also matches */
791 else if (n == idata->ctx->msgcount - 1) {
792 snprintf (uid, sizeof (uid), ":%u", HEADER_DATA (hdrs[n])->uid);
793 mutt_buffer_addstr (buf, uid);
796 /* this message is not expunged and doesn't match. End current set. */
797 else if (setstart && hdrs[n]->active) {
798 if (HEADER_DATA (hdrs[n - 1])->uid > setstart) {
799 snprintf (uid, sizeof (uid), ":%u", HEADER_DATA (hdrs[n - 1])->uid);
800 mutt_buffer_addstr (buf, uid);
811 /* Update the IMAP server to reflect the flags a single message. */
813 int imap_sync_message (IMAP_DATA *idata, HEADER *hdr, BUFFER *cmd,
816 char flags[LONG_STRING];
821 snprintf (uid, sizeof (uid), "%u", HEADER_DATA(hdr)->uid);
822 cmd->dptr = cmd->data;
823 mutt_buffer_addstr (cmd, "UID STORE ");
824 mutt_buffer_addstr (cmd, uid);
828 imap_set_flag (idata, ACL_SEEN, hdr->read, "\\Seen ",
829 flags, sizeof (flags));
830 imap_set_flag (idata, ACL_WRITE, hdr->flagged,
831 "\\Flagged ", flags, sizeof (flags));
832 imap_set_flag (idata, ACL_WRITE, hdr->replied,
833 "\\Answered ", flags, sizeof (flags));
834 imap_set_flag (idata, ACL_DELETE, hdr->deleted,
835 "\\Deleted ", flags, sizeof (flags));
837 /* now make sure we don't lose custom tags */
838 if (mutt_bit_isset (idata->rights, ACL_WRITE))
839 imap_add_keywords (flags, hdr, idata->flags, sizeof (flags));
843 /* UW-IMAP is OK with null flags, Cyrus isn't. The only solution is to
844 * explicitly revoke all system flags (if we have permission) */
847 imap_set_flag (idata, ACL_SEEN, 1, "\\Seen ", flags, sizeof (flags));
848 imap_set_flag (idata, ACL_WRITE, 1, "\\Flagged ", flags, sizeof (flags));
849 imap_set_flag (idata, ACL_WRITE, 1, "\\Answered ", flags, sizeof (flags));
850 imap_set_flag (idata, ACL_DELETE, 1, "\\Deleted ", flags, sizeof (flags));
854 mutt_buffer_addstr (cmd, " -FLAGS.SILENT (");
856 mutt_buffer_addstr (cmd, " FLAGS.SILENT (");
858 mutt_buffer_addstr (cmd, flags);
859 mutt_buffer_addstr (cmd, ")");
861 /* dumb hack for bad UW-IMAP 4.7 servers spurious FLAGS updates */
864 /* after all this it's still possible to have no flags, if you
865 * have no ACL rights */
866 if (*flags && (imap_exec (idata, cmd->data, 0) != 0) &&
867 err_continue && (*err_continue != M_YES))
869 *err_continue = imap_continue ("imap_sync_message: STORE failed",
871 if (*err_continue != M_YES)
876 idata->ctx->changed--;
881 /* update the IMAP server to reflect message changes done within mutt.
883 * ctx: the current context
884 * expunge: 0 or 1 - do expunge?
887 int imap_sync_mailbox (CONTEXT * ctx, int expunge, int *index_hint)
890 CONTEXT *appendctx = NULL;
894 int err_continue = M_NO; /* continue on error? */
897 idata = (IMAP_DATA *) ctx->data;
899 if (idata->state != IMAP_SELECTED) {
903 /* This function is only called when the calling code expects the context
905 imap_allow_reopen (ctx);
907 if ((rc = imap_check_mailbox (ctx, index_hint, 0)) != 0)
912 /* if we are expunging anyway, we can do deleted messages very quickly... */
913 if (expunge && mutt_bit_isset (idata->rights, ACL_DELETE)) {
914 mutt_buffer_addstr (&cmd, "UID STORE ");
915 deleted = imap_make_msg_set (idata, &cmd, M_DELETE, 1);
917 /* if we have a message set, then let's delete */
919 mutt_message (_("Marking %d messages deleted..."), deleted);
920 mutt_buffer_addstr (&cmd, " +FLAGS.SILENT (\\Deleted)");
921 /* mark these messages as unchanged so second pass ignores them. Done
922 * here so BOGUS UW-IMAP 4.7 SILENT FLAGS updates are ignored. */
923 for (n = 0; n < ctx->msgcount; n++)
924 if (ctx->hdrs[n]->deleted && ctx->hdrs[n]->changed)
925 ctx->hdrs[n]->active = 0;
926 if (imap_exec (idata, cmd.data, 0) != 0) {
927 mutt_error (_("Expunge failed"));
935 /* save status changes */
936 for (n = 0; n < ctx->msgcount; n++) {
937 if (ctx->hdrs[n]->active && ctx->hdrs[n]->changed) {
939 mutt_message (_("Saving message status flags... [%d/%d]"), n + 1,
942 /* if the message has been rethreaded or attachments have been deleted
943 * we delete the message and reupload it.
944 * This works better if we're expunging, of course. */
945 if ((ctx->hdrs[n]->env && (ctx->hdrs[n]->env->refs_changed || ctx->hdrs[n]->env->irt_changed)) ||
946 ctx->hdrs[n]->attach_del) {
948 appendctx = mx_open_mailbox (ctx->path, M_APPEND | M_QUIET, NULL);
950 _mutt_save_message (ctx->hdrs[n], appendctx, 1, 0, 0);
954 if (imap_sync_message (idata, ctx->hdrs[n], &cmd, &err_continue) < 0) {
962 /* We must send an EXPUNGE command if we're not closing. */
963 if (expunge && !(ctx->closing) &&
964 mutt_bit_isset (idata->rights, ACL_DELETE)) {
965 mutt_message _("Expunging messages from server...");
967 /* Set expunge bit so we don't get spurious reopened messages */
968 idata->reopen |= IMAP_EXPUNGE_EXPECTED;
969 if (imap_exec (idata, "EXPUNGE", 0) != 0) {
970 imap_error (_("imap_sync_mailbox: EXPUNGE failed"), idata->cmd.buf);
971 rc = imap_reconnect (ctx);
976 if (expunge && ctx->closing) {
977 if (imap_exec (idata, "CLOSE", 0))
978 mutt_error (_("CLOSE failed"));
979 idata->state = IMAP_AUTHENTICATED;
987 mx_fastclose_mailbox (appendctx);
988 p_delete(&appendctx);
993 /* imap_close_mailbox: clean up IMAP data in CONTEXT */
994 static void imap_close_mailbox (CONTEXT * ctx)
999 idata = (IMAP_DATA *) ctx->data;
1000 /* Check to see if the mailbox is actually open */
1004 if (ctx == idata->ctx) {
1005 if (idata->state != IMAP_FATAL && idata->state == IMAP_SELECTED) {
1006 /* mx_close_mailbox won't sync if there are no deleted messages
1007 * and the mailbox is unchanged, so we may have to close here */
1008 if (!ctx->deleted && imap_exec (idata, "CLOSE", 0))
1009 mutt_error (_("CLOSE failed"));
1010 idata->state = IMAP_AUTHENTICATED;
1013 idata->reopen &= IMAP_REOPEN_ALLOW;
1014 p_delete(&(idata->mailbox));
1015 string_list_wipe(&idata->flags);
1019 /* free IMAP part of headers */
1020 for (i = 0; i < ctx->msgcount; i++)
1021 imap_free_header_data (&(ctx->hdrs[i]->data));
1023 for (i = 0; i < IMAP_CACHE_LEN; i++) {
1024 if (idata->cache[i].path) {
1025 unlink (idata->cache[i].path);
1026 p_delete(&idata->cache[i].path);
1031 /* use the NOOP command to poll for new mail
1034 * M_REOPENED mailbox has been externally modified
1035 * M_NEW_MAIL new mail has arrived!
1039 int imap_check_mailbox (CONTEXT * ctx, int *index_hint __attribute__ ((unused)), int force)
1041 /* overload keyboard timeout to avoid many mailbox checks in a row.
1042 * Most users don't like having to wait exactly when they press a key. */
1046 idata = (IMAP_DATA *) ctx->data;
1048 if ((force || time (NULL) >= idata->lastread + Timeout)
1049 && imap_exec (idata, "NOOP", 0) != 0)
1052 /* We call this even when we haven't run NOOP in case we have pending
1053 * changes to process, since we can reopen here. */
1054 imap_cmd_finish (idata);
1056 if (idata->check_status & IMAP_EXPUNGE_PENDING)
1057 result = M_REOPENED;
1058 else if (idata->check_status & IMAP_NEWMAIL_PENDING)
1059 result = M_NEW_MAIL;
1060 else if (idata->check_status & IMAP_FLAGS_PENDING)
1063 idata->check_status = 0;
1074 * 0+ number of messages in mailbox
1075 * -1 error while polling mailboxes
1077 int imap_mailbox_check (char *path, int new)
1081 char buf[LONG_STRING];
1082 char mbox[LONG_STRING];
1083 char mbox_unquoted[LONG_STRING];
1090 if (imap_parse_path (path, &mx))
1093 /* If imap_passive is set, don't open a connection to check for new mail */
1094 if (option (OPTIMAPPASSIVE))
1095 connflags = M_IMAP_CONN_NONEW;
1097 if (!(idata = imap_conn_find (&(mx.account), connflags))) {
1103 imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
1106 imap_munge_mbox_name (mbox, sizeof (mbox), buf);
1107 m_strcpy(mbox_unquoted, sizeof(mbox_unquoted), buf);
1109 /* The draft IMAP implementor's guide warns againts using the STATUS
1110 * command on a mailbox that you have selected
1113 if (m_strcmp(mbox_unquoted, idata->mailbox) == 0
1114 || (ascii_strcasecmp (mbox_unquoted, "INBOX") == 0
1115 && m_strcasecmp(mbox_unquoted, idata->mailbox) == 0)) {
1116 m_strcpy(buf, sizeof(buf), "NOOP");
1118 else if (mutt_bit_isset (idata->capabilities, IMAP4REV1) ||
1119 mutt_bit_isset (idata->capabilities, STATUS)) {
1120 snprintf (buf, sizeof (buf), "STATUS %s (%s)", mbox,
1121 new == 1 ? "RECENT" : (new == 2 ? "UNSEEN" : "MESSAGES"));
1124 /* Server does not support STATUS, and this is not the current mailbox.
1125 * There is no lightweight way to check recent arrivals */
1128 imap_cmd_start (idata, buf);
1131 if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
1134 s = imap_next_word (idata->cmd.buf);
1135 if (ascii_strncasecmp ("STATUS", s, 6) == 0) {
1136 s = imap_next_word (s);
1137 /* The mailbox name may or may not be quoted here. We could try to
1138 * munge the server response and compare with quoted (or vise versa)
1139 * but it is probably more efficient to just strncmp against both. */
1140 if (m_strncmp(mbox_unquoted, s, m_strlen(mbox_unquoted)) == 0
1141 || m_strncmp(mbox, s, m_strlen(mbox)) == 0) {
1142 s = imap_next_word (s);
1143 s = imap_next_word (s);
1144 if (isdigit ((unsigned char) *s)) {
1146 msgcount = atoi (s);
1152 while (rc == IMAP_CMD_CONTINUE);
1157 /* returns number of patterns in the search that should be done server-side
1158 * (eg are full-text) */
1159 static int do_search (const pattern_t* search, int allpats)
1162 const pattern_t* pat;
1164 for (pat = search; pat; pat = pat->next) {
1169 if (pat->stringmatch)
1173 if (pat->child && do_search (pat->child, 1))
1184 /* convert mutt pattern_t to IMAP SEARCH command containing only elements
1185 * that require full-text search (mutt already has what it needs for most
1186 * match types, and does a better job (eg server doesn't support regexps). */
1187 static int imap_compile_search (const pattern_t* pat, BUFFER* buf)
1189 if (! do_search (pat, 0))
1193 mutt_buffer_addstr (buf, "NOT ");
1198 if ((clauses = do_search (pat->child, 1)) > 0) {
1199 const pattern_t* clause = pat->child;
1201 mutt_buffer_addch (buf, '(');
1204 if (do_search (clause, 0)) {
1205 if (pat->op == M_OR && clauses > 1)
1206 mutt_buffer_addstr (buf, "OR ");
1208 if (imap_compile_search (clause, buf) < 0)
1212 mutt_buffer_addch (buf, ' ');
1214 clause = clause->next;
1218 mutt_buffer_addch (buf, ')');
1226 mutt_buffer_addstr (buf, "HEADER ");
1228 /* extract header name */
1229 if (! (delim = strchr (pat->str, ':'))) {
1230 mutt_error (_("Header search without header name: %s"), pat->str);
1234 imap_quote_string (term, sizeof (term), pat->str);
1235 mutt_buffer_addstr (buf, term);
1236 mutt_buffer_addch (buf, ' ');
1240 delim = vskipspaces(delim);
1241 imap_quote_string (term, sizeof (term), delim);
1242 mutt_buffer_addstr (buf, term);
1246 mutt_buffer_addstr (buf, "BODY ");
1247 imap_quote_string (term, sizeof (term), pat->str);
1248 mutt_buffer_addstr (buf, term);
1252 mutt_buffer_addstr (buf, "TEXT ");
1253 imap_quote_string (term, sizeof (term), pat->str);
1254 mutt_buffer_addstr (buf, term);
1262 int imap_search (CONTEXT* ctx, const pattern_t* pat) {
1264 IMAP_DATA* idata = (IMAP_DATA*)ctx->data;
1267 for (i = 0; i < ctx->msgcount; i++)
1268 ctx->hdrs[i]->matched = 0;
1270 if (!do_search (pat, 1))
1274 mutt_buffer_addstr (&buf, "UID SEARCH ");
1275 if (imap_compile_search (pat, &buf) < 0) {
1276 p_delete(&buf.data);
1279 if (imap_exec (idata, buf.data, 0) < 0) {
1280 p_delete(&buf.data);
1284 p_delete(&buf.data);
1288 /* all this listing/browsing is a mess. I don't like that name is a pointer
1289 * into idata->buf (used to be a pointer into the passed in buffer, just
1290 * as bad), nor do I like the fact that the fetch is done here. This
1291 * code can't possibly handle non-string_list_t untagged responses properly.
1293 int imap_parse_list_response (IMAP_DATA * idata, char **name, int *noselect,
1294 int *noinferiors, char *delim)
1302 rc = imap_cmd_step (idata);
1303 if (rc == IMAP_CMD_OK)
1305 if (rc != IMAP_CMD_CONTINUE)
1308 s = imap_next_word (idata->cmd.buf);
1309 if ((ascii_strncasecmp ("string_list_t", s, 4) == 0) ||
1310 (ascii_strncasecmp ("LSUB", s, 4) == 0)) {
1314 s = imap_next_word (s); /* flags */
1320 while (*ep && *ep != ')')
1323 if (!ascii_strncasecmp (s, "\\NoSelect", 9))
1325 if (!ascii_strncasecmp (s, "\\NoInferiors", 12))
1327 /* See draft-gahrns-imap-child-mailbox-?? */
1328 if (!ascii_strncasecmp (s, "\\HasNoChildren", 14))
1332 while (*s && *s != '\\' && *s != ')')
1338 s = imap_next_word (s); /* delim */
1339 /* Reset the delimiter, this can change */
1340 if (ascii_strncasecmp (s, "NIL", 3)) {
1341 if (s && s[0] == '\"' && s[1] && s[2] == '\"')
1343 else if (s && s[0] == '\"' && s[1] && s[1] == '\\' && s[2]
1347 s = imap_next_word (s); /* name */
1348 if (s && *s == '{') { /* Literal */
1349 if (imap_get_literal_count (idata->cmd.buf, &bytes) < 0)
1351 if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE)
1353 *name = idata->cmd.buf;
1362 int imap_subscribe (char *path, int subscribe)
1366 char buf[LONG_STRING];
1367 char mbox[LONG_STRING];
1368 char errstr[STRING];
1372 if (mx_get_magic (path) != M_IMAP || imap_parse_path (path, &mx) < 0) {
1373 mutt_error (_("Bad mailbox name"));
1377 if (!(idata = imap_conn_find (&(mx.account), 0)))
1382 imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
1384 if (option (OPTIMAPCHECKSUBSCRIBED)) {
1387 err.dsize = sizeof (errstr);
1388 snprintf (mbox, sizeof (mbox), "%smailboxes \"%s\"",
1389 subscribe ? "" : "un", path);
1390 mutt_parse_rc_line (mbox, &token, &err);
1391 p_delete(&token.data);
1395 mutt_message (_("Subscribing to %s..."), buf);
1397 mutt_message (_("Unsubscribing to %s..."), buf);
1398 imap_munge_mbox_name (mbox, sizeof (mbox), buf);
1400 snprintf (buf, sizeof (buf), "%sSUBSCRIBE %s", subscribe ? "" : "UN", mbox);
1402 if (imap_exec (idata, buf, 0) < 0)
1413 /* trim dest to the length of the longest prefix it shares with src,
1414 * returning the length of the trimmed string */
1415 static int longest_common_prefix (char *dest, const char* src,
1416 int start, ssize_t dlen) {
1419 while (pos < dlen && dest[pos] && dest[pos] == src[pos])
1426 /* look for IMAP URLs to complete from defined mailboxes. Could be extended
1427 * to complete over open connections and account/folder hooks too. */
1428 static int imap_complete_hosts (char *dest, ssize_t len) {
1435 matchlen = m_strlen(dest);
1438 for (i = 0; i < Incoming.len; i++) {
1439 mailbox = Incoming.arr[i];
1440 if (!m_strncmp(dest, mailbox->path, matchlen)) {
1442 m_strcpy(dest, len, mailbox->path);
1445 longest_common_prefix (dest, mailbox->path, matchlen, len);
1449 for (conn = mutt_socket_head (); conn && conn->next; conn = conn->next) {
1451 char urlstr[LONG_STRING];
1453 if (conn->account.type != M_ACCT_TYPE_IMAP)
1456 mutt_account_tourl (&conn->account, &url);
1457 /* FIXME: how to handle multiple users on the same host? */
1460 url_ciss_tostring (&url, urlstr, sizeof (urlstr), 0);
1461 if (!m_strncmp(dest, urlstr, matchlen)) {
1463 m_strcpy(dest, len, urlstr);
1466 longest_common_prefix (dest, urlstr, matchlen, len);
1473 /* imap_complete: given a partial IMAP folder path, return a string which
1474 * adds as much to the path as is unique */
1475 int imap_complete (char *dest, size_t dlen, char *path) {
1478 char list[LONG_STRING];
1479 char buf[LONG_STRING];
1480 char *list_word = NULL;
1481 int noselect, noinferiors;
1483 char completion[LONG_STRING];
1484 int clen, matchlen = 0;
1485 int completions = 0;
1488 if (imap_parse_path (path, &mx) || !mx.mbox) {
1489 m_strcpy(dest, dlen, path);
1490 return imap_complete_hosts (dest, dlen);
1493 /* don't open a new socket just for completion. Instead complete over
1494 * known mailboxes/hooks/etc */
1495 if (!(idata = imap_conn_find (&(mx.account), M_IMAP_CONN_NONEW))) {
1497 m_strcpy(dest, dlen, path);
1498 return imap_complete_hosts (dest, dlen);
1502 /* reformat path for IMAP list, and append wildcard */
1503 /* don't use INBOX in place of "" */
1504 if (mx.mbox && mx.mbox[0])
1505 imap_fix_path (idata, mx.mbox, list, sizeof (list));
1509 /* fire off command */
1510 snprintf (buf, sizeof (buf), "%s \"\" \"%s%%\"",
1511 option (OPTIMAPLSUB) ? "LSUB" : "string_list_t", list);
1513 imap_cmd_start (idata, buf);
1515 /* and see what the results are */
1516 m_strcpy(completion, sizeof(completion), NONULL(mx.mbox));
1518 if (imap_parse_list_response (idata, &list_word, &noselect, &noinferiors,
1523 /* store unquoted */
1524 imap_unmunge_mbox_name (list_word);
1526 /* if the folder isn't selectable, append delimiter to force browse
1527 * to enter it on second tab. */
1529 clen = m_strlen(list_word);
1530 list_word[clen++] = delim;
1531 list_word[clen] = '\0';
1533 /* copy in first word */
1535 m_strcpy(completion, sizeof(completion), list_word);
1536 matchlen = m_strlen(completion);
1541 matchlen = longest_common_prefix (completion, list_word, 0, matchlen);
1545 while (m_strncmp(idata->cmd.seq, idata->cmd.buf, SEQLEN));
1548 /* reformat output */
1549 imap_qualify_path (dest, dlen, &mx, completion);
1550 mutt_pretty_mailbox (dest);
1559 /* reconnect if connection was lost */
1560 int imap_reconnect (CONTEXT * ctx)
1562 IMAP_DATA *imap_data;
1567 imap_data = (IMAP_DATA *) ctx->data;
1570 if (imap_data->status == IMAP_CONNECTED)
1574 if (query_quadoption
1576 _("Connection lost. Reconnect to IMAP server?")) != M_YES)
1579 mx_open_mailbox (ctx->path, 0, ctx);
1583 int imap_is_magic (const char* path, struct stat* st __attribute__ ((unused))) {
1585 if (!path || !*path)
1587 s = url_check_scheme (NONULL (path));
1588 return ((s == U_IMAP || s == U_IMAPS) ? M_IMAP : -1);
1591 static int acl_check_imap (CONTEXT* ctx, int bit) {
1592 return (!mutt_bit_isset (((IMAP_DATA*) ctx->data)->capabilities, ACL) ||
1593 mutt_bit_isset (((IMAP_DATA*) ctx->data)->rights, bit));
1596 static int imap_open_new_message (MESSAGE * msg,
1597 CONTEXT * dest __attribute__ ((unused)),
1598 HEADER * hdr __attribute__ ((unused)))
1600 char tmp[_POSIX_PATH_MAX];
1602 msg->fp = m_tempfile(tmp, sizeof(tmp), NONULL(Tempdir), NULL);
1608 msg->path = m_strdup(tmp);
1612 /* this ugly kludge is required since the last int to
1613 * imap_check_mailbox() doesn't mean 'lock' but 'force'... */
1614 static int _imap_check_mailbox (CONTEXT* ctx,
1616 int lock __attribute__ ((unused))) {
1617 return (imap_check_mailbox (ctx, index_hint, 0));
1620 static int imap_commit_message (MESSAGE* msg, CONTEXT* ctx) {
1623 if ((r = m_fclose(&msg->fp)) == 0)
1624 r = imap_append_message (ctx, msg);
1628 mx_t const imap_mx = {
1635 imap_open_new_message,
1637 _imap_check_mailbox,
1640 imap_commit_message,