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>
23 #include "imap_private.h"
26 /* imap forward declarations */
27 static int imap_get_delim (IMAP_DATA * idata);
28 static char *imap_get_flags (string_list_t ** hflags, char *s);
29 static int imap_check_acl (IMAP_DATA * idata);
30 static int imap_check_capabilities (IMAP_DATA * idata);
31 static void imap_set_flag (IMAP_DATA * idata, int aclbit, int flag,
32 const char *str, char *flags, size_t flsize);
34 /* imap_access: Check permissions on an IMAP mailbox.
35 * TODO: ACL checks. Right now we assume if it exists we can
37 static int imap_access (const char *path, int flags __attribute__ ((unused)))
41 char buf[LONG_STRING];
42 char mailbox[LONG_STRING];
43 char mbox[LONG_STRING];
45 if (imap_parse_path (path, &mx))
48 if (!(idata = imap_conn_find (&mx.account,
49 option (OPTIMAPPASSIVE) ? M_IMAP_CONN_NONEW :
55 imap_fix_path (idata, mx.mbox, mailbox, sizeof (mailbox));
57 /* we may already be in the folder we're checking */
58 if (!m_strcmp(idata->mailbox, mx.mbox)) {
64 imap_munge_mbox_name (mbox, sizeof (mbox), mailbox);
66 if (mutt_bit_isset (idata->capabilities, IMAP4REV1))
67 snprintf (buf, sizeof (buf), "STATUS %s (UIDVALIDITY)", mbox);
68 else if (mutt_bit_isset (idata->capabilities, STATUS))
69 snprintf (buf, sizeof (buf), "STATUS %s (UID-VALIDITY)", mbox);
74 if (imap_exec (idata, buf, IMAP_CMD_FAIL_OK) < 0) {
81 int imap_create_mailbox (IMAP_DATA * idata, char *mailbox)
83 char buf[LONG_STRING], mbox[LONG_STRING];
85 imap_munge_mbox_name (mbox, sizeof (mbox), mailbox);
86 snprintf (buf, sizeof (buf), "CREATE %s", mbox);
88 if (imap_exec (idata, buf, 0) != 0)
94 int imap_rename_mailbox (IMAP_DATA * idata, IMAP_MBOX * mx,
97 char oldmbox[LONG_STRING];
98 char newmbox[LONG_STRING];
99 char buf[LONG_STRING];
101 imap_munge_mbox_name (oldmbox, sizeof (oldmbox), mx->mbox);
102 imap_munge_mbox_name (newmbox, sizeof (newmbox), newname);
104 snprintf (buf, sizeof (buf), "RENAME %s %s", oldmbox, newmbox);
106 if (imap_exec (idata, buf, 0) != 0)
112 int imap_delete_mailbox (CONTEXT * ctx, IMAP_MBOX mx)
114 char buf[LONG_STRING], mbox[LONG_STRING];
117 if (!ctx || !ctx->data) {
118 if (!(idata = imap_conn_find (&mx.account,
119 option (OPTIMAPPASSIVE) ? M_IMAP_CONN_NONEW
129 imap_munge_mbox_name (mbox, sizeof (mbox), mx.mbox);
130 snprintf (buf, sizeof (buf), "DELETE %s", mbox);
132 if (imap_exec ((IMAP_DATA *) idata, buf, 0) != 0)
138 /* imap_logout_all: close all open connections. Quick and dirty until we can
139 * make sure we've got all the context we need. */
140 void imap_logout_all (void)
145 conn = mutt_socket_head ();
150 if (conn->account.type == M_ACCT_TYPE_IMAP && conn->fd >= 0) {
151 mutt_message (_("Closing connection to %s..."), conn->account.host);
152 imap_logout ((IMAP_DATA *) conn->data);
154 mutt_socket_close (conn);
155 mutt_socket_free (conn);
162 /* imap_read_literal: read bytes bytes from server into file. Not explicitly
163 * buffered, relies on FILE buffering. NOTE: strips \r from \r\n.
164 * Apparently even literals use \r\n-terminated strings ?! */
165 int imap_read_literal (FILE * fp, IMAP_DATA * idata, long bytes, progress_t* bar)
172 for (pos = 0; pos < bytes; pos++) {
173 if (mutt_socket_readchar (idata->conn, &c) != 1) {
174 idata->status = IMAP_FATAL;
179 if (r == 1 && c != '\n')
190 if (bar && !(pos % 1024))
191 mutt_progress_bar (bar, pos);
197 /* imap_expunge_mailbox: Purge IMAP portion of expunged messages from the
198 * context. Must not be done while something has a handle on any headers
199 * (eg inside pager or editor). That is, check IMAP_REOPEN_ALLOW. */
200 void imap_expunge_mailbox (IMAP_DATA * idata)
205 for (i = 0; i < idata->ctx->msgcount; i++) {
206 h = idata->ctx->hdrs[i];
208 if (h->index == -1) {
211 /* free cached body from disk, if neccessary */
212 cacheno = HEADER_DATA (h)->uid % IMAP_CACHE_LEN;
213 if (idata->cache[cacheno].uid == HEADER_DATA (h)->uid &&
214 idata->cache[cacheno].path) {
215 unlink (idata->cache[cacheno].path);
216 p_delete(&idata->cache[cacheno].path);
219 imap_free_header_data (&h->data);
223 /* We may be called on to expunge at any time. We can't rely on the caller
224 * to always know to rethread */
225 mx_update_tables (idata->ctx, 0);
226 mutt_sort_headers (idata->ctx, 1);
229 static int imap_get_delim (IMAP_DATA * idata)
234 /* assume that the delim is /. If this fails, we're in bigger trouble
235 * than getting the delim wrong */
238 imap_cmd_start (idata, "LIST\"\" \"\"");
241 if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
244 s = imap_next_word (idata->cmd.buf);
245 if (ascii_strncasecmp ("LIST", s, 4) == 0) {
246 s = imap_next_word (s);
247 s = imap_next_word (s);
248 if (s && s[0] == '\"' && s[1] && s[2] == '\"')
250 else if (s && s[0] == '\"' && s[1] && s[1] == '\\' && s[2]
255 while (rc == IMAP_CMD_CONTINUE);
260 /* get rights for folder, let imap_handle_untagged do the rest */
261 static int imap_check_acl (IMAP_DATA * idata)
263 char buf[LONG_STRING];
264 char mbox[LONG_STRING];
266 imap_munge_mbox_name (mbox, sizeof (mbox), idata->mailbox);
267 snprintf (buf, sizeof (buf), "MYRIGHTS %s", mbox);
268 if (imap_exec (idata, buf, 0) != 0) {
269 imap_error ("imap_check_acl", buf);
275 /* imap_check_capabilities: make sure we can log in to this server. */
276 static int imap_check_capabilities (IMAP_DATA * idata)
278 if (imap_exec (idata, "CAPABILITY", 0) != 0) {
279 imap_error ("imap_check_capabilities", idata->cmd.buf);
283 if (!(mutt_bit_isset (idata->capabilities, IMAP4)
284 || mutt_bit_isset (idata->capabilities, IMAP4REV1))) {
285 mutt_error _("This IMAP server is ancient. Mutt does not work with it.");
287 mutt_sleep (2); /* pause a moment to let the user see the error */
295 /* imap_conn_find: Find an open IMAP connection matching account, or open
296 * a new one if none can be found. */
297 IMAP_DATA *imap_conn_find (const ACCOUNT * account, int flags)
304 if (!(conn = mutt_conn_find (NULL, account)))
307 /* if opening a new UNSELECTED connection, preserve existing creds */
308 creds = &(conn->account);
310 /* make sure this connection is not in SELECTED state, if neccessary */
311 if (flags & M_IMAP_CONN_NOSELECT)
312 while (conn->data && ((IMAP_DATA *) conn->data)->state == IMAP_SELECTED) {
313 if (!(conn = mutt_conn_find (conn, account)))
315 memcpy (&(conn->account), creds, sizeof (ACCOUNT));
318 idata = (IMAP_DATA *) conn->data;
320 /* don't open a new connection if one isn't wanted */
321 if (flags & M_IMAP_CONN_NONEW) {
323 mutt_socket_free (conn);
326 if (idata->state < IMAP_AUTHENTICATED)
331 /* The current connection is a new connection */
332 if (!(idata = imap_new_idata ())) {
333 mutt_socket_free (conn);
342 if (idata->state == IMAP_DISCONNECTED)
343 imap_open_connection (idata);
344 if (idata->state == IMAP_CONNECTED) {
345 if (!imap_authenticate (idata)) {
346 idata->state = IMAP_AUTHENTICATED;
348 mutt_account_unsetpass (&idata->conn->account);
351 p_delete(&idata->capstr);
353 if (new && idata->state == IMAP_AUTHENTICATED) {
354 imap_get_delim (idata);
355 if (option (OPTIMAPCHECKSUBSCRIBED)) {
356 mutt_message _("Checking mailbox subscriptions");
357 imap_exec (idata, "LSUB \"\" \"*\"", 0);
364 int imap_open_connection (IMAP_DATA * idata)
366 char buf[LONG_STRING];
368 if (mutt_socket_open (idata->conn) < 0)
371 idata->state = IMAP_CONNECTED;
373 if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE) {
374 mutt_socket_close (idata->conn);
375 idata->state = IMAP_DISCONNECTED;
379 if (ascii_strncasecmp ("* OK", idata->cmd.buf, 4) == 0) {
380 /* TODO: Parse new tagged CAPABILITY data (* OK [CAPABILITY...]) */
381 if (imap_check_capabilities (idata))
383 /* Attempt STARTTLS if available and desired. */
384 if (!idata->conn->ssf && (mod_ssl.force_tls ||
385 mutt_bit_isset (idata->capabilities, STARTTLS))) {
388 if (mod_ssl.force_tls)
390 else if (mod_ssl.starttls) {
391 if ((rc = imap_exec (idata, "STARTTLS", IMAP_CMD_FAIL_OK)) == -1)
394 if (mutt_ssl_starttls (idata->conn))
396 mutt_error (_("Could not negotiate TLS connection"));
401 /* RFC 2595 demands we recheck CAPABILITY after TLS completes. */
402 if (imap_exec (idata, "CAPABILITY", 0))
409 if (mod_ssl.force_tls && ! idata->conn->ssf) {
410 mutt_error _("Encrypted connection unavailable");
415 else if (ascii_strncasecmp ("* PREAUTH", idata->cmd.buf, 9) == 0) {
416 idata->state = IMAP_AUTHENTICATED;
417 if (imap_check_capabilities (idata) != 0)
419 p_delete(&idata->capstr);
422 imap_error ("imap_open_connection()", buf);
429 mutt_socket_close (idata->conn);
430 idata->state = IMAP_DISCONNECTED;
432 p_delete(&idata->capstr);
436 /* imap_get_flags: Make a simple list out of a FLAGS response.
437 * return stream following FLAGS response */
438 static char *imap_get_flags (string_list_t ** hflags, char *s)
440 string_list_t *flags;
444 /* sanity-check string */
445 if (ascii_strncasecmp ("FLAGS", s, 5) != 0) {
448 s = vskipspaces(s + 5);
453 /* create list, update caller's flags handle */
454 flags = string_item_new();
457 while (*s && *s != ')') {
458 s = vskipspaces(s + 1);
460 while (*s && (*s != ')') && !ISSPACE (*s))
465 mutt_add_list (flags, flag_word);
469 /* note bad flags response */
471 string_list_wipe(hflags);
481 static int imap_open_mailbox (CONTEXT * ctx)
485 char buf[LONG_STRING];
486 char bufout[LONG_STRING];
491 if (imap_parse_path (ctx->path, &mx)) {
492 mutt_error (_("%s is an invalid IMAP path"), ctx->path);
496 /* we require a connection which isn't currently in IMAP_SELECTED state */
497 if (!(idata = imap_conn_find (&(mx.account), M_IMAP_CONN_NOSELECT)))
499 if (idata->state < IMAP_AUTHENTICATED)
504 /* once again the context is new */
507 /* Clean up path and replace the one in the ctx */
508 imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
509 p_delete(&(idata->mailbox));
510 idata->mailbox = m_strdup(buf);
511 imap_qualify_path (buf, sizeof (buf), &mx, idata->mailbox);
513 p_delete(&(ctx->path));
514 ctx->path = m_strdup(buf);
518 /* clear mailbox status */
520 memset (idata->rights, 0, (RIGHTSMAX + 7) / 8);
521 idata->newMailCount = 0;
523 mutt_message (_("Selecting %s..."), idata->mailbox);
524 imap_munge_mbox_name (buf, sizeof (buf), idata->mailbox);
525 snprintf (bufout, sizeof (bufout), "%s %s",
526 ctx->readonly ? "EXAMINE" : "SELECT", buf);
528 idata->state = IMAP_SELECTED;
530 imap_cmd_start (idata, bufout);
535 if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
538 pc = idata->cmd.buf + 2;
540 /* Obtain list of available flags here, may be overridden by a
541 * PERMANENTFLAGS tag in the OK response */
542 if (ascii_strncasecmp ("FLAGS", pc, 5) == 0) {
543 /* don't override PERMANENTFLAGS */
545 if ((pc = imap_get_flags (&(idata->flags), pc)) == NULL)
549 /* PERMANENTFLAGS are massaged to look like FLAGS, then override FLAGS */
550 else if (ascii_strncasecmp ("OK [PERMANENTFLAGS", pc, 18) == 0) {
551 /* safe to call on NULL */
552 string_list_wipe(&(idata->flags));
553 /* skip "OK [PERMANENT" so syntax is the same as FLAGS */
555 if ((pc = imap_get_flags (&(idata->flags), pc)) == NULL)
559 /* save UIDVALIDITY for the header cache */
560 else if (ascii_strncasecmp ("OK [UIDVALIDITY", pc, 14) == 0) {
562 pc = imap_next_word (pc);
564 sscanf (pc, "%lu", &(idata->uid_validity));
568 pc = imap_next_word (pc);
569 if (!ascii_strncasecmp ("EXISTS", pc, 6)) {
570 count = idata->newMailCount;
571 idata->newMailCount = 0;
575 while (rc == IMAP_CMD_CONTINUE);
577 if (rc == IMAP_CMD_NO) {
580 s = imap_next_word (idata->cmd.buf); /* skip seq */
581 s = imap_next_word (s); /* Skip response */
582 mutt_error ("%s", s);
587 if (rc != IMAP_CMD_OK)
590 /* check for READ-ONLY notification */
591 if (!ascii_strncasecmp
592 (imap_get_qualifier (idata->cmd.buf), "[READ-ONLY]", 11)
593 && !mutt_bit_isset (idata->capabilities, ACL)) {
597 if (mutt_bit_isset (idata->capabilities, ACL)) {
598 if (imap_check_acl (idata))
600 if (!(mutt_bit_isset (idata->rights, ACL_DELETE) ||
601 mutt_bit_isset (idata->rights, ACL_SEEN) ||
602 mutt_bit_isset (idata->rights, ACL_WRITE) ||
603 mutt_bit_isset (idata->rights, ACL_INSERT)))
606 /* assume we have all rights if ACL is unavailable */
608 mutt_bit_set (idata->rights, ACL_LOOKUP);
609 mutt_bit_set (idata->rights, ACL_READ);
610 mutt_bit_set (idata->rights, ACL_SEEN);
611 mutt_bit_set (idata->rights, ACL_WRITE);
612 mutt_bit_set (idata->rights, ACL_INSERT);
613 mutt_bit_set (idata->rights, ACL_POST);
614 mutt_bit_set (idata->rights, ACL_CREATE);
615 mutt_bit_set (idata->rights, ACL_DELETE);
619 ctx->hdrs = p_new(HEADER *, count);
620 ctx->v2r = p_new(int, count);
622 if (count && (imap_read_headers (idata, 0, count - 1) < 0)) {
623 mutt_error _("Error opening mailbox");
633 if (idata->state == IMAP_SELECTED)
634 idata->state = IMAP_AUTHENTICATED;
640 int imap_open_mailbox_append (CONTEXT * ctx)
644 char buf[LONG_STRING];
645 char mailbox[LONG_STRING];
648 if (imap_parse_path (ctx->path, &mx))
651 /* in APPEND mode, we appear to hijack an existing IMAP connection -
652 * ctx is brand new and mostly empty */
654 if (!(idata = imap_conn_find (&(mx.account), 0))) {
663 imap_fix_path (idata, mx.mbox, mailbox, sizeof (mailbox));
667 /* really we should also check for W_OK */
668 if (!imap_access (ctx->path, F_OK))
671 snprintf (buf, sizeof (buf), _("Create %s?"), mailbox);
672 if (option (OPTCONFIRMCREATE) && mutt_yesorno (buf, 1) < 1)
675 if (imap_create_mailbox (idata, mailbox) < 0)
681 /* imap_logout: Gracefully log out of server. */
682 void imap_logout (IMAP_DATA * idata)
684 /* we set status here to let imap_handle_untagged know we _expect_ to
685 * receive a bye response (so it doesn't freak out and close the conn) */
686 idata->status = IMAP_BYE;
687 imap_cmd_start (idata, "LOGOUT");
688 while (imap_cmd_step (idata) == IMAP_CMD_CONTINUE);
689 p_delete(&idata->cmd.buf);
694 int imap_close_connection (CONTEXT *ctx)
696 if (CTX_DATA->status != IMAP_BYE)
698 mutt_message _("Closing connection to IMAP server...");
699 imap_logout (CTX_DATA);
702 mutt_socket_close (CTX_DATA->conn);
703 CTX_DATA->state = IMAP_DISCONNECTED;
704 CTX_DATA->conn->data = NULL;
709 /* imap_set_flag: append str to flags if we currently have permission
710 * according to aclbit */
711 static void imap_set_flag(IMAP_DATA *idata, int aclbit, int flag,
712 const char *str, char *flags, size_t flsize)
714 if (mutt_bit_isset(idata->rights, aclbit)) {
716 m_strcat(flags, flsize, str);
720 /* imap_make_msg_set: make an IMAP4rev1 UID message set out of a set of
721 * headers, given a flag enum to filter on.
722 * Params: idata: IMAP_DATA containing context containing header set
723 * buf: to write message set into
724 * buflen: length of buffer
725 * flag: enum of flag type on which to filter
726 * changed: include only changed messages in message set
727 * Returns: number of messages in message set (0 if no matches) */
728 int imap_make_msg_set (IMAP_DATA * idata, BUFFER * buf, int flag, int changed)
730 HEADER **hdrs; /* sorted local copy */
731 int count = 0; /* number of messages in message set */
732 int match = 0; /* whether current message matches flag condition */
733 int setstart = 0; /* start of current message range */
735 short oldsort; /* we clobber reverse, must restore it */
737 /* assuming 32-bit UIDs */
741 /* make copy of header pointers to sort in natural order */
742 hdrs = p_dup(idata->ctx->hdrs, idata->ctx->msgcount);
744 if (Sort != SORT_ORDER) {
747 qsort ((void *) hdrs, idata->ctx->msgcount, sizeof (HEADER *),
748 mutt_get_sort_func (SORT_ORDER));
752 for (n = 0; n < idata->ctx->msgcount; n++) {
754 /* don't include pending expunged messages */
758 if (hdrs[n]->deleted)
767 if (match && (!changed || hdrs[n]->changed)) {
770 setstart = HEADER_DATA (hdrs[n])->uid;
772 snprintf (uid, sizeof (uid), "%u", HEADER_DATA (hdrs[n])->uid);
773 mutt_buffer_addstr (buf, uid);
777 snprintf (uid, sizeof (uid), ",%u", HEADER_DATA (hdrs[n])->uid);
778 mutt_buffer_addstr (buf, uid);
781 /* tie up if the last message also matches */
782 else if (n == idata->ctx->msgcount - 1) {
783 snprintf (uid, sizeof (uid), ":%u", HEADER_DATA (hdrs[n])->uid);
784 mutt_buffer_addstr (buf, uid);
787 /* this message is not expunged and doesn't match. End current set. */
788 else if (setstart && hdrs[n]->active) {
789 if (HEADER_DATA (hdrs[n - 1])->uid > setstart) {
790 snprintf (uid, sizeof (uid), ":%u", HEADER_DATA (hdrs[n - 1])->uid);
791 mutt_buffer_addstr (buf, uid);
802 /* Update the IMAP server to reflect the flags a single message. */
804 int imap_sync_message (IMAP_DATA *idata, HEADER *hdr, BUFFER *cmd,
807 char flags[LONG_STRING];
812 snprintf (uid, sizeof (uid), "%u", HEADER_DATA(hdr)->uid);
813 cmd->dptr = cmd->data;
814 mutt_buffer_addstr (cmd, "UID STORE ");
815 mutt_buffer_addstr (cmd, uid);
819 imap_set_flag (idata, ACL_SEEN, hdr->read, "\\Seen ",
820 flags, sizeof (flags));
821 imap_set_flag (idata, ACL_WRITE, hdr->flagged,
822 "\\Flagged ", flags, sizeof (flags));
823 imap_set_flag (idata, ACL_WRITE, hdr->replied,
824 "\\Answered ", flags, sizeof (flags));
825 imap_set_flag (idata, ACL_DELETE, hdr->deleted,
826 "\\Deleted ", flags, sizeof (flags));
828 /* now make sure we don't lose custom tags */
829 if (mutt_bit_isset (idata->rights, ACL_WRITE))
830 imap_add_keywords (flags, hdr, idata->flags, sizeof (flags));
834 /* UW-IMAP is OK with null flags, Cyrus isn't. The only solution is to
835 * explicitly revoke all system flags (if we have permission) */
838 imap_set_flag (idata, ACL_SEEN, 1, "\\Seen ", flags, sizeof (flags));
839 imap_set_flag (idata, ACL_WRITE, 1, "\\Flagged ", flags, sizeof (flags));
840 imap_set_flag (idata, ACL_WRITE, 1, "\\Answered ", flags, sizeof (flags));
841 imap_set_flag (idata, ACL_DELETE, 1, "\\Deleted ", flags, sizeof (flags));
845 mutt_buffer_addstr (cmd, " -FLAGS.SILENT (");
847 mutt_buffer_addstr (cmd, " FLAGS.SILENT (");
849 mutt_buffer_addstr (cmd, flags);
850 mutt_buffer_addstr (cmd, ")");
852 /* dumb hack for bad UW-IMAP 4.7 servers spurious FLAGS updates */
855 /* after all this it's still possible to have no flags, if you
856 * have no ACL rights */
857 if (*flags && (imap_exec (idata, cmd->data, 0) != 0) &&
858 err_continue && (*err_continue != M_YES))
860 *err_continue = imap_continue ("imap_sync_message: STORE failed",
862 if (*err_continue != M_YES)
867 idata->ctx->changed--;
872 /* update the IMAP server to reflect message changes done within mutt.
874 * ctx: the current context
875 * expunge: 0 or 1 - do expunge?
878 int imap_sync_mailbox (CONTEXT * ctx, int expunge, int *index_hint)
881 CONTEXT *appendctx = NULL;
885 int err_continue = M_NO; /* continue on error? */
888 idata = (IMAP_DATA *) ctx->data;
890 if (idata->state != IMAP_SELECTED) {
894 /* This function is only called when the calling code expects the context
896 imap_allow_reopen (ctx);
898 if ((rc = imap_check_mailbox (ctx, index_hint, 0)) != 0)
903 /* if we are expunging anyway, we can do deleted messages very quickly... */
904 if (expunge && mutt_bit_isset (idata->rights, ACL_DELETE)) {
905 mutt_buffer_addstr (&cmd, "UID STORE ");
906 deleted = imap_make_msg_set (idata, &cmd, M_DELETE, 1);
908 /* if we have a message set, then let's delete */
910 mutt_message (_("Marking %d messages deleted..."), deleted);
911 mutt_buffer_addstr (&cmd, " +FLAGS.SILENT (\\Deleted)");
912 /* mark these messages as unchanged so second pass ignores them. Done
913 * here so BOGUS UW-IMAP 4.7 SILENT FLAGS updates are ignored. */
914 for (n = 0; n < ctx->msgcount; n++)
915 if (ctx->hdrs[n]->deleted && ctx->hdrs[n]->changed)
916 ctx->hdrs[n]->active = 0;
917 if (imap_exec (idata, cmd.data, 0) != 0) {
918 mutt_error (_("Expunge failed"));
926 /* save status changes */
927 for (n = 0; n < ctx->msgcount; n++) {
928 if (ctx->hdrs[n]->active && ctx->hdrs[n]->changed) {
930 mutt_message (_("Saving message status flags... [%d/%d]"), n + 1,
933 /* if the message has been rethreaded or attachments have been deleted
934 * we delete the message and reupload it.
935 * This works better if we're expunging, of course. */
936 if ((ctx->hdrs[n]->env && (ctx->hdrs[n]->env->refs_changed || ctx->hdrs[n]->env->irt_changed)) ||
937 ctx->hdrs[n]->attach_del) {
939 appendctx = mx_open_mailbox (ctx->path, M_APPEND | M_QUIET, NULL);
941 _mutt_save_message (ctx->hdrs[n], appendctx, 1, 0, 0);
945 if (imap_sync_message (idata, ctx->hdrs[n], &cmd, &err_continue) < 0) {
953 /* We must send an EXPUNGE command if we're not closing. */
954 if (expunge && !(ctx->closing) &&
955 mutt_bit_isset (idata->rights, ACL_DELETE)) {
956 mutt_message _("Expunging messages from server...");
958 /* Set expunge bit so we don't get spurious reopened messages */
959 idata->reopen |= IMAP_EXPUNGE_EXPECTED;
960 if (imap_exec (idata, "EXPUNGE", 0) != 0) {
961 imap_error (_("imap_sync_mailbox: EXPUNGE failed"), idata->cmd.buf);
962 rc = imap_reconnect (ctx);
967 if (expunge && ctx->closing) {
968 if (imap_exec (idata, "CLOSE", 0))
969 mutt_error (_("CLOSE failed"));
970 idata->state = IMAP_AUTHENTICATED;
978 mx_fastclose_mailbox (appendctx);
979 p_delete(&appendctx);
984 /* imap_close_mailbox: clean up IMAP data in CONTEXT */
985 static void imap_close_mailbox (CONTEXT * ctx)
990 idata = (IMAP_DATA *) ctx->data;
991 /* Check to see if the mailbox is actually open */
995 if (ctx == idata->ctx) {
996 if (idata->state != IMAP_FATAL && idata->state == IMAP_SELECTED) {
997 /* mx_close_mailbox won't sync if there are no deleted messages
998 * and the mailbox is unchanged, so we may have to close here */
999 if (!ctx->deleted && imap_exec (idata, "CLOSE", 0))
1000 mutt_error (_("CLOSE failed"));
1001 idata->state = IMAP_AUTHENTICATED;
1004 idata->reopen &= IMAP_REOPEN_ALLOW;
1005 p_delete(&(idata->mailbox));
1006 string_list_wipe(&idata->flags);
1010 /* free IMAP part of headers */
1011 for (i = 0; i < ctx->msgcount; i++)
1012 imap_free_header_data (&(ctx->hdrs[i]->data));
1014 for (i = 0; i < IMAP_CACHE_LEN; i++) {
1015 if (idata->cache[i].path) {
1016 unlink (idata->cache[i].path);
1017 p_delete(&idata->cache[i].path);
1022 /* use the NOOP command to poll for new mail
1025 * M_REOPENED mailbox has been externally modified
1026 * M_NEW_MAIL new mail has arrived!
1030 int imap_check_mailbox (CONTEXT * ctx, int *index_hint __attribute__ ((unused)), int force)
1032 /* overload keyboard timeout to avoid many mailbox checks in a row.
1033 * Most users don't like having to wait exactly when they press a key. */
1037 idata = (IMAP_DATA *) ctx->data;
1039 if ((force || time (NULL) >= idata->lastread + Timeout)
1040 && imap_exec (idata, "NOOP", 0) != 0)
1043 /* We call this even when we haven't run NOOP in case we have pending
1044 * changes to process, since we can reopen here. */
1045 imap_cmd_finish (idata);
1047 if (idata->check_status & IMAP_EXPUNGE_PENDING)
1048 result = M_REOPENED;
1049 else if (idata->check_status & IMAP_NEWMAIL_PENDING)
1050 result = M_NEW_MAIL;
1051 else if (idata->check_status & IMAP_FLAGS_PENDING)
1054 idata->check_status = 0;
1065 * 0+ number of messages in mailbox
1066 * -1 error while polling mailboxes
1068 int imap_mailbox_check (char *path, int new)
1072 char buf[LONG_STRING];
1073 char mbox[LONG_STRING];
1074 char mbox_unquoted[LONG_STRING];
1081 if (imap_parse_path (path, &mx))
1084 /* If imap_passive is set, don't open a connection to check for new mail */
1085 if (option (OPTIMAPPASSIVE))
1086 connflags = M_IMAP_CONN_NONEW;
1088 if (!(idata = imap_conn_find (&(mx.account), connflags))) {
1094 imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
1097 imap_munge_mbox_name (mbox, sizeof (mbox), buf);
1098 m_strcpy(mbox_unquoted, sizeof(mbox_unquoted), buf);
1100 /* The draft IMAP implementor's guide warns againts using the STATUS
1101 * command on a mailbox that you have selected
1104 if (m_strcmp(mbox_unquoted, idata->mailbox) == 0
1105 || (ascii_strcasecmp (mbox_unquoted, "INBOX") == 0
1106 && m_strcasecmp(mbox_unquoted, idata->mailbox) == 0)) {
1107 m_strcpy(buf, sizeof(buf), "NOOP");
1109 else if (mutt_bit_isset (idata->capabilities, IMAP4REV1) ||
1110 mutt_bit_isset (idata->capabilities, STATUS)) {
1111 snprintf (buf, sizeof (buf), "STATUS %s (%s)", mbox,
1112 new == 1 ? "RECENT" : (new == 2 ? "UNSEEN" : "MESSAGES"));
1115 /* Server does not support STATUS, and this is not the current mailbox.
1116 * There is no lightweight way to check recent arrivals */
1119 imap_cmd_start (idata, buf);
1122 if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
1125 s = imap_next_word (idata->cmd.buf);
1126 if (ascii_strncasecmp ("STATUS", s, 6) == 0) {
1127 s = imap_next_word (s);
1128 /* The mailbox name may or may not be quoted here. We could try to
1129 * munge the server response and compare with quoted (or vise versa)
1130 * but it is probably more efficient to just strncmp against both. */
1131 if (m_strncmp(mbox_unquoted, s, m_strlen(mbox_unquoted)) == 0
1132 || m_strncmp(mbox, s, m_strlen(mbox)) == 0) {
1133 s = imap_next_word (s);
1134 s = imap_next_word (s);
1135 if (isdigit ((unsigned char) *s)) {
1137 msgcount = atoi (s);
1143 while (rc == IMAP_CMD_CONTINUE);
1148 /* returns number of patterns in the search that should be done server-side
1149 * (eg are full-text) */
1150 static int do_search (const pattern_t* search, int allpats)
1153 const pattern_t* pat;
1155 for (pat = search; pat; pat = pat->next) {
1160 if (pat->stringmatch)
1164 if (pat->child && do_search (pat->child, 1))
1175 /* convert mutt pattern_t to IMAP SEARCH command containing only elements
1176 * that require full-text search (mutt already has what it needs for most
1177 * match types, and does a better job (eg server doesn't support regexps). */
1178 static int imap_compile_search (const pattern_t* pat, BUFFER* buf)
1180 if (! do_search (pat, 0))
1184 mutt_buffer_addstr (buf, "NOT ");
1189 if ((clauses = do_search (pat->child, 1)) > 0) {
1190 const pattern_t* clause = pat->child;
1192 mutt_buffer_addch (buf, '(');
1195 if (do_search (clause, 0)) {
1196 if (pat->op == M_OR && clauses > 1)
1197 mutt_buffer_addstr (buf, "OR ");
1199 if (imap_compile_search (clause, buf) < 0)
1203 mutt_buffer_addch (buf, ' ');
1205 clause = clause->next;
1209 mutt_buffer_addch (buf, ')');
1217 mutt_buffer_addstr (buf, "HEADER ");
1219 /* extract header name */
1220 if (! (delim = strchr (pat->str, ':'))) {
1221 mutt_error (_("Header search without header name: %s"), pat->str);
1225 imap_quote_string (term, sizeof (term), pat->str);
1226 mutt_buffer_addstr (buf, term);
1227 mutt_buffer_addch (buf, ' ');
1231 delim = vskipspaces(delim);
1232 imap_quote_string (term, sizeof (term), delim);
1233 mutt_buffer_addstr (buf, term);
1237 mutt_buffer_addstr (buf, "BODY ");
1238 imap_quote_string (term, sizeof (term), pat->str);
1239 mutt_buffer_addstr (buf, term);
1243 mutt_buffer_addstr (buf, "TEXT ");
1244 imap_quote_string (term, sizeof (term), pat->str);
1245 mutt_buffer_addstr (buf, term);
1253 int imap_search (CONTEXT* ctx, const pattern_t* pat) {
1255 IMAP_DATA* idata = (IMAP_DATA*)ctx->data;
1258 for (i = 0; i < ctx->msgcount; i++)
1259 ctx->hdrs[i]->matched = 0;
1261 if (!do_search (pat, 1))
1265 mutt_buffer_addstr (&buf, "UID SEARCH ");
1266 if (imap_compile_search (pat, &buf) < 0) {
1267 p_delete(&buf.data);
1270 if (imap_exec (idata, buf.data, 0) < 0) {
1271 p_delete(&buf.data);
1275 p_delete(&buf.data);
1279 /* all this listing/browsing is a mess. I don't like that name is a pointer
1280 * into idata->buf (used to be a pointer into the passed in buffer, just
1281 * as bad), nor do I like the fact that the fetch is done here. This
1282 * code can't possibly handle non-string_list_t untagged responses properly.
1284 int imap_parse_list_response (IMAP_DATA * idata, char **name, int *noselect,
1285 int *noinferiors, char *delim)
1293 rc = imap_cmd_step (idata);
1294 if (rc == IMAP_CMD_OK)
1296 if (rc != IMAP_CMD_CONTINUE)
1299 s = imap_next_word (idata->cmd.buf);
1300 if ((ascii_strncasecmp ("LIST", s, 4) == 0) ||
1301 (ascii_strncasecmp ("LSUB", s, 4) == 0)) {
1305 s = imap_next_word (s); /* flags */
1311 while (*ep && *ep != ')')
1314 if (!ascii_strncasecmp (s, "\\NoSelect", 9))
1316 if (!ascii_strncasecmp (s, "\\NoInferiors", 12))
1318 /* See draft-gahrns-imap-child-mailbox-?? */
1319 if (!ascii_strncasecmp (s, "\\HasNoChildren", 14))
1323 while (*s && *s != '\\' && *s != ')')
1329 s = imap_next_word (s); /* delim */
1330 /* Reset the delimiter, this can change */
1331 if (ascii_strncasecmp (s, "NIL", 3)) {
1332 if (s && s[0] == '\"' && s[1] && s[2] == '\"')
1334 else if (s && s[0] == '\"' && s[1] && s[1] == '\\' && s[2]
1338 s = imap_next_word (s); /* name */
1339 if (s && *s == '{') { /* Literal */
1340 if (imap_get_literal_count (idata->cmd.buf, &bytes) < 0)
1342 if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE)
1344 *name = idata->cmd.buf;
1353 int imap_subscribe (char *path, int subscribe)
1357 char buf[LONG_STRING];
1358 char mbox[LONG_STRING];
1359 char errstr[STRING];
1363 if (mx_get_magic (path) != M_IMAP || imap_parse_path (path, &mx) < 0) {
1364 mutt_error (_("Bad mailbox name"));
1368 if (!(idata = imap_conn_find (&(mx.account), 0)))
1373 imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
1375 if (option (OPTIMAPCHECKSUBSCRIBED)) {
1378 err.dsize = sizeof (errstr);
1379 snprintf (mbox, sizeof (mbox), "%smailboxes \"%s\"",
1380 subscribe ? "" : "un", path);
1381 mutt_parse_rc_line (mbox, &token, &err);
1382 p_delete(&token.data);
1386 mutt_message (_("Subscribing to %s..."), buf);
1388 mutt_message (_("Unsubscribing to %s..."), buf);
1389 imap_munge_mbox_name (mbox, sizeof (mbox), buf);
1391 snprintf (buf, sizeof (buf), "%sSUBSCRIBE %s", subscribe ? "" : "UN", mbox);
1393 if (imap_exec (idata, buf, 0) < 0)
1404 /* trim dest to the length of the longest prefix it shares with src,
1405 * returning the length of the trimmed string */
1406 static int longest_common_prefix (char *dest, const char* src,
1407 int start, ssize_t dlen) {
1410 while (pos < dlen && dest[pos] && dest[pos] == src[pos])
1417 /* look for IMAP URLs to complete from defined mailboxes. Could be extended
1418 * to complete over open connections and account/folder hooks too. */
1419 static int imap_complete_hosts (char *dest, ssize_t len) {
1426 matchlen = m_strlen(dest);
1429 for (i = 0; i < Incoming.len; i++) {
1430 mailbox = Incoming.arr[i];
1431 if (!m_strncmp(dest, mailbox->path, matchlen)) {
1433 m_strcpy(dest, len, mailbox->path);
1436 longest_common_prefix (dest, mailbox->path, matchlen, len);
1440 for (conn = mutt_socket_head (); conn && conn->next; conn = conn->next) {
1442 char urlstr[LONG_STRING];
1444 if (conn->account.type != M_ACCT_TYPE_IMAP)
1447 mutt_account_tourl (&conn->account, &url);
1448 /* FIXME: how to handle multiple users on the same host? */
1451 url_ciss_tostring (&url, urlstr, sizeof (urlstr), 0);
1452 if (!m_strncmp(dest, urlstr, matchlen)) {
1454 m_strcpy(dest, len, urlstr);
1457 longest_common_prefix (dest, urlstr, matchlen, len);
1464 /* imap_complete: given a partial IMAP folder path, return a string which
1465 * adds as much to the path as is unique */
1466 int imap_complete (char *dest, size_t dlen, char *path) {
1469 char list[LONG_STRING];
1470 char buf[LONG_STRING];
1471 char *list_word = NULL;
1472 int noselect, noinferiors;
1474 char completion[LONG_STRING];
1475 int clen, matchlen = 0;
1476 int completions = 0;
1479 if (imap_parse_path (path, &mx) || !mx.mbox) {
1480 m_strcpy(dest, dlen, path);
1481 return imap_complete_hosts (dest, dlen);
1484 /* don't open a new socket just for completion. Instead complete over
1485 * known mailboxes/hooks/etc */
1486 if (!(idata = imap_conn_find (&(mx.account), M_IMAP_CONN_NONEW))) {
1488 m_strcpy(dest, dlen, path);
1489 return imap_complete_hosts (dest, dlen);
1493 /* reformat path for IMAP list, and append wildcard */
1494 /* don't use INBOX in place of "" */
1495 if (mx.mbox && mx.mbox[0])
1496 imap_fix_path (idata, mx.mbox, list, sizeof (list));
1500 /* fire off command */
1501 snprintf (buf, sizeof (buf), "%s \"\" \"%s%%\"",
1502 option (OPTIMAPLSUB) ? "LSUB" : "LIST", list);
1504 imap_cmd_start (idata, buf);
1506 /* and see what the results are */
1507 m_strcpy(completion, sizeof(completion), NONULL(mx.mbox));
1509 if (imap_parse_list_response (idata, &list_word, &noselect, &noinferiors,
1514 /* store unquoted */
1515 imap_unmunge_mbox_name (list_word);
1517 /* if the folder isn't selectable, append delimiter to force browse
1518 * to enter it on second tab. */
1520 clen = m_strlen(list_word);
1521 list_word[clen++] = delim;
1522 list_word[clen] = '\0';
1524 /* copy in first word */
1526 m_strcpy(completion, sizeof(completion), list_word);
1527 matchlen = m_strlen(completion);
1532 matchlen = longest_common_prefix (completion, list_word, 0, matchlen);
1536 while (m_strncmp(idata->cmd.seq, idata->cmd.buf, SEQLEN));
1539 /* reformat output */
1540 imap_qualify_path (dest, dlen, &mx, completion);
1541 mutt_pretty_mailbox (dest);
1550 /* reconnect if connection was lost */
1551 int imap_reconnect (CONTEXT * ctx)
1553 IMAP_DATA *imap_data;
1558 imap_data = (IMAP_DATA *) ctx->data;
1561 if (imap_data->status == IMAP_CONNECTED)
1565 if (query_quadoption
1567 _("Connection lost. Reconnect to IMAP server?")) != M_YES)
1570 mx_open_mailbox (ctx->path, 0, ctx);
1574 int imap_is_magic (const char* path, struct stat* st __attribute__ ((unused))) {
1576 if (!path || !*path)
1578 s = url_check_scheme (NONULL (path));
1579 return ((s == U_IMAP || s == U_IMAPS) ? M_IMAP : -1);
1582 static int acl_check_imap (CONTEXT* ctx, int bit) {
1583 return (!mutt_bit_isset (((IMAP_DATA*) ctx->data)->capabilities, ACL) ||
1584 mutt_bit_isset (((IMAP_DATA*) ctx->data)->rights, bit));
1587 static int imap_open_new_message (MESSAGE * msg,
1588 CONTEXT * dest __attribute__ ((unused)),
1589 HEADER * hdr __attribute__ ((unused)))
1591 char tmp[_POSIX_PATH_MAX];
1593 msg->fp = m_tempfile(tmp, sizeof(tmp), NONULL(MCore.tmpdir), NULL);
1599 msg->path = m_strdup(tmp);
1603 /* this ugly kludge is required since the last int to
1604 * imap_check_mailbox() doesn't mean 'lock' but 'force'... */
1605 static int _imap_check_mailbox (CONTEXT* ctx,
1607 int lock __attribute__ ((unused))) {
1608 return (imap_check_mailbox (ctx, index_hint, 0));
1611 static int imap_commit_message (MESSAGE* msg, CONTEXT* ctx) {
1614 if ((r = m_fclose(&msg->fp)) == 0)
1615 r = imap_append_message (ctx, msg);
1619 mx_t const imap_mx = {
1626 imap_open_new_message,
1628 _imap_check_mailbox,
1631 imap_commit_message,