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. */
22 #include <sys/types.h>
25 #include <lib-lib/lib-lib.h>
33 #include "imap_private.h"
34 #if defined(USE_SSL) || defined(USE_GNUTLS)
35 # include <lib-sys/mutt_ssl.h>
39 /* imap forward declarations */
40 static int imap_get_delim (IMAP_DATA * idata);
41 static char *imap_get_flags (string_list_t ** hflags, char *s);
42 static int imap_check_acl (IMAP_DATA * idata);
43 static int imap_check_capabilities (IMAP_DATA * idata);
44 static void imap_set_flag (IMAP_DATA * idata, int aclbit, int flag,
45 const char *str, char *flags, size_t flsize);
47 /* imap_access: Check permissions on an IMAP mailbox.
48 * TODO: ACL checks. Right now we assume if it exists we can
50 int imap_access (const char *path, int flags __attribute__ ((unused)))
54 char buf[LONG_STRING];
55 char mailbox[LONG_STRING];
56 char mbox[LONG_STRING];
58 if (imap_parse_path (path, &mx))
61 if (!(idata = imap_conn_find (&mx.account,
62 option (OPTIMAPPASSIVE) ? M_IMAP_CONN_NONEW :
68 imap_fix_path (idata, mx.mbox, mailbox, sizeof (mailbox));
70 /* we may already be in the folder we're checking */
71 if (!ascii_strcmp(idata->mailbox, mx.mbox)) {
77 imap_munge_mbox_name (mbox, sizeof (mbox), mailbox);
79 if (mutt_bit_isset (idata->capabilities, IMAP4REV1))
80 snprintf (buf, sizeof (buf), "STATUS %s (UIDVALIDITY)", mbox);
81 else if (mutt_bit_isset (idata->capabilities, STATUS))
82 snprintf (buf, sizeof (buf), "STATUS %s (UID-VALIDITY)", mbox);
87 if (imap_exec (idata, buf, IMAP_CMD_FAIL_OK) < 0) {
94 int imap_create_mailbox (IMAP_DATA * idata, char *mailbox)
96 char buf[LONG_STRING], mbox[LONG_STRING];
98 imap_munge_mbox_name (mbox, sizeof (mbox), mailbox);
99 snprintf (buf, sizeof (buf), "CREATE %s", mbox);
101 if (imap_exec (idata, buf, 0) != 0)
107 int imap_rename_mailbox (IMAP_DATA * idata, IMAP_MBOX * mx,
110 char oldmbox[LONG_STRING];
111 char newmbox[LONG_STRING];
112 char buf[LONG_STRING];
114 imap_munge_mbox_name (oldmbox, sizeof (oldmbox), mx->mbox);
115 imap_munge_mbox_name (newmbox, sizeof (newmbox), newname);
117 snprintf (buf, sizeof (buf), "RENAME %s %s", oldmbox, newmbox);
119 if (imap_exec (idata, buf, 0) != 0)
125 int imap_delete_mailbox (CONTEXT * ctx, IMAP_MBOX mx)
127 char buf[LONG_STRING], mbox[LONG_STRING];
130 if (!ctx || !ctx->data) {
131 if (!(idata = imap_conn_find (&mx.account,
132 option (OPTIMAPPASSIVE) ? M_IMAP_CONN_NONEW
142 imap_munge_mbox_name (mbox, sizeof (mbox), mx.mbox);
143 snprintf (buf, sizeof (buf), "DELETE %s", mbox);
145 if (imap_exec ((IMAP_DATA *) idata, buf, 0) != 0)
151 /* imap_logout_all: close all open connections. Quick and dirty until we can
152 * make sure we've got all the context we need. */
153 void imap_logout_all (void)
158 conn = mutt_socket_head ();
163 if (conn->account.type == M_ACCT_TYPE_IMAP && conn->fd >= 0) {
164 mutt_message (_("Closing connection to %s..."), conn->account.host);
165 imap_logout ((IMAP_DATA *) conn->data);
167 mutt_socket_close (conn);
168 mutt_socket_free (conn);
175 /* imap_read_literal: read bytes bytes from server into file. Not explicitly
176 * buffered, relies on FILE buffering. NOTE: strips \r from \r\n.
177 * Apparently even literals use \r\n-terminated strings ?! */
178 int imap_read_literal (FILE * fp, IMAP_DATA * idata, long bytes, progress_t* bar)
185 for (pos = 0; pos < bytes; pos++) {
186 if (mutt_socket_readchar (idata->conn, &c) != 1) {
187 idata->status = IMAP_FATAL;
193 if (r == 1 && c != '\n')
204 if (bar && !(pos % 1024))
205 mutt_progress_bar (bar, pos);
211 /* imap_expunge_mailbox: Purge IMAP portion of expunged messages from the
212 * context. Must not be done while something has a handle on any headers
213 * (eg inside pager or editor). That is, check IMAP_REOPEN_ALLOW. */
214 void imap_expunge_mailbox (IMAP_DATA * idata)
219 for (i = 0; i < idata->ctx->msgcount; i++) {
220 h = idata->ctx->hdrs[i];
222 if (h->index == -1) {
225 /* free cached body from disk, if neccessary */
226 cacheno = HEADER_DATA (h)->uid % IMAP_CACHE_LEN;
227 if (idata->cache[cacheno].uid == HEADER_DATA (h)->uid &&
228 idata->cache[cacheno].path) {
229 unlink (idata->cache[cacheno].path);
230 p_delete(&idata->cache[cacheno].path);
233 imap_free_header_data (&h->data);
237 /* We may be called on to expunge at any time. We can't rely on the caller
238 * to always know to rethread */
239 mx_update_tables (idata->ctx, 0);
240 mutt_sort_headers (idata->ctx, 1);
243 static int imap_get_delim (IMAP_DATA * idata)
248 /* assume that the delim is /. If this fails, we're in bigger trouble
249 * than getting the delim wrong */
252 imap_cmd_start (idata, "string_list_t \"\" \"\"");
255 if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
258 s = imap_next_word (idata->cmd.buf);
259 if (ascii_strncasecmp ("string_list_t", s, 4) == 0) {
260 s = imap_next_word (s);
261 s = imap_next_word (s);
262 if (s && s[0] == '\"' && s[1] && s[2] == '\"')
264 else if (s && s[0] == '\"' && s[1] && s[1] == '\\' && s[2]
269 while (rc == IMAP_CMD_CONTINUE);
274 /* get rights for folder, let imap_handle_untagged do the rest */
275 static int imap_check_acl (IMAP_DATA * idata)
277 char buf[LONG_STRING];
278 char mbox[LONG_STRING];
280 imap_munge_mbox_name (mbox, sizeof (mbox), idata->mailbox);
281 snprintf (buf, sizeof (buf), "MYRIGHTS %s", mbox);
282 if (imap_exec (idata, buf, 0) != 0) {
283 imap_error ("imap_check_acl", buf);
289 /* imap_check_capabilities: make sure we can log in to this server. */
290 static int imap_check_capabilities (IMAP_DATA * idata)
292 if (imap_exec (idata, "CAPABILITY", 0) != 0) {
293 imap_error ("imap_check_capabilities", idata->cmd.buf);
297 if (!(mutt_bit_isset (idata->capabilities, IMAP4)
298 || mutt_bit_isset (idata->capabilities, IMAP4REV1))) {
299 mutt_error _("This IMAP server is ancient. Mutt does not work with it.");
301 mutt_sleep (2); /* pause a moment to let the user see the error */
309 /* imap_conn_find: Find an open IMAP connection matching account, or open
310 * a new one if none can be found. */
311 IMAP_DATA *imap_conn_find (const ACCOUNT * account, int flags)
318 if (!(conn = mutt_conn_find (NULL, account)))
321 /* if opening a new UNSELECTED connection, preserve existing creds */
322 creds = &(conn->account);
324 /* make sure this connection is not in SELECTED state, if neccessary */
325 if (flags & M_IMAP_CONN_NOSELECT)
326 while (conn->data && ((IMAP_DATA *) conn->data)->state == IMAP_SELECTED) {
327 if (!(conn = mutt_conn_find (conn, account)))
329 memcpy (&(conn->account), creds, sizeof (ACCOUNT));
332 idata = (IMAP_DATA *) conn->data;
334 /* don't open a new connection if one isn't wanted */
335 if (flags & M_IMAP_CONN_NONEW) {
337 mutt_socket_free (conn);
340 if (idata->state < IMAP_AUTHENTICATED)
345 /* The current connection is a new connection */
346 if (!(idata = imap_new_idata ())) {
347 mutt_socket_free (conn);
356 if (idata->state == IMAP_DISCONNECTED)
357 imap_open_connection (idata);
358 if (idata->state == IMAP_CONNECTED) {
359 if (!imap_authenticate (idata)) {
360 idata->state = IMAP_AUTHENTICATED;
363 mutt_account_unsetpass (&idata->conn->account);
365 p_delete(&idata->capstr);
367 if (new && idata->state == IMAP_AUTHENTICATED) {
368 imap_get_delim (idata);
369 if (option (OPTIMAPCHECKSUBSCRIBED)) {
370 mutt_message _("Checking mailbox subscriptions");
371 imap_exec (idata, "LSUB \"\" \"*\"", 0);
378 int imap_open_connection (IMAP_DATA * idata)
380 char buf[LONG_STRING];
382 if (mutt_socket_open (idata->conn) < 0)
385 idata->state = IMAP_CONNECTED;
387 if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE) {
388 mutt_socket_close (idata->conn);
389 idata->state = IMAP_DISCONNECTED;
393 if (ascii_strncasecmp ("* OK", idata->cmd.buf, 4) == 0) {
394 /* TODO: Parse new tagged CAPABILITY data (* OK [CAPABILITY...]) */
395 if (imap_check_capabilities (idata))
397 #if defined(USE_SSL) || defined(USE_GNUTLS)
398 /* Attempt STARTTLS if available and desired. */
399 if (!idata->conn->ssf && (option(OPTSSLFORCETLS) ||
400 mutt_bit_isset (idata->capabilities, STARTTLS))) {
403 if (option (OPTSSLFORCETLS))
405 else if ((rc = query_quadoption (OPT_SSLSTARTTLS,
406 _("Secure connection with TLS?"))) == -1)
409 if ((rc = imap_exec (idata, "STARTTLS", IMAP_CMD_FAIL_OK)) == -1)
412 #if defined (USE_SSL) || defined (USE_GNUTLS)
413 if (mutt_ssl_starttls (idata->conn))
416 mutt_error (_("Could not negotiate TLS connection"));
421 /* RFC 2595 demands we recheck CAPABILITY after TLS completes. */
422 if (imap_exec (idata, "CAPABILITY", 0))
429 if (option(OPTSSLFORCETLS) && ! idata->conn->ssf) {
430 mutt_error _("Encrypted connection unavailable");
436 else if (ascii_strncasecmp ("* PREAUTH", idata->cmd.buf, 9) == 0) {
437 idata->state = IMAP_AUTHENTICATED;
438 if (imap_check_capabilities (idata) != 0)
440 p_delete(&idata->capstr);
443 imap_error ("imap_open_connection()", buf);
450 mutt_socket_close (idata->conn);
451 idata->state = IMAP_DISCONNECTED;
453 p_delete(&idata->capstr);
457 /* imap_get_flags: Make a simple list out of a FLAGS response.
458 * return stream following FLAGS response */
459 static char *imap_get_flags (string_list_t ** hflags, char *s)
461 string_list_t *flags;
465 /* sanity-check string */
466 if (ascii_strncasecmp ("FLAGS", s, 5) != 0) {
469 s = vskipspaces(s + 5);
474 /* create list, update caller's flags handle */
475 flags = string_item_new();
478 while (*s && *s != ')') {
479 s = vskipspaces(s + 1);
481 while (*s && (*s != ')') && !ISSPACE (*s))
486 mutt_add_list (flags, flag_word);
490 /* note bad flags response */
492 string_list_wipe(hflags);
502 int imap_open_mailbox (CONTEXT * ctx)
506 char buf[LONG_STRING];
507 char bufout[LONG_STRING];
512 if (imap_parse_path (ctx->path, &mx)) {
513 mutt_error (_("%s is an invalid IMAP path"), ctx->path);
517 /* we require a connection which isn't currently in IMAP_SELECTED state */
518 if (!(idata = imap_conn_find (&(mx.account), M_IMAP_CONN_NOSELECT)))
520 if (idata->state < IMAP_AUTHENTICATED)
525 /* once again the context is new */
528 /* Clean up path and replace the one in the ctx */
529 imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
530 p_delete(&(idata->mailbox));
531 idata->mailbox = m_strdup(buf);
532 imap_qualify_path (buf, sizeof (buf), &mx, idata->mailbox);
534 p_delete(&(ctx->path));
535 ctx->path = m_strdup(buf);
539 /* clear mailbox status */
541 memset (idata->rights, 0, (RIGHTSMAX + 7) / 8);
542 idata->newMailCount = 0;
544 mutt_message (_("Selecting %s..."), idata->mailbox);
545 imap_munge_mbox_name (buf, sizeof (buf), idata->mailbox);
546 snprintf (bufout, sizeof (bufout), "%s %s",
547 ctx->readonly ? "EXAMINE" : "SELECT", buf);
549 idata->state = IMAP_SELECTED;
551 imap_cmd_start (idata, bufout);
556 if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
559 pc = idata->cmd.buf + 2;
561 /* Obtain list of available flags here, may be overridden by a
562 * PERMANENTFLAGS tag in the OK response */
563 if (ascii_strncasecmp ("FLAGS", pc, 5) == 0) {
564 /* don't override PERMANENTFLAGS */
566 if ((pc = imap_get_flags (&(idata->flags), pc)) == NULL)
570 /* PERMANENTFLAGS are massaged to look like FLAGS, then override FLAGS */
571 else if (ascii_strncasecmp ("OK [PERMANENTFLAGS", pc, 18) == 0) {
572 /* safe to call on NULL */
573 string_list_wipe(&(idata->flags));
574 /* skip "OK [PERMANENT" so syntax is the same as FLAGS */
576 if ((pc = imap_get_flags (&(idata->flags), pc)) == NULL)
580 /* save UIDVALIDITY for the header cache */
581 else if (ascii_strncasecmp ("OK [UIDVALIDITY", pc, 14) == 0) {
583 pc = imap_next_word (pc);
585 sscanf (pc, "%lu", &(idata->uid_validity));
589 pc = imap_next_word (pc);
590 if (!ascii_strncasecmp ("EXISTS", pc, 6)) {
591 count = idata->newMailCount;
592 idata->newMailCount = 0;
596 while (rc == IMAP_CMD_CONTINUE);
598 if (rc == IMAP_CMD_NO) {
601 s = imap_next_word (idata->cmd.buf); /* skip seq */
602 s = imap_next_word (s); /* Skip response */
603 mutt_error ("%s", s);
608 if (rc != IMAP_CMD_OK)
611 /* check for READ-ONLY notification */
612 if (!ascii_strncasecmp
613 (imap_get_qualifier (idata->cmd.buf), "[READ-ONLY]", 11)
614 && !mutt_bit_isset (idata->capabilities, ACL)) {
618 if (mutt_bit_isset (idata->capabilities, ACL)) {
619 if (imap_check_acl (idata))
621 if (!(mutt_bit_isset (idata->rights, ACL_DELETE) ||
622 mutt_bit_isset (idata->rights, ACL_SEEN) ||
623 mutt_bit_isset (idata->rights, ACL_WRITE) ||
624 mutt_bit_isset (idata->rights, ACL_INSERT)))
627 /* assume we have all rights if ACL is unavailable */
629 mutt_bit_set (idata->rights, ACL_LOOKUP);
630 mutt_bit_set (idata->rights, ACL_READ);
631 mutt_bit_set (idata->rights, ACL_SEEN);
632 mutt_bit_set (idata->rights, ACL_WRITE);
633 mutt_bit_set (idata->rights, ACL_INSERT);
634 mutt_bit_set (idata->rights, ACL_POST);
635 mutt_bit_set (idata->rights, ACL_CREATE);
636 mutt_bit_set (idata->rights, ACL_DELETE);
640 ctx->hdrs = p_new(HEADER *, count);
641 ctx->v2r = p_new(int, count);
643 if (count && (imap_read_headers (idata, 0, count - 1) < 0)) {
644 mutt_error _("Error opening mailbox");
654 if (idata->state == IMAP_SELECTED)
655 idata->state = IMAP_AUTHENTICATED;
661 int imap_open_mailbox_append (CONTEXT * ctx)
665 char buf[LONG_STRING];
666 char mailbox[LONG_STRING];
669 if (imap_parse_path (ctx->path, &mx))
672 /* in APPEND mode, we appear to hijack an existing IMAP connection -
673 * ctx is brand new and mostly empty */
675 if (!(idata = imap_conn_find (&(mx.account), 0))) {
684 imap_fix_path (idata, mx.mbox, mailbox, sizeof (mailbox));
688 /* really we should also check for W_OK */
689 if (!imap_access (ctx->path, F_OK))
692 snprintf (buf, sizeof (buf), _("Create %s?"), mailbox);
693 if (option (OPTCONFIRMCREATE) && mutt_yesorno (buf, 1) < 1)
696 if (imap_create_mailbox (idata, mailbox) < 0)
702 /* imap_logout: Gracefully log out of server. */
703 void imap_logout (IMAP_DATA * idata)
705 /* we set status here to let imap_handle_untagged know we _expect_ to
706 * receive a bye response (so it doesn't freak out and close the conn) */
707 idata->status = IMAP_BYE;
708 imap_cmd_start (idata, "LOGOUT");
709 while (imap_cmd_step (idata) == IMAP_CMD_CONTINUE);
710 p_delete(&idata->cmd.buf);
715 int imap_close_connection (CONTEXT *ctx)
717 if (CTX_DATA->status != IMAP_BYE)
719 mutt_message _("Closing connection to IMAP server...");
720 imap_logout (CTX_DATA);
723 mutt_socket_close (CTX_DATA->conn);
724 CTX_DATA->state = IMAP_DISCONNECTED;
725 CTX_DATA->conn->data = NULL;
730 /* imap_set_flag: append str to flags if we currently have permission
731 * according to aclbit */
732 static void imap_set_flag(IMAP_DATA *idata, int aclbit, int flag,
733 const char *str, char *flags, size_t flsize)
735 if (mutt_bit_isset(idata->rights, aclbit)) {
737 m_strcat(flags, flsize, str);
741 /* imap_make_msg_set: make an IMAP4rev1 UID message set out of a set of
742 * headers, given a flag enum to filter on.
743 * Params: idata: IMAP_DATA containing context containing header set
744 * buf: to write message set into
745 * buflen: length of buffer
746 * flag: enum of flag type on which to filter
747 * changed: include only changed messages in message set
748 * Returns: number of messages in message set (0 if no matches) */
749 int imap_make_msg_set (IMAP_DATA * idata, BUFFER * buf, int flag, int changed)
751 HEADER **hdrs; /* sorted local copy */
752 int count = 0; /* number of messages in message set */
753 int match = 0; /* whether current message matches flag condition */
754 int setstart = 0; /* start of current message range */
756 short oldsort; /* we clobber reverse, must restore it */
758 /* assuming 32-bit UIDs */
762 /* make copy of header pointers to sort in natural order */
763 hdrs = p_dup(idata->ctx->hdrs, idata->ctx->msgcount);
765 if (Sort != SORT_ORDER) {
768 qsort ((void *) hdrs, idata->ctx->msgcount, sizeof (HEADER *),
769 mutt_get_sort_func (SORT_ORDER));
773 for (n = 0; n < idata->ctx->msgcount; n++) {
775 /* don't include pending expunged messages */
779 if (hdrs[n]->deleted)
788 if (match && (!changed || hdrs[n]->changed)) {
791 setstart = HEADER_DATA (hdrs[n])->uid;
793 snprintf (uid, sizeof (uid), "%u", HEADER_DATA (hdrs[n])->uid);
794 mutt_buffer_addstr (buf, uid);
798 snprintf (uid, sizeof (uid), ",%u", HEADER_DATA (hdrs[n])->uid);
799 mutt_buffer_addstr (buf, uid);
802 /* tie up if the last message also matches */
803 else if (n == idata->ctx->msgcount - 1) {
804 snprintf (uid, sizeof (uid), ":%u", HEADER_DATA (hdrs[n])->uid);
805 mutt_buffer_addstr (buf, uid);
808 /* this message is not expunged and doesn't match. End current set. */
809 else if (setstart && hdrs[n]->active) {
810 if (HEADER_DATA (hdrs[n - 1])->uid > setstart) {
811 snprintf (uid, sizeof (uid), ":%u", HEADER_DATA (hdrs[n - 1])->uid);
812 mutt_buffer_addstr (buf, uid);
823 /* Update the IMAP server to reflect the flags a single message. */
825 int imap_sync_message (IMAP_DATA *idata, HEADER *hdr, BUFFER *cmd,
828 char flags[LONG_STRING];
833 snprintf (uid, sizeof (uid), "%u", HEADER_DATA(hdr)->uid);
834 cmd->dptr = cmd->data;
835 mutt_buffer_addstr (cmd, "UID STORE ");
836 mutt_buffer_addstr (cmd, uid);
840 imap_set_flag (idata, ACL_SEEN, hdr->read, "\\Seen ",
841 flags, sizeof (flags));
842 imap_set_flag (idata, ACL_WRITE, hdr->flagged,
843 "\\Flagged ", flags, sizeof (flags));
844 imap_set_flag (idata, ACL_WRITE, hdr->replied,
845 "\\Answered ", flags, sizeof (flags));
846 imap_set_flag (idata, ACL_DELETE, hdr->deleted,
847 "\\Deleted ", flags, sizeof (flags));
849 /* now make sure we don't lose custom tags */
850 if (mutt_bit_isset (idata->rights, ACL_WRITE))
851 imap_add_keywords (flags, hdr, idata->flags, sizeof (flags));
855 /* UW-IMAP is OK with null flags, Cyrus isn't. The only solution is to
856 * explicitly revoke all system flags (if we have permission) */
859 imap_set_flag (idata, ACL_SEEN, 1, "\\Seen ", flags, sizeof (flags));
860 imap_set_flag (idata, ACL_WRITE, 1, "\\Flagged ", flags, sizeof (flags));
861 imap_set_flag (idata, ACL_WRITE, 1, "\\Answered ", flags, sizeof (flags));
862 imap_set_flag (idata, ACL_DELETE, 1, "\\Deleted ", flags, sizeof (flags));
866 mutt_buffer_addstr (cmd, " -FLAGS.SILENT (");
868 mutt_buffer_addstr (cmd, " FLAGS.SILENT (");
870 mutt_buffer_addstr (cmd, flags);
871 mutt_buffer_addstr (cmd, ")");
873 /* dumb hack for bad UW-IMAP 4.7 servers spurious FLAGS updates */
876 /* after all this it's still possible to have no flags, if you
877 * have no ACL rights */
878 if (*flags && (imap_exec (idata, cmd->data, 0) != 0) &&
879 err_continue && (*err_continue != M_YES))
881 *err_continue = imap_continue ("imap_sync_message: STORE failed",
883 if (*err_continue != M_YES)
888 idata->ctx->changed--;
893 /* update the IMAP server to reflect message changes done within mutt.
895 * ctx: the current context
896 * expunge: 0 or 1 - do expunge?
899 int imap_sync_mailbox (CONTEXT * ctx, int expunge, int *index_hint)
902 CONTEXT *appendctx = NULL;
906 int err_continue = M_NO; /* continue on error? */
909 idata = (IMAP_DATA *) ctx->data;
911 if (idata->state != IMAP_SELECTED) {
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)
924 /* if we are expunging anyway, we can do deleted messages very quickly... */
925 if (expunge && mutt_bit_isset (idata->rights, 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) {
951 mutt_message (_("Saving message status flags... [%d/%d]"), n + 1,
954 /* if the message has been rethreaded or attachments have been deleted
955 * we delete the message and reupload it.
956 * This works better if we're expunging, of course. */
957 if ((ctx->hdrs[n]->env && (ctx->hdrs[n]->env->refs_changed || ctx->hdrs[n]->env->irt_changed)) ||
958 ctx->hdrs[n]->attach_del) {
960 appendctx = mx_open_mailbox (ctx->path, M_APPEND | M_QUIET, NULL);
962 _mutt_save_message (ctx->hdrs[n], appendctx, 1, 0, 0);
966 if (imap_sync_message (idata, ctx->hdrs[n], &cmd, &err_continue) < 0) {
974 /* We must send an EXPUNGE command if we're not closing. */
975 if (expunge && !(ctx->closing) &&
976 mutt_bit_isset (idata->rights, ACL_DELETE)) {
977 mutt_message _("Expunging messages from server...");
979 /* Set expunge bit so we don't get spurious reopened messages */
980 idata->reopen |= IMAP_EXPUNGE_EXPECTED;
981 if (imap_exec (idata, "EXPUNGE", 0) != 0) {
982 imap_error (_("imap_sync_mailbox: EXPUNGE failed"), idata->cmd.buf);
983 rc = imap_reconnect (ctx);
988 if (expunge && ctx->closing) {
989 if (imap_exec (idata, "CLOSE", 0))
990 mutt_error (_("CLOSE failed"));
991 idata->state = IMAP_AUTHENTICATED;
999 mx_fastclose_mailbox (appendctx);
1000 p_delete(&appendctx);
1005 /* imap_close_mailbox: clean up IMAP data in CONTEXT */
1006 void imap_close_mailbox (CONTEXT * ctx)
1011 idata = (IMAP_DATA *) ctx->data;
1012 /* Check to see if the mailbox is actually open */
1016 if (ctx == idata->ctx) {
1017 if (idata->state != IMAP_FATAL && idata->state == IMAP_SELECTED) {
1018 /* mx_close_mailbox won't sync if there are no deleted messages
1019 * and the mailbox is unchanged, so we may have to close here */
1020 if (!ctx->deleted && imap_exec (idata, "CLOSE", 0))
1021 mutt_error (_("CLOSE failed"));
1022 idata->state = IMAP_AUTHENTICATED;
1025 idata->reopen &= IMAP_REOPEN_ALLOW;
1026 p_delete(&(idata->mailbox));
1027 string_list_wipe(&idata->flags);
1031 /* free IMAP part of headers */
1032 for (i = 0; i < ctx->msgcount; i++)
1033 imap_free_header_data (&(ctx->hdrs[i]->data));
1035 for (i = 0; i < IMAP_CACHE_LEN; i++) {
1036 if (idata->cache[i].path) {
1037 unlink (idata->cache[i].path);
1038 p_delete(&idata->cache[i].path);
1043 /* use the NOOP command to poll for new mail
1046 * M_REOPENED mailbox has been externally modified
1047 * M_NEW_MAIL new mail has arrived!
1051 int imap_check_mailbox (CONTEXT * ctx, int *index_hint __attribute__ ((unused)), int force)
1053 /* overload keyboard timeout to avoid many mailbox checks in a row.
1054 * Most users don't like having to wait exactly when they press a key. */
1058 idata = (IMAP_DATA *) ctx->data;
1060 if ((force || time (NULL) >= idata->lastread + Timeout)
1061 && imap_exec (idata, "NOOP", 0) != 0)
1064 /* We call this even when we haven't run NOOP in case we have pending
1065 * changes to process, since we can reopen here. */
1066 imap_cmd_finish (idata);
1068 if (idata->check_status & IMAP_EXPUNGE_PENDING)
1069 result = M_REOPENED;
1070 else if (idata->check_status & IMAP_NEWMAIL_PENDING)
1071 result = M_NEW_MAIL;
1072 else if (idata->check_status & IMAP_FLAGS_PENDING)
1075 idata->check_status = 0;
1086 * 0+ number of messages in mailbox
1087 * -1 error while polling mailboxes
1089 int imap_mailbox_check (char *path, int new)
1093 char buf[LONG_STRING];
1094 char mbox[LONG_STRING];
1095 char mbox_unquoted[LONG_STRING];
1102 if (imap_parse_path (path, &mx))
1105 /* If imap_passive is set, don't open a connection to check for new mail */
1106 if (option (OPTIMAPPASSIVE))
1107 connflags = M_IMAP_CONN_NONEW;
1109 if (!(idata = imap_conn_find (&(mx.account), connflags))) {
1115 imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
1118 imap_munge_mbox_name (mbox, sizeof (mbox), buf);
1119 m_strcpy(mbox_unquoted, sizeof(mbox_unquoted), buf);
1121 /* The draft IMAP implementor's guide warns againts using the STATUS
1122 * command on a mailbox that you have selected
1125 if (m_strcmp(mbox_unquoted, idata->mailbox) == 0
1126 || (ascii_strcasecmp (mbox_unquoted, "INBOX") == 0
1127 && m_strcasecmp(mbox_unquoted, idata->mailbox) == 0)) {
1128 m_strcpy(buf, sizeof(buf), "NOOP");
1130 else if (mutt_bit_isset (idata->capabilities, IMAP4REV1) ||
1131 mutt_bit_isset (idata->capabilities, STATUS)) {
1132 snprintf (buf, sizeof (buf), "STATUS %s (%s)", mbox,
1133 new == 1 ? "RECENT" : (new == 2 ? "UNSEEN" : "MESSAGES"));
1136 /* Server does not support STATUS, and this is not the current mailbox.
1137 * There is no lightweight way to check recent arrivals */
1140 imap_cmd_start (idata, buf);
1143 if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
1146 s = imap_next_word (idata->cmd.buf);
1147 if (ascii_strncasecmp ("STATUS", s, 6) == 0) {
1148 s = imap_next_word (s);
1149 /* The mailbox name may or may not be quoted here. We could try to
1150 * munge the server response and compare with quoted (or vise versa)
1151 * but it is probably more efficient to just strncmp against both. */
1152 if (m_strncmp(mbox_unquoted, s, m_strlen(mbox_unquoted)) == 0
1153 || m_strncmp(mbox, s, m_strlen(mbox)) == 0) {
1154 s = imap_next_word (s);
1155 s = imap_next_word (s);
1156 if (isdigit ((unsigned char) *s)) {
1158 msgcount = atoi (s);
1164 while (rc == IMAP_CMD_CONTINUE);
1169 /* returns number of patterns in the search that should be done server-side
1170 * (eg are full-text) */
1171 static int do_search (const pattern_t* search, int allpats)
1174 const pattern_t* pat;
1176 for (pat = search; pat; pat = pat->next) {
1181 if (pat->stringmatch)
1185 if (pat->child && do_search (pat->child, 1))
1196 /* convert mutt pattern_t to IMAP SEARCH command containing only elements
1197 * that require full-text search (mutt already has what it needs for most
1198 * match types, and does a better job (eg server doesn't support regexps). */
1199 static int imap_compile_search (const pattern_t* pat, BUFFER* buf)
1201 if (! do_search (pat, 0))
1205 mutt_buffer_addstr (buf, "NOT ");
1210 if ((clauses = do_search (pat->child, 1)) > 0) {
1211 const pattern_t* clause = pat->child;
1213 mutt_buffer_addch (buf, '(');
1216 if (do_search (clause, 0)) {
1217 if (pat->op == M_OR && clauses > 1)
1218 mutt_buffer_addstr (buf, "OR ");
1220 if (imap_compile_search (clause, buf) < 0)
1224 mutt_buffer_addch (buf, ' ');
1226 clause = clause->next;
1230 mutt_buffer_addch (buf, ')');
1238 mutt_buffer_addstr (buf, "HEADER ");
1240 /* extract header name */
1241 if (! (delim = strchr (pat->str, ':'))) {
1242 mutt_error (_("Header search without header name: %s"), pat->str);
1246 imap_quote_string (term, sizeof (term), pat->str);
1247 mutt_buffer_addstr (buf, term);
1248 mutt_buffer_addch (buf, ' ');
1252 delim = vskipspaces(delim);
1253 imap_quote_string (term, sizeof (term), delim);
1254 mutt_buffer_addstr (buf, term);
1258 mutt_buffer_addstr (buf, "BODY ");
1259 imap_quote_string (term, sizeof (term), pat->str);
1260 mutt_buffer_addstr (buf, term);
1264 mutt_buffer_addstr (buf, "TEXT ");
1265 imap_quote_string (term, sizeof (term), pat->str);
1266 mutt_buffer_addstr (buf, term);
1274 int imap_search (CONTEXT* ctx, const pattern_t* pat) {
1276 IMAP_DATA* idata = (IMAP_DATA*)ctx->data;
1279 for (i = 0; i < ctx->msgcount; i++)
1280 ctx->hdrs[i]->matched = 0;
1282 if (!do_search (pat, 1))
1286 mutt_buffer_addstr (&buf, "UID SEARCH ");
1287 if (imap_compile_search (pat, &buf) < 0) {
1288 p_delete(&buf.data);
1291 if (imap_exec (idata, buf.data, 0) < 0) {
1292 p_delete(&buf.data);
1296 p_delete(&buf.data);
1300 /* all this listing/browsing is a mess. I don't like that name is a pointer
1301 * into idata->buf (used to be a pointer into the passed in buffer, just
1302 * as bad), nor do I like the fact that the fetch is done here. This
1303 * code can't possibly handle non-string_list_t untagged responses properly.
1305 int imap_parse_list_response (IMAP_DATA * idata, char **name, int *noselect,
1306 int *noinferiors, char *delim)
1314 rc = imap_cmd_step (idata);
1315 if (rc == IMAP_CMD_OK)
1317 if (rc != IMAP_CMD_CONTINUE)
1320 s = imap_next_word (idata->cmd.buf);
1321 if ((ascii_strncasecmp ("string_list_t", s, 4) == 0) ||
1322 (ascii_strncasecmp ("LSUB", s, 4) == 0)) {
1326 s = imap_next_word (s); /* flags */
1332 while (*ep && *ep != ')')
1335 if (!ascii_strncasecmp (s, "\\NoSelect", 9))
1337 if (!ascii_strncasecmp (s, "\\NoInferiors", 12))
1339 /* See draft-gahrns-imap-child-mailbox-?? */
1340 if (!ascii_strncasecmp (s, "\\HasNoChildren", 14))
1344 while (*s && *s != '\\' && *s != ')')
1350 s = imap_next_word (s); /* delim */
1351 /* Reset the delimiter, this can change */
1352 if (ascii_strncasecmp (s, "NIL", 3)) {
1353 if (s && s[0] == '\"' && s[1] && s[2] == '\"')
1355 else if (s && s[0] == '\"' && s[1] && s[1] == '\\' && s[2]
1359 s = imap_next_word (s); /* name */
1360 if (s && *s == '{') { /* Literal */
1361 if (imap_get_literal_count (idata->cmd.buf, &bytes) < 0)
1363 if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE)
1365 *name = idata->cmd.buf;
1374 int imap_subscribe (char *path, int subscribe)
1378 char buf[LONG_STRING];
1379 char mbox[LONG_STRING];
1380 char errstr[STRING];
1384 if (mx_get_magic (path) != M_IMAP || imap_parse_path (path, &mx) < 0) {
1385 mutt_error (_("Bad mailbox name"));
1389 if (!(idata = imap_conn_find (&(mx.account), 0)))
1394 imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
1396 if (option (OPTIMAPCHECKSUBSCRIBED)) {
1399 err.dsize = sizeof (errstr);
1400 snprintf (mbox, sizeof (mbox), "%smailboxes \"%s\"",
1401 subscribe ? "" : "un", path);
1402 mutt_parse_rc_line (mbox, &token, &err);
1403 p_delete(&token.data);
1407 mutt_message (_("Subscribing to %s..."), buf);
1409 mutt_message (_("Unsubscribing to %s..."), buf);
1410 imap_munge_mbox_name (mbox, sizeof (mbox), buf);
1412 snprintf (buf, sizeof (buf), "%sSUBSCRIBE %s", subscribe ? "" : "UN", mbox);
1414 if (imap_exec (idata, buf, 0) < 0)
1425 /* trim dest to the length of the longest prefix it shares with src,
1426 * returning the length of the trimmed string */
1427 static int longest_common_prefix (char *dest, const char* src,
1428 int start, ssize_t dlen) {
1431 while (pos < dlen && dest[pos] && dest[pos] == src[pos])
1438 /* look for IMAP URLs to complete from defined mailboxes. Could be extended
1439 * to complete over open connections and account/folder hooks too. */
1440 static int imap_complete_hosts (char *dest, ssize_t len) {
1447 matchlen = m_strlen(dest);
1448 if (list_empty (Incoming))
1450 for (i = 0; i < Incoming->length; i++) {
1451 mailbox = (BUFFY*) Incoming->data[i];
1452 if (!m_strncmp(dest, mailbox->path, matchlen)) {
1454 m_strcpy(dest, len, mailbox->path);
1457 longest_common_prefix (dest, mailbox->path, matchlen, len);
1461 for (conn = mutt_socket_head (); conn && conn->next; conn = conn->next) {
1463 char urlstr[LONG_STRING];
1465 if (conn->account.type != M_ACCT_TYPE_IMAP)
1468 mutt_account_tourl (&conn->account, &url);
1469 /* FIXME: how to handle multiple users on the same host? */
1472 url_ciss_tostring (&url, urlstr, sizeof (urlstr), 0);
1473 if (!m_strncmp(dest, urlstr, matchlen)) {
1475 m_strcpy(dest, len, urlstr);
1478 longest_common_prefix (dest, urlstr, matchlen, len);
1485 /* imap_complete: given a partial IMAP folder path, return a string which
1486 * adds as much to the path as is unique */
1487 int imap_complete (char *dest, size_t dlen, char *path) {
1490 char list[LONG_STRING];
1491 char buf[LONG_STRING];
1492 char *list_word = NULL;
1493 int noselect, noinferiors;
1495 char completion[LONG_STRING];
1496 int clen, matchlen = 0;
1497 int completions = 0;
1500 if (imap_parse_path (path, &mx) || !mx.mbox) {
1501 m_strcpy(dest, dlen, path);
1502 return imap_complete_hosts (dest, dlen);
1505 /* don't open a new socket just for completion. Instead complete over
1506 * known mailboxes/hooks/etc */
1507 if (!(idata = imap_conn_find (&(mx.account), M_IMAP_CONN_NONEW))) {
1509 m_strcpy(dest, dlen, path);
1510 return imap_complete_hosts (dest, dlen);
1514 /* reformat path for IMAP list, and append wildcard */
1515 /* don't use INBOX in place of "" */
1516 if (mx.mbox && mx.mbox[0])
1517 imap_fix_path (idata, mx.mbox, list, sizeof (list));
1521 /* fire off command */
1522 snprintf (buf, sizeof (buf), "%s \"\" \"%s%%\"",
1523 option (OPTIMAPLSUB) ? "LSUB" : "string_list_t", list);
1525 imap_cmd_start (idata, buf);
1527 /* and see what the results are */
1528 m_strcpy(completion, sizeof(completion), NONULL(mx.mbox));
1530 if (imap_parse_list_response (idata, &list_word, &noselect, &noinferiors,
1535 /* store unquoted */
1536 imap_unmunge_mbox_name (list_word);
1538 /* if the folder isn't selectable, append delimiter to force browse
1539 * to enter it on second tab. */
1541 clen = m_strlen(list_word);
1542 list_word[clen++] = delim;
1543 list_word[clen] = '\0';
1545 /* copy in first word */
1547 m_strcpy(completion, sizeof(completion), list_word);
1548 matchlen = m_strlen(completion);
1553 matchlen = longest_common_prefix (completion, list_word, 0, matchlen);
1557 while (ascii_strncmp (idata->cmd.seq, idata->cmd.buf, SEQLEN));
1560 /* reformat output */
1561 imap_qualify_path (dest, dlen, &mx, completion);
1562 mutt_pretty_mailbox (dest);
1571 /* reconnect if connection was lost */
1572 int imap_reconnect (CONTEXT * ctx)
1574 IMAP_DATA *imap_data;
1579 imap_data = (IMAP_DATA *) ctx->data;
1582 if (imap_data->status == IMAP_CONNECTED)
1586 if (query_quadoption
1588 _("Connection lost. Reconnect to IMAP server?")) != M_YES)
1591 mx_open_mailbox (ctx->path, 0, ctx);