/*
 * ========================================================================
 * Copyright 2013-2026 Eduardo Chappa
 * Copyright 2006-2008 University of Washington
 *
 * 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, ps_global->signal_in_progress)));
    
    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") || !strncmp(d->name, "graph", 5))){
	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 usable, 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, ps_global->signal_in_progress)));
	    }

	    /*
	     * 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, ps_global->signal_in_progress)));

    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, ps_global->signal_in_progress)));
	    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, ps_global->signal_in_progress)));

	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, ps_global->signal_in_progress),
		(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)
		  alpine_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];
				/* MAILTMPLEN = sizeof(tmp)/2 */
	snprintf(tmp, sizeof(tmp), "%.*s%.*s", MAILTMPLEN-1, r ? r : "",
		MAILTMPLEN-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);

    ps_global->can_interrupt = 0;	/* never interrupt anything */

    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);
		 /* but can interrupt these ones */
	if(cmd && (!strucmp(cmd, "NOOP") || !strucmp(cmd, "LOGOUT")))
	  ps_global->can_interrupt = 1;
    }
}


/*
 * 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, ps_global->signal_in_progress)));
			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, ps_global->signal_in_progress)));
			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, ps_global->signal_in_progress)));
		    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, ps_global->signal_in_progress)));
			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
is_graph_stream(MAILSTREAM *stream)
{
    return(stream && stream->dtb && stream->dtb->name
           && !strncmp(stream->dtb->name, "graph", 5));
}


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;
    unsigned 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 = folder_name_decoded((unsigned char *)STREAMNAME(m));
		    q_status_message1(SM_ORDER | SM_DING, 3, 3,
			  _("MAIL FOLDER \"%s\" CLOSED DUE TO ACCESS ERROR"),
			  short_str(pretty_fn((char *) folder) ? pretty_fn((char *) 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((char *)folder) ? pretty_fn((char *)folder) : "?",
			  tmp_20k_buf+1000, SIZEOF_20KBUF-1000, 35, FrontDots));
			if(pith_opt_icon_text)
			  (*pith_opt_icon_text)(tmp_20k_buf, IT_MCLOSED);
		    }
		    if(folder) fs_give((void **)&folder);
		}
	    }
	    else{
		if(!sp_noticed_dead_stream(m)){
		    sp_set_noticed_dead_stream(m, 1);
		    folder = (unsigned char *) 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;
}

/*
 * checks if a specific configuration server is a graph server
 * returns 1 if it is a graph server, 0 otherwise
 */
int
is_graph_server(char *server)
{
  NETMBX mb;
  char *mailbox;
  int rv = 0;

  if (!server) return rv;
  mailbox = fs_get(strlen(server) + 2 + strlen("<none>") + 1);
  sprintf(mailbox, "{%s}<none>", server);
  mail_valid_net_parse (mailbox,&mb);
  if(!strucmp(mb.orighost, "graph.microsoft.com")
	&& !strncmp(mb.service, "graph", 5))
    rv = 1;
  fs_give((void **) &mailbox);

  return rv;
}
