NeoMutt  2025-12-11-860-g80c9cc
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
imap.c
Go to the documentation of this file.
1
34
42
43#include "config.h"
44#include <errno.h>
45#include <limits.h>
46#include <stdbool.h>
47#include <stdint.h>
48#include <stdio.h>
49#include <string.h>
50#include <time.h>
51#include "private.h"
52#include "mutt/lib.h"
53#include "config/lib.h"
54#include "email/lib.h"
55#include "core/lib.h"
56#include "conn/lib.h"
57#include "mutt.h"
58#include "lib.h"
59#include "commands/lib.h"
60#include "editor/lib.h"
61#include "history/lib.h"
62#include "hooks/lib.h"
63#include "parse/lib.h"
64#include "progress/lib.h"
65#include "question/lib.h"
66#include "adata.h"
67#include "auth.h"
68#include "edata.h"
69#include "external.h"
70#include "mdata.h"
71#include "msg_set.h"
72#include "msn.h"
73#include "mutt_logging.h"
74#include "mutt_socket.h"
75#include "muttlib.h"
76#include "mx.h"
77#ifdef ENABLE_NLS
78#include <libintl.h>
79#endif
80
81struct Progress;
82struct stat;
83
93enum CommandResult parse_subscribe_to(const struct Command *cmd, struct Buffer *line,
94 const struct ParseContext *pc, struct ParseError *pe)
95{
96 struct Buffer *err = pe->message;
97
98 if (!MoreArgs(line))
99 {
100 buf_printf(err, _("%s: too few arguments"), cmd->name);
101 return MUTT_CMD_WARNING;
102 }
103
104 struct Buffer *token = buf_pool_get();
106
107 buf_reset(err);
108
110
111 if (MoreArgs(line))
112 {
113 buf_printf(err, _("%s: too many arguments"), cmd->name);
114 goto done;
115 }
116
117 // Expand and subscribe
118 expand_path(token, false);
119 if (imap_subscribe(buf_string(token), true) != 0)
120 {
121 buf_printf(err, _("Could not subscribe to %s"), buf_string(token));
122 rc = MUTT_CMD_ERROR;
123 goto done;
124 }
125
126 mutt_message(_("Subscribed to %s"), buf_string(token));
127 rc = MUTT_CMD_SUCCESS;
128
129done:
130 buf_pool_release(&token);
131 return rc;
132}
133
143enum CommandResult parse_unsubscribe_from(const struct Command *cmd, struct Buffer *line,
144 const struct ParseContext *pc,
145 struct ParseError *pe)
146{
147 struct Buffer *err = pe->message;
148
149 if (!MoreArgs(line))
150 {
151 buf_printf(err, _("%s: too few arguments"), cmd->name);
152 return MUTT_CMD_WARNING;
153 }
154
155 struct Buffer *token = buf_pool_get();
157
159
160 if (MoreArgs(line))
161 {
162 buf_printf(err, _("%s: too many arguments"), cmd->name);
163 goto done;
164 }
165
166 // Expand and unsubscribe
167 expand_path(token, false);
168 if (imap_subscribe(buf_string(token), false) != 0)
169 {
170 buf_printf(err, _("Could not unsubscribe from %s"), buf_string(token));
171 rc = MUTT_CMD_ERROR;
172 goto done;
173 }
174
175 mutt_message(_("Unsubscribed from %s"), buf_string(token));
176 rc = MUTT_CMD_SUCCESS;
177
178done:
179 buf_pool_release(&token);
180 return rc;
181}
182
186const struct Command ImapCommands[] = {
187 // clang-format off
188 { "subscribe-to", CMD_SUBSCRIBE_TO, parse_subscribe_to,
189 N_("Subscribe to an IMAP mailbox"),
190 N_("subscribe-to <imap-folder-uri>"),
191 "optionalfeatures.html#imap" },
192 { "unsubscribe-from", CMD_UNSUBSCRIBE_FROM, parse_unsubscribe_from,
193 N_("Unsubscribe from an IMAP mailbox"),
194 N_("unsubscribe-from <imap-folder-uri>"),
195 "optionalfeatures.html#imap" },
196
197 { NULL, CMD_NONE, NULL, NULL, NULL, NULL, CF_NO_FLAGS },
198 // clang-format on
199};
200
207static int check_capabilities(struct ImapAccountData *adata)
208{
209 if (imap_exec(adata, "CAPABILITY", IMAP_CMD_NO_FLAGS) != IMAP_EXEC_SUCCESS)
210 {
211 imap_error("check_capabilities", adata->buf);
212 return -1;
213 }
214
215 if (!((adata->capabilities & IMAP_CAP_IMAP4) || (adata->capabilities & IMAP_CAP_IMAP4REV1)))
216 {
217 mutt_error(_("This IMAP server is ancient. NeoMutt does not work with it."));
218 return -1;
219 }
220
221 return 0;
222}
223
233static char *get_flags(struct ListHead *hflags, char *s)
234{
235 /* sanity-check string */
236 const size_t plen = mutt_istr_startswith(s, "FLAGS");
237 if (plen == 0)
238 {
239 mutt_debug(LL_DEBUG1, "not a FLAGS response: %s\n", s);
240 return NULL;
241 }
242 s += plen;
243 SKIPWS(s);
244 if (*s != '(')
245 {
246 mutt_debug(LL_DEBUG1, "bogus FLAGS response: %s\n", s);
247 return NULL;
248 }
249
250 /* update caller's flags handle */
251 while (*s && (*s != ')'))
252 {
253 s++;
254 SKIPWS(s);
255 const char *flag_word = s;
256 while (*s && (*s != ')') && !mutt_isspace(*s))
257 s++;
258 const char ctmp = *s;
259 *s = '\0';
260 if (*flag_word)
261 mutt_list_insert_tail(hflags, mutt_str_dup(flag_word));
262 *s = ctmp;
263 }
264
265 /* note bad flags response */
266 if (*s != ')')
267 {
268 mutt_debug(LL_DEBUG1, "Unterminated FLAGS response: %s\n", s);
269 mutt_list_free(hflags);
270
271 return NULL;
272 }
273
274 s++;
275
276 return s;
277}
278
287static void set_flag(struct Mailbox *m, AclFlags aclflag, bool flag,
288 const char *str, struct Buffer *flags)
289{
290 struct ImapMboxData *mdata = imap_mdata_get(m);
291 if (!mdata)
292 return;
293
294 if (m->rights & aclflag)
295 if (flag && imap_has_flag(&mdata->flags, str))
296 buf_addstr(flags, str);
297}
298
307static bool compare_flags_for_copy(struct Email *e)
308{
309 struct ImapEmailData *edata = e->edata;
310
311 if (e->read != edata->read)
312 return true;
313 if (e->old != edata->old)
314 return true;
315 if (e->flagged != edata->flagged)
316 return true;
317 if (e->replied != edata->replied)
318 return true;
319
320 return false;
321}
322
334static int select_email_uids(struct Email **emails, int num_emails, enum MessageType flag,
335 bool changed, bool invert, struct UidArray *uida)
336{
337 if (!emails || !uida)
338 return -1;
339
340 for (int i = 0; i < num_emails; i++)
341 {
342 struct Email *e = emails[i];
343 if (!e || (changed && !e->changed))
344 continue;
345
346 /* don't include pending expunged messages.
347 *
348 * TODO: can we unset active in cmd_parse_expunge() and
349 * cmd_parse_vanished() instead of checking for index != INT_MAX. */
350 if (!e || !e->active || (e->index == INT_MAX))
351 continue;
352
354
355 bool match = false;
356 switch (flag)
357 {
358 case MUTT_DELETED:
359 if (e->deleted != edata->deleted)
360 match = invert ^ e->deleted;
361 break;
362 case MUTT_FLAG:
363 if (e->flagged != edata->flagged)
364 match = invert ^ e->flagged;
365 break;
366 case MUTT_OLD:
367 if (e->old != edata->old)
368 match = invert ^ e->old;
369 break;
370 case MUTT_READ:
371 if (e->read != edata->read)
372 match = invert ^ e->read;
373 break;
374 case MUTT_REPLIED:
375 if (e->replied != edata->replied)
376 match = invert ^ e->replied;
377 break;
378 case MUTT_TRASH:
379 if (e->deleted && !e->purge)
380 match = true;
381 break;
382 default:
383 break;
384 }
385
386 if (match)
387 ARRAY_ADD(uida, edata->uid);
388 }
389
390 return ARRAY_SIZE(uida);
391}
392
404static int sync_helper(struct Mailbox *m, struct Email **emails, int num_emails,
405 AclFlags right, enum MessageType flag, const char *name)
406{
408 struct ImapMboxData *mdata = imap_mdata_get(m);
409 if (!adata || !mdata)
410 return -1;
411
412 if ((m->rights & right) == 0)
413 return 0;
414
415 if ((right == MUTT_ACL_WRITE) && !imap_has_flag(&mdata->flags, name))
416 return 0;
417
418 int count = 0;
419 char buf[1024] = { 0 };
420
421 struct UidArray uida = ARRAY_HEAD_INITIALIZER;
422
423 // Set the flag (+FLAGS) on matching emails
424 select_email_uids(emails, num_emails, flag, true, false, &uida);
425 snprintf(buf, sizeof(buf), "+FLAGS.SILENT (%s)", name);
426 int rc = imap_exec_msg_set(adata, "UID STORE", buf, &uida);
427 if (rc < 0)
428 return rc;
429 count += rc;
430 ARRAY_FREE(&uida);
431
432 // Clear the flag (-FLAGS) on non-matching emails
433 select_email_uids(emails, num_emails, flag, true, true, &uida);
434 buf[0] = '-';
435 rc = imap_exec_msg_set(adata, "UID STORE", buf, &uida);
436 if (rc < 0)
437 return rc;
438 count += rc;
439 ARRAY_FREE(&uida);
440
441 return count;
442}
443
453static size_t longest_common_prefix(struct Buffer *buf, const char *src, size_t start)
454{
455 size_t pos = start;
456
457 size_t len = buf_len(buf);
458 while ((pos < len) && (buf_at(buf, pos) != '\0') && (buf_at(buf, pos) == src[pos]))
459 pos++;
460 buf->data[pos] = '\0';
461
462 buf_fix_dptr(buf);
463
464 return pos;
465}
466
476static int complete_hosts(struct Buffer *buf)
477{
478 int rc = -1;
479 size_t matchlen;
480
481 matchlen = buf_len(buf);
482 struct MailboxArray ma = neomutt_mailboxes_get(NeoMutt, MUTT_MAILBOX_ANY);
483 struct Mailbox **mp = NULL;
484 ARRAY_FOREACH(mp, &ma)
485 {
486 struct Mailbox *m = *mp;
487
489 continue;
490
491 if (rc)
492 {
493 buf_strcpy(buf, mailbox_path(m));
494 rc = 0;
495 }
496 else
497 {
498 longest_common_prefix(buf, mailbox_path(m), matchlen);
499 }
500 }
501 ARRAY_FREE(&ma); // Clean up the ARRAY, but not the Mailboxes
502
503#if 0
504 TAILQ_FOREACH(conn, mutt_socket_head(), entries)
505 {
506 struct Url url = { 0 };
507 char urlstr[1024] = { 0 };
508
509 if (conn->account.type != MUTT_ACCT_TYPE_IMAP)
510 continue;
511
512 account_to_url(&conn->account, &url);
513 /* FIXME: how to handle multiple users on the same host? */
514 url.user = NULL;
515 url.path = NULL;
516 url_tostring(&url, urlstr, sizeof(urlstr), U_NO_FLAGS);
517 if (mutt_strn_equal(buf, urlstr, matchlen))
518 {
519 if (rc)
520 {
521 mutt_str_copy(buf, urlstr, buflen);
522 rc = 0;
523 }
524 else
525 {
526 longest_common_prefix(buf, urlstr, matchlen);
527 }
528 }
529 }
530#endif
531
532 return rc;
533}
534
542int imap_create_mailbox(struct ImapAccountData *adata, const char *mailbox)
543{
544 char buf[2048] = { 0 };
545 char mbox[1024] = { 0 };
546
547 imap_munge_mbox_name(adata->unicode, mbox, sizeof(mbox), mailbox);
548 snprintf(buf, sizeof(buf), "CREATE %s", mbox);
549
551 {
552 mutt_error(_("CREATE failed: %s"), imap_cmd_trailer(adata));
553 return -1;
554 }
555
556 return 0;
557}
558
569int imap_access(const char *path)
570{
571 if (imap_path_status(path, false) >= 0)
572 return 0;
573 return -1;
574}
575
584int imap_rename_mailbox(struct ImapAccountData *adata, char *oldname, const char *newname)
585{
586 char oldmbox[1024] = { 0 };
587 char newmbox[1024] = { 0 };
588 int rc = 0;
589
590 imap_munge_mbox_name(adata->unicode, oldmbox, sizeof(oldmbox), oldname);
591 imap_munge_mbox_name(adata->unicode, newmbox, sizeof(newmbox), newname);
592
593 struct Buffer *buf = buf_pool_get();
594 buf_printf(buf, "RENAME %s %s", oldmbox, newmbox);
595
597 rc = -1;
598
599 buf_pool_release(&buf);
600
601 return rc;
602}
603
611int imap_delete_mailbox(struct Mailbox *m, char *path)
612{
613 char buf[PATH_MAX + 7];
614 char mbox[PATH_MAX] = { 0 };
615
617 if (!adata)
618 return -1;
619
620 struct Url *url = url_parse(path);
621 if (!url)
622 return -1;
623
624 imap_munge_mbox_name(adata->unicode, mbox, sizeof(mbox), url->path);
625 url_free(&url);
626 snprintf(buf, sizeof(buf), "DELETE %s", mbox);
628 return -1;
629
630 return 0;
631}
632
637static void imap_logout(struct ImapAccountData *adata)
638{
639 if (adata->status != IMAP_FATAL)
640 {
641 /* we set status here to let imap_handle_untagged know we _expect_ to
642 * receive a bye response (so it doesn't freak out and close the conn) */
643 if (adata->state == IMAP_DISCONNECTED)
644 {
645 return;
646 }
647
648 adata->status = IMAP_BYE;
649 imap_cmd_start(adata, "LOGOUT");
650 const short c_imap_poll_timeout = cs_subset_number(NeoMutt->sub, "imap_poll_timeout");
651 if ((c_imap_poll_timeout <= 0) ||
652 (mutt_socket_poll(adata->conn, c_imap_poll_timeout) != 0))
653 {
654 while (imap_cmd_step(adata) == IMAP_RES_CONTINUE)
655 ; // do nothing
656 }
657 }
658 mutt_socket_close(adata->conn);
659 adata->state = IMAP_DISCONNECTED;
660}
661
668{
669 struct Account **ap = NULL;
671 {
672 struct Account *a = *ap;
673 if (a->type != MUTT_IMAP)
674 continue;
675
676 struct ImapAccountData *adata = a->adata;
677 if (!adata)
678 continue;
679
680 struct Connection *conn = adata->conn;
681 if (!conn || (conn->fd < 0))
682 continue;
683
684 mutt_message(_("Closing connection to %s..."), conn->account.host);
685 imap_logout(a->adata);
687 }
688}
689
704int imap_read_literal(FILE *fp, struct ImapAccountData *adata,
705 unsigned long bytes, struct Progress *progress)
706{
707 char c;
708 bool r = false;
709 struct Buffer buf = { 0 }; // Do not allocate, maybe it won't be used
710
711 const short c_debug_level = cs_subset_number(NeoMutt->sub, "debug_level");
712 if (c_debug_level >= IMAP_LOG_LTRL)
713 buf_alloc(&buf, bytes + 1);
714
715 mutt_debug(LL_DEBUG2, "reading %lu byte literal from server\n", bytes);
716
717 /* For large transfers, calculate checkpoint for progress logging */
718 unsigned long checkpoint = bytes / 10; // Log every 10%
719 if (checkpoint == 0)
720 checkpoint = bytes; // For small transfers, don't log progress
721
722 time_t start_time = mutt_date_now();
723 time_t last_progress = start_time;
724 time_t last_activity = start_time;
725
726 /* Get timeout value - use imap_poll_timeout or default to 60 seconds */
727 const short c_imap_poll_timeout = cs_subset_number(NeoMutt->sub, "imap_poll_timeout");
728 const int stall_timeout = (c_imap_poll_timeout > 0) ? c_imap_poll_timeout : 60;
729
730 for (unsigned long pos = 0; pos < bytes; pos++)
731 {
732 /* Check for user interrupt (Ctrl-C) periodically */
733 if ((pos % 4096) == 0)
734 {
735 if (SigInt)
736 {
737 mutt_debug(LL_DEBUG1, "Literal read interrupted by user at %lu/%lu bytes\n",
738 pos, bytes);
739 mutt_error(_("Download interrupted"));
740 SigInt = false;
741 adata->status = IMAP_FATAL;
742 buf_dealloc(&buf);
743 return -1;
744 }
745
746 /* Check for stalled transfer */
747 time_t now = mutt_date_now();
748 if ((now - last_activity) > stall_timeout)
749 {
750 mutt_debug(LL_DEBUG1, "Literal read stalled at %lu/%lu bytes (no data for %d seconds)\n",
751 pos, bytes, stall_timeout);
752 mutt_error(_("Download stalled - no data received for %d seconds"), stall_timeout);
753 adata->status = IMAP_FATAL;
754 buf_dealloc(&buf);
755 return -1;
756 }
757 }
758
759 if (mutt_socket_readchar(adata->conn, &c) != 1)
760 {
761 time_t duration = mutt_date_now() - start_time;
762 mutt_debug(LL_DEBUG1, "Error during literal read at byte %lu/%lu (%.1f%% complete)\n",
763 pos, bytes, (bytes > 0) ? ((double) pos / bytes * 100.0) : 0.0);
764 mutt_debug(LL_DEBUG1, "Read failed after %ld seconds (errno=%d: %s)\n",
765 (long) duration, errno, strerror(errno));
766 adata->status = IMAP_FATAL;
767
768 buf_dealloc(&buf);
769 return -1;
770 }
771
772 last_activity = mutt_date_now();
773
774 if (r && (c != '\n'))
775 fputc('\r', fp);
776
777 if (c == '\r')
778 {
779 r = true;
780 continue;
781 }
782 else
783 {
784 r = false;
785 }
786
787 fputc(c, fp);
788
789 if ((pos % 1024) == 0)
790 progress_update(progress, pos, -1);
791
792 /* Log progress every 10% for large transfers */
793 if ((checkpoint > 0) && ((pos % checkpoint) == 0) && (pos > 0))
794 {
795 time_t now = mutt_date_now();
796 if (now > last_progress)
797 {
798 mutt_debug(LL_DEBUG2, "Literal read progress: %lu/%lu bytes (%.1f%%)\n",
799 pos, bytes, ((double) pos / bytes * 100.0));
800 last_progress = now;
801 }
802 }
803
804 if (c_debug_level >= IMAP_LOG_LTRL)
805 buf_addch(&buf, c);
806 }
807
808 time_t duration = mutt_date_now() - start_time;
809 if (duration > 0)
810 {
811 mutt_debug(LL_DEBUG2, "Literal read complete: %lu bytes in %ld seconds\n",
812 bytes, (long) duration);
813 }
814
815 if (c_debug_level >= IMAP_LOG_LTRL)
816 {
817 mutt_debug(IMAP_LOG_LTRL, "\n%s", buf.data);
818 buf_dealloc(&buf);
819 }
820 return 0;
821}
822
828void imap_notify_delete_email(struct Mailbox *m, struct Email *e)
829{
830 struct ImapMboxData *mdata = imap_mdata_get(m);
832
833 if (!mdata || !edata)
834 return;
835
836 imap_msn_remove(&mdata->msn, edata->msn - 1);
837 edata->msn = 0;
838}
839
849void imap_expunge_mailbox(struct Mailbox *m, bool resort)
850{
852 struct ImapMboxData *mdata = imap_mdata_get(m);
853 if (!adata || !mdata)
854 return;
855
856 struct Email *e = NULL;
857
858#ifdef USE_HCACHE
859 imap_hcache_open(adata, mdata, false);
860#endif
861
862 for (int i = 0; i < m->msg_count; i++)
863 {
864 e = m->emails[i];
865 if (!e)
866 break;
867
868 if (e->index == INT_MAX)
869 {
870 mutt_debug(LL_DEBUG2, "Expunging message UID %u\n", imap_edata_get(e)->uid);
871
872 e->deleted = true;
873
874 imap_cache_del(m, e);
875#ifdef USE_HCACHE
876 imap_hcache_del(mdata, imap_edata_get(e)->uid);
877#endif
878
879 mutt_hash_int_delete(mdata->uid_hash, imap_edata_get(e)->uid, e);
880
881 imap_edata_free((void **) &e->edata);
882 }
883 else
884 {
885 /* NeoMutt has several places where it turns off e->active as a
886 * hack. For example to avoid FLAG updates, or to exclude from
887 * imap_exec_msg_set.
888 *
889 * Unfortunately, when a reopen is allowed and the IMAP_EXPUNGE_PENDING
890 * flag becomes set (e.g. a flag update to a modified header),
891 * this function will be called by imap_cmd_finish().
892 *
893 * The ctx_update_tables() will free and remove these "inactive" headers,
894 * despite that an EXPUNGE was not received for them.
895 * This would result in memory leaks and segfaults due to dangling
896 * pointers in the msn_index and uid_hash.
897 *
898 * So this is another hack to work around the hacks. We don't want to
899 * remove the messages, so make sure active is on. */
900 e->active = true;
901 }
902 }
903
904#ifdef USE_HCACHE
905 imap_hcache_close(mdata);
906#endif
907
909 if (resort)
910 {
912 }
913}
914
922{
923 if (mutt_socket_open(adata->conn) < 0)
924 return -1;
925
926 adata->state = IMAP_CONNECTED;
927
928 if (imap_cmd_step(adata) != IMAP_RES_OK)
929 {
931 return -1;
932 }
933
934 if (mutt_istr_startswith(adata->buf, "* OK"))
935 {
936 if (!mutt_istr_startswith(adata->buf, "* OK [CAPABILITY") && check_capabilities(adata))
937 {
938 goto bail;
939 }
940#ifdef USE_SSL
941 /* Attempt STARTTLS if available and desired. */
942 const bool c_ssl_force_tls = cs_subset_bool(NeoMutt->sub, "ssl_force_tls");
943 if ((adata->conn->ssf == 0) &&
944 (c_ssl_force_tls || (adata->capabilities & IMAP_CAP_STARTTLS)))
945 {
946 enum QuadOption ans;
947
948 if (c_ssl_force_tls)
949 {
950 ans = MUTT_YES;
951 }
952 else if ((ans = query_quadoption(_("Secure connection with TLS?"),
953 NeoMutt->sub, "ssl_starttls")) == MUTT_ABORT)
954 {
955 goto bail;
956 }
957 if (ans == MUTT_YES)
958 {
959 enum ImapExecResult rc = imap_exec(adata, "STARTTLS", IMAP_CMD_SINGLE);
960 // Clear any data after the STARTTLS acknowledgement
961 mutt_socket_empty(adata->conn);
962
963 if (rc == IMAP_EXEC_FATAL)
964 goto bail;
965 if (rc != IMAP_EXEC_ERROR)
966 {
967 if (mutt_ssl_starttls(adata->conn))
968 {
969 mutt_error(_("Could not negotiate TLS connection"));
970 goto bail;
971 }
972 else
973 {
974 /* RFC2595 demands we recheck CAPABILITY after TLS completes. */
975 if (imap_exec(adata, "CAPABILITY", IMAP_CMD_NO_FLAGS) != IMAP_EXEC_SUCCESS)
976 goto bail;
977 }
978 }
979 }
980 }
981
982 if (c_ssl_force_tls && (adata->conn->ssf == 0))
983 {
984 mutt_error(_("Encrypted connection unavailable"));
985 goto bail;
986 }
987#endif
988 }
989 else if (mutt_istr_startswith(adata->buf, "* PREAUTH"))
990 {
991#ifdef USE_SSL
992 /* Unless using a secure $tunnel, an unencrypted PREAUTH response may be a
993 * MITM attack. The only way to stop "STARTTLS" MITM attacks is via
994 * $ssl_force_tls: an attacker can easily spoof "* OK" and strip the
995 * STARTTLS capability. So consult $ssl_force_tls, not $ssl_starttls, to
996 * decide whether to abort. Note that if using $tunnel and
997 * $tunnel_is_secure, adata->conn->ssf will be set to 1. */
998 const bool c_ssl_force_tls = cs_subset_bool(NeoMutt->sub, "ssl_force_tls");
999 if ((adata->conn->ssf == 0) && c_ssl_force_tls)
1000 {
1001 mutt_error(_("Encrypted connection unavailable"));
1002 goto bail;
1003 }
1004#endif
1005
1006 adata->state = IMAP_AUTHENTICATED;
1007 if (check_capabilities(adata) != 0)
1008 goto bail;
1009 FREE(&adata->capstr);
1010 }
1011 else
1012 {
1013 imap_error("imap_open_connection()", adata->buf);
1014 goto bail;
1015 }
1016
1017 return 0;
1018
1019bail:
1020 imap_close_connection(adata);
1021 FREE(&adata->capstr);
1022 return -1;
1023}
1024
1030{
1031 if (adata->state != IMAP_DISCONNECTED)
1032 {
1033 mutt_socket_close(adata->conn);
1034 adata->state = IMAP_DISCONNECTED;
1035 }
1036 adata->seqno = 0;
1037 adata->nextcmd = 0;
1038 adata->lastcmd = 0;
1039 adata->status = 0;
1040 memset(adata->cmds, 0, sizeof(struct ImapCommand) * adata->cmdslots);
1041}
1042
1054bool imap_has_flag(struct ListHead *flag_list, const char *flag)
1055{
1056 if (STAILQ_EMPTY(flag_list))
1057 return false;
1058
1059 const size_t flaglen = mutt_str_len(flag);
1060 struct ListNode *np = NULL;
1061 STAILQ_FOREACH(np, flag_list, entries)
1062 {
1063 const size_t nplen = strlen(np->data);
1064 if ((flaglen >= nplen) && ((flag[nplen] == '\0') || (flag[nplen] == ' ')) &&
1065 mutt_istrn_equal(np->data, flag, nplen))
1066 {
1067 return true;
1068 }
1069
1070 if (mutt_str_equal(np->data, "\\*"))
1071 return true;
1072 }
1073
1074 return false;
1075}
1076
1080static int imap_sort_email_uid(const void *a, const void *b, void *sdata)
1081{
1082 struct Email *ea = *(struct Email *const *) a;
1083 struct Email *eb = *(struct Email *const *) b;
1084
1085 // Sort NULLs to the end
1086 if (!ea && !eb)
1087 return 0;
1088 if (!ea)
1089 return 1;
1090 if (!eb)
1091 return -1;
1092
1093 const unsigned int ua = imap_edata_get((struct Email *) ea)->uid;
1094 const unsigned int ub = imap_edata_get((struct Email *) eb)->uid;
1095
1096 return mutt_numeric_cmp(ua, ub);
1097}
1098
1115 struct Buffer *cmd, enum QuadOption *err_continue)
1116{
1118 struct ImapEmailData *edata = imap_edata_get(e);
1119
1120 if (!adata || (adata->mailbox != m) || !e)
1121 return -1;
1122
1123 if (!compare_flags_for_copy(e))
1124 {
1125 if (e->deleted == edata->deleted)
1126 e->changed = false;
1127 return 0;
1128 }
1129
1130 buf_printf(cmd, "UID STORE %u", edata->uid);
1131
1132 struct Buffer *flags = buf_pool_get();
1133
1134 set_flag(m, MUTT_ACL_SEEN, e->read, "\\Seen ", flags);
1135 set_flag(m, MUTT_ACL_WRITE, e->old, "Old ", flags);
1136 set_flag(m, MUTT_ACL_WRITE, e->flagged, "\\Flagged ", flags);
1137 set_flag(m, MUTT_ACL_WRITE, e->replied, "\\Answered ", flags);
1138 set_flag(m, MUTT_ACL_DELETE, edata->deleted, "\\Deleted ", flags);
1139
1140 if (m->rights & MUTT_ACL_WRITE)
1141 {
1142 /* restore system flags */
1143 if (edata->flags_system)
1144 buf_addstr(flags, edata->flags_system);
1145
1146 /* set custom flags */
1147 struct Buffer *tags = buf_pool_get();
1149 if (!buf_is_empty(tags))
1150 buf_addstr(flags, buf_string(tags));
1151 buf_pool_release(&tags);
1152 }
1153
1155 buf_fix_dptr(flags);
1156
1157 /* UW-IMAP is OK with null flags, Cyrus isn't. The only solution is to
1158 * explicitly revoke all system flags (if we have permission) */
1159 if (buf_is_empty(flags))
1160 {
1161 set_flag(m, MUTT_ACL_SEEN, true, "\\Seen ", flags);
1162 set_flag(m, MUTT_ACL_WRITE, true, "Old ", flags);
1163 set_flag(m, MUTT_ACL_WRITE, true, "\\Flagged ", flags);
1164 set_flag(m, MUTT_ACL_WRITE, true, "\\Answered ", flags);
1165 set_flag(m, MUTT_ACL_DELETE, !edata->deleted, "\\Deleted ", flags);
1166
1167 /* erase custom flags */
1168 if ((m->rights & MUTT_ACL_WRITE) && edata->flags_remote)
1169 buf_addstr(flags, edata->flags_remote);
1170
1172 buf_fix_dptr(flags);
1173
1174 buf_addstr(cmd, " -FLAGS.SILENT (");
1175 }
1176 else
1177 {
1178 buf_addstr(cmd, " FLAGS.SILENT (");
1179 }
1180
1181 buf_addstr(cmd, buf_string(flags));
1182 buf_addstr(cmd, ")");
1183
1184 int rc = -1;
1185
1186 /* after all this it's still possible to have no flags, if you
1187 * have no ACL rights */
1188 if (!buf_is_empty(flags) &&
1190 err_continue && (*err_continue != MUTT_YES))
1191 {
1192 *err_continue = imap_continue("imap_sync_message: STORE failed", adata->buf);
1193 if (*err_continue != MUTT_YES)
1194 goto done;
1195 }
1196
1197 /* server have now the updated flags */
1198 FREE(&edata->flags_remote);
1199 struct Buffer *flags_remote = buf_pool_get();
1200 driver_tags_get_with_hidden(&e->tags, flags_remote);
1201 edata->flags_remote = buf_strdup(flags_remote);
1202 buf_pool_release(&flags_remote);
1203
1204 if (e->deleted == edata->deleted)
1205 e->changed = false;
1206
1207 rc = 0;
1208
1209done:
1210 buf_pool_release(&flags);
1211 return rc;
1212}
1213
1220enum MxStatus imap_check_mailbox(struct Mailbox *m, bool force)
1221{
1222 if (!m || !m->account)
1223 return MX_STATUS_ERROR;
1224
1226 struct ImapMboxData *mdata = imap_mdata_get(m);
1227 if (!adata || !mdata)
1228 return MX_STATUS_ERROR;
1229
1230 /* overload keyboard timeout to avoid many mailbox checks in a row.
1231 * Most users don't like having to wait exactly when they press a key. */
1232 int rc = 0;
1233
1234 /* try IDLE first, unless force is set */
1235 const bool c_imap_idle = cs_subset_bool(NeoMutt->sub, "imap_idle");
1236 const short c_imap_keep_alive = cs_subset_number(NeoMutt->sub, "imap_keep_alive");
1237 if (!force && c_imap_idle && (adata->capabilities & IMAP_CAP_IDLE) &&
1238 ((adata->state != IMAP_IDLE) || (mutt_date_now() >= adata->lastread + c_imap_keep_alive)))
1239 {
1240 if (imap_cmd_idle(adata) < 0)
1241 return MX_STATUS_ERROR;
1242 }
1243 if (adata->state == IMAP_IDLE)
1244 {
1245 while ((rc = mutt_socket_poll(adata->conn, 0)) > 0)
1246 {
1247 if (imap_cmd_step(adata) != IMAP_RES_CONTINUE)
1248 {
1249 mutt_debug(LL_DEBUG1, "Error reading IDLE response\n");
1250 return MX_STATUS_ERROR;
1251 }
1252 }
1253 if (rc < 0)
1254 {
1255 mutt_debug(LL_DEBUG1, "Poll failed, disabling IDLE\n");
1256 adata->capabilities &= ~IMAP_CAP_IDLE; // Clear the flag
1257 }
1258 }
1259
1260 const short c_timeout = cs_subset_number(NeoMutt->sub, "timeout");
1261 if ((force || ((adata->state != IMAP_IDLE) && (mutt_date_now() >= adata->lastread + c_timeout))) &&
1262 (imap_exec(adata, "NOOP", IMAP_CMD_POLL) != IMAP_EXEC_SUCCESS))
1263 {
1264 return MX_STATUS_ERROR;
1265 }
1266
1267 /* We call this even when we haven't run NOOP in case we have pending
1268 * changes to process, since we can reopen here. */
1269 imap_cmd_finish(adata);
1270
1271 enum MxStatus check = MX_STATUS_OK;
1272 if (mdata->check_status & IMAP_EXPUNGE_PENDING)
1273 check = MX_STATUS_REOPENED;
1274 else if (mdata->check_status & IMAP_NEWMAIL_PENDING)
1275 check = MX_STATUS_NEW_MAIL;
1276 else if (mdata->check_status & IMAP_FLAGS_PENDING)
1277 check = MX_STATUS_FLAGS;
1278 else if (rc < 0)
1279 check = MX_STATUS_ERROR;
1280
1281 mdata->check_status = IMAP_OPEN_NO_FLAGS;
1282
1283 if (force)
1284 m->last_checked = 0; // force a check on the next mx_mbox_check() call
1285 return check;
1286}
1287
1295static int imap_status(struct ImapAccountData *adata, struct ImapMboxData *mdata, bool queue)
1296{
1297 char *uidvalidity_flag = NULL;
1298 char cmd[2048] = { 0 };
1299
1300 if (!adata || !mdata)
1301 return -1;
1302
1303 /* Don't issue STATUS on the selected mailbox, it will be NOOPed or
1304 * IDLEd elsewhere.
1305 * adata->mailbox may be NULL for connections other than the current
1306 * mailbox's. */
1307 if (adata->mailbox && (adata->mailbox->mdata == mdata))
1308 {
1309 adata->mailbox->has_new = false;
1310 return mdata->messages;
1311 }
1312
1313 if (adata->mailbox && !adata->mailbox->poll_new_mail)
1314 return mdata->messages;
1315
1316 if (adata->capabilities & IMAP_CAP_IMAP4REV1)
1317 {
1318 uidvalidity_flag = "UIDVALIDITY";
1319 }
1320 else if (adata->capabilities & IMAP_CAP_STATUS)
1321 {
1322 uidvalidity_flag = "UID-VALIDITY";
1323 }
1324 else
1325 {
1326 mutt_debug(LL_DEBUG2, "Server doesn't support STATUS\n");
1327 return -1;
1328 }
1329
1330 snprintf(cmd, sizeof(cmd), "STATUS %s (UIDNEXT %s UNSEEN RECENT MESSAGES)",
1331 mdata->munge_name, uidvalidity_flag);
1332
1333 int rc = imap_exec(adata, cmd, queue ? IMAP_CMD_QUEUE : IMAP_CMD_POLL);
1334 if (rc != IMAP_EXEC_SUCCESS)
1335 {
1336 mutt_debug(LL_DEBUG1, "Error queueing command\n");
1337 return rc;
1338 }
1339 return mdata->messages;
1340}
1341
1345static enum MxStatus imap_mbox_check_stats(struct Mailbox *m, uint8_t flags)
1346{
1347 const bool queue = (flags & MUTT_MAILBOX_CHECK_IMMEDIATE) == 0;
1348 const int new_msgs = imap_mailbox_status(m, queue);
1349 if (new_msgs == -1)
1350 return MX_STATUS_ERROR;
1351 if (new_msgs == 0)
1352 return MX_STATUS_OK;
1353 return MX_STATUS_NEW_MAIL;
1354}
1355
1362int imap_path_status(const char *path, bool queue)
1363{
1364 struct Mailbox *m = mx_mbox_find2(path);
1365
1366 const bool is_temp = !m;
1367 if (is_temp)
1368 {
1369 m = mx_path_resolve(path);
1370 if (!mx_mbox_ac_link(m))
1371 {
1372 mailbox_free(&m);
1373 return 0;
1374 }
1375 }
1376
1377 int rc = imap_mailbox_status(m, queue);
1378
1379 if (is_temp)
1380 {
1381 mx_ac_remove(m, false);
1382 mailbox_free(&m);
1383 }
1384
1385 return rc;
1386}
1387
1397int imap_mailbox_status(struct Mailbox *m, bool queue)
1398{
1400 struct ImapMboxData *mdata = imap_mdata_get(m);
1401 if (!adata || !mdata)
1402 return -1;
1403 return imap_status(adata, mdata, queue);
1404}
1405
1413int imap_subscribe(const char *path, bool subscribe)
1414{
1415 struct ImapAccountData *adata = NULL;
1416 struct ImapMboxData *mdata = NULL;
1417
1418 if (imap_adata_find(path, &adata, &mdata) < 0)
1419 return -1;
1420
1421 if (subscribe)
1422 mutt_message(_("Subscribing to %s..."), mdata->name);
1423 else
1424 mutt_message(_("Unsubscribing from %s..."), mdata->name);
1425
1426 char buf[2048] = { 0 };
1427 snprintf(buf, sizeof(buf), "%sSUBSCRIBE %s", subscribe ? "" : "UN", mdata->munge_name);
1428
1429 if (imap_exec(adata, buf, IMAP_CMD_NO_FLAGS) != IMAP_EXEC_SUCCESS)
1430 {
1431 imap_mdata_free((void *) &mdata);
1432 return -1;
1433 }
1434
1435 const bool c_imap_check_subscribed = cs_subset_bool(NeoMutt->sub, "imap_check_subscribed");
1436 if (c_imap_check_subscribed)
1437 {
1438 if (subscribe)
1439 {
1440 struct Buffer *err = buf_pool_get();
1441 if (!mailbox_add_simple(path, err))
1442 mutt_debug(LL_DEBUG1, "Error adding subscribed mailbox: %s\n", buf_string(err));
1443 buf_pool_release(&err);
1444 }
1445 else
1446 {
1447 if (!mailbox_remove_simple(path))
1448 mutt_debug(LL_DEBUG1, "Error removing subscribed mailbox: %s\n", path);
1449 }
1450 }
1451
1452 if (subscribe)
1453 mutt_message(_("Subscribed to %s"), mdata->name);
1454 else
1455 mutt_message(_("Unsubscribed from %s"), mdata->name);
1456 imap_mdata_free((void *) &mdata);
1457 return 0;
1458}
1459
1470int imap_complete(struct Buffer *buf, const char *path)
1471{
1472 struct ImapAccountData *adata = NULL;
1473 struct ImapMboxData *mdata = NULL;
1474 char tmp[2048] = { 0 };
1475 struct ImapList listresp = { 0 };
1476 struct Buffer *completion_buf = NULL;
1477 size_t clen;
1478 int completions = 0;
1479 int rc;
1480
1481 if (imap_adata_find(path, &adata, &mdata) < 0)
1482 {
1483 buf_strcpy(buf, path);
1484 return complete_hosts(buf);
1485 }
1486
1487 /* fire off command */
1488 const bool c_imap_list_subscribed = cs_subset_bool(NeoMutt->sub, "imap_list_subscribed");
1489 snprintf(tmp, sizeof(tmp), "%s \"\" \"%s%%\"",
1490 c_imap_list_subscribed ? "LSUB" : "LIST", mdata->real_name);
1491
1492 imap_cmd_start(adata, tmp);
1493
1494 /* and see what the results are */
1495 completion_buf = buf_pool_get();
1496 buf_strcpy(completion_buf, mdata->name);
1497 imap_mdata_free((void *) &mdata);
1498
1499 adata->cmdresult = &listresp;
1500 do
1501 {
1502 listresp.name = NULL;
1503 rc = imap_cmd_step(adata);
1504
1505 if ((rc == IMAP_RES_CONTINUE) && listresp.name)
1506 {
1507 /* if the folder isn't selectable, append delimiter to force browse
1508 * to enter it on second tab. */
1509 if (listresp.noselect)
1510 {
1511 clen = strlen(listresp.name);
1512 listresp.name[clen++] = listresp.delim;
1513 listresp.name[clen] = '\0';
1514 }
1515 /* copy in first word */
1516 if (!completions)
1517 {
1518 buf_strcpy(completion_buf, listresp.name);
1519 completions++;
1520 continue;
1521 }
1522
1523 longest_common_prefix(completion_buf, listresp.name, 0);
1524 completions++;
1525 }
1526 } while (rc == IMAP_RES_CONTINUE);
1527 adata->cmdresult = NULL;
1528
1529 if (completions)
1530 {
1531 /* reformat output */
1532 imap_buf_qualify_path(buf, &adata->conn->account, completion_buf->data);
1533 pretty_mailbox(buf);
1534 buf_fix_dptr(buf);
1535 buf_pool_release(&completion_buf);
1536 return 0;
1537 }
1538
1539 buf_pool_release(&completion_buf);
1540 return -1;
1541}
1542
1551int imap_fast_trash(struct Mailbox *m, const char *dest)
1552{
1553 char prompt[1024] = { 0 };
1554 int rc = -1;
1555 bool triedcreate = false;
1556 enum QuadOption err_continue = MUTT_NO;
1557
1559 if (!adata)
1560 return -1;
1561
1562 struct ImapAccountData *dest_adata = NULL;
1563 struct ImapMboxData *dest_mdata = NULL;
1564
1565 if (imap_adata_find(dest, &dest_adata, &dest_mdata) < 0)
1566 return -1;
1567
1568 struct Buffer *sync_cmd = buf_pool_get();
1569
1570 /* check that the save-to folder is in the same account */
1571 if (!imap_account_match(&(adata->conn->account), &(dest_adata->conn->account)))
1572 {
1573 mutt_debug(LL_DEBUG3, "%s not same server as %s\n", dest, mailbox_path(m));
1574 goto out;
1575 }
1576
1577 for (int i = 0; i < m->msg_count; i++)
1578 {
1579 struct Email *e = m->emails[i];
1580 if (!e)
1581 break;
1582
1583 /* If message has already been checkpoint-deleted server-side, don't use
1584 * UID COPY because that can propagate the deleted flag to trash too. */
1585 struct ImapEmailData *edata = imap_edata_get(e);
1586 if (e->active && e->deleted && !e->purge && edata && edata->deleted)
1587 {
1588 mutt_debug(LL_DEBUG1, "imap_fast_trash: server-side delete flag set. aborting.\n");
1589 goto out;
1590 }
1591
1592 if (e->active && e->changed && e->deleted && !e->purge)
1593 {
1594 rc = imap_sync_message_for_copy(m, e, sync_cmd, &err_continue);
1595 if (rc < 0)
1596 {
1597 mutt_debug(LL_DEBUG1, "could not sync\n");
1598 goto out;
1599 }
1600 }
1601 }
1602
1603 /* loop in case of TRYCREATE */
1604 do
1605 {
1606 struct UidArray uida = ARRAY_HEAD_INITIALIZER;
1607 select_email_uids(m->emails, m->msg_count, MUTT_TRASH, false, false, &uida);
1608 ARRAY_SORT(&uida, imap_sort_uid, NULL);
1609 rc = imap_exec_msg_set(adata, "UID COPY", dest_mdata->munge_name, &uida);
1610 if (rc == 0)
1611 {
1612 mutt_debug(LL_DEBUG1, "No messages to trash\n");
1613 rc = -1;
1614 goto out;
1615 }
1616 else if (rc < 0)
1617 {
1618 mutt_debug(LL_DEBUG1, "could not queue copy\n");
1619 goto out;
1620 }
1621 else if (m->verbose)
1622 {
1623 mutt_message(ngettext("Copying %d message to %s...", "Copying %d messages to %s...", rc),
1624 rc, dest_mdata->name);
1625 }
1626 ARRAY_FREE(&uida);
1627
1628 /* let's get it on */
1629 rc = imap_exec(adata, NULL, IMAP_CMD_NO_FLAGS);
1630 if (rc == IMAP_EXEC_ERROR)
1631 {
1632 if (triedcreate)
1633 {
1634 mutt_debug(LL_DEBUG1, "Already tried to create mailbox %s\n", dest_mdata->name);
1635 break;
1636 }
1637 /* bail out if command failed for reasons other than nonexistent target */
1638 if (!mutt_istr_startswith(imap_get_qualifier(adata->buf), "[TRYCREATE]"))
1639 break;
1640 mutt_debug(LL_DEBUG3, "server suggests TRYCREATE\n");
1641 snprintf(prompt, sizeof(prompt), _("Create %s?"), dest_mdata->name);
1642 const bool c_confirm_create = cs_subset_bool(NeoMutt->sub, "confirm_create");
1643 if (c_confirm_create &&
1644 (query_yesorno_help(prompt, MUTT_YES, NeoMutt->sub, "confirm_create") != MUTT_YES))
1645 {
1647 goto out;
1648 }
1649 if (imap_create_mailbox(adata, dest_mdata->name) < 0)
1650 break;
1651 triedcreate = true;
1652 }
1653 } while (rc == IMAP_EXEC_ERROR);
1654
1655 if (rc != IMAP_EXEC_SUCCESS)
1656 {
1657 imap_error("imap_fast_trash", adata->buf);
1658 goto out;
1659 }
1660
1661 rc = IMAP_EXEC_SUCCESS;
1662
1663out:
1664 buf_pool_release(&sync_cmd);
1665 imap_mdata_free((void *) &dest_mdata);
1666
1667 return ((rc == IMAP_EXEC_SUCCESS) ? 0 : -1);
1668}
1669
1679enum MxStatus imap_sync_mailbox(struct Mailbox *m, bool expunge, bool close)
1680{
1681 if (!m)
1682 return -1;
1683
1684 struct Email **emails = NULL;
1685 int rc;
1686
1688 struct ImapMboxData *mdata = imap_mdata_get(m);
1689 if (!adata || !mdata)
1690 return MX_STATUS_ERROR;
1691
1692 if (adata->state < IMAP_SELECTED)
1693 {
1694 mutt_debug(LL_DEBUG2, "no mailbox selected\n");
1695 return -1;
1696 }
1697
1698 /* Mark sync in progress to prevent expunge/newmail processing mid-sync.
1699 * Do NOT set IMAP_REOPEN_ALLOW here — it makes timeouts destructive
1700 * by allowing imap_cmd_finish to process expunges during sync commands. */
1701 mdata->reopen |= IMAP_SYNC_IN_PROGRESS;
1702
1703 enum MxStatus check = imap_check_mailbox(m, false);
1704 if (check == MX_STATUS_ERROR)
1705 {
1706 /* Persist pending flag changes to hcache before aborting, so they
1707 * survive a reconnect and can be replayed on next sync. */
1708#ifdef USE_HCACHE
1709 imap_hcache_open(adata, mdata, true);
1710 for (int i = 0; i < m->msg_count; i++)
1711 {
1712 struct Email *e = m->emails[i];
1713 if (!e)
1714 break;
1715 if (e->active && e->changed)
1716 imap_hcache_put(mdata, e);
1717 }
1718 imap_hcache_close(mdata);
1719#endif
1721 return check;
1722 }
1723
1724 /* if we are expunging anyway, we can do deleted messages very quickly... */
1725 if (expunge && (m->rights & MUTT_ACL_DELETE))
1726 {
1727 struct UidArray uida = ARRAY_HEAD_INITIALIZER;
1728 select_email_uids(m->emails, m->msg_count, MUTT_DELETED, true, false, &uida);
1729 ARRAY_SORT(&uida, imap_sort_uid, NULL);
1730 rc = imap_exec_msg_set(adata, "UID STORE", "+FLAGS.SILENT (\\Deleted)", &uida);
1731 ARRAY_FREE(&uida);
1732 if (rc < 0)
1733 {
1734 mutt_error(_("Expunge failed"));
1736 return rc;
1737 }
1738
1739 if (rc > 0)
1740 {
1741 /* mark these messages as unchanged so second pass ignores them. Done
1742 * here so BOGUS UW-IMAP 4.7 SILENT FLAGS updates are ignored. */
1743 for (int i = 0; i < m->msg_count; i++)
1744 {
1745 struct Email *e = m->emails[i];
1746 if (!e)
1747 break;
1748 if (e->deleted && e->changed)
1749 e->active = false;
1750 }
1751 if (m->verbose)
1752 {
1753 mutt_message(ngettext("Marking %d message deleted...",
1754 "Marking %d messages deleted...", rc),
1755 rc);
1756 }
1757 }
1758 }
1759
1760#ifdef USE_HCACHE
1761 imap_hcache_open(adata, mdata, true);
1762#endif
1763
1764 /* save messages with real (non-flag) changes */
1765 for (int i = 0; i < m->msg_count; i++)
1766 {
1767 struct Email *e = m->emails[i];
1768 if (!e)
1769 break;
1770
1771 if (e->deleted)
1772 {
1773 imap_cache_del(m, e);
1774#ifdef USE_HCACHE
1775 imap_hcache_del(mdata, imap_edata_get(e)->uid);
1776#endif
1777 }
1778
1779 if (e->active && e->changed)
1780 {
1781#ifdef USE_HCACHE
1782 imap_hcache_put(mdata, e);
1783#endif
1784 /* if the message has been rethreaded or attachments have been deleted
1785 * we delete the message and reupload it.
1786 * This works better if we're expunging, of course. */
1787 if (e->env->changed || e->attach_del)
1788 {
1789 /* L10N: The plural is chosen by the last %d, i.e. the total number */
1790 if (m->verbose)
1791 {
1792 mutt_message(ngettext("Saving changed message... [%d/%d]",
1793 "Saving changed messages... [%d/%d]", m->msg_count),
1794 i + 1, m->msg_count);
1795 }
1796 bool save_append = m->append;
1797 m->append = true;
1799 m->append = save_append;
1800 e->env->changed = false;
1801 }
1802 }
1803 }
1804
1805#ifdef USE_HCACHE
1806 imap_hcache_close(mdata);
1807#endif
1808
1809 /* presort here to avoid doing 10 resorts in imap_exec_msg_set */
1810 emails = MUTT_MEM_MALLOC(m->msg_count, struct Email *);
1811 memcpy(emails, m->emails, m->msg_count * sizeof(struct Email *));
1812 mutt_qsort_r(emails, m->msg_count, sizeof(struct Email *), imap_sort_email_uid, NULL);
1813
1814 rc = sync_helper(m, emails, m->msg_count, MUTT_ACL_DELETE, MUTT_DELETED, "\\Deleted");
1815 if (rc >= 0)
1816 rc |= sync_helper(m, emails, m->msg_count, MUTT_ACL_WRITE, MUTT_FLAG, "\\Flagged");
1817 if (rc >= 0)
1818 rc |= sync_helper(m, emails, m->msg_count, MUTT_ACL_WRITE, MUTT_OLD, "Old");
1819 if (rc >= 0)
1820 rc |= sync_helper(m, emails, m->msg_count, MUTT_ACL_SEEN, MUTT_READ, "\\Seen");
1821 if (rc >= 0)
1822 rc |= sync_helper(m, emails, m->msg_count, MUTT_ACL_WRITE, MUTT_REPLIED, "\\Answered");
1823
1824 FREE(&emails);
1825
1826 /* Flush the queued flags if any were changed in sync_helper. */
1827 if (rc > 0)
1828 if (imap_exec(adata, NULL, IMAP_CMD_NO_FLAGS) != IMAP_EXEC_SUCCESS)
1829 rc = -1;
1830
1831 if (rc < 0)
1832 {
1833 if (close)
1834 {
1835 if (query_yesorno(_("Error saving flags. Close anyway?"), MUTT_NO) == MUTT_YES)
1836 {
1837 adata->state = IMAP_AUTHENTICATED;
1839 return 0;
1840 }
1841 }
1842 else
1843 {
1844 mutt_error(_("Error saving flags"));
1845 }
1847 return -1;
1848 }
1849
1850 /* Update local record of server state to reflect the synchronization just
1851 * completed. imap_read_headers always overwrites hcache-origin flags, so
1852 * there is no need to mutate the hcache after flag-only changes. */
1853 for (int i = 0; i < m->msg_count; i++)
1854 {
1855 struct Email *e = m->emails[i];
1856 if (!e)
1857 break;
1858 struct ImapEmailData *edata = imap_edata_get(e);
1859 edata->deleted = e->deleted;
1860 edata->flagged = e->flagged;
1861 edata->old = e->old;
1862 edata->read = e->read;
1863 edata->replied = e->replied;
1864 e->changed = false;
1865 }
1866 m->changed = false;
1867
1868 /* Now that sync is done, allow reopen for the EXPUNGE phase. */
1871
1872 /* We must send an EXPUNGE command if we're not closing. */
1873 if (expunge && !close && (m->rights & MUTT_ACL_DELETE))
1874 {
1875 if (m->verbose)
1876 mutt_message(_("Expunging messages from server..."));
1877 /* Set expunge bit so we don't get spurious reopened messages */
1878 mdata->reopen |= IMAP_EXPUNGE_EXPECTED;
1879 if (imap_exec(adata, "EXPUNGE", IMAP_CMD_NO_FLAGS) != IMAP_EXEC_SUCCESS)
1880 {
1882 imap_error(_("imap_sync_mailbox: EXPUNGE failed"), adata->buf);
1884 return -1;
1885 }
1887 }
1888
1889 if (expunge && close)
1890 {
1891 adata->closing = true;
1892 imap_exec(adata, "CLOSE", IMAP_CMD_NO_FLAGS);
1893 adata->state = IMAP_AUTHENTICATED;
1894 }
1895
1896 const bool c_message_cache_clean = cs_subset_bool(NeoMutt->sub, "message_cache_clean");
1897 if (c_message_cache_clean)
1899
1900 return check;
1901}
1902
1906static bool imap_ac_owns_path(struct Account *a, const char *path)
1907{
1908 struct Url *url = url_parse(path);
1909 if (!url)
1910 return false;
1911
1912 struct ImapAccountData *adata = a->adata;
1913 struct ConnAccount *cac = &adata->conn->account;
1914
1915 const bool rc = mutt_istr_equal(url->host, cac->host) &&
1916 (!url->user || mutt_istr_equal(url->user, cac->user));
1917 url_free(&url);
1918 return rc;
1919}
1920
1924static bool imap_ac_add(struct Account *a, struct Mailbox *m)
1925{
1926 struct ImapAccountData *adata = a->adata;
1927
1928 if (!adata)
1929 {
1930 struct ConnAccount cac = { { 0 } };
1931 char mailbox[PATH_MAX] = { 0 };
1932
1933 if (imap_parse_path(mailbox_path(m), &cac, mailbox, sizeof(mailbox)) < 0)
1934 return false;
1935
1936 adata = imap_adata_new(a);
1937 adata->conn = mutt_conn_new(&cac);
1938 if (!adata->conn)
1939 {
1940 imap_adata_free((void **) &adata);
1941 return false;
1942 }
1943
1945
1946 if (imap_login(adata) < 0)
1947 {
1948 imap_adata_free((void **) &adata);
1949 return false;
1950 }
1951
1952 a->adata = adata;
1954 }
1955
1956 if (!m->mdata)
1957 {
1958 struct Url *url = url_parse(mailbox_path(m));
1959 if (!url)
1960 return false;
1961 struct ImapMboxData *mdata = imap_mdata_new(adata, url->path);
1962
1963 /* fixup path and realpath, mainly to replace / by /INBOX */
1964 char buf[1024] = { 0 };
1965 imap_qualify_path(buf, sizeof(buf), &adata->conn->account, mdata->name);
1966 buf_strcpy(&m->pathbuf, buf);
1968
1969 m->mdata = mdata;
1971 url_free(&url);
1972 }
1973 return true;
1974}
1975
1980static void imap_mbox_select(struct Mailbox *m)
1981{
1983 struct ImapMboxData *mdata = imap_mdata_get(m);
1984 if (!adata || !mdata)
1985 return;
1986
1987 const char *condstore = NULL;
1988#ifdef USE_HCACHE
1989 const bool c_imap_condstore = cs_subset_bool(NeoMutt->sub, "imap_condstore");
1990 if ((adata->capabilities & IMAP_CAP_CONDSTORE) && c_imap_condstore)
1991 condstore = " (CONDSTORE)";
1992 else
1993#endif
1994 condstore = "";
1995
1996 char buf[PATH_MAX] = { 0 };
1997 snprintf(buf, sizeof(buf), "%s %s%s", m->readonly ? "EXAMINE" : "SELECT",
1998 mdata->munge_name, condstore);
1999
2000 adata->state = IMAP_SELECTED;
2001
2002 imap_cmd_start(adata, buf);
2003}
2004
2013int imap_login(struct ImapAccountData *adata)
2014{
2015 if (!adata)
2016 return -1;
2017
2018 if (adata->state == IMAP_DISCONNECTED)
2019 {
2020 buf_reset(&adata->cmdbuf); // purge outstanding queued commands
2021 imap_open_connection(adata);
2022 }
2023 if (adata->state == IMAP_CONNECTED)
2024 {
2026 {
2027 adata->state = IMAP_AUTHENTICATED;
2028 FREE(&adata->capstr);
2029 if (adata->conn->ssf != 0)
2030 {
2031 mutt_debug(LL_DEBUG2, "Communication encrypted at %d bits\n",
2032 adata->conn->ssf);
2033 }
2034 }
2035 else
2036 {
2038 }
2039 }
2040 if (adata->state == IMAP_AUTHENTICATED)
2041 {
2042 /* capabilities may have changed */
2043 imap_exec(adata, "CAPABILITY", IMAP_CMD_PASS);
2044
2045#ifdef USE_ZLIB
2046 /* RFC4978 */
2047 const bool c_imap_deflate = cs_subset_bool(NeoMutt->sub, "imap_deflate");
2048 if ((adata->capabilities & IMAP_CAP_COMPRESS) && c_imap_deflate &&
2049 (imap_exec(adata, "COMPRESS DEFLATE", IMAP_CMD_PASS) == IMAP_EXEC_SUCCESS))
2050 {
2051 mutt_debug(LL_DEBUG2, "IMAP compression is enabled on connection to %s\n",
2052 adata->conn->account.host);
2053 mutt_zstrm_wrap_conn(adata->conn);
2054 }
2055#endif
2056
2057 /* enable RFC2971, if the server supports that */
2058 const bool c_imap_send_id = cs_subset_bool(NeoMutt->sub, "imap_send_id");
2059 if (c_imap_send_id && (adata->capabilities & IMAP_CAP_ID))
2060 {
2061 imap_exec(adata, "ID (\"name\" \"NeoMutt\" \"version\" \"" PACKAGE_VERSION "\")",
2063 }
2064
2065 /* enable RFC6855, if the server supports that */
2066 const bool c_imap_rfc5161 = cs_subset_bool(NeoMutt->sub, "imap_rfc5161");
2067 if (c_imap_rfc5161 && (adata->capabilities & IMAP_CAP_ENABLE))
2068 imap_exec(adata, "ENABLE UTF8=ACCEPT", IMAP_CMD_QUEUE);
2069
2070 /* enable QRESYNC. Advertising QRESYNC also means CONDSTORE
2071 * is supported (even if not advertised), so flip that bit. */
2072 if (adata->capabilities & IMAP_CAP_QRESYNC)
2073 {
2075 const bool c_imap_qresync = cs_subset_bool(NeoMutt->sub, "imap_qresync");
2076 if (c_imap_rfc5161 && c_imap_qresync)
2077 imap_exec(adata, "ENABLE QRESYNC", IMAP_CMD_QUEUE);
2078 }
2079
2080 /* get root delimiter, '/' as default */
2081 adata->delim = '/';
2082 imap_exec(adata, "LIST \"\" \"\"", IMAP_CMD_QUEUE);
2083
2084 /* we may need the root delimiter before we open a mailbox */
2085 imap_exec(adata, NULL, IMAP_CMD_NO_FLAGS);
2086
2087 /* select the mailbox that used to be open before disconnect */
2088 if (adata->mailbox)
2089 {
2090 imap_mbox_select(adata->mailbox);
2091 }
2092 }
2093
2094 if (adata->state < IMAP_AUTHENTICATED)
2095 return -1;
2096
2097 return 0;
2098}
2099
2112static int imap_select_and_poll(struct Mailbox *m, int *countp)
2113{
2115 struct ImapMboxData *mdata = imap_mdata_get(m);
2116
2118
2119 int count = 0;
2120 int rc;
2121 do
2122 {
2123 char *pc = NULL;
2124
2125 rc = imap_cmd_step(adata);
2126 if (rc != IMAP_RES_CONTINUE)
2127 break;
2128
2129 if (!mutt_strn_equal(adata->buf, "* ", 2))
2130 continue;
2131 pc = imap_next_word(adata->buf);
2132
2133 /* Obtain list of available flags here, may be overridden by a
2134 * PERMANENTFLAGS tag in the OK response */
2135 if (mutt_istr_startswith(pc, "FLAGS"))
2136 {
2137 /* don't override PERMANENTFLAGS */
2138 if (STAILQ_EMPTY(&mdata->flags))
2139 {
2140 mutt_debug(LL_DEBUG3, "Getting mailbox FLAGS\n");
2141 pc = get_flags(&mdata->flags, pc);
2142 if (!pc)
2143 return -1;
2144 }
2145 }
2146 else if (mutt_istr_startswith(pc, "OK [PERMANENTFLAGS"))
2147 {
2148 /* PERMANENTFLAGS are massaged to look like FLAGS, then override FLAGS */
2149 mutt_debug(LL_DEBUG3, "Getting mailbox PERMANENTFLAGS\n");
2150 /* safe to call on NULL */
2151 mutt_list_free(&mdata->flags);
2152 /* skip "OK [PERMANENT" so syntax is the same as FLAGS */
2153 pc += 13;
2154 pc = get_flags(&(mdata->flags), pc);
2155 if (!pc)
2156 return -1;
2157 }
2158 else if (mutt_istr_startswith(pc, "OK [UIDVALIDITY"))
2159 {
2160 /* save UIDVALIDITY for the header cache */
2161 mutt_debug(LL_DEBUG3, "Getting mailbox UIDVALIDITY\n");
2162 pc += 3;
2163 pc = imap_next_word(pc);
2164 if (!mutt_str_atoui(pc, &mdata->uidvalidity))
2165 return -1;
2166 }
2167 else if (mutt_istr_startswith(pc, "OK [UIDNEXT"))
2168 {
2169 mutt_debug(LL_DEBUG3, "Getting mailbox UIDNEXT\n");
2170 pc += 3;
2171 pc = imap_next_word(pc);
2172 if (!mutt_str_atoui(pc, &mdata->uid_next))
2173 return -1;
2174 }
2175 else if (mutt_istr_startswith(pc, "OK [HIGHESTMODSEQ"))
2176 {
2177 mutt_debug(LL_DEBUG3, "Getting mailbox HIGHESTMODSEQ\n");
2178 pc += 3;
2179 pc = imap_next_word(pc);
2180 if (!mutt_str_atoull(pc, &mdata->modseq))
2181 return -1;
2182 }
2183 else if (mutt_istr_startswith(pc, "OK [NOMODSEQ"))
2184 {
2185 mutt_debug(LL_DEBUG3, "Mailbox has NOMODSEQ set\n");
2186 mdata->modseq = 0;
2187 }
2188 else
2189 {
2190 pc = imap_next_word(pc);
2191 if (mutt_istr_startswith(pc, "EXISTS"))
2192 {
2193 count = mdata->new_mail_count;
2194 mdata->new_mail_count = 0;
2195 }
2196 }
2197 } while (rc == IMAP_RES_CONTINUE);
2198
2199 if (rc == IMAP_RES_NO)
2200 {
2201 char *s = imap_next_word(adata->buf); /* skip seq */
2202 s = imap_next_word(s); /* Skip response */
2203 mutt_error("%s", s);
2204 return -1;
2205 }
2206
2207 if (rc != IMAP_RES_OK)
2208 return -1;
2209
2210 /* check for READ-ONLY notification */
2211 if (mutt_istr_startswith(imap_get_qualifier(adata->buf), "[READ-ONLY]") &&
2212 !(adata->capabilities & IMAP_CAP_ACL))
2213 {
2214 mutt_debug(LL_DEBUG2, "Mailbox is read-only\n");
2215 m->readonly = true;
2216 }
2217
2218 /* dump the mailbox flags we've found */
2219 const short c_debug_level = cs_subset_number(NeoMutt->sub, "debug_level");
2220 if (c_debug_level > LL_DEBUG2)
2221 {
2222 if (STAILQ_EMPTY(&mdata->flags))
2223 {
2224 mutt_debug(LL_DEBUG3, "No folder flags found\n");
2225 }
2226 else
2227 {
2228 struct ListNode *np = NULL;
2229 struct Buffer *flag_buffer = buf_pool_get();
2230 buf_printf(flag_buffer, "Mailbox flags: ");
2231 STAILQ_FOREACH(np, &mdata->flags, entries)
2232 {
2233 buf_add_printf(flag_buffer, "[%s] ", np->data);
2234 }
2235 mutt_debug(LL_DEBUG3, "%s\n", buf_string(flag_buffer));
2236 buf_pool_release(&flag_buffer);
2237 }
2238 }
2239
2240 if (!((m->rights & MUTT_ACL_DELETE) || (m->rights & MUTT_ACL_SEEN) ||
2241 (m->rights & MUTT_ACL_WRITE) || (m->rights & MUTT_ACL_INSERT)))
2242 {
2243 m->readonly = true;
2244 }
2245
2246 *countp = count;
2247 return 0;
2248}
2249
2262{
2263 if (!adata || !adata->mailbox)
2264 return -1;
2265
2266 struct Mailbox *m = adata->mailbox;
2267 struct ImapMboxData *mdata = imap_mdata_get(m);
2268 if (!mdata)
2269 return -1;
2270
2271 mutt_debug(LL_DEBUG1, "Re-selecting mailbox %s after reconnect\n", mdata->name);
2272
2273 for (int i = 0; i < m->msg_count; i++)
2274 {
2275 struct Email *e = m->emails[i];
2276 if (!e)
2277 continue;
2278 imap_edata_free((void **) &e->edata);
2279 email_free(&m->emails[i]);
2280 }
2281 m->msg_count = 0;
2282 m->msg_unread = 0;
2283 m->msg_flagged = 0;
2284 m->msg_new = 0;
2285 m->msg_deleted = 0;
2286 m->size = 0;
2287 m->vcount = 0;
2288
2290
2291 mdata->new_mail_count = 0;
2292 mdata->reopen = IMAP_OPEN_NO_FLAGS;
2294
2295 int count = 0;
2296 if (imap_select_and_poll(m, &count) < 0)
2297 goto fail;
2298
2299 mx_alloc_memory(m, count);
2300
2301 if ((count > 0) && (imap_read_headers(m, 1, count, true) < 0))
2302 goto fail;
2303
2304 mutt_debug(LL_DEBUG1, "Reopened mailbox %s with %d messages\n", mdata->name, m->msg_count);
2307 return 0;
2308
2309fail:
2310 mutt_debug(LL_DEBUG1, "Failed to reopen mailbox %s\n", mdata->name);
2311 if (adata->state == IMAP_SELECTED)
2312 adata->state = IMAP_AUTHENTICATED;
2313 return -1;
2314}
2315
2320{
2321 if (!m->account || !m->mdata)
2322 return MX_OPEN_ERROR;
2323
2324 char buf[PATH_MAX] = { 0 };
2325 int count = 0;
2326
2328 struct ImapMboxData *mdata = imap_mdata_get(m);
2329 if (!adata || !mdata)
2330 return MX_OPEN_ERROR;
2331
2332 mutt_debug(LL_DEBUG3, "opening %s, stranding %p\n", m->pathbuf.data,
2333 (void *) adata->mailbox);
2334 adata->prev_mailbox = adata->mailbox;
2335 adata->mailbox = m;
2336
2337 /* clear mailbox status */
2338 adata->status = 0;
2339 m->rights = 0;
2340 mdata->new_mail_count = 0;
2341
2342 if (m->verbose)
2343 mutt_message(_("Selecting %s..."), mdata->name);
2344
2345 /* pipeline ACL test */
2346 if (adata->capabilities & IMAP_CAP_ACL)
2347 {
2348 snprintf(buf, sizeof(buf), "MYRIGHTS %s", mdata->munge_name);
2349 imap_exec(adata, buf, IMAP_CMD_QUEUE);
2350 }
2351 else
2352 {
2353 /* assume we have all rights if ACL is unavailable */
2356 }
2357
2358 /* pipeline the postponed count if possible */
2359 const char *const c_postponed = cs_subset_string(NeoMutt->sub, "postponed");
2360 struct Mailbox *m_postponed = mx_mbox_find2(c_postponed);
2361 struct ImapAccountData *postponed_adata = imap_adata_get(m_postponed);
2362 if (postponed_adata &&
2363 imap_account_match(&postponed_adata->conn->account, &adata->conn->account))
2364 {
2365 imap_mailbox_status(m_postponed, true);
2366 }
2367
2368 const bool c_imap_check_subscribed = cs_subset_bool(NeoMutt->sub, "imap_check_subscribed");
2369 if (c_imap_check_subscribed)
2370 imap_exec(adata, "LSUB \"\" \"*\"", IMAP_CMD_QUEUE);
2371
2372 if (imap_select_and_poll(m, &count) < 0)
2373 goto fail;
2374
2375 mx_alloc_memory(m, count);
2376
2377 m->msg_count = 0;
2378 m->msg_unread = 0;
2379 m->msg_flagged = 0;
2380 m->msg_new = 0;
2381 m->msg_deleted = 0;
2382 m->size = 0;
2383 m->vcount = 0;
2384
2385 if ((count > 0) && (imap_read_headers(m, 1, count, true) < 0))
2386 {
2387 mutt_error(_("Error opening mailbox"));
2388 goto fail;
2389 }
2390
2391 mutt_debug(LL_DEBUG2, "msg_count is %d\n", m->msg_count);
2392 return MX_OPEN_OK;
2393
2394fail:
2395 adata->mailbox = adata->prev_mailbox;
2396 adata->prev_mailbox = NULL;
2397 if (adata->state == IMAP_SELECTED)
2398 adata->state = IMAP_AUTHENTICATED;
2399 return MX_OPEN_ERROR;
2400}
2401
2406{
2407 if (!m->account)
2408 return false;
2409
2410 /* in APPEND mode, we appear to hijack an existing IMAP connection -
2411 * mailbox is brand new and mostly empty */
2413 struct ImapMboxData *mdata = imap_mdata_get(m);
2414 if (!adata || !mdata)
2415 return false;
2416
2417 int rc = imap_mailbox_status(m, false);
2418 if (rc >= 0)
2419 return true;
2420 if (rc == -1)
2421 return false;
2422
2423 char buf[PATH_MAX + 64];
2424 snprintf(buf, sizeof(buf), _("Create %s?"), mdata->name);
2425 const bool c_confirm_create = cs_subset_bool(NeoMutt->sub, "confirm_create");
2426 if (c_confirm_create &&
2427 (query_yesorno_help(buf, MUTT_YES, NeoMutt->sub, "confirm_create") != MUTT_YES))
2428 return false;
2429
2430 if (imap_create_mailbox(adata, mdata->name) < 0)
2431 return false;
2432
2433 return true;
2434}
2435
2442static enum MxStatus imap_mbox_check(struct Mailbox *m)
2443{
2445 enum MxStatus rc = imap_check_mailbox(m, false);
2446 /* NOTE - mv might have been changed at this point. In particular,
2447 * m could be NULL. Beware. */
2449
2450 return rc;
2451}
2452
2456static enum MxStatus imap_mbox_close(struct Mailbox *m)
2457{
2459 struct ImapMboxData *mdata = imap_mdata_get(m);
2460
2461 /* Check to see if the mailbox is actually open */
2462 if (!adata || !mdata)
2463 return MX_STATUS_OK;
2464
2465 /* imap_mbox_open_append() borrows the struct ImapAccountData temporarily,
2466 * just for the connection.
2467 *
2468 * So when these are equal, it means we are actually closing the
2469 * mailbox and should clean up adata. Otherwise, we don't want to
2470 * touch adata - it's still being used. */
2471 if (m == adata->mailbox)
2472 {
2473 if ((adata->status != IMAP_FATAL) && (adata->state >= IMAP_SELECTED))
2474 {
2475 /* mx_mbox_close won't sync if there are no deleted messages
2476 * and the mailbox is unchanged, so we may have to close here */
2477 if (m->msg_deleted == 0)
2478 {
2479 adata->closing = true;
2480 imap_exec(adata, "CLOSE", IMAP_CMD_NO_FLAGS);
2481 }
2482 adata->state = IMAP_AUTHENTICATED;
2483 }
2484
2485 mutt_debug(LL_DEBUG3, "closing %s, restoring %p\n", m->pathbuf.data,
2486 (void *) adata->prev_mailbox);
2487 adata->mailbox = adata->prev_mailbox;
2488 adata->prev_mailbox = NULL;
2489 imap_mbox_select(adata->mailbox);
2491 }
2492
2493 return MX_STATUS_OK;
2494}
2495
2499static bool imap_msg_open_new(struct Mailbox *m, struct Message *msg, const struct Email *e)
2500{
2501 bool success = false;
2502
2503 struct Buffer *tempfile = buf_pool_get();
2504 buf_mktemp(tempfile);
2505
2506 msg->fp = mutt_file_fopen(buf_string(tempfile), "w");
2507 if (!msg->fp)
2508 {
2509 mutt_perror("%s", buf_string(tempfile));
2510 goto cleanup;
2511 }
2512
2513 msg->path = buf_strdup(tempfile);
2514 success = true;
2515
2516cleanup:
2517 buf_pool_release(&tempfile);
2518 return success;
2519}
2520
2524static int imap_tags_edit(struct Mailbox *m, const char *tags, struct Buffer *buf)
2525{
2526 struct ImapMboxData *mdata = imap_mdata_get(m);
2527 if (!mdata)
2528 return -1;
2529
2530 char *new_tag = NULL;
2531 char *checker = NULL;
2532
2533 /* Check for \* flags capability */
2534 if (!imap_has_flag(&mdata->flags, NULL))
2535 {
2536 mutt_error(_("IMAP server doesn't support custom flags"));
2537 return -1;
2538 }
2539
2540 buf_reset(buf);
2541 if (tags)
2542 buf_strcpy(buf, tags);
2543
2544 if (mw_get_field("Tags: ", buf, MUTT_COMP_NO_FLAGS, HC_OTHER, NULL, NULL) != 0)
2545 return -1;
2546
2547 /* each keyword must be atom defined by rfc822 as:
2548 *
2549 * atom = 1*<any CHAR except specials, SPACE and CTLs>
2550 * CHAR = ( 0.-127. )
2551 * specials = "(" / ")" / "<" / ">" / "@"
2552 * / "," / ";" / ":" / "\" / <">
2553 * / "." / "[" / "]"
2554 * SPACE = ( 32. )
2555 * CTLS = ( 0.-31., 127.)
2556 *
2557 * And must be separated by one space.
2558 */
2559
2560 new_tag = buf->data;
2561 checker = buf->data;
2562 SKIPWS(checker);
2563 while (*checker != '\0')
2564 {
2565 if ((*checker < 32) || (*checker >= 127) || // We allow space because it's the separator
2566 (*checker == 40) || // (
2567 (*checker == 41) || // )
2568 (*checker == 60) || // <
2569 (*checker == 62) || // >
2570 (*checker == 64) || // @
2571 (*checker == 44) || // ,
2572 (*checker == 59) || // ;
2573 (*checker == 58) || // :
2574 (*checker == 92) || // backslash
2575 (*checker == 34) || // "
2576 (*checker == 46) || // .
2577 (*checker == 91) || // [
2578 (*checker == 93)) // ]
2579 {
2580 mutt_error(_("Invalid IMAP flags"));
2581 return 0;
2582 }
2583
2584 /* Skip duplicate space */
2585 while ((checker[0] == ' ') && (checker[1] == ' '))
2586 checker++;
2587
2588 /* copy char to new_tag and go the next one */
2589 *new_tag++ = *checker++;
2590 }
2591 *new_tag = '\0';
2592 new_tag = buf->data; /* rewind */
2594
2595 return !mutt_str_equal(tags, buf_string(buf));
2596}
2597
2611static int imap_tags_commit(struct Mailbox *m, struct Email *e, const char *buf)
2612{
2613 char uid[11] = { 0 };
2614
2616 if (!adata)
2617 return -1;
2618
2619 if (*buf == '\0')
2620 buf = NULL;
2621
2622 if (!(m->rights & MUTT_ACL_WRITE))
2623 return 0;
2624
2625 snprintf(uid, sizeof(uid), "%u", imap_edata_get(e)->uid);
2626
2627 /* Remove old custom flags */
2628 if (imap_edata_get(e)->flags_remote)
2629 {
2630 struct Buffer *cmd = buf_pool_get();
2631 buf_addstr(cmd, "UID STORE ");
2632 buf_addstr(cmd, uid);
2633 buf_addstr(cmd, " -FLAGS.SILENT (");
2634 buf_addstr(cmd, imap_edata_get(e)->flags_remote);
2635 buf_addstr(cmd, ")");
2636
2637 /* Should we return here, or we are fine and we could
2638 * continue to add new flags */
2639 int rc = imap_exec(adata, buf_string(cmd), IMAP_CMD_NO_FLAGS);
2640 buf_pool_release(&cmd);
2641 if (rc != IMAP_EXEC_SUCCESS)
2642 {
2643 return -1;
2644 }
2645 }
2646
2647 /* Add new custom flags */
2648 if (buf)
2649 {
2650 struct Buffer *cmd = buf_pool_get();
2651 buf_addstr(cmd, "UID STORE ");
2652 buf_addstr(cmd, uid);
2653 buf_addstr(cmd, " +FLAGS.SILENT (");
2654 buf_addstr(cmd, buf);
2655 buf_addstr(cmd, ")");
2656
2657 int rc = imap_exec(adata, buf_string(cmd), IMAP_CMD_NO_FLAGS);
2658 buf_pool_release(&cmd);
2659 if (rc != IMAP_EXEC_SUCCESS)
2660 {
2661 mutt_debug(LL_DEBUG1, "fail to add new flags\n");
2662 return -1;
2663 }
2664 }
2665
2666 /* We are good sync them */
2667 mutt_debug(LL_DEBUG1, "NEW TAGS: %s\n", buf);
2668 driver_tags_replace(&e->tags, buf);
2669 FREE(&imap_edata_get(e)->flags_remote);
2670 struct Buffer *flags_remote = buf_pool_get();
2671 driver_tags_get_with_hidden(&e->tags, flags_remote);
2672 imap_edata_get(e)->flags_remote = buf_strdup(flags_remote);
2673 buf_pool_release(&flags_remote);
2675 return 0;
2676}
2677
2681enum MailboxType imap_path_probe(const char *path, const struct stat *st)
2682{
2683 if (mutt_istr_startswith(path, "imap://"))
2684 return MUTT_IMAP;
2685
2686 if (mutt_istr_startswith(path, "imaps://"))
2687 return MUTT_IMAP;
2688
2689 return MUTT_UNKNOWN;
2690}
2691
2695int imap_path_canon(struct Buffer *path)
2696{
2697 struct Url *url = url_parse(buf_string(path));
2698 if (!url)
2699 return 0;
2700
2701 char tmp[PATH_MAX] = { 0 };
2702 char tmp2[PATH_MAX] = { 0 };
2703 if (url->path)
2704 {
2705 struct ImapAccountData *adata = NULL;
2706 if (imap_adata_find(buf_string(path), &adata, NULL) == 0)
2707 {
2708 imap_fix_path_with_delim(adata->delim, url->path, tmp, sizeof(tmp));
2709 }
2710 else
2711 {
2712 imap_fix_path(url->path, tmp, sizeof(tmp));
2713 }
2714 url->path = tmp;
2715 }
2716 url_tostring(url, tmp2, sizeof(tmp2), U_NO_FLAGS);
2717 buf_strcpy(path, tmp2);
2718 url_free(&url);
2719
2720 return 0;
2721}
2722
2726static int imap_path_is_empty(struct Buffer *path)
2727{
2728 int rc = imap_path_status(buf_string(path), false);
2729 if (rc < 0)
2730 return -1;
2731 if (rc == 0)
2732 return 1;
2733 return 0;
2734}
2735
2739const struct MxOps MxImapOps = {
2740 // clang-format off
2741 .type = MUTT_IMAP,
2742 .name = "imap",
2743 .is_local = false,
2744 .ac_owns_path = imap_ac_owns_path,
2745 .ac_add = imap_ac_add,
2746 .mbox_open = imap_mbox_open,
2747 .mbox_open_append = imap_mbox_open_append,
2748 .mbox_check = imap_mbox_check,
2749 .mbox_check_stats = imap_mbox_check_stats,
2750 .mbox_sync = NULL, /* imap syncing is handled by imap_sync_mailbox */
2751 .mbox_close = imap_mbox_close,
2752 .msg_open = imap_msg_open,
2753 .msg_open_new = imap_msg_open_new,
2754 .msg_commit = imap_msg_commit,
2755 .msg_close = imap_msg_close,
2756 .msg_padding_size = NULL,
2757 .msg_save_hcache = imap_msg_save_hcache,
2758 .tags_edit = imap_tags_edit,
2759 .tags_commit = imap_tags_commit,
2760 .path_probe = imap_path_probe,
2761 .path_canon = imap_path_canon,
2762 .path_is_empty = imap_path_is_empty,
2763 // clang-format on
2764};
#define ARRAY_SORT(head, fn, sdata)
Sort an array.
Definition array.h:373
#define ARRAY_ADD(head, elem)
Add an element at the end of the array.
Definition array.h:157
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition array.h:223
#define ARRAY_SIZE(head)
The number of elements stored.
Definition array.h:87
#define ARRAY_FREE(head)
Release all memory.
Definition array.h:209
#define ARRAY_HEAD_INITIALIZER
Static initializer for arrays.
Definition array.h:58
const char * mutt_str_atoull(const char *str, unsigned long long *dst)
Convert ASCII string to an unsigned long long.
Definition atoi.c:295
const char * mutt_str_atoui(const char *str, unsigned int *dst)
Convert ASCII string to an unsigned integer.
Definition atoi.c:217
IMAP authenticator multiplexor.
@ IMAP_AUTH_SUCCESS
Authentication successful.
Definition auth.h:40
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition buffer.c:161
int buf_add_printf(struct Buffer *buf, const char *fmt,...)
Format a string appending a Buffer.
Definition buffer.c:204
size_t buf_len(const struct Buffer *buf)
Calculate the length of a Buffer.
Definition buffer.c:491
void buf_dealloc(struct Buffer *buf)
Release the memory allocated by a buffer.
Definition buffer.c:377
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition buffer.c:76
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition buffer.c:291
void buf_fix_dptr(struct Buffer *buf)
Move the dptr to end of the Buffer.
Definition buffer.c:182
char buf_at(const struct Buffer *buf, size_t offset)
Return the character at the given offset.
Definition buffer.c:668
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition buffer.c:241
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition buffer.c:226
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition buffer.c:395
char * buf_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition buffer.c:571
void buf_alloc(struct Buffer *buf, size_t new_size)
Make sure a buffer can store at least new_size bytes.
Definition buffer.c:337
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition buffer.h:96
#define CF_NO_FLAGS
No flags are set.
Definition command.h:48
@ CMD_UNSUBSCRIBE_FROM
:unsubscribe-from
Definition command.h:146
@ CMD_SUBSCRIBE_TO
:subscribe-to
Definition command.h:118
@ CMD_NONE
No Command.
Definition command.h:59
CommandResult
Error codes for command_t parse functions.
Definition command.h:37
@ MUTT_CMD_SUCCESS
Success: Command worked.
Definition command.h:40
@ MUTT_CMD_ERROR
Error: Can't help the user.
Definition command.h:38
@ MUTT_CMD_WARNING
Warning: Help given to the user.
Definition command.h:39
NeoMutt Commands.
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition helpers.c:291
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition helpers.c:143
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition helpers.c:47
Convenience wrapper for the config headers.
#define mutt_numeric_cmp(a, b)
Compare two numbers, return -1, 0, or 1.
Definition sort.h:27
Connection Library.
void mutt_account_unsetpass(struct ConnAccount *cac)
Unset ConnAccount's password.
Convenience wrapper for the core headers.
void mailbox_free(struct Mailbox **ptr)
Free a Mailbox.
Definition mailbox.c:90
void mailbox_changed(struct Mailbox *m, enum NotifyMailbox action)
Notify observers of a change to a Mailbox.
Definition mailbox.c:232
#define MUTT_ACL_CREATE
Create a mailbox.
Definition mailbox.h:61
@ NT_MAILBOX_RESORT
Email list needs resorting.
Definition mailbox.h:180
@ NT_MAILBOX_UPDATE
Update internal tables.
Definition mailbox.h:181
#define MUTT_ACL_POST
Post (submit messages to the server)
Definition mailbox.h:67
#define MUTT_ACL_LOOKUP
Lookup mailbox (visible to 'list')
Definition mailbox.h:66
#define MUTT_ACL_INSERT
Add/copy into the mailbox (used when editing a message)
Definition mailbox.h:65
#define MUTT_ACL_DELETE
Delete a message.
Definition mailbox.h:62
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox's path string.
Definition mailbox.h:213
uint16_t AclFlags
ACL Rights - These show permission to...
Definition mailbox.h:58
#define MUTT_ACL_WRITE
Write to a message (for flagging or linking threads)
Definition mailbox.h:70
MailboxType
Supported mailbox formats.
Definition mailbox.h:40
@ MUTT_IMAP
'IMAP' Mailbox type
Definition mailbox.h:49
@ MUTT_MAILBOX_ANY
Match any Mailbox type.
Definition mailbox.h:41
@ MUTT_UNKNOWN
Mailbox wasn't recognised.
Definition mailbox.h:43
#define MUTT_ACL_SEEN
Change the 'seen' status of a message.
Definition mailbox.h:69
#define MUTT_ACL_READ
Read the mailbox.
Definition mailbox.h:68
bool mutt_isspace(int arg)
Wrapper for isspace(3)
Definition ctype.c:96
Edit a string.
#define MUTT_COMP_NO_FLAGS
No flags are set.
Definition wdata.h:42
void email_free(struct Email **ptr)
Free an Email.
Definition email.c:46
Structs that make up an email.
int mutt_save_message_mbox(struct Mailbox *m_src, struct Email *e, enum MessageSaveOpt save_opt, enum MessageTransformOpt transform_opt, struct Mailbox *m_dst)
Save a message to a given mailbox.
Definition external.c:741
Manage where the email is piped to external commands.
@ TRANSFORM_NONE
No transformation.
Definition external.h:43
@ SAVE_MOVE
Move message to another mailbox, removing the original.
Definition external.h:54
int parse_extract_token(struct Buffer *dest, struct Buffer *line, TokenFlags flags)
Extract one token from a string.
Definition extract.c:49
#define MoreArgs(buf)
Definition extract.h:31
#define TOKEN_NO_FLAGS
No flags are set.
Definition extract.h:45
#define mutt_file_fopen(PATH, MODE)
Definition file.h:138
int mutt_ssl_starttls(struct Connection *conn)
Negotiate TLS over an already opened connection.
Definition gnutls.c:1172
void imap_adata_free(void **ptr)
Free the private Account data - Implements Account::adata_free() -.
Definition adata.c:105
enum CommandResult parse_subscribe_to(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe)
Parse the 'subscribe-to' command - Implements Command::parse() -.
Definition imap.c:93
enum CommandResult parse_unsubscribe_from(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe)
Parse the 'unsubscribe-from' command - Implements Command::parse() -.
Definition imap.c:143
void imap_edata_free(void **ptr)
Free the private Email data - Implements Email::edata_free() -.
Definition edata.c:39
int mw_get_field(const char *prompt, struct Buffer *buf, CompletionFlags complete, enum HistoryClass hclass, const struct CompleteOps *comp_api, void *cdata)
Ask the user for a string -.
Definition window.c:467
#define mutt_error(...)
Definition logging2.h:94
#define mutt_message(...)
Definition logging2.h:93
#define mutt_debug(LEVEL,...)
Definition logging2.h:91
#define mutt_perror(...)
Definition logging2.h:95
void imap_mdata_free(void **ptr)
Free the private Mailbox data - Implements Mailbox::mdata_free() -.
Definition mdata.c:40
static bool imap_ac_add(struct Account *a, struct Mailbox *m)
Add a Mailbox to an Account - Implements MxOps::ac_add() -.
Definition imap.c:1924
static bool imap_ac_owns_path(struct Account *a, const char *path)
Check whether an Account owns a Mailbox path - Implements MxOps::ac_owns_path() -.
Definition imap.c:1906
const struct MxOps MxImapOps
IMAP Mailbox - Implements MxOps -.
Definition imap.c:2739
static enum MxStatus imap_mbox_check_stats(struct Mailbox *m, uint8_t flags)
Check the Mailbox statistics - Implements MxOps::mbox_check_stats() -.
Definition imap.c:1345
static enum MxStatus imap_mbox_check(struct Mailbox *m)
Check for new mail - Implements MxOps::mbox_check() -.
Definition imap.c:2442
static enum MxStatus imap_mbox_close(struct Mailbox *m)
Close a Mailbox - Implements MxOps::mbox_close() -.
Definition imap.c:2456
static bool imap_mbox_open_append(struct Mailbox *m, OpenMailboxFlags flags)
Open a Mailbox for appending - Implements MxOps::mbox_open_append() -.
Definition imap.c:2405
static enum MxOpenReturns imap_mbox_open(struct Mailbox *m)
Open a mailbox - Implements MxOps::mbox_open() -.
Definition imap.c:2319
int imap_msg_close(struct Mailbox *m, struct Message *msg)
Close an email - Implements MxOps::msg_close() -.
Definition message.c:2212
int imap_msg_commit(struct Mailbox *m, struct Message *msg)
Save changes to an email - Implements MxOps::msg_commit() -.
Definition message.c:2198
static bool imap_msg_open_new(struct Mailbox *m, struct Message *msg, const struct Email *e)
Open a new message in a Mailbox - Implements MxOps::msg_open_new() -.
Definition imap.c:2499
bool imap_msg_open(struct Mailbox *m, struct Message *msg, struct Email *e)
Open an email message in a Mailbox - Implements MxOps::msg_open() -.
Definition message.c:2005
int imap_msg_save_hcache(struct Mailbox *m, struct Email *e)
Save message to the header cache - Implements MxOps::msg_save_hcache() -.
Definition message.c:2220
int imap_path_canon(struct Buffer *path)
Canonicalise a Mailbox path - Implements MxOps::path_canon() -.
Definition imap.c:2695
static int imap_path_is_empty(struct Buffer *path)
Is the mailbox empty - Implements MxOps::path_is_empty() -.
Definition imap.c:2726
enum MailboxType imap_path_probe(const char *path, const struct stat *st)
Is this an IMAP Mailbox?
Definition imap.c:2681
static int imap_tags_commit(struct Mailbox *m, struct Email *e, const char *buf)
Save the tags to a message - Implements MxOps::tags_commit() -.
Definition imap.c:2611
static int imap_tags_edit(struct Mailbox *m, const char *tags, struct Buffer *buf)
Prompt and validate new messages tags - Implements MxOps::tags_edit() -.
Definition imap.c:2524
int imap_sort_uid(const void *a, const void *b, void *sdata)
Compare two UIDs - Implements sort_t -.
Definition msg_set.c:48
static int imap_sort_email_uid(const void *a, const void *b, void *sdata)
Compare two Emails by UID - Implements sort_t -.
Definition imap.c:1080
void mutt_hash_int_delete(struct HashTable *table, unsigned int intkey, const void *data)
Remove an element from a Hash Table.
Definition hash.c:446
Read/write command history from/to a file.
@ HC_OTHER
Miscellaneous strings.
Definition lib.h:61
void exec_account_hook(const char *url)
Perform an account hook.
Definition exec.c:328
Hook Commands.
struct ImapAccountData * imap_adata_new(struct Account *a)
Allocate and initialise a new ImapAccountData structure.
Definition adata.c:136
struct ImapAccountData * imap_adata_get(struct Mailbox *m)
Get the Account data for this mailbox.
Definition adata.c:162
Imap-specific Account data.
int imap_authenticate(struct ImapAccountData *adata)
Authenticate to an IMAP server.
Definition auth.c:116
int imap_cmd_start(struct ImapAccountData *adata, const char *cmdstr)
Given an IMAP command, send it to the server.
Definition command.c:1216
const char * imap_cmd_trailer(struct ImapAccountData *adata)
Extra information after tagged command response if any.
Definition command.c:1383
int imap_cmd_idle(struct ImapAccountData *adata)
Enter the IDLE state.
Definition command.c:1586
int imap_cmd_step(struct ImapAccountData *adata)
Reads server responses from an IMAP command.
Definition command.c:1230
int imap_exec(struct ImapAccountData *adata, const char *cmdstr, ImapCmdFlags flags)
Execute a command and wait for the response from the server.
Definition command.c:1420
void imap_cmd_finish(struct ImapAccountData *adata)
Attempt to perform cleanup.
Definition command.c:1511
struct ImapEmailData * imap_edata_get(struct Email *e)
Get the private data for this Email.
Definition edata.c:66
Imap-specific Email data.
IMAP network mailbox.
int imap_parse_path(const char *path, struct ConnAccount *cac, char *mailbox, size_t mailboxlen)
Parse an IMAP mailbox name into ConnAccount, name.
Definition util.c:482
struct ImapMboxData * imap_mdata_new(struct ImapAccountData *adata, const char *name)
Allocate and initialise a new ImapMboxData structure.
Definition mdata.c:74
struct ImapMboxData * imap_mdata_get(struct Mailbox *m)
Get the Mailbox data for this mailbox.
Definition mdata.c:61
Imap-specific Mailbox data.
int imap_cache_clean(struct Mailbox *m)
Delete all the entries in the message cache.
Definition message.c:1916
int imap_cache_del(struct Mailbox *m, struct Email *e)
Delete an email from the body cache.
Definition message.c:1897
int imap_read_headers(struct Mailbox *m, unsigned int msn_begin, unsigned int msn_end, bool initial_download)
Read headers from the server.
Definition message.c:1363
Shared constants/structs that are private to IMAP.
#define IMAP_CAP_ENABLE
RFC5161.
Definition private.h:136
#define IMAP_CAP_IDLE
RFC2177: IDLE.
Definition private.h:134
#define IMAP_CMD_NO_FLAGS
No flags are set.
Definition private.h:72
#define IMAP_SYNC_IN_PROGRESS
Sync is in progress, block expunge/newmail processing.
Definition private.h:69
void imap_qualify_path(char *buf, size_t buflen, struct ConnAccount *conn_account, char *path)
Make an absolute IMAP folder target.
Definition util.c:860
#define IMAP_CAP_ID
RFC2971: IMAP4 ID extension.
Definition private.h:142
void imap_allow_reopen(struct Mailbox *m)
Allow re-opening a folder upon expunge.
Definition util.c:1078
void imap_disallow_reopen(struct Mailbox *m)
Disallow re-opening a folder upon expunge.
Definition util.c:1091
@ IMAP_DISCONNECTED
Disconnected from server.
Definition private.h:106
@ IMAP_IDLE
Connection is idle.
Definition private.h:112
@ IMAP_AUTHENTICATED
Connection is authenticated.
Definition private.h:108
@ IMAP_SELECTED
Mailbox is selected.
Definition private.h:109
@ IMAP_CONNECTED
Connected to server.
Definition private.h:107
#define IMAP_EXPUNGE_PENDING
Messages on the server have been expunged.
Definition private.h:66
void imap_hcache_open(struct ImapAccountData *adata, struct ImapMboxData *mdata, bool create)
Open a header cache.
Definition util.c:307
#define IMAP_RES_OK
<tag> OK ...
Definition private.h:54
#define IMAP_OPEN_NO_FLAGS
No flags are set.
Definition private.h:63
#define IMAP_EXPUNGE_EXPECTED
Messages will be expunged from the server.
Definition private.h:65
int imap_hcache_put(struct ImapMboxData *mdata, struct Email *e)
Add an entry to the header cache.
Definition util.c:388
#define IMAP_LOG_LTRL
Log literal values.
Definition private.h:48
#define IMAP_CMD_POLL
Poll the tcp connection before running the imap command.
Definition private.h:75
void imap_mdata_cache_reset(struct ImapMboxData *mdata)
Release and clear cache data of ImapMboxData structure.
Definition util.c:111
#define IMAP_CAP_IMAP4
Server supports IMAP4.
Definition private.h:122
#define IMAP_CAP_STARTTLS
RFC2595: STARTTLS.
Definition private.h:132
#define IMAP_CAP_IMAP4REV1
Server supports IMAP4rev1.
Definition private.h:123
#define IMAP_CAP_STATUS
Server supports STATUS command.
Definition private.h:124
enum QuadOption imap_continue(const char *msg, const char *resp)
Display a message and ask the user if they want to go on.
Definition util.c:654
#define IMAP_CMD_PASS
Command contains a password. Suppress logging.
Definition private.h:73
void imap_buf_qualify_path(struct Buffer *buf, struct ConnAccount *conn_account, char *path)
Make an absolute IMAP folder target to a buffer.
Definition util.c:874
ImapExecResult
Imap_exec return code.
Definition private.h:82
@ IMAP_EXEC_SUCCESS
Imap command executed or queued successfully.
Definition private.h:83
@ IMAP_EXEC_ERROR
Imap command failure.
Definition private.h:84
@ IMAP_EXEC_FATAL
Imap connection failure.
Definition private.h:85
#define IMAP_CAP_ACL
RFC2086: IMAP4 ACL extension.
Definition private.h:125
#define IMAP_CAP_QRESYNC
RFC7162.
Definition private.h:138
#define IMAP_NEWMAIL_PENDING
New mail is waiting on the server.
Definition private.h:67
char * imap_fix_path(const char *mailbox, char *path, size_t plen)
Fix up the imap path.
Definition util.c:686
void imap_error(const char *where, const char *msg)
Show an error and abort.
Definition util.c:665
#define IMAP_FLAGS_PENDING
Flags have changed on the server.
Definition private.h:68
void imap_hcache_close(struct ImapMboxData *mdata)
Close the header cache.
Definition util.c:348
@ IMAP_BYE
Logged out from server.
Definition private.h:97
@ IMAP_FATAL
Unrecoverable error occurred.
Definition private.h:96
char * imap_fix_path_with_delim(char delim, const char *mailbox, char *path, size_t plen)
Fix up the imap path.
Definition util.c:718
#define IMAP_CAP_COMPRESS
RFC4978: COMPRESS=DEFLATE.
Definition private.h:140
int imap_hcache_del(struct ImapMboxData *mdata, unsigned int uid)
Delete an item from the header cache.
Definition util.c:406
#define IMAP_RES_NO
<tag> NO ...
Definition private.h:52
int imap_adata_find(const char *path, struct ImapAccountData **adata, struct ImapMboxData **mdata)
Find the Account data for this path.
Definition util.c:73
bool imap_account_match(const struct ConnAccount *a1, const struct ConnAccount *a2)
Compare two Accounts.
Definition util.c:1106
void imap_munge_mbox_name(bool unicode, char *dest, size_t dlen, const char *src)
Quote awkward characters in a mailbox name.
Definition util.c:971
#define IMAP_CMD_SINGLE
Run a single command.
Definition private.h:76
#define IMAP_RES_CONTINUE
* ...
Definition private.h:55
char * imap_next_word(char *s)
Find where the next IMAP word begins.
Definition util.c:829
#define IMAP_CAP_CONDSTORE
RFC7162.
Definition private.h:137
#define IMAP_CMD_QUEUE
Queue a command, do not execute.
Definition private.h:74
char * imap_get_qualifier(char *buf)
Get the qualifier from a tagged response.
Definition util.c:812
static void imap_logout(struct ImapAccountData *adata)
Gracefully log out of server.
Definition imap.c:637
int imap_mailbox_status(struct Mailbox *m, bool queue)
Refresh the number of total and new messages.
Definition imap.c:1397
int imap_path_status(const char *path, bool queue)
Refresh the number of total and new messages.
Definition imap.c:1362
void imap_notify_delete_email(struct Mailbox *m, struct Email *e)
Inform IMAP that an Email has been deleted.
Definition imap.c:828
void imap_close_connection(struct ImapAccountData *adata)
Close an IMAP connection.
Definition imap.c:1029
static int imap_status(struct ImapAccountData *adata, struct ImapMboxData *mdata, bool queue)
Refresh the number of total and new messages.
Definition imap.c:1295
static int imap_select_and_poll(struct Mailbox *m, int *countp)
Send SELECT and parse the untagged responses.
Definition imap.c:2112
int imap_reopen_mailbox(struct ImapAccountData *adata)
Re-SELECT the current mailbox after reconnecting.
Definition imap.c:2261
int imap_complete(struct Buffer *buf, const char *path)
Try to complete an IMAP folder path.
Definition imap.c:1470
int imap_subscribe(const char *path, bool subscribe)
Subscribe to a mailbox.
Definition imap.c:1413
int imap_delete_mailbox(struct Mailbox *m, char *path)
Delete a mailbox.
Definition imap.c:611
void imap_expunge_mailbox(struct Mailbox *m, bool resort)
Purge messages from the server.
Definition imap.c:849
static size_t longest_common_prefix(struct Buffer *buf, const char *src, size_t start)
Find longest prefix common to two strings.
Definition imap.c:453
int imap_open_connection(struct ImapAccountData *adata)
Open an IMAP connection.
Definition imap.c:921
static int sync_helper(struct Mailbox *m, struct Email **emails, int num_emails, AclFlags right, enum MessageType flag, const char *name)
Sync flag changes to the server.
Definition imap.c:404
int imap_rename_mailbox(struct ImapAccountData *adata, char *oldname, const char *newname)
Rename a mailbox.
Definition imap.c:584
static int complete_hosts(struct Buffer *buf)
Look for completion matches for mailboxes.
Definition imap.c:476
int imap_create_mailbox(struct ImapAccountData *adata, const char *mailbox)
Create a new mailbox.
Definition imap.c:542
static int check_capabilities(struct ImapAccountData *adata)
Make sure we can log in to this server.
Definition imap.c:207
int imap_fast_trash(struct Mailbox *m, const char *dest)
Use server COPY command to copy deleted messages to trash.
Definition imap.c:1551
int imap_sync_message_for_copy(struct Mailbox *m, struct Email *e, struct Buffer *cmd, enum QuadOption *err_continue)
Update server to reflect the flags of a single message.
Definition imap.c:1114
static int select_email_uids(struct Email **emails, int num_emails, enum MessageType flag, bool changed, bool invert, struct UidArray *uida)
Create a list of Email UIDs by type.
Definition imap.c:334
enum MxStatus imap_sync_mailbox(struct Mailbox *m, bool expunge, bool close)
Sync all the changes to the server.
Definition imap.c:1679
int imap_access(const char *path)
Check permissions on an IMAP mailbox with a new connection.
Definition imap.c:569
void imap_logout_all(void)
Close all open connections.
Definition imap.c:667
enum MxStatus imap_check_mailbox(struct Mailbox *m, bool force)
Use the NOOP or IDLE command to poll for new mail.
Definition imap.c:1220
static char * get_flags(struct ListHead *hflags, char *s)
Make a simple list out of a FLAGS response.
Definition imap.c:233
int imap_read_literal(FILE *fp, struct ImapAccountData *adata, unsigned long bytes, struct Progress *progress)
Read bytes bytes from server into file.
Definition imap.c:704
bool imap_has_flag(struct ListHead *flag_list, const char *flag)
Does the flag exist in the list.
Definition imap.c:1054
static void set_flag(struct Mailbox *m, AclFlags aclflag, bool flag, const char *str, struct Buffer *flags)
Append str to flags if we currently have permission according to aclflag.
Definition imap.c:287
static void imap_mbox_select(struct Mailbox *m)
Select a Mailbox.
Definition imap.c:1980
int imap_login(struct ImapAccountData *adata)
Open an IMAP connection.
Definition imap.c:2013
const struct Command ImapCommands[]
Imap Commands.
Definition imap.c:186
static bool compare_flags_for_copy(struct Email *e)
Compare local flags against the server.
Definition imap.c:307
struct ListNode * mutt_list_insert_tail(struct ListHead *h, char *s)
Append a string to the end of a List.
Definition list.c:65
void mutt_list_free(struct ListHead *h)
Free a List AND its strings.
Definition list.c:123
@ LL_DEBUG3
Log at debug level 3.
Definition logging2.h:47
@ LL_DEBUG2
Log at debug level 2.
Definition logging2.h:46
@ LL_DEBUG1
Log at debug level 1.
Definition logging2.h:45
bool mailbox_remove_simple(const char *mailbox)
Remove a Mailbox.
Definition mailboxes.c:397
bool mailbox_add_simple(const char *mailbox, struct Buffer *err)
Add a new Mailbox.
Definition mailboxes.c:157
#define FREE(x)
Free memory and set the pointer to NULL.
Definition memory.h:68
#define MUTT_MEM_MALLOC(n, type)
Definition memory.h:53
int imap_exec_msg_set(struct ImapAccountData *adata, const char *pre, const char *post, struct UidArray *uida)
Execute a command using a set of UIDs.
Definition msg_set.c:127
IMAP Message Sets.
void imap_msn_remove(struct MSNArray *msn, int idx)
Remove an entry from the cache.
Definition msn.c:116
IMAP MSN helper functions.
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition date.c:457
Convenience wrapper for the library headers.
#define N_(a)
Definition message.h:32
#define _(a)
Definition message.h:28
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition string.c:570
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition string.c:677
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition string.c:257
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition string.c:665
bool mutt_strn_equal(const char *a, const char *b, size_t num)
Check for equality of two strings (to a maximum), safely.
Definition string.c:429
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition string.c:234
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition string.c:503
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition string.c:586
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition string.c:246
bool mutt_istrn_equal(const char *a, const char *b, size_t num)
Check for equality of two strings ignoring case (to a maximum), safely.
Definition string.c:457
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition string.c:284
Many unsorted constants and some structs.
MessageType
To set flags or match patterns.
Definition mutt.h:86
@ MUTT_TRASH
Trashed messages.
Definition mutt.h:104
@ MUTT_READ
Messages that have been read.
Definition mutt.h:92
@ MUTT_OLD
Old messages.
Definition mutt.h:90
@ MUTT_FLAG
Flagged messages.
Definition mutt.h:98
@ MUTT_DELETED
Deleted messages.
Definition mutt.h:97
@ MUTT_REPLIED
Messages that have been replied to.
Definition mutt.h:91
#define PATH_MAX
Definition mutt.h:49
void account_to_url(struct ConnAccount *cac, struct Url *url)
Fill URL with info from account.
@ MUTT_ACCT_TYPE_IMAP
Imap Account.
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
NeoMutt Logging.
struct Connection * mutt_conn_new(const struct ConnAccount *cac)
Create a new Connection.
Definition mutt_socket.c:47
NeoMutt connections.
void pretty_mailbox(struct Buffer *buf)
Shorten a mailbox path using '~' or '='.
Definition muttlib.c:428
void expand_path(struct Buffer *buf, bool regex)
Create the canonical path.
Definition muttlib.c:122
Some miscellaneous functions.
void mx_alloc_memory(struct Mailbox *m, int req_size)
Create storage for the emails.
Definition mx.c:1208
int mx_ac_remove(struct Mailbox *m, bool keep_account)
Remove a Mailbox from an Account and delete Account if empty.
Definition mx.c:1754
struct Mailbox * mx_mbox_find2(const char *path)
Find a Mailbox on an Account.
Definition mx.c:1615
bool mx_mbox_ac_link(struct Mailbox *m)
Link a Mailbox to an existing or new Account.
Definition mx.c:248
struct Mailbox * mx_path_resolve(const char *path)
Get a Mailbox for a path.
Definition mx.c:1647
API for mailboxes.
uint8_t OpenMailboxFlags
Flags for mutt_open_mailbox(), e.g. MUTT_NOSORT.
Definition mxapi.h:38
MxOpenReturns
Return values for mbox_open()
Definition mxapi.h:72
@ MX_OPEN_ERROR
Open failed with an error.
Definition mxapi.h:74
@ MX_OPEN_OK
Open succeeded.
Definition mxapi.h:73
#define MUTT_MAILBOX_CHECK_IMMEDIATE
Don't postpone the actual checking.
Definition mxapi.h:52
MxStatus
Return values from mbox_check(), mbox_check_stats(), mbox_sync(), and mbox_close()
Definition mxapi.h:59
@ MX_STATUS_ERROR
An error occurred.
Definition mxapi.h:60
@ MX_STATUS_OK
No changes.
Definition mxapi.h:61
@ MX_STATUS_FLAGS
Nondestructive flags change (IMAP)
Definition mxapi.h:65
@ MX_STATUS_REOPENED
Mailbox was reopened.
Definition mxapi.h:64
@ MX_STATUS_NEW_MAIL
New mail received in Mailbox.
Definition mxapi.h:62
struct MailboxArray neomutt_mailboxes_get(struct NeoMutt *n, enum MailboxType type)
Get an Array of matching Mailboxes.
Definition neomutt.c:604
Text parsing functions.
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition pool.c:91
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition pool.c:111
Progress Bar.
bool progress_update(struct Progress *progress, size_t pos, int percent)
Update the state of the progress bar.
Definition progress.c:80
void mutt_qsort_r(void *base, size_t nmemb, size_t size, sort_t compar, void *sdata)
Sort an array, where the comparator has access to opaque data rather than requiring global variables.
Definition qsort_r.c:72
QuadOption
Possible values for a quad-option.
Definition quad.h:36
@ MUTT_ABORT
User aborted the question (with Ctrl-G)
Definition quad.h:37
@ MUTT_NO
User answered 'No', or assume 'No'.
Definition quad.h:38
@ MUTT_YES
User answered 'Yes', or assume 'Yes'.
Definition quad.h:39
Ask the user a question.
enum QuadOption query_yesorno_help(const char *prompt, enum QuadOption def, struct ConfigSubset *sub, const char *name)
Ask the user a Yes/No question offering help.
Definition question.c:357
enum QuadOption query_quadoption(const char *prompt, struct ConfigSubset *sub, const char *name)
Ask the user a quad-question.
Definition question.c:384
enum QuadOption query_yesorno(const char *prompt, enum QuadOption def)
Ask the user a Yes/No question.
Definition question.c:329
#define TAILQ_FOREACH(var, head, field)
Definition queue.h:782
#define STAILQ_FOREACH(var, head, field)
Definition queue.h:390
#define STAILQ_EMPTY(head)
Definition queue.h:382
volatile sig_atomic_t SigInt
true after SIGINT is received
Definition signal.c:68
int mutt_socket_close(struct Connection *conn)
Close a socket.
Definition socket.c:100
int mutt_socket_poll(struct Connection *conn, time_t wait_secs)
Checks whether reads would block.
Definition socket.c:182
int mutt_socket_readchar(struct Connection *conn, char *c)
Simple read buffering to speed things up.
Definition socket.c:200
void mutt_socket_empty(struct Connection *conn)
Clear out any queued data.
Definition socket.c:306
int mutt_socket_open(struct Connection *conn)
Simple wrapper.
Definition socket.c:76
#define SKIPWS(ch)
Definition string2.h:52
A group of associated Mailboxes.
Definition account.h:36
enum MailboxType type
Type of Mailboxes this Account contains.
Definition account.h:37
char * name
Name of Account.
Definition account.h:38
void(* adata_free)(void **ptr)
Definition account.h:53
void * adata
Private data (for Mailbox backends)
Definition account.h:42
String manipulation buffer.
Definition buffer.h:36
char * data
Pointer to data.
Definition buffer.h:37
CommandFlags flags
Command flags, e.g. CF_SYNONYM.
Definition command.h:184
const char * name
Name of the Command.
Definition command.h:159
Login details for a remote server.
Definition connaccount.h:53
char user[128]
Username.
Definition connaccount.h:56
char host[128]
Server to login to.
Definition connaccount.h:54
unsigned int ssf
Security strength factor, in bits (see notes)
Definition connection.h:50
struct ConnAccount account
Account details: username, password, etc.
Definition connection.h:49
int fd
Socket file descriptor.
Definition connection.h:53
The envelope/body of an email.
Definition email.h:39
bool read
Email is read.
Definition email.h:50
bool purge
Skip trash folder when deleting.
Definition email.h:79
struct Envelope * env
Envelope information.
Definition email.h:68
void * edata
Driver-specific data.
Definition email.h:74
bool active
Message is not to be removed.
Definition email.h:76
bool old
Email is seen, but unread.
Definition email.h:49
bool changed
Email has been edited.
Definition email.h:77
bool attach_del
Has an attachment marked for deletion.
Definition email.h:99
bool flagged
Marked important?
Definition email.h:47
bool replied
Email has been replied to.
Definition email.h:51
struct TagList tags
For drivers that support server tagging.
Definition email.h:72
char * path
Path of Email (for local Mailboxes)
Definition email.h:70
bool deleted
Email is deleted.
Definition email.h:78
int index
The absolute (unsorted) message number.
Definition email.h:110
unsigned char changed
Changed fields, e.g. MUTT_ENV_CHANGED_SUBJECT.
Definition envelope.h:90
IMAP-specific Account data -.
Definition adata.h:40
char delim
Path delimiter.
Definition adata.h:79
struct Mailbox * prev_mailbox
Previously selected mailbox.
Definition adata.h:81
struct ImapList * cmdresult
Resuls of complicated commands.
Definition adata.h:70
int lastcmd
Last command in the queue.
Definition adata.h:76
bool closing
If true, we are waiting for CLOSE completion.
Definition adata.h:43
time_t lastread
last time we read a command for the server
Definition adata.h:59
bool unicode
If true, we can send UTF-8, and the server will use UTF8 rather than mUTF7.
Definition adata.h:64
ImapCapFlags capabilities
Capability flags.
Definition adata.h:56
int nextcmd
Next command to be sent.
Definition adata.h:75
unsigned char state
ImapState, e.g. IMAP_AUTHENTICATED.
Definition adata.h:45
struct Mailbox * mailbox
Current selected mailbox.
Definition adata.h:80
char * capstr
Capability string from the server.
Definition adata.h:55
struct ImapCommand * cmds
Queue of commands for the server.
Definition adata.h:73
unsigned char status
ImapFlags, e.g. IMAP_FATAL.
Definition adata.h:46
int cmdslots
Size of the command queue.
Definition adata.h:74
char * buf
Command buffer.
Definition adata.h:61
unsigned int seqno
tag sequence number, e.g. '{seqid}0001'
Definition adata.h:58
struct Connection * conn
Connection to IMAP server.
Definition adata.h:41
struct Buffer cmdbuf
Command queue.
Definition adata.h:77
IMAP command structure.
Definition private.h:161
IMAP-specific Email data -.
Definition edata.h:35
unsigned int uid
32-bit Message UID
Definition edata.h:45
char * flags_remote
Remote flags.
Definition edata.h:49
bool deleted
Email has been deleted.
Definition edata.h:39
char * flags_system
System flags.
Definition edata.h:48
Items in an IMAP browser.
Definition private.h:150
bool noselect
Mailbox is not selectable.
Definition private.h:153
char * name
Mailbox name.
Definition private.h:151
char delim
Hierarchy delimiter.
Definition private.h:152
IMAP-specific Mailbox data -.
Definition mdata.h:40
ImapOpenFlags reopen
Flags, e.g. IMAP_REOPEN_ALLOW.
Definition mdata.h:45
struct ListHead flags
List of permanent flags.
Definition mdata.h:50
char * real_name
Original Mailbox name, e.g.: INBOX can be just \0.
Definition mdata.h:43
unsigned int new_mail_count
Set when EXISTS notifies of new mail.
Definition mdata.h:47
struct HashTable * uid_hash
Hash Table: "uid" -> Email.
Definition mdata.h:60
ImapOpenFlags check_status
Flags, e.g. IMAP_NEWMAIL_PENDING.
Definition mdata.h:46
char * munge_name
Munged version of the mailbox name.
Definition mdata.h:42
char * name
Mailbox name.
Definition mdata.h:41
A List node for strings.
Definition list.h:37
char * data
String.
Definition list.h:38
A mailbox.
Definition mailbox.h:78
void(* mdata_free)(void **ptr)
Definition mailbox.h:142
int vcount
The number of virtual messages.
Definition mailbox.h:98
bool changed
Mailbox has been modified.
Definition mailbox.h:109
bool has_new
Mailbox has new mail.
Definition mailbox.h:84
char * realpath
Used for duplicate detection, context comparison, and the sidebar.
Definition mailbox.h:80
bool append
Mailbox is opened in append mode.
Definition mailbox.h:108
int msg_new
Number of new messages.
Definition mailbox.h:91
time_t last_checked
Last time we checked this mailbox for new mail.
Definition mailbox.h:104
int msg_count
Total number of messages.
Definition mailbox.h:87
AclFlags rights
ACL bits, see AclFlags.
Definition mailbox.h:118
bool poll_new_mail
Check for new mail.
Definition mailbox.h:114
void * mdata
Driver specific data.
Definition mailbox.h:131
struct Email ** emails
Array of Emails.
Definition mailbox.h:95
struct Buffer pathbuf
Path of the Mailbox.
Definition mailbox.h:79
int msg_deleted
Number of deleted messages.
Definition mailbox.h:92
struct Account * account
Account that owns this Mailbox.
Definition mailbox.h:126
off_t size
Size of the Mailbox.
Definition mailbox.h:83
int msg_flagged
Number of flagged messages.
Definition mailbox.h:89
bool readonly
Don't allow changes to the mailbox.
Definition mailbox.h:115
bool verbose
Display status messages?
Definition mailbox.h:116
int msg_unread
Number of unread messages.
Definition mailbox.h:88
A local copy of an email.
Definition message.h:34
FILE * fp
pointer to the message data
Definition message.h:35
char * path
path to temp file
Definition message.h:36
Definition mxapi.h:87
Container for Accounts, Notifications.
Definition neomutt.h:41
struct AccountArray accounts
All Accounts.
Definition neomutt.h:50
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:49
Context for config parsing (history/backtrace)
Definition pcontext.h:34
Detailed error information from config parsing.
Definition perror.h:34
struct Buffer * message
Error message.
Definition perror.h:35
A parsed URL proto://user:password@host:port/path?a=1&b=2
Definition url.h:69
char * user
Username.
Definition url.h:71
char * host
Host.
Definition url.h:73
char * path
Path.
Definition url.h:75
bool driver_tags_replace(struct TagList *tl, const char *tags)
Replace all tags.
Definition tags.c:202
void driver_tags_get_with_hidden(struct TagList *tl, struct Buffer *tags)
Get all tags, also hidden ones, separated by space.
Definition tags.c:175
#define buf_mktemp(buf)
Definition tmp.h:33
struct Url * url_parse(const char *src)
Fill in Url.
Definition url.c:242
void url_free(struct Url **ptr)
Free the contents of a URL.
Definition url.c:124
int url_tostring(const struct Url *url, char *dest, size_t len, uint8_t flags)
Output the URL string for a given Url object.
Definition url.c:426
#define U_NO_FLAGS
No flags are set for URL parsing.
Definition url.h:49
void mutt_zstrm_wrap_conn(struct Connection *conn)
Wrap a compression layer around a Connection.
Definition zstrm.c:297