diff options
Diffstat (limited to 'pith/stream.c')
-rw-r--r-- | pith/stream.c | 3392 |
1 files changed, 3392 insertions, 0 deletions
diff --git a/pith/stream.c b/pith/stream.c new file mode 100644 index 00000000..f13fd73f --- /dev/null +++ b/pith/stream.c @@ -0,0 +1,3392 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: stream.c 1012 2008-03-26 00:44:22Z 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 + * + * ======================================================================== + */ + +/*====================================================================== + stream.c + Implements the Pine mail stream management routines + and c-client wrapper functions + ====*/ + + +#include "../pith/headers.h" +#include "../pith/stream.h" +#include "../pith/state.h" +#include "../pith/conf.h" +#include "../pith/flag.h" +#include "../pith/msgno.h" +#include "../pith/adrbklib.h" +#include "../pith/status.h" +#include "../pith/newmail.h" +#include "../pith/detach.h" +#include "../pith/folder.h" +#include "../pith/mailcmd.h" +#include "../pith/util.h" +#include "../pith/news.h" +#include "../pith/sequence.h" +#include "../pith/options.h" +#include "../pith/mimedesc.h" + + +void (*pith_opt_closing_stream)(MAILSTREAM *); + + +/* + * Internal prototypes + */ +void reset_stream_view_state(MAILSTREAM *); +void carefully_reset_sp_flags(MAILSTREAM *, unsigned long); +char *partial_text_gets(readfn_t, void *, unsigned long, GETS_DATA *); +void mail_list_internal(MAILSTREAM *, char *, char *); +int recent_activity(MAILSTREAM *); +int hibit_in_searchpgm(SEARCHPGM *); +int hibit_in_strlist(STRINGLIST *); +int hibit_in_header(SEARCHHEADER *); +int hibit_in_sizedtext(SIZEDTEXT *); +int sp_nusepool_notperm(void); +int sp_add(MAILSTREAM *, int); +void sp_delete(MAILSTREAM *); +void sp_free(PER_STREAM_S **); + + +static FETCH_READC_S *g_pft_desc; + + +MAILSTATUS *pine_cached_status; /* implement status for #move folder */ + + + +/* + * Pine wrapper around mail_open. If we have the PREFER_ALT_AUTH flag turned + * on, we need to set the TRYALT flag before trying the open. + * This routine manages the stream pool, too. It tries to re-use existing + * streams instead of opening new ones, or maybe it will leave one open and + * use a new one if that seems to make more sense. Pine_mail_close leaves + * streams open so that they may be re-used. Each pine_mail_open should have + * a matching pine_mail_close (or possible pine_mail_actually_close) somewhere + * that goes with it. + * + * Args: + * stream -- A possible stream for recycling. This isn't usually the + * way recycling happens. Usually it is automatic. + * mailbox -- The mailbox to be opened. + * openflags -- Flags passed here to modify the behavior. + * retflags -- Flags returned from here. SP_MATCH will be lit if that is + * what happened. If SP_MATCH is lit then SP_LOCKED may also + * be lit if the matched stream was already locked when + * we got here. + */ +MAILSTREAM * +pine_mail_open(MAILSTREAM *stream, char *mailbox, long int openflags, long int *retflags) +{ + MAILSTREAM *retstream = NULL; + DRIVER *d; + int permlocked = 0, is_inbox = 0, usepool = 0, tempuse = 0, uf = 0; + unsigned long flags; + char **lock_these, *target = NULL; + static unsigned long streamcounter = 0; + + dprint((7, + "pine_mail_open: opening \"%s\"%s openflags=0x%x %s%s%s%s%s%s%s%s%s (%s)\n", + mailbox ? mailbox : "(NULL)", + stream ? "" : " (stream was NULL)", + openflags, + openflags & OP_HALFOPEN ? " OP_HALFOPEN" : "", + openflags & OP_READONLY ? " OP_READONLY" : "", + openflags & OP_SILENT ? " OP_SILENT" : "", + openflags & OP_DEBUG ? " OP_DEBUG" : "", + openflags & SP_PERMLOCKED ? " SP_PERMLOCKED" : "", + openflags & SP_INBOX ? " SP_INBOX" : "", + openflags & SP_USERFLDR ? " SP_USERFLDR" : "", + openflags & SP_USEPOOL ? " SP_USEPOOL" : "", + openflags & SP_TEMPUSE ? " SP_TEMPUSE" : "", + debug_time(1, ps_global->debug_timestamp))); + + if(retflags) + *retflags = 0L; + + if(ps_global->user_says_cancel){ + dprint((7, "pine_mail_open: cancelled by user\n")); + return(retstream); + } + + is_inbox = openflags & SP_INBOX; + uf = openflags & SP_USERFLDR; + + /* inbox is still special, assume that we want to permlock it */ + permlocked = (is_inbox || openflags & SP_PERMLOCKED) ? 1 : 0; + + /* check to see if user wants this folder permlocked */ + for(lock_these = ps_global->VAR_PERMLOCKED; + uf && !permlocked && lock_these && *lock_these; lock_these++){ + char *p = NULL, *dummy = NULL, *lt, *lname, *mname; + char tmp1[MAILTMPLEN], tmp2[MAILTMPLEN]; + + /* there isn't really a pair, it just dequotes for us */ + get_pair(*lock_these, &dummy, &p, 0, 0); + + /* + * Check to see if this is an incoming nickname and replace it + * with the full name. + */ + if(!(p && ps_global->context_list + && ps_global->context_list->use & CNTXT_INCMNG + && (lt=folder_is_nick(p, FOLDERS(ps_global->context_list), 0)))) + lt = p; + + if(dummy) + fs_give((void **) &dummy); + + if(lt && mailbox + && (same_remote_mailboxes(mailbox, lt) + || + (!IS_REMOTE(mailbox) + && (lname=mailboxfile(tmp1, lt)) + && (mname=mailboxfile(tmp2, mailbox)) + && !strcmp(lname, mname)))) + permlocked++; + + if(p) + fs_give((void **) &p); + } + + /* + * Only cache if remote, not nntp, not pop, and caller asked us to. + * It might make sense to do some caching for nntp and pop, as well, but + * we aren't doing it right now. For example, an nntp stream open to + * one group could be reused for another group. An open pop stream could + * be used for mail_copy_full. + * + * An implication of doing only imap here is that sp_stream_get will only + * be concerned with imap streams. + */ + if((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap")){ + usepool = openflags & SP_USEPOOL; + tempuse = openflags & SP_TEMPUSE; + } + else{ + if(IS_REMOTE(mailbox)){ + dprint((9, "pine_mail_open: not cacheable: %s\n", !d ? "no driver?" : d->name ? d->name : "?" )); + } + else{ + if(permlocked || (openflags & OP_READONLY)){ + /* + * This is a strange case. We want to allow stay-open local + * folders, but they don't fit into the rest of the framework + * well. So we'll look for it being already open in this case + * and special-case it (the already_open_stream() case + * below). + */ + dprint((9, + "pine_mail_open: not cacheable: not remote, but check for local stream\n")); + } + else{ + dprint((9, + "pine_mail_open: not cacheable: not remote\n")); + } + } + } + + /* If driver doesn't support halfopen, just open it. */ + if(d && (openflags & OP_HALFOPEN) && !(d->flags & DR_HALFOPEN)){ + openflags &= ~OP_HALFOPEN; + dprint((9, + "pine_mail_open: turning off OP_HALFOPEN flag\n")); + } + + /* + * Some of the flags are pine's, the rest are meant for mail_open. + * We've noted the pine flags, now remove them before we call mail_open. + */ + openflags &= ~(SP_USEPOOL | SP_TEMPUSE | SP_INBOX + | SP_PERMLOCKED | SP_USERFLDR); + +#ifdef DEBUG + if(ps_global->debug_imap > 3 || ps_global->debugmem) + openflags |= OP_DEBUG; +#endif + + if(F_ON(F_PREFER_ALT_AUTH, ps_global)){ + if((d = mail_valid (NIL, mailbox, (char *) NIL)) + && !strcmp(d->name, "imap")) + openflags |= OP_TRYALT; + } + + if(F_ON(F_ENABLE_MULNEWSRCS, ps_global)){ + char source[MAILTMPLEN]; + if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){ + DRIVER *d; + if((d = mail_valid(NIL, source, (char *) NIL)) + && (!strcmp(d->name, "news") + || !strcmp(d->name, "nntp"))) + openflags |= OP_MULNEWSRC; + } + else if((d = mail_valid(NIL, mailbox, (char *) NIL)) + && !strcmp(d->name, "nntp")) + openflags |= OP_MULNEWSRC; + } + + /* + * One of the problems is that the new-style stream caching (the + * sp_stream_get stuff) may conflict with some of the old-style caching + * (the passed in argument stream) that is still in the code. We should + * probably eliminate the old-style caching, but some of it is still useful, + * especially if it deals with something other than IMAP. We want to prevent + * mistakes caused by conflicts between the two styles. In particular, we + * don't want to have a new-style cached stream re-opened because of the + * old-style caching code. This can happen if a stream is passed in that + * is not useable, and then a new stream is opened because the passed in + * stream causes us to bypass the new caching code. Play it safe. If it + * is an IMAP stream, just close it. This should leave it in the new-style + * cache anyway, causing no loss. Maybe not if the cache wasn't large + * enough to have it in there in the first place, in which case we get + * a possibly unnecessary close and open. If it isn't IMAP we still have + * to worry about it because it will cause us to bypass the caching code. + * So if the stream isn't IMAP but the mailbox we're opening is, close it. + * The immediate alternative would be to try to emulate the code in + * mail_open that checks whether it is re-usable or not, but that is + * dangerous if that code changes on us. + */ + if(stream){ + if(is_imap_stream(stream) + || ((d = mail_valid (NIL, mailbox, (char *) NIL)) + && !strcmp(d->name, "imap"))){ + if(is_imap_stream(stream)){ + dprint((7, + "pine_mail_open: closing passed in IMAP stream %s\n", + stream->mailbox ? stream->mailbox : "?")); + } + else{ + dprint((7, + "pine_mail_open: closing passed in non-IMAP stream %s\n", + stream->mailbox ? stream->mailbox : "?")); + } + + pine_mail_close(stream); + stream = NULL; + } + } + + /* + * Maildrops are special. The mailbox name will be a #move name. If the + * target of the maildrop is an IMAP folder we want to be sure it isn't + * already open in another cached stream, to avoid double opens. This + * could have happened if the user opened it manually as the target + * instead of as a maildrop. It could also be a side-effect of marking + * an answered flag after a reply. + */ + target = NULL; + if(check_for_move_mbox(mailbox, NULL, 0, &target)){ + MAILSTREAM *targetstream = NULL; + + if((d = mail_valid (NIL, target, (char *) NIL)) && !strcmp(d->name, "imap")){ + targetstream = sp_stream_get(target, SP_MATCH | SP_RO_OK); + if(targetstream){ + dprint((9, "pine_mail_open: close previously opened target of maildrop\n")); + pine_mail_actually_close(targetstream); + } + } + } + + if((usepool && !stream && permlocked) + || (!usepool && (permlocked || (openflags & OP_READONLY)) + && (retstream = already_open_stream(mailbox, AOS_NONE)))){ + if(retstream) + stream = retstream; + else + stream = sp_stream_get(mailbox, + SP_MATCH | ((openflags & OP_READONLY) ? SP_RO_OK : 0)); + if(stream){ + flags = SP_LOCKED + | (usepool ? SP_USEPOOL : 0) + | (permlocked ? SP_PERMLOCKED : 0) + | (is_inbox ? SP_INBOX : 0) + | (uf ? SP_USERFLDR : 0) + | (tempuse ? SP_TEMPUSE : 0); + + /* + * If the stream wasn't already locked, then we reset it so it + * looks like we are reopening it. We have to worry about recent + * messages since they will still be recent, if that affects us. + */ + if(!(sp_flags(stream) & SP_LOCKED)) + reset_stream_view_state(stream); + + if(retflags){ + *retflags |= SP_MATCH; + if(sp_flags(stream) & SP_LOCKED) + *retflags |= SP_LOCKED; + } + + if(sp_flags(stream) & SP_LOCKED + && sp_flags(stream) & SP_USERFLDR + && !(flags & SP_USERFLDR)){ + sp_set_ref_cnt(stream, sp_ref_cnt(stream)+1); + dprint((7, + "pine_mail_open: permlocked: ref cnt up to %d\n", + sp_ref_cnt(stream))); + } + else if(sp_ref_cnt(stream) <= 0){ + sp_set_ref_cnt(stream, 1); + dprint((7, + "pine_mail_open: permexact: ref cnt set to %d\n", + sp_ref_cnt(stream))); + } + + carefully_reset_sp_flags(stream, flags); + + if(stream->silent && !(openflags & OP_SILENT)) + stream->silent = NIL; + + dprint((9, "pine_mail_open: stream was already open\n")); + if(stream && stream->dtb && stream->dtb->name + && !strcmp(stream->dtb->name, "imap")){ + dprint((7, "pine_mail_open: next TAG %08lx\n", + stream->gensym)); + } + + return(stream); + } + } + + if(usepool && !stream){ + /* + * First, we look for an exact match, a stream which is already + * open to the mailbox we are trying to re-open, and we use that. + * Skip permlocked only because we did it above already. + */ + if(!permlocked) + stream = sp_stream_get(mailbox, + SP_MATCH | ((openflags & OP_READONLY) ? SP_RO_OK : 0)); + + if(stream){ + flags = SP_LOCKED + | (usepool ? SP_USEPOOL : 0) + | (permlocked ? SP_PERMLOCKED : 0) + | (is_inbox ? SP_INBOX : 0) + | (uf ? SP_USERFLDR : 0) + | (tempuse ? SP_TEMPUSE : 0); + + /* + * If the stream wasn't already locked, then we reset it so it + * looks like we are reopening it. We have to worry about recent + * messages since they will still be recent, if that affects us. + */ + if(!(sp_flags(stream) & SP_LOCKED)) + reset_stream_view_state(stream); + + if(retflags){ + *retflags |= SP_MATCH; + if(sp_flags(stream) & SP_LOCKED) + *retflags |= SP_LOCKED; + } + + if(sp_flags(stream) & SP_LOCKED + && sp_flags(stream) & SP_USERFLDR + && !(flags & SP_USERFLDR)){ + sp_set_ref_cnt(stream, sp_ref_cnt(stream)+1); + dprint((7, + "pine_mail_open: matched: ref cnt up to %d\n", + sp_ref_cnt(stream))); + } + else if(sp_ref_cnt(stream) <= 0){ + sp_set_ref_cnt(stream, 1); + dprint((7, + "pine_mail_open: exact: ref cnt set to %d\n", + sp_ref_cnt(stream))); + } + + carefully_reset_sp_flags(stream, flags); + + /* + * We may be re-using a stream that was previously open + * with OP_SILENT and now we don't want OP_SILENT. + * We don't turn !silent into silent because the !silentness + * could be important in the original context (for example, + * silent suppresses mm_expunged calls). + * + * WARNING: we're messing with c-client internals. + */ + if(stream->silent && !(openflags & OP_SILENT)) + stream->silent = NIL; + + dprint((9, "pine_mail_open: stream already open\n")); + if(stream && stream->dtb && stream->dtb->name + && !strcmp(stream->dtb->name, "imap")){ + dprint((7, "pine_mail_open: next TAG %08lx\n", + stream->gensym)); + } + + return(stream); + } + + /* + * No exact match, look for a stream which is open to the same + * server and marked for TEMPUSE. + */ + stream = sp_stream_get(mailbox, SP_SAME | SP_TEMPUSE); + if(stream){ + dprint((9, + "pine_mail_open: attempting to re-use TEMP stream\n")); + } + /* + * No SAME/TEMPUSE stream so we look to see if there is an + * open slot available (we're not yet at max_remstream). If there + * is an open slot, we'll just open a new stream and put it there. + * If not, we'll go inside this conditional. + */ + else if(!permlocked + && sp_nusepool_notperm() >= ps_global->s_pool.max_remstream){ + dprint((9, + "pine_mail_open: no empty slots\n")); + /* + * No empty remote slots available. See if there is a + * TEMPUSE stream that is open but to the wrong server. + */ + stream = sp_stream_get(mailbox, SP_TEMPUSE); + if(stream){ + /* + * We will close this stream and use the empty slot + * that that creates. + */ + dprint((9, + "pine_mail_open: close a TEMPUSE stream and re-use that slot\n")); + pine_mail_actually_close(stream); + stream = NULL; + } + else{ + + /* + * Still no luck. Look for a stream open to the same + * server that is just not locked. This would be a + * stream that might be reusable in the future, but we + * need it now instead. + */ + stream = sp_stream_get(mailbox, SP_SAME | SP_UNLOCKED); + if(stream){ + dprint((9, + "pine_mail_open: attempting to re-use stream\n")); + } + else{ + /* + * We'll take any UNLOCKED stream and re-use it. + */ + stream = sp_stream_get(mailbox, 0); + if(stream){ + /* + * We will close this stream and use the empty slot + * that that creates. + */ + dprint((9, + "pine_mail_open: close an UNLOCKED stream and re-use the slot\n")); + pine_mail_actually_close(stream); + stream = NULL; + } + else{ + if(ps_global->s_pool.max_remstream){ + dprint((9, "pine_mail_open: all USEPOOL slots full of LOCKED streams, nothing to use\n")); + } + else{ + dprint((9, "pine_mail_open: no caching, max_remstream == 0\n")); + } + + usepool = 0; + tempuse = 0; + if(permlocked){ + permlocked = 0; + dprint((2, + "pine_mail_open5: Can't mark folder Stay-Open: at max-remote limit\n")); + q_status_message1(SM_ORDER, 3, 5, + "Can't mark folder Stay-Open: reached max-remote limit (%s)", + comatose((long) ps_global->s_pool.max_remstream)); + } + } + } + } + } + else{ + dprint((9, + "pine_mail_open: there is an empty slot to use\n")); + } + + /* + * We'll make an assumption here. If we were asked to halfopen a + * stream then we'll assume that the caller doesn't really care if + * the stream is halfopen or if it happens to be open to some mailbox + * already. They are just saying halfopen because they don't need to + * SELECT a mailbox. That's the assumption, anyway. + */ + if(openflags & OP_HALFOPEN && stream){ + dprint((9, + "pine_mail_open: asked for HALFOPEN so returning stream as is\n")); + sp_set_ref_cnt(stream, sp_ref_cnt(stream)+1); + dprint((7, + "pine_mail_open: halfopen: ref cnt up to %d\n", + sp_ref_cnt(stream))); + if(stream && stream->dtb && stream->dtb->name + && !strcmp(stream->dtb->name, "imap")){ + dprint((7, "pine_mail_open: next TAG %08lx\n", + stream->gensym)); + } + + if(stream->silent && !(openflags & OP_SILENT)) + stream->silent = NIL; + + return(stream); + } + + /* + * We're going to SELECT another folder with this open stream. + */ + if(stream){ + /* + * We will have just pinged the stream to make sure it is + * still alive. That ping may have discovered some new mail. + * Before unselecting the folder, we should process the filters + * for that new mail. + */ + if(!sp_flagged(stream, SP_LOCKED) + && !sp_flagged(stream, SP_USERFLDR) + && sp_new_mail_count(stream)) + process_filter_patterns(stream, sp_msgmap(stream), + sp_new_mail_count(stream)); + + if(stream && stream->dtb && stream->dtb->name + && !strcmp(stream->dtb->name, "imap")){ + dprint((7, + "pine_mail_open: cancel idle timer: TAG %08lx (%s)\n", + stream->gensym, debug_time(1, ps_global->debug_timestamp))); + } + + /* + * We need to reset the counters and everything. + * The easiest way to do that is just to delete all of the + * sp_s data and let the open fill it in correctly for + * the new folder. + */ + sp_free((PER_STREAM_S **) &stream->sparep); + } + } + + /* + * When we pass a stream to mail_open, it will either re-use it or + * close it. + */ + retstream = mail_open(stream, mailbox, openflags); + + if(retstream){ + + dprint((7, "pine_mail_open: mail_open returns stream:\n original_mailbox=%s\n mailbox=%s\n driver=%s rdonly=%d halfopen=%d secure=%d nmsgs=%ld recent=%ld\n", retstream->original_mailbox ? retstream->original_mailbox : "?", retstream->mailbox ? retstream->mailbox : "?", (retstream->dtb && retstream->dtb->name) ? retstream->dtb->name : "?", retstream->rdonly, retstream->halfopen, retstream->secure, retstream->nmsgs, retstream->recent)); + + /* + * So it is easier to figure out which command goes with which + * stream when debugging, change the tag associated with each stream. + * Of course, this will come after the SELECT, so the startup IMAP + * commands will have confusing tags. + */ + if(retstream != stream && retstream->dtb && retstream->dtb->name + && !strcmp(retstream->dtb->name, "imap")){ + retstream->gensym += (streamcounter * 0x1000000); + streamcounter = (streamcounter + 1) % (8 * 16); + } + + if(retstream && retstream->dtb && retstream->dtb->name + && !strcmp(retstream->dtb->name, "imap")){ + dprint((7, "pine_mail_open: next TAG %08lx\n", + retstream->gensym)); + } + + /* + * Catch the case where our test up above (where usepool was set) + * did not notice that this was news, but that we can tell once + * we've opened the stream. (One such case would be an imap proxy + * to an nntp server.) Remove it from being cached here. There was + * a possible penalty for not noticing sooner. If all the usepool + * slots were full, we will have closed one of the UNLOCKED streams + * above, preventing us from future re-use of that stream. + * We could figure out how to do the test better with just the + * name. We could open the stream and then close the other one + * after the fact. Or we could just not worry about it since it is + * rare. + */ + if(IS_NEWS(retstream)){ + usepool = 0; + tempuse = 0; + } + + /* make sure the message map has been instantiated */ + (void) sp_msgmap(retstream); + + /* + * If a referral during the mail_open above causes a stream + * re-use which involves a BYE on an IMAP stream, then that + * BYE will have caused an mm_notify which will have + * instantiated the sp_data and set dead_stream! Trust that + * it is alive up to here and reset it to zero. + */ + sp_set_dead_stream(retstream, 0); + + flags = SP_LOCKED + | (usepool ? SP_USEPOOL : 0) + | (permlocked ? SP_PERMLOCKED : 0) + | (is_inbox ? SP_INBOX : 0) + | (uf ? SP_USERFLDR : 0) + | (tempuse ? SP_TEMPUSE : 0); + + sp_flag(retstream, flags); + sp_set_recent_since_visited(retstream, retstream->recent); + + /* initialize the reference count */ + sp_set_ref_cnt(retstream, 1); + dprint((7, "pine_mail_open: reset: ref cnt set to %d\n", + sp_ref_cnt(retstream))); + + if(sp_add(retstream, usepool) != 0 && usepool){ + usepool = 0; + tempuse = 0; + flags = SP_LOCKED + | (usepool ? SP_USEPOOL : 0) + | (permlocked ? SP_PERMLOCKED : 0) + | (is_inbox ? SP_INBOX : 0) + | (uf ? SP_USERFLDR : 0) + | (tempuse ? SP_TEMPUSE : 0); + + sp_flag(retstream, flags); + (void) sp_add(retstream, usepool); + } + + + /* + * When opening a newsgroup, c-client marks the messages up to the + * last Deleted as Unseen. If the feature news-approximates-new-status + * is on, we'd rather they be treated as Seen. That way, selecting New + * messages will give us the ones past the last Deleted. So we're going + * to change them to Seen. Since Seen is a session flag for news, making + * this change won't have any permanent effect. C-client also marks the + * messages after the last deleted Recent, which is the bit of + * information we'll use to find the messages we want to change. + */ + if(F_ON(F_FAKE_NEW_IN_NEWS, ps_global) && + retstream->nmsgs > 0 && IS_NEWS(retstream)){ + char *seq; + long i, mflags = ST_SET; + MESSAGECACHE *mc; + + /* + * Search for !recent messages to set the searched bit for + * those messages we want to change. Then we'll flip the bits. + */ + (void)count_flagged(retstream, F_UNRECENT); + + for(i = 1L; i <= retstream->nmsgs; i++) + if((mc = mail_elt(retstream,i)) && mc->searched) + mc->sequence = 1; + else + mc->sequence = 0; + + if(!is_imap_stream(retstream)) + mflags |= ST_SILENT; + if((seq = build_sequence(retstream, NULL, NULL)) != NULL){ + mail_flag(retstream, seq, "\\SEEN", mflags); + fs_give((void **)&seq); + } + } + } + + return(retstream); +} + + +void +reset_stream_view_state(MAILSTREAM *stream) +{ + MSGNO_S *mm; + + if(!stream) + return; + + mm = sp_msgmap(stream); + + if(!mm) + return; + + sp_set_viewing_a_thread(stream, 0); + sp_set_need_to_rethread(stream, 0); + + mn_reset_cur(mm, stream->nmsgs > 0L ? stream->nmsgs : 0L); /* default */ + + mm->visible_threads = -1L; + mm->top = 0L; + mm->max_thrdno = 0L; + mm->top_after_thrd = 0L; + + mn_set_mansort(mm, 0); + + /* + * Get rid of zooming and selections, but leave filtering flags. All the + * flag counts and everything should still be correct because set_lflag + * preserves them correctly. + */ + if(any_lflagged(mm, MN_SLCT | MN_HIDE)){ + long i; + + for(i = 1L; i <= mn_get_total(mm); i++) + set_lflag(stream, mm, i, MN_SLCT | MN_HIDE, 0); + } + + /* + * We could try to set up a default sort order, but the caller is going + * to re-sort anyway if they are interested in sorting. So we won't do + * it here. + */ +} + + +/* + * We have to be careful when we change the flags of an already + * open stream, because there may be more than one section of + * code actively using the stream. + * We allow turning on (but not off) SP_LOCKED + * SP_PERMLOCKED + * SP_USERFLDR + * SP_INBOX + * We allow turning off (but not on) SP_TEMPUSE + */ +void +carefully_reset_sp_flags(MAILSTREAM *stream, long unsigned int flags) +{ + if(sp_flags(stream) != flags){ + /* allow turning on but not off */ + if(sp_flags(stream) & SP_LOCKED && !(flags & SP_LOCKED)) + flags |= SP_LOCKED; + + if(sp_flags(stream) & SP_PERMLOCKED && !(flags & SP_PERMLOCKED)) + flags |= SP_PERMLOCKED; + + if(sp_flags(stream) & SP_USERFLDR && !(flags & SP_USERFLDR)) + flags |= SP_USERFLDR; + + if(sp_flags(stream) & SP_INBOX && !(flags & SP_INBOX)) + flags |= SP_INBOX; + + + /* allow turning off but not on */ + if(!(sp_flags(stream) & SP_TEMPUSE) && flags & SP_TEMPUSE) + flags &= ~SP_TEMPUSE; + + + /* leave the way they already are */ + if((sp_flags(stream) & SP_FILTERED) != (flags & SP_FILTERED)) + flags = (flags & ~SP_FILTERED) | (sp_flags(stream) & SP_FILTERED); + + + if(sp_flags(stream) != flags) + sp_flag(stream, flags); + } +} + + +/* + * Pine wrapper around mail_create. If we have the PREFER_ALT_AUTH flag turned + * on we don't want to pass a NULL stream to c-client because it will open + * a non-ssl connection when we want it to be ssl. + */ +long +pine_mail_create(MAILSTREAM *stream, char *mailbox) +{ + MAILSTREAM *ourstream = NULL; + long return_val; + long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE); + char source[MAILTMPLEN], *target = NULL; + DRIVER *d; + + dprint((7, "pine_mail_create: creating \"%s\"%s\n", + mailbox ? mailbox : "(NULL)", + stream ? "" : " (stream was NULL)")); + + if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){ + mailbox = target; + dprint((7, + "pine_mail_create: #move special case, creating \"%s\"\n", + mailbox ? mailbox : "(NULL)")); + } + + /* + * We don't really need this anymore, since we are now using IMAPTRYALT. + * We'll leave it since it works. + */ + if((F_ON(F_PREFER_ALT_AUTH, ps_global) + || (ps_global->debug_imap > 3 || ps_global->debugmem))){ + + if((d = mail_valid (NIL, mailbox, (char *) NIL)) + && !strcmp(d->name, "imap")){ + + if(F_ON(F_PREFER_ALT_AUTH, ps_global)) + openflags |= OP_TRYALT; + } + } + + if(!stream) + stream = sp_stream_get(mailbox, SP_MATCH); + if(!stream) + stream = sp_stream_get(mailbox, SP_SAME); + + if(!stream){ + /* + * It is only useful to open a stream in the imap case. + */ + if((d = mail_valid (NIL, mailbox, (char *) NIL)) + && !strcmp(d->name, "imap")){ + + stream = pine_mail_open(NULL, mailbox, openflags, NULL); + ourstream = stream; + } + } + + return_val = mail_create(stream, mailbox); + + if(ourstream) + pine_mail_close(ourstream); + + return(return_val); +} + + +/* + * Pine wrapper around mail_delete. + */ +long +pine_mail_delete(MAILSTREAM *stream, char *mailbox) +{ + MAILSTREAM *ourstream = NULL; + long return_val; + long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE); + char source[MAILTMPLEN], *target = NULL; + DRIVER *d; + + dprint((7, "pine_mail_delete: deleting \"%s\"%s\n", + mailbox ? mailbox : "(NULL)", + stream ? "" : " (stream was NULL)")); + + if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){ + mailbox = target; + dprint((7, + "pine_mail_delete: #move special case, deleting \"%s\"\n", + mailbox ? mailbox : "(NULL)")); + } + + /* + * We don't really need this anymore, since we are now using IMAPTRYALT. + */ + if((F_ON(F_PREFER_ALT_AUTH, ps_global) + || (ps_global->debug_imap > 3 || ps_global->debugmem))){ + + if((d = mail_valid (NIL, mailbox, (char *) NIL)) + && !strcmp(d->name, "imap")){ + + if(F_ON(F_PREFER_ALT_AUTH, ps_global)) + openflags |= OP_TRYALT; + } + } + + /* oops, we seem to be deleting a selected stream */ + if(!stream && (stream = sp_stream_get(mailbox, SP_MATCH))){ + pine_mail_actually_close(stream); + stream = NULL; + } + + if(!stream) + stream = sp_stream_get(mailbox, SP_SAME); + + if(!stream){ + /* + * It is only useful to open a stream in the imap case. + */ + if((d = mail_valid (NIL, mailbox, (char *) NIL)) + && !strcmp(d->name, "imap")){ + + stream = pine_mail_open(NULL, mailbox, openflags, NULL); + ourstream = stream; + } + } + + return_val = mail_delete(stream, mailbox); + + if(ourstream) + pine_mail_close(ourstream); + + return(return_val); +} + + +/* + * Pine wrapper around mail_append. + */ +long +pine_mail_append_full(MAILSTREAM *stream, char *mailbox, char *flags, char *date, STRING *message) +{ + MAILSTREAM *ourstream = NULL; + long return_val; + long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE); + char source[MAILTMPLEN], *target = NULL; + char mailbox_nodelim[MAILTMPLEN]; + int delim; + DRIVER *d; + + dprint((7, "pine_mail_append_full: appending to \"%s\"%s\n", + mailbox ? mailbox : "(NULL)", + stream ? "" : " (stream was NULL)")); + + /* strip delimiter, it has no meaning in an APPEND and could cause trouble */ + if(mailbox && (delim = get_folder_delimiter(mailbox)) != '\0'){ + size_t len; + + len = strlen(mailbox); + if(mailbox[len-1] == delim){ + strncpy(mailbox_nodelim, mailbox, MIN(len-1,sizeof(mailbox_nodelim)-1)); + mailbox_nodelim[MIN(len-1,sizeof(mailbox_nodelim)-1)] = '\0'; + mailbox = mailbox_nodelim; + } + } + + if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){ + mailbox = target; + dprint((7, + "pine_mail_append_full: #move special case, appending to \"%s\"\n", + mailbox ? mailbox : "(NULL)")); + } + + /* + * We don't really need this anymore, since we are now using IMAPTRYALT. + */ + if((F_ON(F_PREFER_ALT_AUTH, ps_global) + || (ps_global->debug_imap > 3 || ps_global->debugmem))){ + + if((d = mail_valid (NIL, mailbox, (char *) NIL)) + && !strcmp(d->name, "imap")){ + + if(F_ON(F_PREFER_ALT_AUTH, ps_global)) + openflags |= OP_TRYALT; + } + } + + if(!stream) + stream = sp_stream_get(mailbox, SP_MATCH); + if(!stream) + stream = sp_stream_get(mailbox, SP_SAME); + + if(!stream){ + /* + * It is only useful to open a stream in the imap case. + */ + if((d = mail_valid (NIL, mailbox, (char *) NIL)) + && !strcmp(d->name, "imap")){ + + stream = pine_mail_open(NULL, mailbox, openflags, NULL); + ourstream = stream; + } + } + + return_val = mail_append_full(stream, mailbox, flags, date, message); + + if(ourstream) + pine_mail_close(ourstream); + + return(return_val); +} + + +/* + * Pine wrapper around mail_append. + */ +long +pine_mail_append_multiple(MAILSTREAM *stream, char *mailbox, append_t af, + APPENDPACKAGE *data, MAILSTREAM *not_this_stream) +{ + MAILSTREAM *ourstream = NULL; + long return_val; + long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE); + char source[MAILTMPLEN], *target = NULL; + DRIVER *d; + int we_blocked_reuse = 0; + + dprint((7, "pine_mail_append_multiple: appending to \"%s\"%s\n", + mailbox ? mailbox : "(NULL)", + stream ? "" : " (stream was NULL)")); + + if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){ + mailbox = target; + dprint((7, + "pine_mail_append_multiple: #move special case, appending to \"%s\"\n", + mailbox ? mailbox : "(NULL)")); + } + + if((F_ON(F_PREFER_ALT_AUTH, ps_global) + || (ps_global->debug_imap > 3 || ps_global->debugmem))){ + + if((d = mail_valid (NIL, mailbox, (char *) NIL)) + && !strcmp(d->name, "imap")){ + + if(F_ON(F_PREFER_ALT_AUTH, ps_global)) + openflags |= OP_TRYALT; + } + } + + /* + * We have to be careful re-using streams for multiappend, because of + * the way it works. We call into c-client below but part of the call + * is data containing a callback function to us to supply the data to + * be appended. That function may need to get the data from the server. + * If that uses the same stream as we're trying to append on, we're + * in trouble. We can't call back into c-client from c-client on the same + * stream. (Just think about it, we're in the middle of an APPEND command. + * We can't issue a FETCH before the APPEND completes in order to complete + * the APPEND.) We can re-use a stream if it is a different stream from + * the one we are reading from, so that's what the not_this_stream + * stuff is for. If we mark it !SP_USEPOOL, it won't get reused. + */ + if(sp_flagged(not_this_stream, SP_USEPOOL)){ + we_blocked_reuse++; + sp_unflag(not_this_stream, SP_USEPOOL); + } + + if(!stream) + stream = sp_stream_get(mailbox, SP_MATCH); + if(!stream) + stream = sp_stream_get(mailbox, SP_SAME); + + if(!stream){ + /* + * It is only useful to open a stream in the imap case. + */ + if((d = mail_valid (NIL, mailbox, (char *) NIL)) + && !strcmp(d->name, "imap")){ + + stream = pine_mail_open(NULL, mailbox, openflags, NULL); + ourstream = stream; + } + } + + if(we_blocked_reuse) + sp_set_flags(not_this_stream, sp_flags(not_this_stream) | SP_USEPOOL); + + return_val = mail_append_multiple(stream, mailbox, af, (void *) data); + + if(ourstream) + pine_mail_close(ourstream); + + return(return_val); +} + + +/* + * Pine wrapper around mail_copy. + */ +long +pine_mail_copy_full(MAILSTREAM *stream, char *sequence, char *mailbox, long int options) +{ + MAILSTREAM *ourstream = NULL; + long return_val; + long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE); + char source[MAILTMPLEN], *target = NULL; + char mailbox_nodelim[MAILTMPLEN]; + int delim; + DRIVER *d; + + dprint((7, "pine_mail_copy_full: copying to \"%s\"%s\n", + mailbox ? mailbox : "(NULL)", + stream ? "" : " (stream was NULL)")); + + /* strip delimiter, it has no meaning in a COPY and could cause trouble */ + if(mailbox && (delim = get_folder_delimiter(mailbox)) != '\0'){ + size_t len; + + len = strlen(mailbox); + if(mailbox[len-1] == delim){ + strncpy(mailbox_nodelim, mailbox, MIN(len-1,sizeof(mailbox_nodelim)-1)); + mailbox_nodelim[MIN(len-1,sizeof(mailbox_nodelim)-1)] = '\0'; + mailbox = mailbox_nodelim; + } + } + + if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){ + mailbox = target; + dprint((7, + "pine_mail_copy_full: #move special case, copying to \"%s\"\n", + mailbox ? mailbox : "(NULL)")); + } + + /* + * We don't really need this anymore, since we are now using IMAPTRYALT. + */ + if((F_ON(F_PREFER_ALT_AUTH, ps_global) + || (ps_global->debug_imap > 3 || ps_global->debugmem))){ + + if((d = mail_valid (NIL, mailbox, (char *) NIL)) + && !strcmp(d->name, "imap")){ + + if(F_ON(F_PREFER_ALT_AUTH, ps_global)) + openflags |= OP_TRYALT; + } + } + + if(!stream) + stream = sp_stream_get(mailbox, SP_MATCH); + if(!stream) + stream = sp_stream_get(mailbox, SP_SAME); + + if(!stream){ + /* + * It is only useful to open a stream in the imap case. + * Actually, mail_copy_full is the case where it might also be + * useful to provide a stream in the nntp and pop3 cases. If we + * cache such streams, then we will probably want to open one + * here so that it gets cached. + */ + if((d = mail_valid (NIL, mailbox, (char *) NIL)) + && !strcmp(d->name, "imap")){ + + stream = pine_mail_open(NULL, mailbox, openflags, NULL); + ourstream = stream; + } + } + + return_val = mail_copy_full(stream, sequence, mailbox, options); + + if(ourstream) + pine_mail_close(ourstream); + + return(return_val); +} + + +/* + * Pine wrapper around mail_rename. + */ +long +pine_mail_rename(MAILSTREAM *stream, char *old, char *new) +{ + MAILSTREAM *ourstream = NULL; + long return_val; + long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE); + DRIVER *d; + + dprint((7, "pine_mail_rename(%s,%s)\n", old ? old : "", + new ? new : "")); + + /* + * We don't really need this anymore, since we are now using IMAPTRYALT. + */ + if((F_ON(F_PREFER_ALT_AUTH, ps_global) + || (ps_global->debug_imap > 3 || ps_global->debugmem))){ + + if((d = mail_valid (NIL, old, (char *) NIL)) + && !strcmp(d->name, "imap")){ + + if(F_ON(F_PREFER_ALT_AUTH, ps_global)) + openflags |= OP_TRYALT; + } + } + + /* oops, we seem to be renaming a selected stream */ + if(!stream && (stream = sp_stream_get(old, SP_MATCH))){ + pine_mail_actually_close(stream); + stream = NULL; + } + + if(!stream) + stream = sp_stream_get(old, SP_SAME); + + if(!stream){ + /* + * It is only useful to open a stream in the imap case. + */ + if((d = mail_valid (NIL, old, (char *) NIL)) + && !strcmp(d->name, "imap")){ + + stream = pine_mail_open(NULL, old, openflags, NULL); + ourstream = stream; + } + } + + return_val = mail_rename(stream, old, new); + + if(ourstream) + pine_mail_close(ourstream); + + return(return_val); +} + + +/*---------------------------------------------------------------------- + Our mail_close wrapper to clean up anything on the mailstream we may have + added to it. mostly in the unused bits of the elt's. + ----*/ +void +pine_mail_close(MAILSTREAM *stream) +{ + unsigned long uid_last, last_uid; + int refcnt; + + if(!stream) + return; + + dprint((7, "pine_mail_close: %s (%s)\n", + stream && stream->mailbox ? stream->mailbox : "(NULL)", + debug_time(1, ps_global->debug_timestamp))); + + if(sp_flagged(stream, SP_USEPOOL) && !sp_dead_stream(stream)){ + + refcnt = sp_ref_cnt(stream); + dprint((7, "pine_mail_close: ref cnt is %d\n", refcnt)); + + /* + * Instead of checkpointing here, which takes time that the user + * definitely notices, we checkpoint in new_mail at the next + * opportune time, hopefully when the user is idle. + */ +#if 0 + if(sp_flagged(stream, SP_LOCKED) && sp_flagged(stream, SP_USERFLDR) + && !stream->halfopen && refcnt <= 1){ + if(changes_to_checkpoint(stream)) + pine_mail_check(stream); + else{ + dprint((7, + "pine_mail_close: dont think we need to checkpoint\n")); + } + } +#endif + + /* + * Uid_last is valid when we first open a stream, but not always + * valid after that. So if we know the last uid should be higher + * than uid_last (!#%) use that instead. + */ + uid_last = stream->uid_last; + if(stream->nmsgs > 0L + && (last_uid=mail_uid(stream,stream->nmsgs)) > uid_last) + uid_last = last_uid; + + sp_set_saved_uid_validity(stream, stream->uid_validity); + sp_set_saved_uid_last(stream, uid_last); + + /* + * If the reference count is down to 0, unlock it. + * In any case, don't actually do any real closing. + */ + if(refcnt > 0) + sp_set_ref_cnt(stream, refcnt-1); + + refcnt = sp_ref_cnt(stream); + dprint((7, "pine_mail_close: ref cnt is %d\n", refcnt)); + if(refcnt <= 0){ + dprint((7, + "pine_mail_close: unlocking: start idle timer: TAG %08lx (%s)\n", + stream->gensym, debug_time(1, ps_global->debug_timestamp))); + sp_set_last_use_time(stream, time(0)); + + /* + * Logically, we ought to be unflagging SP_INBOX, too. However, + * the filtering code uses SP_INBOX when deciding if it should + * filter some things, and we keep filtering after the mailbox + * is closed. So leave SP_INBOX alone. This (the closing of INBOX) + * usually only happens in goodnight_gracey when we're + * shutting everything down. + */ + sp_unflag(stream, SP_LOCKED | SP_PERMLOCKED | SP_USERFLDR); + } + else{ + dprint((7, "pine_mail_close: ref cnt is now %d\n", + refcnt)); + } + } + else{ + dprint((7, "pine_mail_close: %s\n", + sp_flagged(stream, SP_USEPOOL) ? "dead stream" : "no pool")); + + refcnt = sp_ref_cnt(stream); + dprint((7, "pine_mail_close: ref cnt is %d\n", refcnt)); + + /* + * If the reference count is down to 0, unlock it. + * In any case, don't actually do any real closing. + */ + if(refcnt > 0) + sp_set_ref_cnt(stream, refcnt-1); + + refcnt = sp_ref_cnt(stream); + if(refcnt <= 0){ + pine_mail_actually_close(stream); + } + else{ + dprint((7, "pine_mail_close: ref cnt is now %d\n", + refcnt)); + } + + } +} + + +void +pine_mail_actually_close(MAILSTREAM *stream) +{ + if(!stream) + return; + + if(!sp_closing(stream)){ + dprint((7, "pine_mail_actually_close: %s (%s)\n", + stream && stream->mailbox ? stream->mailbox : "(NULL)", + debug_time(1, ps_global->debug_timestamp))); + + sp_set_closing(stream, 1); + + if(!sp_flagged(stream, SP_LOCKED) + && !sp_flagged(stream, SP_USERFLDR) + && !sp_dead_stream(stream) + && sp_new_mail_count(stream)) + process_filter_patterns(stream, sp_msgmap(stream), + sp_new_mail_count(stream)); + sp_delete(stream); + + /* + * let sp_free_callback() free the sp_s stuff and the callbacks to + * free_pine_elt free the per-elt pine stuff. + */ + mail_close(stream); + } +} + + +/* + * If we haven't used a stream for a while, we may want to logout. + */ +void +maybe_kill_old_stream(MAILSTREAM *stream) +{ +#define KILL_IF_IDLE_TIME (25 * 60) + if(stream + && !sp_flagged(stream, SP_LOCKED) + && !sp_flagged(stream, SP_USERFLDR) + && time(0) - sp_last_use_time(stream) > KILL_IF_IDLE_TIME){ + + dprint((7, + "killing idle stream: %s (%s): idle timer = %ld secs\n", + stream && stream->mailbox ? stream->mailbox : "(NULL)", + debug_time(1, ps_global->debug_timestamp), + (long) (time(0)-sp_last_use_time(stream)))); + + /* + * Another thing we could do here instead is to unselect the + * mailbox, leaving the stream open to the server. + */ + pine_mail_actually_close(stream); + } +} + + +/* + * Catch searches that don't need to go to the server. + * (Not anymore, now c-client does this for us.) + */ +long +pine_mail_search_full(MAILSTREAM *stream, char *charset, + SEARCHPGM *pgm, long int flags) +{ + /* + * The charset should either be UTF-8 or NULL for alpine. + * If it is UTF-8 we may be able to change it to NULL if + * everything in the search is actually ascii. We try to + * do this because not all servers understand the CHARSET + * parameter. + */ + if(charset && !strucmp(charset, "utf-8") + && is_imap_stream(stream) && !hibit_in_searchpgm(pgm)) + charset = NULL; + + if(F_ON(F_NNTP_SEARCH_USES_OVERVIEW, ps_global)) + flags |= SO_OVERVIEW; + + return(stream ? mail_search_full(stream, charset, pgm, flags) : NIL); +} + + +int +hibit_in_searchpgm(SEARCHPGM *pgm) +{ + SEARCHOR *or; + SEARCHPGMLIST *not; + + if(!pgm) + return 0; + + if((pgm->subject && hibit_in_strlist(pgm->subject)) + || (pgm->text && hibit_in_strlist(pgm->text)) + || (pgm->body && hibit_in_strlist(pgm->body)) + || (pgm->cc && hibit_in_strlist(pgm->cc)) + || (pgm->from && hibit_in_strlist(pgm->from)) + || (pgm->to && hibit_in_strlist(pgm->to)) + || (pgm->bcc && hibit_in_strlist(pgm->bcc)) + || (pgm->keyword && hibit_in_strlist(pgm->keyword)) + || (pgm->unkeyword && hibit_in_strlist(pgm->return_path)) + || (pgm->sender && hibit_in_strlist(pgm->sender)) + || (pgm->reply_to && hibit_in_strlist(pgm->reply_to)) + || (pgm->in_reply_to && hibit_in_strlist(pgm->in_reply_to)) + || (pgm->message_id && hibit_in_strlist(pgm->message_id)) + || (pgm->newsgroups && hibit_in_strlist(pgm->newsgroups)) + || (pgm->followup_to && hibit_in_strlist(pgm->followup_to)) + || (pgm->references && hibit_in_strlist(pgm->references)) + || (pgm->header && hibit_in_header(pgm->header))) + return 1; + + for(or = pgm->or; or; or = or->next) + if(hibit_in_searchpgm(or->first) || hibit_in_searchpgm(or->second)) + return 1; + + for(not = pgm->not; not; not = not->next) + if(hibit_in_searchpgm(not->pgm)) + return 1; + + return 0; +} + + +int +hibit_in_strlist(STRINGLIST *sl) +{ + for(; sl; sl = sl->next) + if(hibit_in_sizedtext(&sl->text)) + return 1; + + return 0; +} + + +int +hibit_in_header(SEARCHHEADER *header) +{ + SEARCHHEADER *hdr; + + for(hdr = header; hdr; hdr = hdr->next) + if(hibit_in_sizedtext(&hdr->line) || hibit_in_sizedtext(&hdr->text)) + return 1; + + return 0; +} + + +int +hibit_in_sizedtext(SIZEDTEXT *st) +{ + unsigned char *p, *end; + + p = st ? st->data : NULL; + if(p) + for(end = p + st->size; p < end; p++) + if(*p & 0x80) + return 1; + + return 0; +} + + +void +pine_mail_fetch_flags(MAILSTREAM *stream, char *sequence, long int flags) +{ + ps_global->dont_count_flagchanges = 1; + mail_fetch_flags(stream, sequence, flags); + ps_global->dont_count_flagchanges = 0; +} + + +ENVELOPE * +pine_mail_fetchenvelope(MAILSTREAM *stream, long unsigned int msgno) +{ + ENVELOPE *env = NULL; + + ps_global->dont_count_flagchanges = 1; + if(stream && msgno > 0L && msgno <= stream->nmsgs) + env = mail_fetchenvelope(stream, msgno); + + ps_global->dont_count_flagchanges = 0; + return(env); +} + + +ENVELOPE * +pine_mail_fetch_structure(MAILSTREAM *stream, long unsigned int msgno, + struct mail_bodystruct **body, long int flags) +{ + ENVELOPE *env = NULL; + + ps_global->dont_count_flagchanges = 1; + if(stream && (flags & FT_UID || (msgno > 0L && msgno <= stream->nmsgs))) + env = mail_fetch_structure(stream, msgno, body, flags); + + ps_global->dont_count_flagchanges = 0; + return(env); +} + + +ENVELOPE * +pine_mail_fetchstructure(MAILSTREAM *stream, long unsigned int msgno, struct mail_bodystruct **body) +{ + ENVELOPE *env = NULL; + + ps_global->dont_count_flagchanges = 1; + if(stream && msgno > 0L && msgno <= stream->nmsgs) + env = mail_fetchstructure(stream, msgno, body); + + ps_global->dont_count_flagchanges = 0; + return(env); +} + + +/* + * Wrapper around mail_fetch_body. + * Currently only used to turn on partial fetching if trying + * to work around the microsoft ssl bug. + */ +char * +pine_mail_fetch_body(MAILSTREAM *stream, long unsigned int msgno, char *section, + long unsigned int *len, long int flags) +{ +#ifdef _WINDOWS + if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global)) + return(pine_mail_partial_fetch_wrapper(stream, msgno, + section, len, flags, 0, NULL, 0)); + else +#endif + return(mail_fetch_body(stream, msgno, section, len, flags)); +} + +/* + * Wrapper around mail_fetch_text. + * Currently the only purpose this wrapper serves is to turn + * on partial fetching for quell-ssl-largeblocks. + */ +char * +pine_mail_fetch_text(MAILSTREAM *stream, long unsigned int msgno, char *section, + long unsigned int *len, long int flags) +{ +#ifdef _WINDOWS + if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global)) + return(pine_mail_partial_fetch_wrapper(stream, msgno, + section, len, flags, + 0, NULL, 1)); + else +#endif + return(mail_fetch_text(stream, msgno, section, len, flags)); +} + + +/* + * Determine whether to do partial-fetching or not, and do it + * args - same as c-client functions being wrapped around + * get_n_bytes - try to partial fetch for the first n bytes. + * makes no guarantees, might wind up fetching + * the entire text anyway. + * str_to_free - pointer to string to free if we only get + * (non-cachable) partial text (required for + * get_n_bytes). + * is_text_fetch - + * set, tells us to do mail_fetch_text and mail_partial_text + * unset, tells us to do mail_fetch_body and mail_partial_body + */ +char * +pine_mail_partial_fetch_wrapper(MAILSTREAM *stream, long unsigned int msgno, + char *section, long unsigned int *len, + long int flags, long unsigned int get_n_bytes, + char **str_to_free, int is_text_fetch) +{ + BODY *body; + unsigned long size, firstbyte, lastbyte; + void *old_gets; + FETCH_READC_S *pftc; + char imap_cache_section[MAILTMPLEN]; + SIZEDTEXT new_text; + MESSAGECACHE *mc; + char *(*fetch_full)(MAILSTREAM *, unsigned long, char *, + unsigned long *, long); + long (*fetch_partial)(MAILSTREAM *, unsigned long, char *, + unsigned long, unsigned long, long); + + fetch_full = is_text_fetch ? mail_fetch_text : mail_fetch_body; + fetch_partial = is_text_fetch ? mail_partial_text : mail_partial_body; + if(str_to_free) + *str_to_free = NULL; +#ifdef _WINDOWS + if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global) || get_n_bytes){ +#else + if(get_n_bytes){ +#endif /* _WINDOWS */ + if(section && *section) + body = mail_body(stream, msgno, (unsigned char *) section); + else + pine_mail_fetch_structure(stream, msgno, &body, flags); + if(!body) + return NULL; + if(body->type != TYPEMULTIPART) + size = body->size.bytes; + else if((!section || !*section) && msgno > 0L + && stream && msgno <= stream->nmsgs + && (mc = mail_elt(stream, msgno))) + size = mc->rfc822_size; /* upper bound */ + else /* just a guess, can't get actual size */ + size = fcc_size_guess(body) + 2048; + + /* + * imap_cache, originally intended for c-client internal use, + * takes a section argument that is different from one we + * would pass to mail_body. Typically in this function + * section is NULL, which translates to "TEXT", but in other + * cases we would want to append ".TEXT" to the section + */ + if(is_text_fetch) + snprintf(imap_cache_section, sizeof(imap_cache_section), "%.*s%sTEXT", MAILTMPLEN - 10, + section && *section ? section : "", + section && *section ? "." : ""); + else + snprintf(imap_cache_section, sizeof(imap_cache_section), "%.*s", MAILTMPLEN - 10, + section && *section ? section : ""); + + if(modern_imap_stream(stream) +#ifdef _WINDOWS + && ((size > AVOID_MICROSOFT_SSL_CHUNKING_BUG) + || (get_n_bytes && size > get_n_bytes)) +#else + && (get_n_bytes && size > get_n_bytes) +#endif /* _WINDOWS */ + && !imap_cache(stream, msgno, imap_cache_section, + NULL, NULL)){ + if(get_n_bytes == 0){ + dprint((8, + "fetch_wrapper: doing partial fetching to work around microsoft bug\n")); + } + else{ + dprint((8, + "fetch_wrapper: partial fetching due to %lu get_n_bytes\n", get_n_bytes)); + } + pftc = (FETCH_READC_S *)fs_get(sizeof(FETCH_READC_S)); + memset(g_pft_desc = pftc, 0, sizeof(FETCH_READC_S)); +#ifdef _WINDOWS + if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global)){ + if(get_n_bytes) + pftc->chunksize = MIN(get_n_bytes, + AVOID_MICROSOFT_SSL_CHUNKING_BUG); + else + pftc->chunksize = AVOID_MICROSOFT_SSL_CHUNKING_BUG; + } + else +#endif /* _WINDOWS */ + pftc->chunksize = get_n_bytes; + + pftc->chunk = (char *) fs_get((pftc->chunksize+1) + * sizeof(char)); + pftc->cache = so_get(CharStar, NULL, EDIT_ACCESS); + pftc->read = 0L; + so_truncate(pftc->cache, size + 1); + old_gets = mail_parameters(stream, GET_GETS, (void *)NULL); + mail_parameters(stream, SET_GETS, (void *) partial_text_gets); + /* start fetching */ + do{ + firstbyte = pftc->read ; + lastbyte = firstbyte + pftc->chunksize; + if(get_n_bytes > firstbyte && get_n_bytes < lastbyte){ + pftc->chunksize = get_n_bytes - firstbyte; + lastbyte = get_n_bytes; + } + (*fetch_partial)(stream, msgno, section, firstbyte, + pftc->chunksize, flags); + + if(pftc->read != lastbyte) + break; + } while((pftc->read == lastbyte) + && (!get_n_bytes || (pftc->read < get_n_bytes))); + dprint((8, + "fetch_wrapper: anticipated size=%lu read=%lu\n", + size, pftc->read)); + mail_parameters(stream, SET_GETS, old_gets); + new_text.size = pftc->read; + new_text.data = (unsigned char *)so_text(pftc->cache); + + if(get_n_bytes && pftc->read == get_n_bytes){ + /* + * don't write to cache if we're only dealing with + * partial text. + */ + if(!str_to_free) + panic("Programmer botch: partial fetch attempt w/o string pointer"); + else + *str_to_free = (char *) new_text.data; + } + else { + /* ugh, rewrite string in case it got stomped on last call */ + if(is_text_fetch) + snprintf(imap_cache_section, sizeof(imap_cache_section), "%.*s%sTEXT", MAILTMPLEN - 10, + section && *section ? section : "", + section && *section ? "." : ""); + else + snprintf(imap_cache_section, sizeof(imap_cache_section), "%.*s", MAILTMPLEN - 10, + section && *section ? section : ""); + + imap_cache(stream, msgno, imap_cache_section, NULL, &new_text); + } + + pftc->cache->txt = (void *)NULL; + so_give(&pftc->cache); + fs_give((void **)&pftc->chunk); + fs_give((void **)&pftc); + g_pft_desc = NULL; + if(len) + *len = new_text.size; + return ((char *)new_text.data); + } + else + return((*fetch_full)(stream, msgno, section, len, flags)); + } + else + return((*fetch_full)(stream, msgno, section, len, flags)); +} + +/* + * c-client callback that handles getting the text + */ +char * +partial_text_gets(readfn_t f, void *stream, long unsigned int size, GETS_DATA *md) +{ + unsigned long n; + + n = MIN(g_pft_desc->chunksize, size); + g_pft_desc->read +=n; + + (*f) (stream, n, g_pft_desc->chunk); + + if(g_pft_desc->cache) + so_nputs(g_pft_desc->cache, g_pft_desc->chunk, (long) n); + + + return(NULL); +} + + +/* + * Pings the stream. Returns 0 if the stream is dead, non-zero otherwise. + */ +long +pine_mail_ping(MAILSTREAM *stream) +{ + time_t now; + long ret = 0L; + + if(!sp_dead_stream(stream)){ + ret = mail_ping(stream); + if(ret && sp_dead_stream(stream)) + ret = 0L; + } + + if(ret){ + now = time(0); + sp_set_last_ping(stream, now); + sp_set_last_expunged_reaper(stream, now); + } + + return(ret); +} + + +void +pine_mail_check(MAILSTREAM *stream) +{ + reset_check_point(stream); + mail_check(stream); +} + + +/* + * Unlike mail_list, this version returns a value. The returned value is + * TRUE if the stream is opened ok, and FALSE if we failed opening the + * stream. This allows us to differentiate between a LIST which returns + * no matches and a failure opening the stream. We do this by pre-opening + * the stream ourselves. + */ +int +pine_mail_list(MAILSTREAM *stream, char *ref, char *pat, unsigned int *options) +{ + int we_open = 0; + char *halfopen_target; + char source[MAILTMPLEN], *target = NULL; + MAILSTREAM *ourstream = NULL; + + dprint((7, "pine_mail_list: ref=%s pat=%s%s\n", + ref ? ref : "(NULL)", + pat ? pat : "(NULL)", + stream ? "" : " (stream was NULL)")); + + if((!ref && check_for_move_mbox(pat, source, sizeof(source), &target)) + || + check_for_move_mbox(ref, source, sizeof(source), &target)){ + ref = NIL; + pat = target; + if(options) + *options |= PML_IS_MOVE_MBOX; + + dprint((7, + "pine_mail_list: #move special case, listing \"%s\"%s\n", + pat ? pat : "(NULL)", + stream ? "" : " (stream was NULL)")); + } + + if(!stream && ((pat && *pat == '{') || (ref && *ref == '{'))){ + we_open++; + if(pat && *pat == '{'){ + ref = NIL; + halfopen_target = pat; + } + else + halfopen_target = ref; + } + + if(we_open){ + long flags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE); + + stream = sp_stream_get(halfopen_target, SP_MATCH); + if(!stream) + stream = sp_stream_get(halfopen_target, SP_SAME); + + if(!stream){ + DRIVER *d; + + /* + * POP is a special case. We don't need to have a stream + * to call mail_list for a POP name. The else part is the + * POP part. + */ + if((d = mail_valid(NIL, halfopen_target, (char *) NIL)) + && (d->flags & DR_HALFOPEN)){ + stream = pine_mail_open(NIL, halfopen_target, flags, NULL); + ourstream = stream; + if(!stream) + return(FALSE); + } + else + stream = NULL; + } + + mail_list_internal(stream, ref, pat); + } + else + mail_list_internal(stream, ref, pat); + + if(ourstream) + pine_mail_close(ourstream); + + return(TRUE); +} + + +/* + * mail_list_internal -- A monument to software religion and those who + * would force it upon us. + */ +void +mail_list_internal(MAILSTREAM *s, char *r, char *p) +{ + if(F_ON(F_FIX_BROKEN_LIST, ps_global) + && ((s && s->mailbox && *s->mailbox == '{') + || (!s && ((r && *r == '{') || (p && *p == '{'))))){ + char tmp[2*MAILTMPLEN]; + + snprintf(tmp, sizeof(tmp), "%.*s%.*s", sizeof(tmp)/2-1, r ? r : "", + sizeof(tmp)/2-1, p); + mail_list(s, "", tmp); + } + else + mail_list(s, r, p); +} + + +long +pine_mail_status(MAILSTREAM *stream, char *mailbox, long int flags) +{ + return(pine_mail_status_full(stream, mailbox, flags, NULL, NULL)); +} + + +long +pine_mail_status_full(MAILSTREAM *stream, char *mailbox, long int flags, + imapuid_t *uidvalidity, imapuid_t *uidnext) +{ + char source[MAILTMPLEN], *target = NULL; + long ret = NIL; + MAILSTATUS cache, status; + MAILSTREAM *ourstream = NULL; + + if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){ + memset(&status, 0, sizeof(status)); + memset(&cache, 0, sizeof(cache)); + + /* tell mm_status() to write partial return here */ + pine_cached_status = &status; + + flags |= (SA_UIDVALIDITY | SA_UIDNEXT | SA_MESSAGES); + + /* do status of destination */ + + stream = sp_stream_get(target, SP_SAME); + + /* should never be news, don't worry about mulnewrsc flag*/ + if((ret = pine_mail_status_full(stream, target, flags, uidvalidity, + uidnext)) + && !status.recent){ + + /* do status of source */ + pine_cached_status = &cache; + stream = sp_stream_get(source, SP_SAME); + + if(!stream){ + DRIVER *d; + + if((d = mail_valid (NIL, source, (char *) NIL)) + && !strcmp(d->name, "imap")){ + long openflags =OP_HALFOPEN|OP_SILENT|SP_USEPOOL|SP_TEMPUSE; + + if(F_ON(F_PREFER_ALT_AUTH, ps_global)) + openflags |= OP_TRYALT; + + stream = pine_mail_open(NULL, source, openflags, NULL); + ourstream = stream; + } + else if(F_ON(F_ENABLE_MULNEWSRCS, ps_global) + && d && (!strucmp(d->name, "news") + || !strucmp(d->name, "nntp"))) + flags |= SA_MULNEWSRC; + + } + + if(!ps_global->user_says_cancel && mail_status(stream, source, flags)){ + DRIVER *d; + int is_news = 0; + + /* + * If the target has recent messages, then we don't come + * through here. We just use the answer from the target. + * + * If not, then we leave the target results in "status" and + * run a mail_status on the source that puts its results + * in "cache". Combine the results from cache with the + * results that were already in status. + * + * We count all messages in the source mailbox as recent and + * unseen, even if they are not recent or unseen in the source, + * because they will be recent and unseen in the target + * when we open it. (Not quite true. It is possible that some + * messages from a POP server will end up seen instead + * of unseen. + * It is also possible that it is correct. If we add unseen, or + * if we add messages, we could get it wrong. As far as I + * can tell, Pine doesn't ever even use status.unseen, so it + * is currently academic anyway.) Hubert 2003-03-05 + * (Does now 2004-06-02 in next_folder.) + * + * However, we don't want to count all messages as recent if + * the source mailbox is NNTP or NEWS, because we won't be + * deleting those messages from the source. + * We only count recent. + * + * There are other cases that are trouble. One in particular + * is an IMAP-to-NNTP proxy, where the messages can't be removed + * from the mailbox but they can be deleted. If we count + * messages in the source as being recent and it turns out they + * were all deleted already, then we incorrectly say the folder + * has recent messages when it doesn't. We can recover from that + * case at some cost by actually opening the folder the first + * time if there are not recents, and then checking to see if + * everything is deleted. Subsequently, we store the uid values + * (which are returned by status) so that we can know if the + * mailbox changed or not. The problem being solved is that + * the TAB command indicates new messages in a folder when there + * really aren't any. An alternative would be to use the is_news + * half of the if-else in all cases. A problem with that is + * that there could be non-recent messages sitting in the + * source mailbox that we never discover. Hubert 2003-03-28 + */ + + if((d = mail_valid (NIL, source, (char *) NIL)) + && (!strcmp(d->name, "nntp") || !strcmp(d->name, "news"))) + is_news++; + + if(is_news && cache.flags & SA_RECENT){ + status.messages += cache.recent; + status.recent += cache.recent; + status.unseen += cache.recent; + status.uidnext += cache.recent; + } + else{ + if(uidvalidity && *uidvalidity + && uidnext && *uidnext + && cache.flags & SA_UIDVALIDITY + && cache.uidvalidity == *uidvalidity + && cache.flags & SA_UIDNEXT + && cache.uidnext == *uidnext){ + ; /* nothing changed in source mailbox */ + } + else if(cache.flags & SA_RECENT && cache.recent){ + status.messages += cache.recent; + status.recent += cache.recent; + status.unseen += cache.recent; + status.uidnext += cache.recent; + } + else if(!(cache.flags & SA_MESSAGES) || cache.messages){ + long openflags = OP_SILENT | SP_USEPOOL | SP_TEMPUSE; + long delete_count, not_deleted = 0L; + + /* Actually open it up and check */ + if(F_ON(F_PREFER_ALT_AUTH, ps_global)) + openflags |= OP_TRYALT; + + if(!ourstream) + stream = NULL; + + if(ourstream + && !same_stream_and_mailbox(source, ourstream)){ + pine_mail_close(ourstream); + ourstream = stream = NULL; + } + + if(!stream){ + stream = pine_mail_open(stream, source, openflags, + NULL); + ourstream = stream; + } + + if(stream){ + delete_count = count_flagged(stream, F_DEL); + not_deleted = stream->nmsgs - delete_count; + } + + status.messages += not_deleted; + status.recent += not_deleted; + status.unseen += not_deleted; + status.uidnext += not_deleted; + } + + if(uidvalidity && cache.flags & SA_UIDVALIDITY) + *uidvalidity = cache.uidvalidity; + + if(uidnext && cache.flags & SA_UIDNEXT) + *uidnext = cache.uidnext; + } + } + } + + /* + * Do the regular mm_status callback which we've been intercepting + * into different locations above. + */ + pine_cached_status = NIL; + if(ret) + mm_status(NULL, mailbox, &status); + } + else{ + if(!stream){ + DRIVER *d; + + if((d = mail_valid (NIL, mailbox, (char *) NIL)) + && !strcmp(d->name, "imap")){ + long openflags = OP_HALFOPEN|OP_SILENT|SP_USEPOOL|SP_TEMPUSE; + + if(F_ON(F_PREFER_ALT_AUTH, ps_global)) + openflags |= OP_TRYALT; + + /* + * We just use this to find the answer. + * We're asking for trouble if we do a STATUS on a + * selected mailbox. I don't believe this happens in pine. + * It does now (2004-06-02) in next_folder if the + * F_TAB_USES_UNSEEN option is set and the folder was + * already opened. + */ + stream = sp_stream_get(mailbox, SP_MATCH); + if(stream){ + memset(&status, 0, sizeof(status)); + if(flags & SA_MESSAGES){ + status.flags |= SA_MESSAGES; + status.messages = stream->nmsgs; + } + + if(flags & SA_RECENT){ + status.flags |= SA_RECENT; + status.recent = stream->recent; + } + + if(flags & SA_UNSEEN){ + long i; + SEARCHPGM *srchpgm; + MESSAGECACHE *mc; + + srchpgm = mail_newsearchpgm(); + srchpgm->unseen = 1; + + pine_mail_search_full(stream, NULL, srchpgm, + SE_NOPREFETCH | SE_FREE); + status.flags |= SA_UNSEEN; + status.unseen = 0L; + for(i = 1L; i <= stream->nmsgs; i++) + if((mc = mail_elt(stream, i)) && mc->searched) + status.unseen++; + } + + if(flags & SA_UIDVALIDITY){ + status.flags |= SA_UIDVALIDITY; + status.uidvalidity = stream->uid_validity; + } + + if(flags & SA_UIDNEXT){ + status.flags |= SA_UIDNEXT; + status.uidnext = stream->uid_last + 1L; + } + + mm_status(NULL, mailbox, &status); + return T; /* that's what c-client returns when success */ + } + + if(!stream) + stream = sp_stream_get(mailbox, SP_SAME); + + if(!stream){ + stream = pine_mail_open(NULL, mailbox, openflags, NULL); + ourstream = stream; + } + } + else if(F_ON(F_ENABLE_MULNEWSRCS, ps_global) + && d && (!strucmp(d->name, "news") + || !strucmp(d->name, "nntp"))) + flags |= SA_MULNEWSRC; + } + + if(!ps_global->user_says_cancel) + ret = mail_status(stream, mailbox, flags); /* non #move case */ + } + + if(ourstream) + pine_mail_close(ourstream); + + return ret; +} + + +/* + * Check for a mailbox name that is a legitimate #move mailbox. + * + * Args mbox -- The mailbox name to check + * sourcebuf -- Copy the source mailbox name into this buffer + * sbuflen -- Length of sourcebuf + * targetptr -- Set the pointer this points to to point to the + * target mailbox name in the original string + * + * Returns 1 - is a #move mailbox + * 0 - not + */ +int +check_for_move_mbox(char *mbox, char *sourcebuf, size_t sbuflen, char **targetptr) +{ + char delim, *s; + int i; + + if(mbox && (mbox[0] == '#') + && ((mbox[1] == 'M') || (mbox[1] == 'm')) + && ((mbox[2] == 'O') || (mbox[2] == 'o')) + && ((mbox[3] == 'V') || (mbox[3] == 'v')) + && ((mbox[4] == 'E') || (mbox[4] == 'e')) + && (delim = mbox[5]) + && (s = strchr(mbox+6, delim)) + && (i = s++ - (mbox + 6)) + && (!sourcebuf || i < sbuflen)){ + + if(sourcebuf){ + strncpy(sourcebuf, mbox+6, i); /* copy source mailbox name */ + sourcebuf[i] = '\0'; + } + + if(targetptr) + *targetptr = s; + + return 1; + } + + return 0; +} + + +/* + * Checks through stream cache for a stream pointer already open to + * this mailbox, read/write. Very similar to sp_stream_get, but we want + * to look at all streams, not just imap streams. + * Right now it is very specialized. If we want to use it more generally, + * generalize it or combine it with sp_stream_get somehow. + */ +MAILSTREAM * +already_open_stream(char *mailbox, int flags) +{ + int i; + MAILSTREAM *m; + + if(!mailbox) + return(NULL); + + if(*mailbox == '{'){ + for(i = 0; i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m && !(flags & AOS_RW_ONLY && m->rdonly) + && (*m->mailbox == '{') && !sp_dead_stream(m) + && same_stream_and_mailbox(mailbox, m)) + return(m); + } + } + else{ + char *cn, tmp[MAILTMPLEN]; + + cn = mailboxfile(tmp, mailbox); + for(i = 0; i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m && !(flags & AOS_RW_ONLY && m->rdonly) + && (*m->mailbox != '{') && !sp_dead_stream(m) + && ((cn && *cn && !strcmp(cn, m->mailbox)) + || !strcmp(mailbox, m->original_mailbox) + || !strcmp(mailbox, m->mailbox))) + return(m); + } + } + + return(NULL); +} + + +void +pine_imap_cmd_happened(MAILSTREAM *stream, char *cmd, long int flags) +{ + dprint((9, "imap_cmd(%s, %s, 0x%lx)\n", + STREAMNAME(stream), cmd ? cmd : "?", flags)); + + if(cmd && !strucmp(cmd, "CHECK")) + reset_check_point(stream); + + if(is_imap_stream(stream)){ + time_t now; + + now = time(0); + sp_set_last_ping(stream, now); + sp_set_last_activity(stream, now); + if(!(flags & SC_EXPUNGEDEFERRED)) + sp_set_last_expunged_reaper(stream, now); + } +} + + +/* + * Tells us whether we ought to check for a dead stream or not. + * We assume that we ought to check if it is not IMAP and if it is IMAP we + * don't need to check if the last activity was within the last 5 minutes. + */ +int +recent_activity(MAILSTREAM *stream) +{ + if(is_imap_stream(stream) && !sp_dead_stream(stream) + && (time(0) - sp_last_activity(stream) < 5L * 60L)) + return 1; + else + return 0; +} + +void +sp_cleanup_dead_streams(void) +{ + int i; + MAILSTREAM *m; + + (void) streams_died(); /* tell user in case they don't know yet */ + + for(i = 0; i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m && sp_dead_stream(m)) + pine_mail_close(m); + } +} + + +/* + * Returns 0 if stream flags not set, non-zero if they are. + */ +int +sp_flagged(MAILSTREAM *stream, long unsigned int flags) +{ + return(sp_flags(stream) & flags); +} + + +void +sp_set_fldr(MAILSTREAM *stream, char *folder) +{ + PER_STREAM_S **pss; + + pss = sp_data(stream); + if(pss && *pss){ + if((*pss)->fldr) + fs_give((void **) &(*pss)->fldr); + + if(folder) + (*pss)->fldr = cpystr(folder); + } +} + + +void +sp_set_saved_cur_msg_id(MAILSTREAM *stream, char *id) +{ + PER_STREAM_S **pss; + + pss = sp_data(stream); + if(pss && *pss){ + if((*pss)->saved_cur_msg_id) + fs_give((void **) &(*pss)->saved_cur_msg_id); + + if(id) + (*pss)->saved_cur_msg_id = cpystr(id); + } +} + + +/* + * Sets flags absolutely, erasing old flags. + */ +void +sp_flag(MAILSTREAM *stream, long unsigned int flags) +{ + if(!stream) + return; + + dprint((9, "sp_flag(%s, 0x%x): %s%s%s%s%s%s%s%s\n", + (stream && stream->mailbox) ? stream->mailbox : "?", + flags, + flags ? "set" : "clear", + (flags & SP_LOCKED) ? " SP_LOCKED" : "", + (flags & SP_PERMLOCKED) ? " SP_PERMLOCKED" : "", + (flags & SP_INBOX) ? " SP_INBOX" : "", + (flags & SP_USERFLDR) ? " SP_USERFLDR" : "", + (flags & SP_USEPOOL) ? " SP_USEPOOL" : "", + (flags & SP_TEMPUSE) ? " SP_TEMPUSE" : "", + !flags ? " ALL" : "")); + + sp_set_flags(stream, flags); +} + + +/* + * Clear individual stream flags. + */ +void +sp_unflag(MAILSTREAM *stream, long unsigned int flags) +{ + if(!stream || !flags) + return; + + dprint((9, "sp_unflag(%s, 0x%x): unset%s%s%s%s%s%s\n", + (stream && stream->mailbox) ? stream->mailbox : "?", + flags, + (flags & SP_LOCKED) ? " SP_LOCKED" : "", + (flags & SP_PERMLOCKED) ? " SP_PERMLOCKED" : "", + (flags & SP_INBOX) ? " SP_INBOX" : "", + (flags & SP_USERFLDR) ? " SP_USERFLDR" : "", + (flags & SP_USEPOOL) ? " SP_USEPOOL" : "", + (flags & SP_TEMPUSE) ? " SP_TEMPUSE" : "")); + + sp_set_flags(stream, sp_flags(stream) & ~flags); + + flags = sp_flags(stream); + dprint((9, "sp_unflag(%s, 0x%x): result:%s%s%s%s%s%s\n", + (stream && stream->mailbox) ? stream->mailbox : "?", + flags, + (flags & SP_LOCKED) ? " SP_LOCKED" : "", + (flags & SP_PERMLOCKED) ? " SP_PERMLOCKED" : "", + (flags & SP_INBOX) ? " SP_INBOX" : "", + (flags & SP_USERFLDR) ? " SP_USERFLDR" : "", + (flags & SP_USEPOOL) ? " SP_USEPOOL" : "", + (flags & SP_TEMPUSE) ? " SP_TEMPUSE" : "")); +} + + +/* + * Set dead stream indicator and close if not locked. + */ +void +sp_mark_stream_dead(MAILSTREAM *stream) +{ + if(!stream) + return; + + dprint((9, "sp_mark_stream_dead(%s)\n", + (stream && stream->mailbox) ? stream->mailbox : "?")); + + /* + * If the stream isn't locked, it is no longer useful. Get rid of it. + */ + if(!sp_flagged(stream, SP_LOCKED)) + pine_mail_actually_close(stream); + else{ + /* + * If it is locked, then we have to worry about references to it + * that still exist. For example, it might be a permlocked stream + * or it might be the current stream. We need to let it be discovered + * by those referencers instead of just eliminating it, so that they + * can clean up the mess they need to clean up. + */ + sp_set_dead_stream(stream, 1); + } +} + + +/* + * Returns the number of streams in the stream pool which are + * SP_USEPOOL but not SP_PERMLOCKED. + */ +int +sp_nusepool_notperm(void) +{ + int i, cnt = 0; + MAILSTREAM *m; + + for(i = 0; i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(sp_flagged(m, SP_USEPOOL) && !sp_flagged(m, SP_PERMLOCKED)) + cnt++; + } + + return(cnt); +} + + +/* + * Returns the number of folders that the user has marked to be PERMLOCKED + * folders (plus INBOX) that are remote IMAP folders. + * + * This routine depends on the fact that VAR_INBOX_PATH, VAR_PERMLOCKED, + * and the ps_global->context_list are correctly set. + */ +int +sp_nremote_permlocked(void) +{ + int cnt = 0; + char **lock_these, *p = NULL, *dummy = NULL, *lt; + DRIVER *d; + + /* first check if INBOX is remote */ + lt = ps_global->VAR_INBOX_PATH; + if(lt && (d=mail_valid(NIL, lt, (char *) NIL)) + && !strcmp(d->name, "imap")) + cnt++; + + /* then count the user-specified permlocked folders */ + for(lock_these = ps_global->VAR_PERMLOCKED; lock_these && *lock_these; + lock_these++){ + + /* + * Skip inbox, already done above. Should do this better so that we + * catch the case where the user puts the technical spec of the inbox + * in the list, or where the user lists one folder twice. + */ + if(*lock_these && !strucmp(*lock_these, ps_global->inbox_name)) + continue; + + /* there isn't really a pair, it just dequotes for us */ + get_pair(*lock_these, &dummy, &p, 0, 0); + + /* + * Check to see if this is an incoming nickname and replace it + * with the full name. + */ + if(!(p && ps_global->context_list + && ps_global->context_list->use & CNTXT_INCMNG + && (lt=folder_is_nick(p, FOLDERS(ps_global->context_list), + FN_WHOLE_NAME)))) + lt = p; + + if(dummy) + fs_give((void **) &dummy); + + if(lt && (d=mail_valid(NIL, lt, (char *) NIL)) + && !strcmp(d->name, "imap")) + cnt++; + + if(p) + fs_give((void **) &p); + } + + return(cnt); +} + + +/* + * Look for an already open stream that can be used for a new purpose. + * (Note that we only look through streams flagged SP_USEPOOL.) + * + * Args: mailbox + * flags + * + * Flags is a set of values or'd together which tells us what the request + * is looking for. + * + * Returns: a live stream from the stream pool or NULL. + */ +MAILSTREAM * +sp_stream_get(char *mailbox, long unsigned int flags) +{ + int i; + MAILSTREAM *m; + + dprint((7, "sp_stream_get(%s):%s%s%s%s%s\n", + mailbox ? mailbox : "?", + (flags & SP_MATCH) ? " SP_MATCH" : "", + (flags & SP_RO_OK) ? " SP_RO_OK" : "", + (flags & SP_SAME) ? " SP_SAME" : "", + (flags & SP_UNLOCKED) ? " SP_UNLOCKED" : "", + (flags & SP_TEMPUSE) ? " SP_TEMPUSE" : "")); + + /* look for stream already open to this mailbox */ + if(flags & SP_MATCH){ + for(i = 0; i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m && sp_flagged(m, SP_USEPOOL) + && (!m->rdonly || (flags & SP_RO_OK)) && !sp_dead_stream(m) + && same_stream_and_mailbox(mailbox, m)){ + if((sp_flagged(m, SP_LOCKED) && recent_activity(m)) + || pine_mail_ping(m)){ + dprint((7, + "sp_stream_get: found exact match, slot %d\n", i)); + if(!sp_flagged(m, SP_LOCKED)){ + dprint((7, + "reset idle timer1: next TAG %08lx (%s)\n", + m->gensym, + debug_time(1, ps_global->debug_timestamp))); + sp_set_last_use_time(m, time(0)); + } + + return(m); + } + + sp_mark_stream_dead(m); + } + } + } + + /* + * SP_SAME will not match if an SP_MATCH match would have worked. + * If the caller is interested in SP_MATCH streams as well as SP_SAME + * streams then the caller should make two separate calls to this + * routine. + */ + if(flags & SP_SAME){ + /* + * If the flags arg does not have either SP_TEMPUSE or SP_UNLOCKED + * set, then we'll accept any stream, even if locked. + * We want to prefer the LOCKED case so that we don't have to ping. + */ + if(!(flags & SP_UNLOCKED) && !(flags & SP_TEMPUSE)){ + for(i = 0; i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m && sp_flagged(m, SP_USEPOOL) + && sp_flagged(m, SP_LOCKED) && !sp_dead_stream(m) + && same_stream(mailbox, m) + && !same_stream_and_mailbox(mailbox, m)){ + if(recent_activity(m) || pine_mail_ping(m)){ + dprint((7, + "sp_stream_get: found SAME match, slot %d\n", i)); + return(m); + } + + sp_mark_stream_dead(m); + } + } + + /* consider the unlocked streams */ + for(i = 0; i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m && sp_flagged(m, SP_USEPOOL) + && !sp_flagged(m, SP_LOCKED) && !sp_dead_stream(m) + && same_stream(mailbox, m) + && !same_stream_and_mailbox(mailbox, m)){ + /* always ping unlocked streams */ + if(pine_mail_ping(m)){ + dprint((7, + "sp_stream_get: found SAME match, slot %d\n", i)); + dprint((7, + "reset idle timer4: next TAG %08lx (%s)\n", + m->gensym, + debug_time(1, ps_global->debug_timestamp))); + sp_set_last_use_time(m, time(0)); + + return(m); + } + + sp_mark_stream_dead(m); + } + } + } + + /* + * Prefer streams marked SP_TEMPUSE and not LOCKED. + * If SP_TEMPUSE is set in the flags arg then this is the + * only loop we try. + */ + for(i = 0; i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m && sp_flagged(m, SP_USEPOOL) && sp_flagged(m, SP_TEMPUSE) + && !sp_flagged(m, SP_LOCKED) && !sp_dead_stream(m) + && same_stream(mailbox, m) + && !same_stream_and_mailbox(mailbox, m)){ + if(pine_mail_ping(m)){ + dprint((7, + "sp_stream_get: found SAME/TEMPUSE match, slot %d\n", i)); + dprint((7, + "reset idle timer2: next TAG %08lx (%s)\n", + m->gensym, + debug_time(1, ps_global->debug_timestamp))); + sp_set_last_use_time(m, time(0)); + return(m); + } + + sp_mark_stream_dead(m); + } + } + + /* + * If SP_TEMPUSE is not set in the flags arg but SP_UNLOCKED is, + * then we will consider + * streams which are not marked SP_TEMPUSE (but are still not + * locked). We go through these in reverse order so that we'll get + * the last one added instead of the first one. It's not clear if + * that is a good idea or if a more complex search would somehow + * be better. Maybe we should use a round-robin sort of search + * here so that we don't leave behind unused streams. Or maybe + * we should keep track of when we used it and look for the LRU stream. + */ + if(!(flags & SP_TEMPUSE)){ + for(i = ps_global->s_pool.nstream - 1; i >= 0; i--){ + m = ps_global->s_pool.streams[i]; + if(m && sp_flagged(m, SP_USEPOOL) + && !sp_flagged(m, SP_LOCKED) && !sp_dead_stream(m) + && same_stream(mailbox, m) + && !same_stream_and_mailbox(mailbox, m)){ + if(pine_mail_ping(m)){ + dprint((7, + "sp_stream_get: found SAME/UNLOCKED match, slot %d\n", i)); + dprint((7, + "reset idle timer3: next TAG %08lx (%s)\n", + m->gensym, + debug_time(1, ps_global->debug_timestamp))); + sp_set_last_use_time(m, time(0)); + return(m); + } + + sp_mark_stream_dead(m); + } + } + } + } + + /* + * If we can't find a useful stream to use in pine_mail_open, we may + * want to re-use one that is not actively being used even though it + * is not on the same server. We'll have to close it and then re-use + * the slot. + */ + if(!(flags & (SP_SAME | SP_MATCH))){ + /* + * Prefer streams marked SP_TEMPUSE and not LOCKED. + * If SP_TEMPUSE is set in the flags arg then this is the + * only loop we try. + */ + for(i = 0; i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m && sp_flagged(m, SP_USEPOOL) && sp_flagged(m, SP_TEMPUSE) + && !sp_flagged(m, SP_LOCKED)){ + dprint((7, + "sp_stream_get: found Not-SAME/TEMPUSE match, slot %d\n", i)); + /* + * We ping it in case there is new mail that we should + * pass through our filters. Pine_mail_actually_close will + * do that. + */ + (void) pine_mail_ping(m); + return(m); + } + } + + /* + * If SP_TEMPUSE is not set in the flags arg, then we will consider + * streams which are not marked SP_TEMPUSE (but are still not + * locked). Maybe we should use a round-robin sort of search + * here so that we don't leave behind unused streams. Or maybe + * we should keep track of when we used it and look for the LRU stream. + */ + if(!(flags & SP_TEMPUSE)){ + for(i = ps_global->s_pool.nstream - 1; i >= 0; i--){ + m = ps_global->s_pool.streams[i]; + if(m && sp_flagged(m, SP_USEPOOL) && !sp_flagged(m, SP_LOCKED)){ + dprint((7, + "sp_stream_get: found Not-SAME/UNLOCKED match, slot %d\n", i)); + /* + * We ping it in case there is new mail that we should + * pass through our filters. Pine_mail_actually_close will + * do that. + */ + (void) pine_mail_ping(m); + return(m); + } + } + } + } + + dprint((7, "sp_stream_get: no match found\n")); + + return(NULL); +} + + +void +sp_end(void) +{ + int i; + MAILSTREAM *m; + + dprint((7, "sp_end\n")); + + for(i = 0; i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m) + pine_mail_actually_close(m); + } + + if(ps_global->s_pool.streams) + fs_give((void **) &ps_global->s_pool.streams); + + ps_global->s_pool.nstream = 0; +} + + +/* + * Find a vacant slot to put this new stream in. + * We are willing to close and kick out another stream as long as it isn't + * LOCKED. However, we may find that there is no place to put this one + * because all the slots are used and locked. For now, we'll return -1 + * in that case and leave the new stream out of the pool. + */ +int +sp_add(MAILSTREAM *stream, int usepool) +{ + int i, slot = -1; + MAILSTREAM *m; + + dprint((7, "sp_add(%s, %d)\n", + (stream && stream->mailbox) ? stream->mailbox : "?", usepool)); + + if(!stream){ + dprint((7, "sp_add: NULL stream\n")); + return -1; + } + + /* If this stream is already there, don't add it again */ + for(i = 0; i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m == stream){ + slot = i; + dprint((7, + "sp_add: stream was already in slot %d\n", slot)); + return 0; + } + } + + if(usepool && !sp_flagged(stream, SP_PERMLOCKED) + && sp_nusepool_notperm() >= ps_global->s_pool.max_remstream){ + dprint((7, + "sp_add: reached max implicit SP_USEPOOL of %d\n", + ps_global->s_pool.max_remstream)); + return -1; + } + + /* Look for an unused slot */ + for(i = 0; i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(!m){ + slot = i; + dprint((7, + "sp_add: using empty slot %d\n", slot)); + break; + } + } + + /* else, allocate more space */ + if(slot < 0){ + ps_global->s_pool.nstream++; + slot = ps_global->s_pool.nstream - 1; + if(ps_global->s_pool.streams){ + fs_resize((void **) &ps_global->s_pool.streams, + ps_global->s_pool.nstream * + sizeof(*ps_global->s_pool.streams)); + ps_global->s_pool.streams[slot] = NULL; + } + else{ + ps_global->s_pool.streams = + (MAILSTREAM **) fs_get(ps_global->s_pool.nstream * + sizeof(*ps_global->s_pool.streams)); + memset(ps_global->s_pool.streams, 0, + ps_global->s_pool.nstream * + sizeof(*ps_global->s_pool.streams)); + } + + dprint((7, + "sp_add: allocate more space, using new slot %d\n", slot)); + } + + if(slot >= 0 && slot < ps_global->s_pool.nstream){ + ps_global->s_pool.streams[slot] = stream; + return 0; + } + else{ + dprint((7, "sp_add: failed to find a slot!\n")); + return -1; + } +} + + +/* + * Simply remove this stream from the stream pool. + */ +void +sp_delete(MAILSTREAM *stream) +{ + int i; + MAILSTREAM *m; + + if(!stream) + return; + + dprint((7, "sp_delete(%s)\n", + (stream && stream->mailbox) ? stream->mailbox : "?")); + + /* + * There are some global stream pointers that we have to worry + * about before deleting the stream. + */ + + /* first, mail_stream is the global currently open folder */ + if(ps_global->mail_stream == stream) + ps_global->mail_stream = NULL; + + /* remote address books may have open stream pointers */ + note_closed_adrbk_stream(stream); + + if(pith_opt_closing_stream) + (*pith_opt_closing_stream)(stream); + + for(i = 0; i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m == stream){ + ps_global->s_pool.streams[i] = NULL; + dprint((7, + "sp_delete: stream removed from slot %d\n", i)); + return; + } + } +} + + +/* + * Returns 1 if any locked userfldr is dead, 0 if all alive. + */ +int +sp_a_locked_stream_is_dead(void) +{ + int i, ret = 0; + MAILSTREAM *m; + + for(i = 0; !ret && i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m && sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR) + && sp_dead_stream(m)) + ret++; + } + + return(ret); +} + + +/* + * Returns 1 if any locked stream is changed, 0 otherwise + */ +int +sp_a_locked_stream_changed(void) +{ + int i, ret = 0; + MAILSTREAM *m; + + for(i = 0; !ret && i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m && sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR) + && sp_mail_box_changed(m)) + ret++; + } + + return(ret); +} + + +/* + * Returns the inbox stream or NULL. + */ +MAILSTREAM * +sp_inbox_stream(void) +{ + int i; + MAILSTREAM *m, *ret = NULL; + + for(i = 0; !ret && i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m && sp_flagged(m, SP_INBOX)) + ret = m; + } + + return(ret); +} + + +/* + * Make sure that the sp_data per-stream data storage area exists. + * + * Returns a handle to the sp_data data unless stream is NULL, + * in which case NULL is returned + */ +PER_STREAM_S ** +sp_data(MAILSTREAM *stream) +{ + PER_STREAM_S **pss = NULL; + + if(stream){ + if(*(pss = (PER_STREAM_S **) &stream->sparep) == NULL){ + *pss = (PER_STREAM_S *) fs_get(sizeof(PER_STREAM_S)); + memset(*pss, 0, sizeof(PER_STREAM_S)); + reset_check_point(stream); + } + } + + return(pss); +} + + +/* + * Returns a pointer to the msgmap associated with the argument stream. + * + * If the PER_STREAM_S data or the msgmap does not already exist, it will be + * created. + */ +MSGNO_S * +sp_msgmap(MAILSTREAM *stream) +{ + MSGNO_S **msgmap = NULL; + PER_STREAM_S **pss = NULL; + + pss = sp_data(stream); + + if(pss && *pss + && (*(msgmap = (MSGNO_S **) &(*pss)->msgmap) == NULL)) + mn_init(msgmap, stream->nmsgs); + + return(msgmap ? *msgmap : NULL); +} + + +void +sp_free_callback(void **sparep) +{ + PER_STREAM_S **pss; + MAILSTREAM *stream = NULL, *m; + int i; + + pss = (PER_STREAM_S **) sparep; + + if(pss && *pss){ + /* + * It is possible that this has been called from c-client when + * we weren't expecting it. We need to clean up the stream pool + * entries if the stream that goes with this pointer is in the + * stream pool somewhere. + */ + for(i = 0; !stream && i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(sparep && *sparep && m && m->sparep == *sparep) + stream = m; + } + + if(stream){ + if(ps_global->mail_stream == stream) + ps_global->mail_stream = NULL; + + sp_delete(stream); + } + + sp_free(pss); + } +} + + +/* + * Free the data but don't mess with the stream pool. + */ +void +sp_free(PER_STREAM_S **pss) +{ + if(pss && *pss){ + if((*pss)->msgmap){ + if(ps_global->msgmap == (*pss)->msgmap) + ps_global->msgmap = NULL; + + mn_give(&(*pss)->msgmap); + } + + if((*pss)->fldr) + fs_give((void **) &(*pss)->fldr); + + fs_give((void **) pss); + } +} + + + +/*---------------------------------------------------------------------- + See if stream can be used for a mailbox name + + Accepts: mailbox name + candidate stream + Returns: stream if it can be used, else NIL + + This is called to weed out unnecessary use of c-client streams. In other + words, to help facilitate re-use of streams. + + This code is very similar to the same_remote_mailboxes code below, which + is used in pine_mail_open. That code compares two mailbox names. One is + usually from the config file and the other is either from the config file + or is typed in. Here and in same_stream_and_mailbox below, we're comparing + an open stream to a name instead of two names. We could conceivably use + same_remote_mailboxes to compare stream->mailbox to name, but it isn't + exactly the same and the differences may be important. Some stuff that + happens here seems wrong, but it isn't easy to fix. + Having !mb_n.port count as a match to any mb_s.port isn't right. It should + only match if mb_s.port is equal to the default, but the default isn't + something that is available to us. The same thing is done in c-client in + the mail_usable_network_stream() routine, and it isn't right there, either. + The semantics of a missing user are also suspect, because just like with + port, a default is used. + ----*/ +MAILSTREAM * +same_stream(char *name, MAILSTREAM *stream) +{ + NETMBX mb_s, mb_n, mb_o; + + if(stream && stream->mailbox && *stream->mailbox && name && *name + && !(sp_dead_stream(stream)) + && mail_valid_net_parse(stream->mailbox, &mb_s) + && mail_valid_net_parse(stream->original_mailbox, &mb_o) + && mail_valid_net_parse(name, &mb_n) + && !strucmp(mb_n.service, mb_s.service) + && (!strucmp(mb_n.host, mb_o.host) /* s is already canonical */ + || !strucmp(canonical_name(mb_n.host), mb_s.host)) + && (!mb_n.port || mb_n.port == mb_s.port) + && mb_n.anoflag == stream->anonymous + && ((mb_n.user && *mb_n.user && + mb_s.user && !strcmp(mb_n.user, mb_s.user)) + || + ((!mb_n.user || !*mb_n.user) + && mb_s.user + && ((ps_global->VAR_USER_ID + && !strcmp(ps_global->VAR_USER_ID, mb_s.user)) + || + (!ps_global->VAR_USER_ID + && ps_global->ui.login[0] + && !strcmp(ps_global->ui.login, mb_s.user)))) + || + (!((mb_n.user && *mb_n.user) || (mb_s.user && *mb_s.user)) + && stream->anonymous)) + && (struncmp(mb_n.service, "imap", 4) ? 1 : strcmp(imap_host(stream), ".NO-IMAP-CONNECTION."))){ + dprint((7, "same_stream: name->%s == stream->%s: yes\n", + name ? name : "?", + (stream && stream->mailbox) ? stream->mailbox : "NULL")); + return(stream); + } + + dprint((7, "same_stream: name->%s == stream->%s: no dice\n", + name ? name : "?", + (stream && stream->mailbox) ? stream->mailbox : "NULL")); + return(NULL); +} + + + +/*---------------------------------------------------------------------- + See if this stream has the named mailbox selected. + + Accepts: mailbox name + candidate stream + Returns: stream if it can be used, else NIL + ----*/ +MAILSTREAM * +same_stream_and_mailbox(char *name, MAILSTREAM *stream) +{ + NETMBX mb_s, mb_n; + + if(same_stream(name, stream) + && mail_valid_net_parse(stream->mailbox, &mb_s) + && mail_valid_net_parse(name, &mb_n) + && (mb_n.mailbox && mb_s.mailbox + && (!strcmp(mb_n.mailbox,mb_s.mailbox) /* case depend except INBOX */ + || (!strucmp(mb_n.mailbox,"INBOX") + && !strucmp(mb_s.mailbox,"INBOX"))))){ + dprint((7, + "same_stream_and_mailbox: name->%s == stream->%s: yes\n", + name ? name : "?", + (stream && stream->mailbox) ? stream->mailbox : "NULL")); + return(stream); + } + + dprint((7, + "same_stream_and_mailbox: name->%s == stream->%s: no dice\n", + name ? name : "?", + (stream && stream->mailbox) ? stream->mailbox : "NULL")); + return(NULL); +} + +/* + * Args -- name1 and name2 are remote mailbox names. + * + * Returns -- True if names refer to same mailbox accessed in same way + * False if not + * + * This has some very similar code to same_stream_and_mailbox but we're not + * quite ready to discard the differences. + * The treatment of the port and the user is not quite the same. + */ +int +same_remote_mailboxes(char *name1, char *name2) +{ + NETMBX mb1, mb2; + char *cn1; + + /* + * Probably we should allow !port equal to default port, but we don't + * know how to get the default port. To match what c-client does we + * allow !port to be equal to anything. + */ + return(name1 && IS_REMOTE(name1) + && name2 && IS_REMOTE(name2) + && mail_valid_net_parse(name1, &mb1) + && mail_valid_net_parse(name2, &mb2) + && !strucmp(mb1.service, mb2.service) + && (!strucmp(mb1.host, mb2.host) /* just to save DNS lookups */ + || !strucmp(cn1=canonical_name(mb1.host), mb2.host) + || !strucmp(cn1, canonical_name(mb2.host))) + && (!mb1.port || !mb2.port || mb1.port == mb2.port) + && mb1.anoflag == mb2.anoflag + && mb1.mailbox && mb2.mailbox + && (!strcmp(mb1.mailbox, mb2.mailbox) + || (!strucmp(mb1.mailbox,"INBOX") + && !strucmp(mb2.mailbox,"INBOX"))) + && ((mb1.user && *mb1.user && mb2.user && *mb2.user + && !strcmp(mb1.user, mb2.user)) + || + (!(mb1.user && *mb1.user) && !(mb2.user && *mb2.user)) + || + (!(mb1.user && *mb1.user) + && ((ps_global->VAR_USER_ID + && !strcmp(ps_global->VAR_USER_ID, mb2.user)) + || + (!ps_global->VAR_USER_ID + && ps_global->ui.login[0] + && !strcmp(ps_global->ui.login, mb2.user)))) + || + (!(mb2.user && *mb2.user) + && ((ps_global->VAR_USER_ID + && !strcmp(ps_global->VAR_USER_ID, mb1.user)) + || + (!ps_global->VAR_USER_ID + && ps_global->ui.login[0] + && !strcmp(ps_global->ui.login, mb1.user)))))); +} + + +int +is_imap_stream(MAILSTREAM *stream) +{ + return(stream && stream->dtb && stream->dtb->name + && !strcmp(stream->dtb->name, "imap")); +} + + +int +modern_imap_stream(MAILSTREAM *stream) +{ + return(is_imap_stream(stream) && LEVELIMAP4rev1(stream)); +} + + +/*---------------------------------------------------------------------- + Check and see if all the stream are alive + +Returns: 0 if there was no change + >0 if streams have died since last call + +Also outputs a message that the streams have died + ----*/ +int +streams_died(void) +{ + int rv = 0; + int i; + MAILSTREAM *m; + char *folder; + + for(i = 0; i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m && sp_dead_stream(m)){ + if(sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR)){ + if(!sp_noticed_dead_stream(m)){ + rv++; + sp_set_noticed_dead_stream(m, 1); + folder = STREAMNAME(m); + q_status_message1(SM_ORDER | SM_DING, 3, 3, + _("MAIL FOLDER \"%s\" CLOSED DUE TO ACCESS ERROR"), + short_str(pretty_fn(folder) ? pretty_fn(folder) : "?", + tmp_20k_buf+1000, SIZEOF_20KBUF-1000, 35, FrontDots)); + dprint((6, "streams_died: locked: \"%s\"\n", + folder)); + if(rv == 1){ + snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Folder \"%s\" is Closed"), + short_str(pretty_fn(folder) ? pretty_fn(folder) : "?", + tmp_20k_buf+1000, SIZEOF_20KBUF-1000, 35, FrontDots)); + if(pith_opt_icon_text) + (*pith_opt_icon_text)(tmp_20k_buf, IT_MCLOSED); + } + } + } + else{ + if(!sp_noticed_dead_stream(m)){ + sp_set_noticed_dead_stream(m, 1); + folder = STREAMNAME(m); + /* + * If a cached stream died and then we tried to use it + * it could cause problems. We could warn about it here + * but it may be confusing because it might be + * unrelated to what the user is doing and not cause + * any problem at all. + */ +#if 0 + if(sp_flagged(m, SP_USEPOOL)) + q_status_message(SM_ORDER, 3, 3, + "Warning: Possible problem accessing remote data, connection died."); +#endif + + dprint((6, "streams_died: not locked: \"%s\"\n", + folder)); + } + + pine_mail_actually_close(m); + } + } + } + + return(rv); +} + + +/* Some stream is locked checks to see if there is any stream for which we + * are in a callback from c-client + */ + +int +some_stream_is_locked(void) +{ + int rv = 0, i; + MAILSTREAM *m; + + for(i = 0; rv == 0 && i < ps_global->s_pool.nstream; i++){ + m = ps_global->s_pool.streams[i]; + if(m && m->lock) + rv++; + } + + return(rv); +} + +/* + * Very simple version of appenduid_cb until we need something + * more complex. + */ + +static imapuid_t last_append_uid; + +void +appenduid_cb(char *mailbox,unsigned long uidvalidity, SEARCHSET *set) +{ + last_append_uid = set ? set->first : 0L; +} + + +imapuid_t +get_last_append_uid(void) +{ + return last_append_uid; +} + + +/* + * mail_cmd_stream - return a stream suitable for mail_lsub, + * mail_subscribe, and mail_unsubscribe + * + */ +MAILSTREAM * +mail_cmd_stream(CONTEXT_S *context, int *closeit) +{ + char tmp[MAILTMPLEN]; + + *closeit = 1; + (void) context_apply(tmp, context, "x", sizeof(tmp)); + + return(pine_mail_open(NULL, tmp, + OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE, + NULL)); +} + + +/* + * This is so we can replace the old rfc822_ routines like rfc822_header_line + * with the new version that checks bounds, like rfc822_output_header_line. + * This routine is called when would be a bounds overflow, which we simply log + * and go on with the truncated data. + */ +long +dummy_soutr(void *stream, char *string) +{ + dprint((2, "dummy_soutr unexpected call, caught overflow\n")); + return LONGT; +} |