summaryrefslogtreecommitdiff
path: root/pith/imap.c
diff options
context:
space:
mode:
Diffstat (limited to 'pith/imap.c')
-rw-r--r--pith/imap.c1111
1 files changed, 1111 insertions, 0 deletions
diff --git a/pith/imap.c b/pith/imap.c
new file mode 100644
index 00000000..ea4c5b1f
--- /dev/null
+++ b/pith/imap.c
@@ -0,0 +1,1111 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: imap.c 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/imap.h"
+#include "../pith/msgno.h"
+#include "../pith/state.h"
+#include "../pith/flag.h"
+#include "../pith/pineelt.h"
+#include "../pith/status.h"
+#include "../pith/conftype.h"
+#include "../pith/context.h"
+#include "../pith/thread.h"
+#include "../pith/mailview.h"
+#include "../pith/mailpart.h"
+#include "../pith/mailindx.h"
+#include "../pith/mailcmd.h"
+#include "../pith/save.h"
+#include "../pith/util.h"
+#include "../pith/stream.h"
+#include "../pith/newmail.h"
+#include "../pith/icache.h"
+#include "../pith/options.h"
+
+#ifdef _WINDOWS
+#include "../pico/osdep/mswin.h"
+#endif
+
+
+/*
+ * Internal prototypes
+ */
+long imap_seq_exec(MAILSTREAM *, char *, long (*)(MAILSTREAM *, long, void *), void *);
+long imap_seq_exec_append(MAILSTREAM *, long, void *);
+char *ps_get(size_t);
+
+
+/*
+ * Exported globals setup by searching functions to tell mm_searched
+ * where to put message numbers that matched the search criteria,
+ * and to allow mm_searched to return number of matches.
+ */
+MAILSTREAM *mm_search_stream;
+long mm_search_count = 0L;
+MAILSTATUS mm_status_result;
+
+MM_LIST_S *mm_list_info;
+
+MMLOGIN_S *mm_login_list = NULL;
+MMLOGIN_S *cert_failure_list = NULL;
+
+/*
+ * Instead of storing cached passwords in free storage, store them in this
+ * private space. This makes it easier and more reliable when we want
+ * to zero this space out. We only store passwords here (char *) so we
+ * don't need to worry about alignment.
+ */
+static volatile char private_store[1024];
+
+static int critical_depth = 0;
+
+/* hook to hang callback on "current" message expunge */
+void (*pith_opt_current_expunged)(long unsigned int);
+
+#ifdef SIGINT
+RETSIGTYPE (*hold_int)(int);
+#endif
+
+#ifdef SIGTERM
+RETSIGTYPE (*hold_term)(int);
+#endif
+
+#ifdef SIGHUP
+RETSIGTYPE (*hold_hup)(int);
+#endif
+
+#ifdef SIGUSR2
+RETSIGTYPE (*hold_usr2)(int);
+#endif
+
+
+
+/*----------------------------------------------------------------------
+ receive notification that search found something
+
+ Input: mail stream and message number of located item
+
+ Result: nothing, not used by pine
+ ----*/
+void
+mm_searched(MAILSTREAM *stream, long unsigned int rawno)
+{
+ MESSAGECACHE *mc;
+
+ if(rawno > 0L && stream && rawno <= stream->nmsgs
+ && (mc = mail_elt(stream, rawno))){
+ mc->searched = 1;
+ if(stream == mm_search_stream)
+ mm_search_count++;
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ receive notification of new mail from imap daemon
+
+ Args: stream -- The stream the message count report is for.
+ number -- The number of messages now in folder.
+
+ Result: Sets value in pine state indicating new mailbox size
+
+ Called when the number of messages in the mailbox goes up. This
+ may also be called as a result of an expunge. It increments the
+ new_mail_count based on a the difference between the current idea of
+ the maximum number of messages and what mm_exists claims. The new mail
+ notification is done in newmail.c
+
+ Only worry about the cases when the number grows, as mm_expunged
+ handles shrinkage...
+
+ ----*/
+void
+mm_exists(MAILSTREAM *stream, long unsigned int number)
+{
+ long new_this_call, n;
+ int exbits = 0, lflags = 0;
+ MSGNO_S *msgmap;
+
+#ifdef DEBUG
+ if(ps_global->debug_imap > 1 || ps_global->debugmem)
+ dprint((3, "=== mm_exists(%lu,%s) called ===\n", number,
+ !stream ? "(no stream)" : !stream->mailbox ? "(null)" : stream->mailbox));
+#endif
+
+ msgmap = sp_msgmap(stream);
+ if(!msgmap)
+ return;
+
+ if(mn_get_nmsgs(msgmap) != (long) number){
+ sp_set_mail_box_changed(stream, 1);
+ /* titlebar will be affected */
+ if(ps_global->mail_stream == stream)
+ ps_global->mangled_header = 1;
+ }
+
+ if(mn_get_nmsgs(msgmap) < (long) number){
+ new_this_call = (long) number - mn_get_nmsgs(msgmap);
+ sp_set_new_mail_count(stream,
+ sp_new_mail_count(stream) + new_this_call);
+ sp_set_recent_since_visited(stream,
+ sp_recent_since_visited(stream) + new_this_call);
+
+ mn_add_raw(msgmap, new_this_call);
+
+ /*
+ * Set local "recent" and "hidden" bits...
+ */
+ for(n = 0; n < new_this_call; n++, number--){
+ if(msgno_exceptions(stream, number, "0", &exbits, FALSE))
+ exbits |= MSG_EX_RECENT;
+ else
+ exbits = MSG_EX_RECENT;
+
+ msgno_exceptions(stream, number, "0", &exbits, TRUE);
+
+ if(SORT_IS_THREADED(msgmap))
+ lflags |= MN_USOR;
+
+ /*
+ * If we're zoomed, then hide this message too since
+ * it couldn't have possibly been selected yet...
+ */
+ lflags |= (any_lflagged(msgmap, MN_HIDE) ? MN_HIDE : 0);
+ if(lflags)
+ set_lflag(stream, msgmap, mn_get_total(msgmap) - n, lflags, 1);
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Receive notification from IMAP that a single message has been expunged
+
+ Args: stream -- The stream/folder the message is expunged from
+ rawno -- The raw message number that was expunged
+
+mm_expunged is always called on an expunge. Simply remove all
+reference to the expunged message, shifting internal mappings as
+necessary.
+ ----*/
+void
+mm_expunged(MAILSTREAM *stream, long unsigned int rawno)
+{
+ MESSAGECACHE *mc;
+ long i;
+ int is_current = 0;
+ MSGNO_S *msgmap;
+
+#ifdef DEBUG
+ if(ps_global->debug_imap > 1 || ps_global->debugmem)
+ dprint((3, "mm_expunged(%s,%lu)\n",
+ stream
+ ? (stream->mailbox
+ ? stream->mailbox
+ : "(no stream)")
+ : "(null)", rawno));
+#endif
+
+ msgmap = sp_msgmap(stream);
+ if(!msgmap)
+ return;
+
+ if(ps_global->mail_stream == stream)
+ is_current++;
+
+ if((i = mn_raw2m(msgmap, (long) rawno)) != 0L){
+ dprint((7, "mm_expunged: rawno=%lu msgno=%ld nmsgs=%ld max_msgno=%ld flagged_exld=%ld\n", rawno, i, mn_get_nmsgs(msgmap), mn_get_total(msgmap), msgmap->flagged_exld));
+
+ sp_set_mail_box_changed(stream, 1);
+ sp_set_expunge_count(stream, sp_expunge_count(stream) + 1);
+
+ if(is_current){
+ reset_check_point(stream);
+ ps_global->mangled_header = 1;
+
+ /* flush invalid cache entries */
+ while(i <= mn_get_total(msgmap))
+ clear_index_cache_ent(stream, i++, 0);
+
+ /* let app know what happened */
+ if(pith_opt_current_expunged)
+ (*pith_opt_current_expunged)(rawno);
+ }
+ }
+ else{
+ dprint((7,
+ "mm_expunged: rawno=%lu was excluded, flagged_exld was %d\n",
+ rawno, msgmap->flagged_exld));
+ dprint((7, " nmsgs=%ld max_msgno=%ld\n",
+ mn_get_nmsgs(msgmap), mn_get_total(msgmap)));
+ if(rawno > 0L && rawno <= stream->nmsgs)
+ mc = mail_elt(stream, rawno);
+
+ if(!mc){
+ dprint((7, " cannot get mail_elt(%lu)\n",
+ rawno));
+ }
+ else if(!mc->sparep){
+ dprint((7, " mail_elt(%lu)->sparep is NULL\n",
+ rawno));
+ }
+ else{
+ dprint((7, " mail_elt(%lu)->sparep->excluded=%d\n",
+ rawno, (int) (((PINELT_S *) mc->sparep)->excluded)));
+ }
+ }
+
+ if(SORT_IS_THREADED(msgmap)
+ && (SEP_THRDINDX()
+ || ps_global->thread_disp_style != THREAD_NONE)){
+ long cur;
+
+ /*
+ * When we're sorting with a threaded method an expunged
+ * message may cause the rest of the sort to be wrong. This
+ * isn't so bad if we're just looking at the index. However,
+ * it also causes the thread tree (PINETHRD_S) to become
+ * invalid, so if we're using a threading view we need to
+ * sort in order to fix the tree and to protect fetch_thread().
+ */
+ sp_set_need_to_rethread(stream, 1);
+
+ /*
+ * If we expunged the current message which was a member of the
+ * viewed thread, and the adjustment to current will take us
+ * out of that thread, fix it if we can, by backing current up
+ * into the thread. We'd like to just check after mn_flush_raw
+ * below but the problem is that the elts won't change until
+ * after we return from mm_expunged. So we have to manually
+ * check the other messages for CHID2 flags instead of thinking
+ * that we can expunge the current message and then check. It won't
+ * work because the elt will still refer to the expunged message.
+ */
+ if(sp_viewing_a_thread(stream)
+ && get_lflag(stream, NULL, rawno, MN_CHID2)
+ && mn_total_cur(msgmap) == 1
+ && mn_is_cur(msgmap, mn_raw2m(msgmap, (long) rawno))
+ && (cur = mn_get_cur(msgmap)) > 1L
+ && cur < mn_get_total(msgmap)
+ && !get_lflag(stream, msgmap, cur + 1L, MN_CHID2)
+ && get_lflag(stream, msgmap, cur - 1L, MN_CHID2))
+ mn_set_cur(msgmap, cur - 1L);
+ }
+
+ /*
+ * Keep on top of our special flag counts.
+ *
+ * NOTE: This is allowed since mail_expunged releases
+ * data for this message after the callback.
+ */
+ if(rawno > 0L && rawno <= stream->nmsgs && (mc = mail_elt(stream, rawno))){
+ PINELT_S *pelt = (PINELT_S *) mc->sparep;
+
+ if(pelt){
+ if(pelt->hidden)
+ msgmap->flagged_hid--;
+
+ if(pelt->excluded)
+ msgmap->flagged_exld--;
+
+ if(pelt->selected)
+ msgmap->flagged_tmp--;
+
+ if(pelt->colhid)
+ msgmap->flagged_chid--;
+
+ if(pelt->colhid2)
+ msgmap->flagged_chid2--;
+
+ if(pelt->collapsed)
+ msgmap->flagged_coll--;
+
+ if(pelt->tmp)
+ msgmap->flagged_stmp--;
+
+ if(pelt->unsorted)
+ msgmap->flagged_usor--;
+
+ if(pelt->searched)
+ msgmap->flagged_srch--;
+
+ if(pelt->hidden || pelt->colhid)
+ msgmap->flagged_invisible--;
+
+ free_pine_elt(&mc->sparep);
+ }
+ }
+
+ /*
+ * if it's in the sort array, flush it, otherwise
+ * decrement raw sequence numbers greater than "rawno"
+ */
+ mn_flush_raw(msgmap, (long) rawno);
+}
+
+
+void
+mm_flags(MAILSTREAM *stream, long unsigned int rawno)
+{
+ /*
+ * The idea here is to clean up any data pine might have cached
+ * that has anything to do with the indicated message number.
+ */
+ if(stream == ps_global->mail_stream){
+ long msgno, t;
+ PINETHRD_S *thrd;
+
+ if(scores_are_used(SCOREUSE_GET) & SCOREUSE_STATEDEP)
+ clear_msg_score(stream, rawno);
+
+ msgno = mn_raw2m(sp_msgmap(stream), (long) rawno);
+
+ /* if in thread index */
+ if(THRD_INDX()){
+ if((thrd = fetch_thread(stream, rawno))
+ && thrd->top
+ && (thrd = fetch_thread(stream, thrd->top))
+ && thrd->rawno
+ && (t = mn_raw2m(sp_msgmap(stream), thrd->rawno)))
+ clear_index_cache_ent(stream, t, 0);
+ }
+ else if(THREADING()){
+ if(msgno > 0L)
+ clear_index_cache_ent(stream, msgno, 0);
+
+ /*
+ * If a parent is collapsed, clear that parent's
+ * index cache entry.
+ */
+ if((thrd = fetch_thread(stream, rawno)) && thrd->parent){
+ thrd = fetch_thread(stream, thrd->parent);
+ while(thrd){
+ if(get_lflag(stream, NULL, thrd->rawno, MN_COLL)
+ && (t = mn_raw2m(sp_msgmap(stream), (long) thrd->rawno)))
+ clear_index_cache_ent(stream, t, 0);
+
+ if(thrd->parent)
+ thrd = fetch_thread(stream, thrd->parent);
+ else
+ thrd = NULL;
+ }
+ }
+ }
+ else if(msgno > 0L)
+ clear_index_cache_ent(stream, msgno, 0);
+
+ if(msgno && mn_is_cur(sp_msgmap(stream), msgno))
+ ps_global->mangled_header = 1;
+ }
+
+ /*
+ * We count up flag changes here. The
+ * dont_count_flagchanges variable tries to prevent us from
+ * counting when we're just fetching flags.
+ */
+ if(!(ps_global->dont_count_flagchanges
+ && stream == ps_global->mail_stream)){
+ int exbits;
+
+ check_point_change(stream);
+
+ /* we also note flag changes for filtering purposes */
+ if(msgno_exceptions(stream, rawno, "0", &exbits, FALSE))
+ exbits |= MSG_EX_STATECHG;
+ else
+ exbits = MSG_EX_STATECHG;
+
+ msgno_exceptions(stream, rawno, "0", &exbits, TRUE);
+ }
+}
+
+
+void
+mm_list(MAILSTREAM *stream, int delimiter, char *mailbox, long int attributes)
+{
+#ifdef DEBUG
+ if(ps_global->debug_imap > 2 || ps_global->debugmem)
+ dprint((5, "mm_list \"%s\": delim: '%c', %s%s%s%s%s%s\n",
+ mailbox ? mailbox : "?", delimiter ? delimiter : 'X',
+ (attributes & LATT_NOINFERIORS) ? ", no inferiors" : "",
+ (attributes & LATT_NOSELECT) ? ", no select" : "",
+ (attributes & LATT_MARKED) ? ", marked" : "",
+ (attributes & LATT_UNMARKED) ? ", unmarked" : "",
+ (attributes & LATT_HASCHILDREN) ? ", has children" : "",
+ (attributes & LATT_HASNOCHILDREN) ? ", has no children" : ""));
+#endif
+
+ if(!mm_list_info->stream || stream == mm_list_info->stream)
+ (*mm_list_info->filter)(stream, mailbox, delimiter,
+ attributes, mm_list_info->data,
+ mm_list_info->options);
+}
+
+
+void
+mm_lsub(MAILSTREAM *stream, int delimiter, char *mailbox, long int attributes)
+{
+#ifdef DEBUG
+ if(ps_global->debug_imap > 2 || ps_global->debugmem)
+ dprint((5, "LSUB \"%s\": delim: '%c', %s%s%s%s%s%s\n",
+ mailbox ? mailbox : "?", delimiter ? delimiter : 'X',
+ (attributes & LATT_NOINFERIORS) ? ", no inferiors" : "",
+ (attributes & LATT_NOSELECT) ? ", no select" : "",
+ (attributes & LATT_MARKED) ? ", marked" : "",
+ (attributes & LATT_UNMARKED) ? ", unmarked" : "",
+ (attributes & LATT_HASCHILDREN) ? ", has children" : "",
+ (attributes & LATT_HASNOCHILDREN) ? ", has no children" : ""));
+#endif
+
+ if(!mm_list_info->stream || stream == mm_list_info->stream)
+ (*mm_list_info->filter)(stream, mailbox, delimiter,
+ attributes, mm_list_info->data,
+ mm_list_info->options);
+}
+
+
+void
+mm_status(MAILSTREAM *stream, char *mailbox, MAILSTATUS *status)
+{
+ /*
+ * We implement mail_status for the #move namespace by adding a wrapper
+ * routine, pine_mail_status. It may have to call the real mail_status
+ * twice for #move folders and combine the results. It sets
+ * pine_cached_status to point to a local status variable to store the
+ * intermediate results.
+ */
+ if(status){
+ if(pine_cached_status != NULL)
+ *pine_cached_status = *status;
+ else
+ mm_status_result = *status;
+ }
+
+#ifdef DEBUG
+ if(status){
+ if(pine_cached_status)
+ dprint((2,
+ "mm_status: Preliminary pass for #move\n"));
+
+ dprint((2, "mm_status: Mailbox \"%s\"",
+ mailbox ? mailbox : "?"));
+ if(status->flags & SA_MESSAGES)
+ dprint((2, ", %lu messages", status->messages));
+
+ if(status->flags & SA_RECENT)
+ dprint((2, ", %lu recent", status->recent));
+
+ if(status->flags & SA_UNSEEN)
+ dprint((2, ", %lu unseen", status->unseen));
+
+ if(status->flags & SA_UIDVALIDITY)
+ dprint((2, ", %lu UID validity", status->uidvalidity));
+
+ if(status->flags & SA_UIDNEXT)
+ dprint((2, ", %lu next UID", status->uidnext));
+
+ dprint((2, "\n"));
+ }
+#endif
+}
+
+
+/*----------------------------------------------------------------------
+ Write imap debugging information into log file
+
+ Args: strings -- the string for the debug file
+
+ Result: message written to the debug log file
+ ----*/
+void
+mm_dlog(char *string)
+{
+ char *p, *q = NULL, save, *continued;
+ int more = 1;
+
+#ifdef _WINDOWS
+ mswin_imaptelemetry(string);
+#endif
+#ifdef DEBUG
+ continued = "";
+ p = string;
+#ifdef DEBUGJOURNAL
+ /* because string can be really long and we don't want to lose any of it */
+ if(p)
+ more = 1;
+
+ while(more){
+ if(q){
+ *q = save;
+ p = q;
+ continued = "(Continuation line) ";
+ }
+
+ if(strlen(p) > 63000){
+ q = p + 60000;
+ save = *q;
+ *q = '\0';
+ }
+ else
+ more = 0;
+#endif
+ dprint(((ps_global->debug_imap >= 4 && debug < 4) ? debug : 4,
+ "IMAP DEBUG %s%s: %s\n",
+ continued ? continued : "",
+ debug_time(1, ps_global->debug_timestamp), p ? p : "?"));
+#ifdef DEBUGJOURNAL
+ }
+#endif
+#endif
+}
+
+
+/*----------------------------------------------------------------------
+ Ignore signals when imap is running through critical code
+
+ Args: stream -- The stream on which critical operation is proceeding
+ ----*/
+
+void
+mm_critical(MAILSTREAM *stream)
+{
+ stream = stream; /* For compiler complaints that this isn't used */
+
+ if(++critical_depth == 1){
+
+#ifdef SIGHUP
+ hold_hup = signal(SIGHUP, SIG_IGN);
+#endif
+#ifdef SIGUSR2
+ hold_usr2 = signal(SIGUSR2, SIG_IGN);
+#endif
+#ifdef SIGINT
+ hold_int = signal(SIGINT, SIG_IGN);
+#endif
+#ifdef SIGTERM
+ hold_term = signal(SIGTERM, SIG_IGN);
+#endif
+ }
+
+ dprint((9, "IMAP critical (depth now %d) on %s\n",
+ critical_depth,
+ (stream && stream->mailbox) ? stream->mailbox : "<no folder>" ));
+}
+
+
+/*----------------------------------------------------------------------
+ Reset signals after critical imap code
+ ----*/
+void
+mm_nocritical(MAILSTREAM *stream)
+{
+ stream = stream; /* For compiler complaints that this isn't used */
+
+ if(--critical_depth == 0){
+
+#ifdef SIGHUP
+ (void)signal(SIGHUP, hold_hup);
+#endif
+#ifdef SIGUSR2
+ (void)signal(SIGUSR2, hold_usr2);
+#endif
+#ifdef SIGINT
+ (void)signal(SIGINT, hold_int);
+#endif
+#ifdef SIGTERM
+ (void)signal(SIGTERM, hold_term);
+#endif
+ }
+
+ critical_depth = MAX(critical_depth, 0);
+
+ dprint((9, "Done with IMAP critical (depth now %d) on %s\n",
+ critical_depth,
+ (stream && stream->mailbox) ? stream->mailbox : "<no folder>" ));
+}
+
+
+void
+mm_fatal(char *message)
+{
+ panic(message);
+}
+
+
+char *
+imap_referral(MAILSTREAM *stream, char *ref, long int code)
+{
+ char *buf = NULL;
+
+ if(ref && !struncmp(ref, "imap://", 7)){
+ char *folder = NULL;
+ imapuid_t uid_val, uid;
+ int rv;
+
+ rv = url_imap_folder(ref, &folder, &uid, &uid_val, NULL, 1);
+ switch(code){
+ case REFAUTHFAILED :
+ case REFAUTH :
+ if((rv & URL_IMAP_IMAILBOXLIST) && (rv & URL_IMAP_ISERVERONLY))
+ buf = cpystr(folder);
+
+ break;
+
+ case REFSELECT :
+ case REFCREATE :
+ case REFDELETE :
+ case REFRENAME :
+ case REFSUBSCRIBE :
+ case REFUNSUBSCRIBE :
+ case REFSTATUS :
+ case REFCOPY :
+ case REFAPPEND :
+ if(rv & URL_IMAP_IMESSAGELIST)
+ buf = cpystr(folder);
+
+ break;
+
+ default :
+ break;
+ }
+
+ if(folder)
+ fs_give((void **) &folder);
+ }
+
+ return(buf);
+}
+
+
+long
+imap_proxycopy(MAILSTREAM *stream, char *sequence, char *mailbox, long int flags)
+{
+ SE_APP_S args;
+ long ret;
+
+ args.folder = mailbox;
+ args.flags = flags;
+
+ if(pith_opt_save_size_changed_prompt)
+ (*pith_opt_save_size_changed_prompt)(0L, SSCP_INIT);
+
+ ret = imap_seq_exec(stream, sequence, imap_seq_exec_append, &args);
+
+ if(pith_opt_save_size_changed_prompt)
+ (*pith_opt_save_size_changed_prompt)(0L, SSCP_END);
+
+ return(ret);
+}
+
+
+long
+imap_seq_exec(MAILSTREAM *stream, char *sequence,
+ long int (*func)(MAILSTREAM *, long int, void *),
+ void *args)
+{
+ unsigned long i,j,x;
+
+ while (*sequence) { /* while there is something to parse */
+ if (*sequence == '*') { /* maximum message */
+ if(!(i = stream->nmsgs)){
+ mm_log ("No messages, so no maximum message number",ERROR);
+ return(0L);
+ }
+
+ sequence++; /* skip past * */
+ }
+ else if (!(i = strtoul ((const char *) sequence,&sequence,10))
+ || (i > stream->nmsgs)){
+ mm_log ("Sequence invalid",ERROR);
+ return(0L);
+ }
+
+ switch (*sequence) { /* see what the delimiter is */
+ case ':': /* sequence range */
+ if (*++sequence == '*') { /* maximum message */
+ if (stream->nmsgs) j = stream->nmsgs;
+ else {
+ mm_log ("No messages, so no maximum message number",ERROR);
+ return NIL;
+ }
+
+ sequence++; /* skip past * */
+ }
+ /* parse end of range */
+ else if (!(j = strtoul ((const char *) sequence,&sequence,10)) ||
+ (j > stream->nmsgs)) {
+ mm_log ("Sequence range invalid",ERROR);
+ return NIL;
+ }
+
+ if (*sequence && *sequence++ != ',') {
+ mm_log ("Sequence range syntax error",ERROR);
+ return NIL;
+ }
+
+ if (i > j) { /* swap the range if backwards */
+ x = i; i = j; j = x;
+ }
+
+ while (i <= j)
+ if(!(*func)(stream, i++, args))
+ return(0L);
+
+ break;
+ case ',': /* single message */
+ ++sequence; /* skip the delimiter, fall into end case */
+ case '\0': /* end of sequence */
+ if(!(*func)(stream, i, args))
+ return(0L);
+
+ break;
+ default: /* anything else is a syntax error! */
+ mm_log ("Sequence syntax error",ERROR);
+ return NIL;
+ }
+ }
+
+ return T; /* successfully parsed sequence */
+}
+
+
+long
+imap_seq_exec_append(MAILSTREAM *stream, long int msgno, void *args)
+{
+ char *save_folder, *flags = NULL, date[64];
+ CONTEXT_S *cntxt = NULL;
+ int our_stream = 0;
+ long rv = 0L;
+ MAILSTREAM *save_stream;
+ SE_APP_S *sa = (SE_APP_S *) args;
+ MESSAGECACHE *mc;
+ STORE_S *so;
+
+ save_folder = (strucmp(sa->folder, ps_global->inbox_name) == 0)
+ ? ps_global->VAR_INBOX_PATH : sa->folder;
+
+ save_stream = save_msg_stream(cntxt, save_folder, &our_stream);
+
+ if((so = so_get(CharStar, NULL, WRITE_ACCESS)) != NULL){
+ /* store flags before the fetch so UNSEEN bit isn't flipped */
+ mc = (msgno > 0L && stream && msgno <= stream->nmsgs)
+ ? mail_elt(stream, msgno) : NULL;
+
+ flags = flag_string(stream, msgno, F_ANS|F_FWD|F_FLAG|F_SEEN|F_KEYWORD);
+ if(mc && mc->day)
+ mail_date(date, mc);
+ else
+ *date = '\0';
+
+ rv = save_fetch_append(stream, msgno, NULL,
+ save_stream, save_folder, NULL,
+ mc ? mc->rfc822_size : 0L, flags, date, so);
+ if(flags)
+ fs_give((void **) &flags);
+
+ if(rv < 0 || sp_expunge_count(stream)){
+ cmd_cancelled("Attached message Save");
+ rv = 0L;
+ }
+ /* else whatever broke in save_fetch_append shoulda bitched */
+
+ so_give(&so);
+ }
+ else{
+ dprint((1, "Can't allocate store for save: %s\n",
+ error_description(errno)));
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Problem creating space for message text.");
+ }
+
+ if(our_stream)
+ mail_close(save_stream);
+
+ return(rv);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Get login and password from user for IMAP login
+
+ Args: mb -- The mail box property struct
+ user -- Buffer to return the user name in
+ passwd -- Buffer to return the passwd in
+ trial -- The trial number or number of attempts to login
+ user is at least size NETMAXUSER
+ passwd is apparently at least MAILTMPLEN, but mrc has asked us to
+ use a max size of about 100 instead
+
+ Result: username and password passed back to imap
+ ----*/
+void
+mm_login(NETMBX *mb, char *user, char *pwd, long int trial)
+{
+ mm_login_work(mb, user, pwd, trial, NULL, NULL);
+}
+
+
+/*----------------------------------------------------------------------
+ Exported method to retrieve logged in user name associated with stream
+
+ Args: host -- host to find associated login name with.
+
+ Result:
+ ----*/
+char *
+cached_user_name(char *host)
+{
+ MMLOGIN_S *l;
+ STRLIST_S *h;
+
+ if((l = mm_login_list) && host)
+ do
+ for(h = l->hosts; h; h = h->next)
+ if(!strucmp(host, h->name))
+ return(l->user);
+ while((l = l->next) != NULL);
+
+ return(NULL);
+}
+
+
+int
+imap_same_host(STRLIST_S *hl1, STRLIST_S *hl2)
+{
+ STRLIST_S *lp;
+
+ for( ; hl1; hl1 = hl1->next)
+ for(lp = hl2; lp; lp = lp->next)
+ if(!strucmp(hl1->name, lp->name))
+ return(TRUE);
+
+ return(FALSE);
+}
+
+
+/*
+ * For convenience, we use the same m_list structure (but a different
+ * instance) for storing a list of hosts we've asked the user about when
+ * SSL validation fails. If this function returns TRUE, that means we
+ * have previously asked the user about this host. Ok_novalidate == 1 means
+ * the user said yes, it was ok. Ok_novalidate == 0 means the user
+ * said no. Warned means we warned them already.
+ */
+int
+imap_get_ssl(MMLOGIN_S *m_list, STRLIST_S *hostlist, int *ok_novalidate, int *warned)
+{
+ MMLOGIN_S *l;
+
+ for(l = m_list; l; l = l->next)
+ if(imap_same_host(l->hosts, hostlist)){
+ if(ok_novalidate)
+ *ok_novalidate = l->ok_novalidate;
+
+ if(warned)
+ *warned = l->warned;
+
+ return(TRUE);
+ }
+
+ return(FALSE);
+}
+
+
+/*
+ * Just trying to guess the username the user might want to use on this
+ * host, the user will confirm.
+ */
+char *
+imap_get_user(MMLOGIN_S *m_list, STRLIST_S *hostlist)
+{
+ MMLOGIN_S *l;
+
+ for(l = m_list; l; l = l->next)
+ if(imap_same_host(l->hosts, hostlist))
+ return(l->user);
+
+ return(NULL);
+}
+
+
+/*
+ * If we have a matching hostname, username, and altflag in our cache,
+ * attempt to login with the password from the cache.
+ */
+int
+imap_get_passwd(MMLOGIN_S *m_list, char *passwd, char *user, STRLIST_S *hostlist, int altflag)
+{
+ MMLOGIN_S *l;
+
+ dprint((9,
+ "imap_get_passwd: checking user=%s alt=%d host=%s%s%s\n",
+ user ? user : "(null)",
+ altflag,
+ hostlist->name ? hostlist->name : "",
+ (hostlist->next && hostlist->next->name) ? ", " : "",
+ (hostlist->next && hostlist->next->name) ? hostlist->next->name
+ : ""));
+ for(l = m_list; l; l = l->next)
+ if(imap_same_host(l->hosts, hostlist)
+ && *user
+ && !strcmp(user, l->user)
+ && l->altflag == altflag){
+ if(passwd){
+ strncpy(passwd, l->passwd, NETMAXPASSWD);
+ passwd[NETMAXPASSWD-1] = '\0';
+ }
+ dprint((9, "imap_get_passwd: match\n"));
+ dprint((10, "imap_get_passwd: trying passwd=\"%s\"\n",
+ passwd ? passwd : "?"));
+ return(TRUE);
+ }
+
+ dprint((9, "imap_get_passwd: no match\n"));
+ return(FALSE);
+}
+
+
+
+void
+imap_set_passwd(MMLOGIN_S **l, char *passwd, char *user, STRLIST_S *hostlist,
+ int altflag, int ok_novalidate, int warned)
+{
+ STRLIST_S **listp;
+ size_t len;
+
+ dprint((9, "imap_set_passwd\n"));
+ for(; *l; l = &(*l)->next)
+ if(imap_same_host((*l)->hosts, hostlist)
+ && !strcmp(user, (*l)->user)
+ && altflag == (*l)->altflag){
+ if(strcmp(passwd, (*l)->passwd) ||
+ (*l)->ok_novalidate != ok_novalidate ||
+ (*l)->warned != warned)
+ break;
+ else
+ return;
+ }
+
+ if(!*l){
+ *l = (MMLOGIN_S *)fs_get(sizeof(MMLOGIN_S));
+ memset(*l, 0, sizeof(MMLOGIN_S));
+ }
+
+ len = strlen(passwd);
+ if(!(*l)->passwd || strlen((*l)->passwd) < len)
+ (*l)->passwd = ps_get(len+1);
+
+ strncpy((*l)->passwd, passwd, len+1);
+
+ (*l)->altflag = altflag;
+ (*l)->ok_novalidate = ok_novalidate;
+ (*l)->warned = warned;
+
+ if(!(*l)->user)
+ (*l)->user = cpystr(user);
+
+ dprint((9, "imap_set_passwd: user=%s altflag=%d\n",
+ (*l)->user ? (*l)->user : "?",
+ (*l)->altflag));
+
+ for( ; hostlist; hostlist = hostlist->next){
+ for(listp = &(*l)->hosts;
+ *listp && strucmp((*listp)->name, hostlist->name);
+ listp = &(*listp)->next)
+ ;
+
+ if(!*listp){
+ *listp = new_strlist(hostlist->name);
+ dprint((9, "imap_set_passwd: host=%s\n",
+ (*listp)->name ? (*listp)->name : "?"));
+ }
+ }
+
+ dprint((10, "imap_set_passwd: passwd=\"%s\"\n",
+ passwd ? passwd : "?"));
+}
+
+
+
+void
+imap_flush_passwd_cache(int dumpcache)
+{
+ size_t len;
+ volatile char *p;
+
+ /* equivalent of memset but can't be optimized away */
+ len = sizeof(private_store);
+ p = private_store;
+ while(len-- > 0)
+ *p++ = '\0';
+
+ if(dumpcache){
+ MMLOGIN_S *l;
+
+ while((l = mm_login_list) != NULL){
+ mm_login_list = mm_login_list->next;
+ if(l->user)
+ fs_give((void **) &l->user);
+
+ free_strlist(&l->hosts);
+
+ fs_give((void **) &l);
+ }
+
+ while((l = cert_failure_list) != NULL){
+ cert_failure_list = cert_failure_list->next;
+ if(l->user)
+ fs_give((void **) &l->user);
+
+ free_strlist(&l->hosts);
+
+ fs_give((void **) &l);
+ }
+ }
+}
+
+
+/*
+ * Mimics fs_get except it only works for char * (no alignment hacks), it
+ * stores in a static array so it is easy to zero it out (that's the whole
+ * purpose), allocations always happen at the end (no free).
+ * If we go past array limit, we don't break, we just use free storage.
+ * Should be awfully rare, though.
+ */
+char *
+ps_get(size_t size)
+{
+ static char *last = (char *) private_store;
+ char *block = NULL;
+
+ /* there is enough space */
+ if(size <= sizeof(private_store) - (last - (char *) private_store)){
+ block = last;
+ last += size;
+ }
+ else{
+ dprint((2,
+ "Out of password caching space in private_store\n"));
+ dprint((2,
+ "Using free storage instead\n"));
+ block = fs_get(size);
+ }
+
+ return(block);
+}