/*
 * Program:	Interactive Message Access Protocol 4 (IMAP4) routines
 *
 * Author:	Mark Crispin
 *		Networks and Distributed Computing
 *		Computing & Communications
 *		University of Washington
 *		Administration Building, AG-44
 *		Seattle, WA  98195
 *		Internet: MRC@CAC.Washington.EDU
 *
 * Date:	15 June 1988
 * Last Edited:	11 January 1996
 *
 * Sponsorship:	The original version of this work was developed in the
 *		Symbolic Systems Resources Group of the Knowledge Systems
 *		Laboratory at Stanford University in 1987-88, and was funded
 *		by the Biomedical Research Technology Program of the National
 *		Institutes of Health under grant number RR-00785.
 *
 * Original version Copyright 1988 by The Leland Stanford Junior University
 * Copyright 1996 by the University of Washington
 *
 *  Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notices appear in all copies and that both the
 * above copyright notices and this permission notice appear in supporting
 * documentation, and that the name of the University of Washington or The
 * Leland Stanford Junior University not be used in advertising or publicity
 * pertaining to distribution of the software without specific, written prior
 * permission.  This software is made available "as is", and
 * THE UNIVERSITY OF WASHINGTON AND THE LELAND STANFORD JUNIOR UNIVERSITY
 * DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE,
 * INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL THE UNIVERSITY OF
 * WASHINGTON OR THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY
 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
 * CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */


#include <ctype.h>
#include <stdio.h>
#include "mail.h"
#include "osdep.h"
#include "imap4.h"
#include "rfc822.h"
#include "misc.h"

/* Driver dispatch used by MAIL */

DRIVER imapdriver = {
  "imap4",			/* driver name */
  DR_MAIL|DR_NEWS|DR_NAMESPACE,	/* driver flags */
  (DRIVER *) NIL,		/* next driver */
  imap_valid,			/* mailbox is valid for us */
  imap_parameters,		/* manipulate parameters */
  imap_scan,			/* scan mailboxes */
  imap_list,			/* find mailboxes */
  imap_lsub,			/* find subscribed mailboxes */
  imap_subscribe,		/* subscribe to mailbox */
  imap_unsubscribe,		/* unsubscribe from mailbox */
  imap_create,			/* create mailbox */
  imap_delete,			/* delete mailbox */
  imap_rename,			/* rename mailbox */
  imap_status,			/* status of mailbox */
  imap_open,			/* open mailbox */
  imap_close,			/* close mailbox */
  imap_fetchfast,		/* fetch message "fast" attributes */
  imap_fetchflags,		/* fetch message flags */
  imap_fetchstructure,		/* fetch message envelopes */
  imap_fetchheader,		/* fetch message header only */
  imap_fetchtext,		/* fetch message body only */
  imap_fetchbody,		/* fetch message body section */
  imap_uid,			/* unique identifier */
  imap_setflag,			/* set message flag */
  imap_clearflag,		/* clear message flag */
  imap_search,			/* search for message based on criteria */
  imap_ping,			/* ping mailbox to see if still alive */
  imap_check,			/* check for new messages */
  imap_expunge,			/* expunge deleted messages */
  imap_copy,			/* copy messages to another mailbox */
  imap_append,			/* append string message to mailbox */
  imap_gc			/* garbage collect stream */
};

				/* prototype stream */
MAILSTREAM imapproto = {&imapdriver};

				/* driver parameters */
static long imap_maxlogintrials = MAXLOGINTRIALS;
static long imap_lookahead = IMAPLOOKAHEAD;
static long imap_uidlookahead = IMAPUIDLOOKAHEAD;
static long imap_defaultport = 0;
static long imap_prefetch = IMAPLOOKAHEAD;
static long imap_closeonerror = NIL;



#ifndef WOGGLE
static long imap2_findall = 0;
#endif /* WOGGLE */




/* IMAP validate mailbox
 * Accepts: mailbox name
 * Returns: our driver if name is valid, NIL otherwise
 */

DRIVER *imap_valid (char *name)
{
  return mail_valid_net (name,&imapdriver,NIL,NIL);
}


/* IMAP manipulate driver parameters
 * Accepts: function code
 *	    function-dependent value
 * Returns: function-dependent return value
 */

void *imap_parameters (long function,void *value)
{
  switch ((int) function) {
  case SET_MAXLOGINTRIALS:
    imap_maxlogintrials = (long) value;
    break;
  case GET_MAXLOGINTRIALS:
    value = (void *) imap_maxlogintrials;
    break;
  case SET_LOOKAHEAD:
    imap_lookahead = (long) value;
    break;
  case GET_LOOKAHEAD:
    value = (void *) imap_lookahead;
    break;
  case SET_UIDLOOKAHEAD:
    imap_uidlookahead = (long) value;
    break;
  case GET_UIDLOOKAHEAD:
    value = (void *) imap_uidlookahead;
    break;
  case SET_IMAPPORT:
    imap_defaultport = (long) value;
    break;
  case GET_IMAPPORT:
    value = (void *) imap_defaultport;
    break;
  case SET_PREFETCH:
    imap_prefetch = (long) value;
    break;
  case GET_PREFETCH:
    value = (void *) imap_prefetch;
    break;
  case SET_CLOSEONERROR:
    imap_closeonerror = (long) value;
    break;
  case GET_CLOSEONERROR:
    value = (void *) imap_closeonerror;
    break;

#ifndef WOGGLE
  case SET_IMAP2FINDALL:
    imap2_findall = (long) value;
    break;
#endif /* WOGGLE */

  default:
    value = NIL;		/* error case */
    break;
  }
  return value;
}

/* IMAP scan mailboxes
 * Accepts: mail stream
 *	    reference
 *	    pattern to search
 *	    string to scan
 */

void imap_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
{
  imap_list_work (stream,ref,pat,T,contents);
}


/* IMAP list mailboxes
 * Accepts: mail stream
 *	    reference
 *	    pattern to search
 */

void imap_list (MAILSTREAM *stream,char *ref,char *pat)
{
  imap_list_work (stream,ref,pat,T,NIL);
}


/* IMAP list subscribed mailboxes
 * Accepts: mail stream
 *	    reference
 *	    pattern to search
 */

void imap_lsub (MAILSTREAM *stream,char *ref,char *pat)
{
  void *sdb = NIL;
  char *s;
  imap_list_work (stream,ref,pat,NIL,NIL);
				/* only if null stream and * requested */
  if (!stream && !ref && !strcmp (pat,"*") && (s = sm_read (&sdb)))
    do if (imap_valid (s)) mm_lsub (stream,NIL,s,NIL);
  while (s = sm_read (&sdb));	/* until no more subscriptions */
}

/* IMAP find list of mailboxes
 * Accepts: mail stream
 *	    reference
 *	    pattern to search
 *	    list flag
 *	    string to scan
 */

void imap_list_work (MAILSTREAM *stream,char *ref,char *pat,long list,
		     char *contents)
{
  MAILSTREAM *st = stream;
  int pl;
  char *s,prefix[MAILTMPLEN],mbx[MAILTMPLEN];
  IMAPARG *args[4],aref,apat,acont;
  if (ref && *ref) {		/* have a reference? */
    if (!(imap_valid (ref) &&	/* make sure valid IMAP name and open stream */
	  ((stream && LOCAL && LOCAL->tcpstream) ||
	   (stream = mail_open (NIL,ref,OP_HALFOPEN|OP_SILENT))))) return;
				/* calculate prefix length */
    pl = strchr (ref,'}') + 1 - ref;
    strncpy (prefix,ref,pl);	/* build prefix */
    prefix[pl] = '\0';		/* tie off prefix */
    ref += pl;			/* update reference */
  }
  else {
    if (!(imap_valid (pat) &&	/* make sure valid IMAP name and open stream */
	  ((stream && LOCAL && LOCAL->tcpstream) ||
	   (stream = mail_open (NIL,pat,OP_HALFOPEN|OP_SILENT))))) return;
				/* calculate prefix length */
    pl = strchr (pat,'}') + 1 - pat;
    strncpy (prefix,pat,pl);	/* build prefix */
    prefix[pl] = '\0';		/* tie off prefix */
    pat += pl;			/* update reference */
  }
  LOCAL->prefix = prefix;	/* note prefix */
  if (contents) {		/* want to do a scan? */
    if (LOCAL->use_scan) {	/* can we? */
      args[0] = &aref; args[1] = &apat; args[2] = &acont; args[3] = NIL;
      aref.type = ASTRING; aref.text = (void *) (ref ? ref : "");
      apat.type = ASTRING; apat.text = (void *) pat;
      acont.type = ASTRING; acont.text = (void *) contents;
      imap_send (stream,"SCAN",args);
    }
    else mm_log ("Scan not valid on this IMAP server",WARN);
  }

  else if (LOCAL->imap4) {	/* easy if just IMAP4 */
    args[0] = &aref; args[1] = &apat; args[2] = NIL;
    aref.type = ASTRING; aref.text = (void *) (ref ? ref : "");
    apat.type = ASTRING; apat.text = (void *) pat;
    imap_send (stream,list ? "LIST" : "LSUB",args);
  }
  else if (LOCAL->rfc1176) {	/* convert to IMAP2 format wildcard */
				/* kludgy application of reference */
    if (ref && *ref) sprintf (mbx,"%s%s",ref,pat);
    else strcpy (mbx,pat);
    for (s = mbx; *s; s++) if (*s == '%') *s = '*';
    args[0] = &apat; args[1] = NIL;
    apat.type = ASTRING; apat.text = (void *) mbx;


#ifndef WOGGLE
    if(!list) {
      if(!(strcmp(imap_send(stream,"FIND MAILBOXES",args)->key,"BAD")))
	LOCAL->rfc1176 = NIL;
    }
    else {
      if(!(strcmp(imap_send(stream,"FIND ALL.MAILBOXES",args)->key,"BAD")))
	LOCAL->rfc1176 = NIL;
    }
#else /* WOGGLE */

    if (!(list &&		/* try IMAP2bis, then RFC-1176 */
	  strcmp (imap_send (stream,"FIND ALL.MAILBOXES",args)->key,"BAD")) ||
	!strcmp (imap_send (stream,"FIND MAILBOXES",args)->key,"BAD"))
      LOCAL->rfc1176 = NIL;	/* must be RFC-1064 */

#endif /* WOGGLE */

  }
  LOCAL->prefix = NIL;		/* no more prefix */
				/* close temporary stream if we made one */
  if (stream != st) mail_close (stream);
}


/* IMAP subscribe to mailbox
 * Accepts: mail stream
 *	    mailbox to add to subscription list
 * Returns: T on success, NIL on failure
 */

long imap_subscribe (MAILSTREAM *stream,char *mailbox)
{
  return imap_manage (stream,mailbox,"Subscribe Mailbox",NIL);
}


/* IMAP unsubscribe to mailbox
 * Accepts: mail stream
 *	    mailbox to delete from manage list
 * Returns: T on success, NIL on failure
 */

long imap_unsubscribe (MAILSTREAM *stream,char *mailbox)
{
  return imap_manage (stream,mailbox,"Unsubscribe Mailbox",NIL);
}

/* IMAP create mailbox
 * Accepts: mail stream
 *	    mailbox name to create
 * Returns: T on success, NIL on failure
 */

long imap_create (MAILSTREAM *stream,char *mailbox)
{
  return imap_manage (stream,mailbox,"Create",NIL);
}


/* IMAP delete mailbox
 * Accepts: mail stream
 *	    mailbox name to delete
 * Returns: T on success, NIL on failure
 */

long imap_delete (MAILSTREAM *stream,char *mailbox)
{
  return imap_manage (stream,mailbox,"Delete",NIL);
}


/* IMAP rename mailbox
 * Accepts: mail stream
 *	    old mailbox name
 *	    new mailbox name
 * Returns: T on success, NIL on failure
 */

long imap_rename (MAILSTREAM *stream,char *old,char *newname)
{
  return imap_manage (stream,old,"Rename",newname);
}

/* IMAP manage a mailbox
 * Accepts: mail stream
 *	    mailbox to manipulate
 *	    command to execute
 *	    optional second argument
 * Returns: T on success, NIL on failure
 */

long imap_manage (MAILSTREAM *stream,char *mailbox,char *command,char *arg2)
{
  MAILSTREAM *st = stream;
  IMAPPARSEDREPLY *reply;
  long ret = NIL;
  char mbx[MAILTMPLEN],mbx2[MAILTMPLEN];
  IMAPARG *args[3],ambx,amb2;
  ambx.type = amb2.type = ASTRING; ambx.text = (void *) mbx;
  amb2.text = (void *) mbx2;
  args[0] = &ambx; args[1] = args[2] = NIL;
				/* require valid names and open stream */
  if (mail_valid_net (mailbox,&imapdriver,NIL,mbx) &&
      (arg2 ? mail_valid_net (arg2,&imapdriver,NIL,mbx2) : &imapdriver) &&
      ((stream && LOCAL && LOCAL->tcpstream) ||
       (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT)))) {
    if (arg2) args[1] = &amb2;	/* second arg present? */
    ret = (imap_OK (stream,reply = imap_send (stream,command,args)));
    mm_log (reply->text,ret ? (long) NIL : ERROR);
				/* toss out temporary stream */
    if (st != stream) mail_close (stream);
  }
  return ret;
}

/* IMAP status
 * Accepts: mail stream
 *	    mailbox name
 *	    status flags
 * Returns: T on success, NIL on failure
 */

long imap_status (MAILSTREAM *stream,char *mbx,long flags)
{
  IMAPARG *args[3],ambx,aflg;
  char tmp[MAILTMPLEN];
  NETMBX mb;
  unsigned long i;
  long ret = NIL;
  MAILSTREAM *tstream = stream;
  mail_valid_net_parse (mbx,&mb);
				/* can't use stream if not IMAP4 or halfopen */
  if (stream && (!(LOCAL->use_status || stream->halfopen) ||
		 strcmp (ucase (strcpy (tmp,imap_host (stream))),
			 ucase (mb.host)))) return imap_status(NIL,mbx,flags);
				/* make stream if don't have one */
  if (!stream) stream = mail_open (NIL,mbx,OP_HALFOPEN|OP_SILENT);
  args[0] = &ambx;args[1] = NIL;/* set up first argument as mailbox */
  ambx.type = ASTRING; ambx.text = (void *) mb.mailbox;
  if (LOCAL->use_status) {	/* IMAP4 way */
    aflg.type = FLAGS; aflg.text = (void *) tmp;
    args[1] = &aflg; args[2] = NIL;
    tmp[0] = tmp[1] = '\0';	/* build flag list */
    if (flags & SA_MESSAGES) strcat (tmp," MESSAGES");
    if (flags & SA_RECENT) strcat (tmp," RECENT");
    if (flags & SA_UNSEEN) strcat (tmp," UNSEEN");
    if (flags & SA_UIDNEXT) strcat (tmp," UID-NEXT");
    if (flags & SA_UIDVALIDITY) strcat (tmp," UID-VALIDITY");
    tmp[0] = '(';
    strcat (tmp,")");
				/* send "STATUS mailbox flag" */
    if (imap_OK (stream,imap_send (stream,"STATUS",args))) ret = T;
  }

				/* IMAP2 way */
  else if (imap_OK (stream,imap_send (stream,"EXAMINE",args))) {
    MAILSTATUS status;
    status.flags = flags & ~ (SA_UIDNEXT | SA_UIDVALIDITY);
    status.messages = stream->nmsgs;
    status.recent = stream->recent;
    status.unseen = 0;
    if (flags & SA_UNSEEN) {	/* must search to get unseen messages */
				/* clear search vector */
      for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = NIL;
      if (imap_OK (stream,imap_send (stream,"SEARCH UNSEEN",NIL)))
	for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
	  if (mail_elt (stream,i)->searched) status.unseen++;
    }
    sprintf (tmp,"{%s}%s",imap_host (stream),mb.mailbox);
				/* pass status to main program */
    mm_status (stream,tmp,&status);
    ret = T;			/* note success */
  }
  if (stream != tstream) mail_close (stream);
  return ret;			/* success */
}

/* IMAP open
 * Accepts: stream to open
 * Returns: stream to use on success, NIL on failure
 */

MAILSTREAM *imap_open (MAILSTREAM *stream)
{
  unsigned long i,j;
  char c[2],tmp[MAILTMPLEN];
  NETMBX mb;
  char *s;
  void *tstream;
  IMAPPARSEDREPLY *reply = NIL;
  IMAPARG *args[3];
				/* return prototype for OP_PROTOTYPE call */
  if (!stream) return &imapproto;
  mail_valid_net_parse (stream->mailbox,&mb);
  if (LOCAL) {			/* if stream opened earlier by us */
    if (strcmp (ucase (strcpy (tmp,mb.host)),
		ucase (strcpy (tmp+(MAILTMPLEN/2),imap_host (stream))))) {
				/* if hosts are different punt it */
      sprintf (tmp,"Closing connection to %s",imap_host (stream));
      if (!stream->silent) mm_log (tmp,(long) NIL);
      imap_close (stream,NIL);
    }	
    else {			/* else recycle if still alive */
      i = stream->silent;	/* temporarily mark silent */
      stream->silent = T;	/* don't give mm_exists() events */
      j = imap_ping (stream);	/* learn if stream still alive */
      stream->silent = i;	/* restore prior state */
      if (j) {			/* was stream still alive? */
	sprintf (tmp,"Reusing connection to %s",mb.host);
	if (!stream->silent) mm_log (tmp,(long) NIL);
	imap_gc (stream,GC_TEXTS);
      }
      else imap_close (stream,NIL);
    }
    mail_free_cache (stream);
  }
				/* in case /debug switch given */
  if (mb.dbgflag) stream->debug = T;

  if (!LOCAL) {			/* open new connection if no recycle */
    stream->local = fs_get (sizeof (IMAPLOCAL));
    LOCAL->reply.line = LOCAL->reply.tag = LOCAL->reply.key =
      LOCAL->reply.text = LOCAL->prefix = NIL;
    LOCAL->stat = NIL;
    LOCAL->imap4 = LOCAL->uidsearch = LOCAL->use_status = LOCAL->use_scan =
      NIL;			/* assume not IMAP4 (yet anyway) */
    LOCAL->use_auth = NIL;	/* no authenticators yet */
				/* assume IMAP2bis server */
    LOCAL->imap2bis = LOCAL->rfc1176 = T;
    LOCAL->tcpstream = NIL;	/* no TCP stream yet */
				/* try preauthenticated open */
    if (tstream = (stream->anonymous || mb.anoflag || mb.port) ? NIL :
	tcp_aopen (mb.host,"/etc/rimapd")) {
				/* if success, see if reasonable banner */
      if (tcp_getbuffer (tstream,(long) 1,c) && (*c == '*')) {
	i = 0;			/* copy to buffer */
	do tmp[i++] = *c;
	while (tcp_getbuffer (tstream,(long) 1,c) && (*c != '\015') &&
	       (*c != '\012') && (i < (MAILTMPLEN-1)));
	tmp[i] = '\0';		/* tie off */
				/* snarfed a valid greeting? */
	if ((*c == '\015') && tcp_getbuffer (tstream,(long) 1,c) &&
	    (*c == '\012') &&
	    !strcmp((reply=imap_parse_reply(stream,s=cpystr(tmp)))->tag,"*")) {
				/* parse as IMAP */
	  imap_parse_unsolicited (stream,reply);
				/* set local stream if authenticated */
	  if (!strcmp (reply->key,"PREAUTH")) {
	    LOCAL->tcpstream = tstream;
				/* note if it was IMAP4 */
	    imap_send (stream,"CAPABILITY",NIL);
	  }
	}
      }
    }

    if (!LOCAL->tcpstream) {	/* didn't preauth? */
				/* clean up preauth stream */
      if (tstream) tcp_close (tstream);
				/* set up host with port override */
      if (mb.port || imap_defaultport)
	sprintf(s = tmp,"%s:%ld",mb.host,mb.port ? mb.port : imap_defaultport);
      else s = mb.host;		/* simple host name */
				/* try to open ordinary connection */
      if (LOCAL->tcpstream = tcp_open (s,"imap",IMAPTCPPORT)) {
	if (!imap_OK (stream,reply = imap_reply (stream,NIL))) {
	  mm_log (reply->text,ERROR);
	  if (LOCAL->tcpstream) tcp_close (LOCAL->tcpstream);
	  LOCAL->tcpstream = NIL;
	}
	else {			/* note if IMAP4 */
	  imap_send (stream,"CAPABILITY",NIL);
	  for (i = imap_maxlogintrials; i && LOCAL && LOCAL->tcpstream;) {
	    if (LOCAL->use_auth && !(mb.anoflag || stream->anonymous))
	      imap_auth (stream,&mb,tmp,&i);
	    else {		/* only so many tries to login */
	      char usr[MAILTMPLEN];
	      IMAPARG ausr,apwd;
	      ausr.type = apwd.type = ASTRING;
	      ausr.text = (void *) usr;
	      apwd.text = (void *) tmp;
	      args[0] = &ausr; args[1] = &apwd; args[2] = NIL;

	      tmp[0] = 0;	/* get password */
				/* automatic if want anonymous access */
	      if (mb.anoflag || stream->anonymous) {
		strcpy (usr,"anonymous");
		strcpy (tmp,tcp_localhost (LOCAL->tcpstream));
		i = 1;		/* only one trial */
	      }
				/* otherwise prompt user */
	      else mm_login (&mb,usr,tmp,imap_maxlogintrials - i);
	      if (!tmp[0]) {	/* user refused to give a password */
		mm_log ("Login aborted",ERROR);
		if (LOCAL->tcpstream) tcp_close (LOCAL->tcpstream);
		LOCAL->tcpstream = NIL;
	      }
				/* send "LOGIN usr tmp" */
	      else if (imap_OK(stream,reply = imap_send(stream,"LOGIN",args))){
				/* login successful, note if anonymous */
		stream->anonymous = strcmp (usr,"anonymous") ? NIL : T;
		i = 0;		/* exit login loop */
	      }
	      else mm_log (reply->text,WARN);
	    }
	    if (i && !--i) {	/* login failed */
	      mm_log ("Too many login failures",ERROR);
	      if (LOCAL->tcpstream) tcp_close (LOCAL->tcpstream);
	      LOCAL->tcpstream = NIL;
	    }
	  }
	}
      }
    }
				/* failed utterly to open */
    if (LOCAL && !LOCAL->tcpstream) imap_close (stream,NIL);
  }

  if (LOCAL) {			/* have a connection now??? */
    IMAPARG ambx;
    ambx.type = ASTRING;
    ambx.text = (void *) mb.mailbox;
    args[0] = &ambx; args[1] = NIL;
    stream->perm_seen = stream->perm_deleted = stream->perm_answered =
      stream->perm_draft = LOCAL->imap4 ? NIL : T;
    stream->perm_user_flags = LOCAL->imap4 ? NIL : 0xffffffff;
    stream->sequence++;		/* bump sequence number */
    if ((i = imap_port (stream)) & 0xffff0000) i = imap_defaultport ? 
      imap_defaultport : IMAPTCPPORT;
    sprintf (tmp,"{%s:%lu/imap}",imap_host (stream),i);
    if (stream->halfopen ||	/* send "SELECT/EXAMINE mailbox" */
	!imap_OK (stream,reply = imap_send (stream,stream->rdonly ?
					    "EXAMINE": "SELECT",args))) {
				/* want die on error and not halfopen? */
      if (imap_closeonerror && !stream->halfopen) {
	if (LOCAL && LOCAL->tcpstream) imap_close (stream,NIL);
	return NIL;		/* return open failure */
      }
      strcat (tmp,"<no_mailbox>");
      if (!stream->halfopen) {	/* output error message if didn't ask for it */
	mm_log (reply->text,ERROR);
	stream->halfopen = T;
      }
				/* make sure dummy message counts */
      mail_exists (stream,(long) 0);
      mail_recent (stream,(long) 0);
    }
    else {
      strcat (tmp,mb.mailbox);	/* mailbox name */
      reply->text[11] = '\0';	/* note if server said it was readonly */
      stream->rdonly = !strcmp (ucase (reply->text),"[READ-ONLY]");
    }
    fs_give ((void **) &stream->mailbox);
    stream->mailbox = cpystr (tmp);
    if (!(stream->nmsgs || stream->silent || stream->halfopen))
      mm_log ("Mailbox is empty",(long) NIL);
  }
				/* give up if nuked during startup */
  if (LOCAL && !LOCAL->tcpstream) imap_close (stream,NIL);
  return LOCAL ? stream : NIL;	/* if stream is alive, return to caller */
}

/* IMAP authenticate
 * Accepts: stream to authenticate
 *	    parsed network mailbox structure
 *	    scratch buffer
 *	    pointer to trial countdown
 */

void imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,unsigned long *trial)
{
  int i;
  char tag[16];
  AUTHENTICATOR *auth;
  for (i = 0, auth = authenticators;
       LOCAL->tcpstream && auth && (i < MAXAUTHENTICATORS);
       i++, auth = auth->next) {
  				/* gensym a new tag */
    sprintf (tag,"A%05ld",stream->gensym++);
				/* build command */
    sprintf (tmp,"%s AUTHENTICATE %s",tag,auth->name);
    if (stream->debug) mm_dlog (tmp);
    strcat (tmp,"\015\012");
    if (!tcp_soutr (LOCAL->tcpstream,tmp)) break;
    if (!(*auth->client) (imap_challenge,imap_response,mb,stream,
			  imap_maxlogintrials - *trial)) return;
				/* make sure have tagged response */
    while (LOCAL->tcpstream && strcmp (imap_reply (stream,tag)->tag,tag)) {
      if (stream->debug) mm_dlog ("*");
      if (!tcp_soutr (LOCAL->tcpstream,"*\015\012")) return;
    }
				/* if got success response */
    if (LOCAL->tcpstream && imap_OK (stream,&LOCAL->reply)) {
      *trial = 0;
      return;
    }
  }
  if (LOCAL->tcpstream) tcp_close (LOCAL->tcpstream);
  LOCAL->tcpstream = NIL;
}

/* Get challenge to authenticator in binary
 * Accepts: stream
 *	    pointer to returned size
 * Returns: challenge or NIL if not challenge
 */

void *imap_challenge (void *s,unsigned long *len)
{
  MAILSTREAM *stream = (MAILSTREAM *) s;
  IMAPPARSEDREPLY *reply =
    imap_parse_reply (stream,tcp_getline (LOCAL->tcpstream));
  return strcmp (reply->tag,"+") ? NIL :
    rfc822_base64 ((unsigned char *) reply->text,strlen (reply->text),len);
}


/* Send authenticator response in BASE64
 * Accepts: MAIL stream
 *	    string to send
 *	    length of string
 * Returns: T if successful, else NIL
 */

long imap_response (void *s,char *response,unsigned long size)
{
  MAILSTREAM *stream = (MAILSTREAM *) s;
  unsigned long len;
  char *t = (char *) rfc822_binary ((void *) response,size,&len);
  long ret = tcp_sout (LOCAL->tcpstream,t,len);
  if (stream->debug && (len > 2)) {
    t[len -2] = '\0';		/* tie off CRLF */
    mm_dlog (t);
  }
  fs_give ((void **) &t);
  return ret;
}

/* IMAP close
 * Accepts: MAIL stream
 *	    option flags
 */

void imap_close (MAILSTREAM *stream,long options)
{
  IMAPPARSEDREPLY *reply;
  if (stream && LOCAL) {	/* send "LOGOUT" */
				/* expunge silently if requested */
    if (options & CL_EXPUNGE) imap_send (stream,"EXPUNGE",NIL);
    if (LOCAL->tcpstream &&
	!imap_OK (stream,reply = imap_send (stream,"LOGOUT",NIL)))
      mm_log (reply->text,WARN);
				/* close TCP connection if still open */
    if (LOCAL->tcpstream) tcp_close (LOCAL->tcpstream);
    LOCAL->tcpstream = NIL;
				/* free up memory */
    if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
    imap_gc (stream,GC_TEXTS);	/* nuke the cached strings */
				/* nuke the local data */
    fs_give ((void **) &stream->local);
  }
}


/* IMAP fetch fast information
 * Accepts: MAIL stream
 *	    sequence
 *	    option flags
 *
 * Generally, imap_fetchstructure is preferred
 */

void imap_fetchfast (MAILSTREAM *stream,char *sequence,long flags)
{				/* send "FETCH sequence FAST" */
  char *cmd = (LOCAL->imap4 && (flags & FT_UID)) ? "UID FETCH" : "FETCH";
  IMAPPARSEDREPLY *reply;
  IMAPARG *args[3],aseq,aatt;
  aseq.type = SEQUENCE; aseq.text = (void *) sequence;
  aatt.type = ATOM; aatt.text = (void *) "FAST";
  args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
  if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
    mm_log (reply->text,ERROR);
}


/* IMAP fetch flags
 * Accepts: MAIL stream
 *	    sequence
 *	    option flags
 */

void imap_fetchflags (MAILSTREAM *stream,char *sequence,long flags)
{				/* send "FETCH sequence FLAGS" */
  char *cmd = (LOCAL->imap4 && (flags & FT_UID)) ? "UID FETCH" : "FETCH";
  IMAPPARSEDREPLY *reply;
  IMAPARG *args[3],aseq,aatt;
  aseq.type = SEQUENCE; aseq.text = (void *) sequence;
  aatt.type = ATOM; aatt.text = (void *) "FAST";
  args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
  if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
    mm_log (reply->text,ERROR);
}

/* IMAP fetch structure
 * Accepts: MAIL stream
 *	    message # to fetch
 *	    pointer to return body
 *	    option flags
 * Returns: envelope of this message, body returned in body value
 *
 * Fetches the "fast" information as well
 */

ENVELOPE *imap_fetchstructure (MAILSTREAM *stream,unsigned long msgno,
			       BODY **body,long flags)
{
  unsigned long i,j,k;
  char *cmd = "FETCH";
  char *s,seq[128],tmp[MAILTMPLEN];
  ENVELOPE **env = &stream->env;
  BODY **b = &stream->body;
  IMAPPARSEDREPLY *reply = NIL;
  IMAPARG *args[3],aseq,aatt;
  args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
  aseq.type = SEQUENCE; aseq.text = (void *) seq;
  aatt.type = ATOM;
  sprintf (seq,"%ld",msgno);	/* assume no lookahead */
				/* never lookahead with UID fetching */
  if (LOCAL->imap4 && (flags & FT_UID)) {
    cmd = "UID FETCH";
    mail_free_envelope (env);
    mail_free_body (b);
    stream->msgno = 0;		/* nuke any attempt to reuse this */
  }
  else if (stream->scache) {	/* short cache */
    if (msgno != stream->msgno){/* flush old poop if a different message */
      mail_free_envelope (env);
      mail_free_body (b);
    }
    stream->msgno = msgno;
  }

  else {			/* long cache */
    LONGCACHE *lelt = mail_lelt (stream,msgno);
    env = &lelt->env;		/* get pointers to envelope and body */
    b = &lelt->body;
				/* prefetch if don't have envelope */
    if ((k = imap_lookahead) && !*env)
				/* build message number list */
      for (i = msgno + 1, s = seq; k && (i <= stream->nmsgs); i++)
	if (!mail_lelt (stream,i)->env) {
	  s += strlen (s);	/* find string end, see if nearing end */
	  if ((s - seq) > (MAILTMPLEN - 20)) break;
	  sprintf (s,",%ld",i);	/* append message */
 	  for (j = i + 1, k--;	/* hunt for last message without an envelope */
	       k && (j <= stream->nmsgs) && !mail_lelt (stream,j)->env;
	       j++, k--);
				/* if different, make a range */
	  if (i != --j) sprintf (s + strlen (s),":%ld",i = j);
	}
  }
				/* flush previous header */
  if (stream->text) fs_give ((void **) &stream->text);
  tmp[0] = '\0';		/* initialize command */
				/* have envelope yet? */
  if (!*env) strcat (tmp,LOCAL->imap4 ?
		     " ENVELOPE" :
		     " RFC822.HEADER");
				/* want body? */
  if (LOCAL->imap2bis && body && !*b)
    strcat (tmp,LOCAL->imap4 ? " BODYSTRUCTURE" : " BODY");
  if (tmp[0]) {			/* need to fetch anything? */
    tmp[0] = '(';		/* yes, make into a list */
    strcat (tmp," FLAGS INTERNALDATE RFC822.SIZE)");
    aatt.text = (void *) tmp;	/* do the built command */
    if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) {
				/* failed, probably RFC-1176 server */
      if (LOCAL->imap2bis && body && !*b) {
	aatt.text = (void *) "RFC822.HEADER FLAGS INTERNALDATE RFC822.SIZE";
	reply = imap_send (stream,cmd,args);
	LOCAL->imap2bis = NIL;	/* doesn't have body capabilities */
      }
    }
    if (!imap_OK (stream,reply)) mm_log (reply->text,ERROR);
  }
  if (body) *body = *b;		/* return the body */
  return *env;			/* return the envelope */
}

/* IMAP fetch message header
 * Accepts: MAIL stream
 *	    message # to fetch
 * Returns: message header in RFC822 format
 */

char *imap_fetchheader (MAILSTREAM *stream,unsigned long msgno,
			STRINGLIST *lines,unsigned long *len,long flags)
{
  mailgets_t mg = (mailgets_t) mail_parameters (NIL,GET_GETS,NIL);
  char *cmd = (LOCAL->imap4 && (flags & FT_UID)) ? "UID FETCH" : "FETCH";
  IMAPPARSEDREPLY *reply;
  MESSAGECACHE *elt = NIL;
  unsigned long i;
  IMAPARG *args[4],aseq,aatt,ahdr;
  if (len) *len = 0;		/* no data returned yet */
  if (flags & FT_UID) {		/* UID form of call */
    for (i = 1; i <= stream->nmsgs; i++)
      if (mail_elt (stream,i)->uid == msgno)
	return imap_fetchheader (stream,i,lines,len,flags & ~FT_UID);
  }
  else elt = mail_elt (stream,msgno);
  aseq.type = NUMBER; aseq.text = (void *) msgno;
  aatt.type = ATOM;
  args[0] = &aseq; args[1] = &aatt; args[2] = args[3] = NIL;
  if (LOCAL->imap4 && lines) {	/* want filtered header? */
    aatt.text = (void *)
      ((flags & FT_NOT) ? "RFC822.HEADER.LINES.NOT" : "RFC822.HEADER.LINES");
    ahdr.type = LIST; ahdr.text = (void *) lines;
    args[2] = &ahdr;
  }
  else aatt.text = (void *) ((flags & FT_PREFETCHTEXT) ?
			     "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER");
				/* flush previous header */
  if (stream->text) fs_give ((void **) &stream->text);
				/* force into memory */
  mail_parameters (NIL,SET_GETS,NIL);
				/* send "FETCH msgno RFC822.HEADER" */
  if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
    mm_log (reply->text,ERROR);
				/* restore mailgets routine */
  mail_parameters (NIL,SET_GETS,(void *) mg);
  if (flags & FT_UID) {		/* hunt for UID */
    for (i = 1; i <= stream->nmsgs; i++)
      if ((elt = mail_elt (stream,i))->uid == msgno) break;
    return "";			/* UID not found */
  }	
  if (stream->text) {		/* got returned data? */
    if (!LOCAL->imap4 && lines)	/* see if have to filter locally */
      elt->data3 = mail_filter ((char *) stream->text,elt->data3,lines,flags);
    if (len) *len = elt->data3;	/* return data size */
    return (char *) stream->text;
  }
  else return "";		/* couldn't find anything */
}

/* IMAP fetch message text (body only)
 * Accepts: MAIL stream
 *	    message # to fetch
 *	    returned message length
 *	    option flags
 * Returns: message text in RFC822 format
 */

char *imap_fetchtext (MAILSTREAM *stream,unsigned long msgno,
		      unsigned long *len,long flags)
{
  char *cmd = (LOCAL->imap4 && (flags & FT_UID)) ? "UID FETCH" : "FETCH";
  IMAPPARSEDREPLY *reply;
  MESSAGECACHE *elt = NIL;
  unsigned long i;
  IMAPARG *args[3],aseq,aatt;
  if (len) *len = 0;		/* no data returned yet */
  if (flags & FT_UID) {		/* UID form of call */
    for (i = 1; i <= stream->nmsgs; i++)
      if (mail_elt (stream,i)->uid == msgno)
	return imap_fetchtext (stream,i,len,flags & ~FT_UID);
  }
  else elt = mail_elt (stream,msgno);
  if (!(elt && elt->data2)) {	/* not if already cached */
    aseq.type = NUMBER; aseq.text = (void *) msgno;
    aatt.type = ATOM;
    aatt.text = (void *)
      ((LOCAL->imap4 && (flags & FT_PEEK)) ? "RFC822.TEXT.PEEK":"RFC822.TEXT");
    args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
    if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
      mm_log (reply->text,ERROR);
  }
  if (flags & FT_UID) {		/* hunt for UID */
    for (i = 1; i <= stream->nmsgs; i++)
      if ((elt = mail_elt (stream,i))->uid == msgno) break;
    return "";			/* UID not found */
  }	
  if (elt->data2) {		/* got returned data? */
    if (len) *len = elt->data4;	/* return data size */
    return (char *) elt->data2;
  }
  else return "";		/* couldn't find anything */
}

/* IMAP fetch message body as a structure
 * Accepts: Mail stream
 *	    message # to fetch
 *	    section specifier
 *	    pointer to length
 *	    option flags
 * Returns: pointer to section of message body
 */

char *imap_fetchbody (MAILSTREAM *stream,unsigned long msgno,char *sec,
		      unsigned long *len,long flags)
{
  char *cmd = (LOCAL->imap4 && (flags & FT_UID)) ? "UID FETCH" : "FETCH";
  BODY *b;
  PART *pt;
  char *s = sec;
  char **ss = NIL;
  unsigned long i;
  IMAPPARSEDREPLY *reply;
  IMAPARG *args[3],aseq,aatt;
  if (len) *len = 0;		/* in case failure */
				/* if bodies not supported */
  if (!(LOCAL->imap4 || LOCAL->imap2bis))
    return strcmp (sec,"1") ? NIL : imap_fetchtext (stream,msgno,len,flags);
  if (flags & FT_UID) {		/* UID form of call */
    for (i = 1; i <= stream->nmsgs; i++)
      if (mail_elt (stream,i)->uid == msgno)
	return imap_fetchbody (stream,i,sec,len,flags & ~FT_UID);
  }
				/* make sure have a body */
  if (!(imap_fetchstructure (stream,msgno,&b,flags) && b))
    return strcmp (sec,"1") ? NIL : imap_fetchtext (stream,msgno,len,flags);
  if (!(s && *s && isdigit (*s))) return NIL;
  if (!(i = strtoul (s,&s,10)))	/* get section number, header if 0 */
    return imap_fetchheader (stream,msgno,NIL,len,flags);
  aseq.type = NUMBER; aseq.text = (void *) msgno;
  aatt.type = (LOCAL->imap4 && (flags & FT_PEEK)) ? BODYPEEK : BODYTEXT;
  aatt.text = (void *) sec;
  args[0] = &aseq; args[1] = &aatt; args[2] = NIL;

  do {				/* until find desired body part */
				/* multipart content? */
    if (b->type == TYPEMULTIPART) {
      pt = b->contents.part;	/* yes, find desired part */
      while (--i && (pt = pt->next));
      if (!pt) return NIL;	/* bad specifier */
				/* note new body, check valid nesting */
      if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
    }
    else if (i != 1) return NIL;/* otherwise must be section 1 */
				/* need to go down further? */
    if (i = *s) switch (b->type) {
    case TYPEMESSAGE:		/* embedded message, calculate new base */
      if (!((*s++ == '.') && isdigit (*s))) return NIL;
				/* body section? */
      if (i = strtoul (s,&s,10)) b = b->contents.msg.body;
      else {			/* header section */
	ss = &b->contents.msg.hdr;
				/* fetch data if don't have it yet */
	if (!b->contents.msg.hdr &&
	    !imap_OK (stream,reply = imap_send (stream,cmd,args)))
	  mm_log (reply->text,ERROR);
				/* return data size if have data */
	if (len) *len = b->contents.msg.hdrsize;
	return b->contents.msg.hdr;
      }
      break;
    case TYPEMULTIPART:		/* multipart, get next section */
      if ((*s++ == '.') && isdigit (*s) && (i = strtoul (s,&s,10)) > 0) break;
    default:			/* bogus subpart specification */
      return NIL;
    }
  } while (i);
				/* lose if body bogus */
  if ((!b) || b->type == TYPEMULTIPART) return NIL;
  switch (b->type) {		/* decide where the data is based on type */
  case TYPEMESSAGE:		/* encapsulated message */
    if (!ss) ss = &b->contents.msg.text;
    break;
  case TYPETEXT:		/* textual data */
    ss = (char **) &b->contents.text;
    break;
  default:			/* otherwise assume it is binary */
    ss = (char **) &b->contents.binary;
    break;
  }
				/* fetch data if don't have it yet */
  if (!*ss && !imap_OK (stream,reply = imap_send (stream,cmd,args)))
    mm_log (reply->text,ERROR);
				/* return data size if have data */
  if ((s = *ss) && len) *len = b->size.bytes;
  return s;
}

/* IMAP fetch UID
 * Accepts: MAIL stream
 *	    message number
 * Returns: UID
 */

unsigned long imap_uid (MAILSTREAM *stream,unsigned long msgno)
{
  MESSAGECACHE *elt;
  IMAPPARSEDREPLY *reply;
  IMAPARG *args[3],aseq,aatt;
  char *s,seq[MAILTMPLEN];
  unsigned long i,j,k;
				/* IMAP2 didn't have UIDs */
  if (!LOCAL->imap4) return msgno;
				/* do we know its UID yet? */
  if (!(elt = mail_elt (stream,msgno))->uid) {
    aseq.type = SEQUENCE; aseq.text = (void *) seq;
    aatt.type = ATOM; aatt.text = (void *) "UID";
    args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
    sprintf (seq,"%ld",msgno);
    if (k = imap_uidlookahead) {/* build UID list */
      for (i = msgno + 1, s = seq; k && (i <= stream->nmsgs); i++)
	if (!mail_elt (stream,i)->uid) {
	  s += strlen (s);	/* find string end, see if nearing end */
	  if ((s - seq) > (MAILTMPLEN - 20)) break;
	  sprintf (s,",%ld",i);	/* append message */
	  for (j = i + 1, k--;	/* hunt for last message without a UID */
	       k && (j <= stream->nmsgs) && !mail_elt (stream,j)->uid;
	       j++, k--);
				/* if different, make a range */
	  if (i != --j) sprintf (s + strlen (s),":%ld",i = j);
	}
    }
				/* send "FETCH msgno UID" */
    if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
      mm_log (reply->text,ERROR);
  }
  return elt->uid;		/* return our UID now */
}

/* IMAP set flag
 * Accepts: MAIL stream
 *	    sequence
 *	    flag(s)
 *	    option flags
 */

void imap_setflag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
{
  char *cmd = (LOCAL->imap4 && (flags & ST_UID)) ? "UID STORE" : "STORE";
  IMAPPARSEDREPLY *reply;
  IMAPARG *args[4],aseq,ascm,aflg;
  aseq.type = SEQUENCE; aseq.text = (void *) sequence;
  ascm.type = ATOM; ascm.text = (void *)
    ((LOCAL->imap4 && (flags & ST_SILENT)) ? "+Flags.silent" : "+Flags");
  aflg.type = FLAGS; aflg.text = (void *) flag;
  args[0] = &aseq; args[1] = &ascm; args[2] = &aflg; args[3] = NIL;
				/* send "STORE sequence +Flags flag" */
  if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
    mm_log (reply->text,ERROR);
}


/* IMAP clear flag
 * Accepts: MAIL stream
 *	    sequence
 *	    flag(s)
 *	    option flags
 */

void imap_clearflag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
{
  char *cmd = (LOCAL->imap4 && (flags & ST_UID)) ? "UID STORE" : "STORE";
  IMAPPARSEDREPLY *reply;
  IMAPARG *args[4],aseq,ascm,aflg;
  aseq.type = SEQUENCE; aseq.text = (void *) sequence;
  ascm.type = ATOM; ascm.text = (void *)
    ((LOCAL->imap4 && (flags & ST_SILENT)) ? "-Flags.silent" : "-Flags");
  aflg.type = FLAGS; aflg.text = (void *) flag;
  args[0] = &aseq; args[1] = &ascm; args[2] = &aflg; args[3] = NIL;
				/* send "STORE sequence +Flags flag" */
  if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
    mm_log (reply->text,ERROR);
}

/* IMAP search for messages
 * Accepts: MAIL stream
 *	    character set
 *	    search program
 *	    option flags
 */
void imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags)
{
  unsigned long i,j,k;
  char *s;
  IMAPPARSEDREPLY *reply;
  MESSAGECACHE *elt;
  IMAPARG *args[3],apgm,aseq,aatt;
  args[1] = args[2] = NIL;
  apgm.type = SEARCHPROGRAM; apgm.text = (void *) pgm;
  aseq.type = SEQUENCE;
  aatt.type = ATOM;
  if (charset) {
    args[0] = &aatt; args[1] = &apgm;
    aatt.text = (void *) charset;
  }
  else args[0] = &apgm;
				/* do the SEARCH */
  if (!imap_OK (stream,reply = imap_send (stream,"SEARCH",args)))
    mm_log (reply->text,ERROR);
				/* can never pre-fetch with a short cache */
  else if ((k = imap_prefetch) && !(flags & SE_NOPREFETCH) && !stream->scache){
    s = LOCAL->tmp;		/* build sequence in temporary buffer */
    *s = '\0';			/* initially nothing */
				/* search through mailbox */
    for (i = 1; k && (i <= stream->nmsgs); ++i) 
				/* for searched messages with no envelope */
      if ((elt = mail_elt (stream,i)) && elt->searched &&
	  !mail_lelt (stream,i)->env) {
				/* prepend with comma if not first time */
	if (LOCAL->tmp[0]) *s++ = ',';
	sprintf (s,"%ld",j = i);/* output message number */
	s += strlen (s);	/* point at end of string */
	k--;			/* count one up */
				/* search for possible end of range */
	while (k && (i < stream->nmsgs) && (elt = mail_elt (stream,i+1)) &&
	       elt->searched && !mail_lelt (stream,i+1)->env) i++,k--;
	if (i != j) {		/* if a range */
	  sprintf (s,":%ld",i);	/* output delimiter and end of range */
	  s += strlen (s);	/* point at end of string */
	}
      }
    if (LOCAL->tmp[0]) {	/* anything to pre-fetch? */
      args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
      aseq.text = (void *) cpystr (LOCAL->tmp);
      aatt.text = (void *)
	(LOCAL->imap4 ?
	 "(ENVELOPE FLAGS INTERNALDATE RFC822.SIZE)" :
	 "(RFC822.HEADER FLAGS INTERNALDATE RFC822.SIZE)");
      if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
	mm_log (reply->text,ERROR);
				/* flush copy of sequence */
      fs_give ((void **) &aseq.text);
    }
  }
}

/* IMAP ping mailbox
 * Accepts: MAIL stream
 * Returns: T if stream still alive, else NIL
 */

long imap_ping (MAILSTREAM *stream)
{
  return (LOCAL->tcpstream &&	/* send "NOOP" */
	  imap_OK (stream,imap_send (stream,"NOOP",NIL))) ? T : NIL;
}


/* IMAP check mailbox
 * Accepts: MAIL stream
 */

void imap_check (MAILSTREAM *stream)
{
				/* send "CHECK" */
  IMAPPARSEDREPLY *reply = imap_send (stream,"CHECK",NIL);
  mm_log (reply->text,imap_OK (stream,reply) ? (long) NIL : ERROR);
}


/* IMAP expunge mailbox
 * Accepts: MAIL stream
 */

void imap_expunge (MAILSTREAM *stream)
{
				/* send "EXPUNGE" */
  IMAPPARSEDREPLY *reply = imap_send (stream,"EXPUNGE",NIL);
  mm_log (reply->text,imap_OK (stream,reply) ? (long) NIL : ERROR);
}

/* IMAP copy message(s)
 * Accepts: MAIL stream
 *	    sequence
 *	    destination mailbox
 *	    option flags
 * Returns: T if successful else NIL
 */

long imap_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long flags)
{
  char *cmd = (LOCAL->imap4 && (flags & CP_UID)) ? "UID COPY" : "COPY";
  IMAPPARSEDREPLY *reply;
  IMAPARG *args[3],aseq,ambx;
  aseq.type = SEQUENCE; aseq.text = (void *) sequence;
  ambx.type = ASTRING; ambx.text = (void *) mailbox;
  args[0] = &aseq; args[1] = &ambx; args[2] = NIL;
				/* send "COPY sequence mailbox" */
  if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) {
    mm_log (reply->text,ERROR);
    return NIL;
  }
  if (flags & CP_MOVE)		/* delete the messages if the user said to */
    imap_setflag (stream,sequence,"\\Deleted",(flags & CP_UID) ? ST_UID : NIL);
  return T;
}

/* IMAP append message string
 * Accepts: mail stream
 *	    destination mailbox
 *	    stringstruct of message to append
 * Returns: T on success, NIL on failure
 */

long imap_append (MAILSTREAM *stream,char *mailbox,char *flags,char *date,
		 STRING *msg)
{
  char tmp[MAILTMPLEN];
  long ret = NIL;
  IMAPPARSEDREPLY *reply = NIL;
  IMAPARG *args[5],ambx,aflg,adat,amsg;
				/* make sure useful stream given */
  MAILSTREAM *st = (stream && LOCAL && LOCAL->tcpstream) ? stream :
    mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT);
  if (st) {			/* do the operation if valid stream */
    if (mail_valid_net (mailbox,&imapdriver,NIL,tmp)) {
      ambx.type = ASTRING; ambx.text = (void *) tmp;
      aflg.type = FLAGS; aflg.text = (void *) flags;
      adat.type = ASTRING; adat.text = (void *) date;
      amsg.type = LITERAL; amsg.text = (void *) msg;
      if (flags || date) {	/* IMAP4 form? */
	int i = 0;
	args[i++] = &ambx;
	if (flags) args[i++] = &aflg;
	if (date) args[i++] = &adat;
	args[i++] = &amsg;
	args[i++] = NIL;
	if (!strcmp ((reply = imap_send (st,"APPEND",args))->key,"OK"))
	  ret = T;
      }
				/* try IMAP2bis form if IMAP4 form fails */
      if (!(ret || (reply && strcmp (reply->key,"BAD")))) {
	args[0] = &ambx; args[1] = &amsg; args[2] = NIL;
	if (imap_OK (st,reply = imap_send (st,"APPEND",args))) ret = T;
      }
      if (!ret) mm_log (reply->text,ERROR);
    }
				/* toss out temporary stream */
    if (st != stream) mail_close (st);
  }
  else mm_log ("Can't access server for append",ERROR);
  return ret;			/* return */
}

/* IMAP garbage collect stream
 * Accepts: Mail stream
 *	    garbage collection flags
 */

void imap_gc (MAILSTREAM *stream,long gcflags)
{
  unsigned long i;
  MESSAGECACHE *elt;
  mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
				/* make sure the cache is large enough */
  (*mc) (stream,stream->nmsgs,CH_SIZE);
  if (gcflags & GC_TEXTS) {	/* garbage collect texts? */
    for (i = 1; i <= stream->nmsgs; ++i)
      if (elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) {
	if (elt->data2) fs_give ((void **) &elt->data2);
	if (!stream->scache) imap_gc_body (mail_lelt (stream,i)->body);
      }
    imap_gc_body (stream->body);/* free texts from short cache body */
  }
				/* gc cache if requested and unlocked */
  if (gcflags & GC_ELT) for (i = 1; i <= stream->nmsgs; ++i)
    if ((elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) &&
	(elt->lockcount == 1)) (*mc) (stream,i,CH_FREE);
}

/* IMAP garbage collect body texts
 * Accepts: body to GC
 */

void imap_gc_body (BODY *body)
{
  PART *part;
  if (body) switch (body->type) {
  case TYPETEXT:		/* unformatted text */
    if (body->contents.text) fs_give ((void **) &body->contents.text);
    break;
  case TYPEMULTIPART:		/* multiple part */
    if (part = body->contents.part) do imap_gc_body (&part->body);
    while (part = part->next);
    break;
  case TYPEMESSAGE:		/* encapsulated message */
    imap_gc_body (body->contents.msg.body);
    if (body->contents.msg.hdr)
      fs_give ((void **) &body->contents.msg.hdr);
    if (body->contents.msg.text)
      fs_give ((void **) &body->contents.msg.text);
    break;
  case TYPEAPPLICATION:		/* application data */
  case TYPEAUDIO:		/* audio */
  case TYPEIMAGE:		/* static image */
  case TYPEVIDEO:		/* video */
    if (body->contents.binary) fs_give (&body->contents.binary);
    break;
  default:
    break;
  }
}

/* Internal routines */


/* IMAP send command
 * Accepts: MAIL stream
 *	    command
 *	    argument list
 * Returns: parsed reply
 */

#define MAXSEQUENCE 1000

IMAPPARSEDREPLY *imap_send (MAILSTREAM *stream,char *cmd,IMAPARG *args[])
{
  IMAPPARSEDREPLY *reply;
  IMAPARG *arg;
  STRINGLIST *list;
  char c,*s,*t,tag[16];
  if (!LOCAL->tcpstream) return imap_fake (stream,tag,"No-op dead stream");
  mail_lock (stream);		/* lock up the stream */
  				/* gensym a new tag */
  sprintf (tag,"A%05ld",stream->gensym++);
				/* write tag */
  for (s = LOCAL->tmp,t = tag; *t; *s++ = *t++);
  *s++ = ' ';			/* delimit and write command */
  for (t = cmd; *t; *s++ = *t++);
  if (args) while (arg = *args++) {
    *s++ = ' ';			/* delimit argument with space */
    switch (arg->type) {
    case SEQUENCE:		/* sequence */
      while (t = (strlen ((char *) arg->text) > MAXSEQUENCE) ?
	     strchr (((char *) arg->text) + MAXSEQUENCE - 20,',') : NIL) {
	*t++ = '\0';		/* tie off sequence */
				/* recurse to do part */
	imap_send (stream,cmd,args);
	arg->text = (void *) t;	/* now do rest of data */
      }
    case ATOM:			/* atom */
      for (t = (char *) arg->text; *t; *s++ = *t++);
      break;
    case NUMBER:		/* number */
      sprintf (s,"%lu",(unsigned long) arg->text);
      s += strlen (s);
      break;
    case FLAGS:			/* flag list as a single string */
      if (*(t = (char *) arg->text) != '(') {
	*s++ = '(';		/* wrap parens around string */
	while (*t) *s++ = *t++;
	*s++ = ')';		/* wrap parens around string */
      }
      else while (*t) *s++ = *t++;
      break;

    case ASTRING:		/* atom or string, must be literal? */
      if (reply = imap_send_astring (stream,tag,&s,arg->text,
				     (unsigned long) strlen (arg->text)))
	return reply;
      break;
    case LITERAL:		/* literal, as a stringstruct */
      if (reply = imap_send_literal (stream,tag,&s,arg->text)) return reply;
      break;
    case LIST:			/* list of strings */
      list = (STRINGLIST *) arg->text;
      c = '(';			/* open paren */
      do {			/* for each list item */
	*s++ = c;		/* write prefix character */
	if (reply = imap_send_astring (stream,tag,&s,list->text,list->size))
	  return reply;
	c = ' ';		/* prefix character for subsequent strings */
      }
      while (list = list->next);
      *s++ = ')';		/* close list */
      break;
    case SEARCHPROGRAM:		/* search program */
      if (reply = imap_send_spgm (stream,tag,&s,arg->text)) return reply;
      break;
    case BODYTEXT:		/* body section */
      for (t = "BODY["; *t; *s++ = *t++);
      for (t = (char *) arg->text; *t; *s++ = *t++);
      *s++ = ']';		/* close section */
      break;
    case BODYPEEK:		/* body section */
      for (t = "BODY.PEEK["; *t; *s++ = *t++);
      for (t = (char *) arg->text; *t; *s++ = *t++);
      *s++ = ']';		/* close section */
      break;
    default:
      fatal ("Unknown argument type in imap_send()!");
    }
  }
  mail_unlock (stream);		/* unlock stream */
				/* send the command */
  return imap_sout (stream,tag,LOCAL->tmp,&s);
}

/* IMAP send atom-string
 * Accepts: MAIL stream
 *	    reply tag
 *	    pointer to current position pointer of output bigbuf
 *	    atom-string to output
 *	    string length
 * Returns: error reply or NIL if success
 */

IMAPPARSEDREPLY *imap_send_astring (MAILSTREAM *stream,char *tag,char **s,
				    char *t,unsigned long size)
{
  unsigned long j;
  STRING st;
  int quoted = size ? NIL : T;	/* default to not quoted unless empty */
  for (j = 0; j < size; j++) switch (t[j]) {
  case '\0':			/* not a CHAR */
  case '\012': case '\015':	/* not a TEXT-CHAR */
  case '"': case '\\':		/* quoted-specials (IMAP2 required this) */
    INIT (&st,mail_string,(void *) t,size);
    return imap_send_literal (stream,tag,s,&st);
  default:			/* all other characters */
    if (t[j] > ' ') break;	/* break if not a CTL */
				/* atom_specials */
  case '(': case ')': case '{': case ' ':
#if 0
  case '*': case '%':		/* list_wildcards (not needed) */
  case '"': case '\\':		/* quoted-specials (could work in IMAP4) */
#endif
    quoted = T;			/* must use quoted string format */
    break;
  }
  if (quoted) *(*s)++ = '"';	/* write open quote */
  while (size--) *(*s)++ = *t++;/* write the characters */
  if (quoted) *(*s)++ = '"';	/* write close quote */
  return NIL;
}

/* IMAP send literal
 * Accepts: MAIL stream
 *	    reply tag
 *	    pointer to current position pointer of output bigbuf
 *	    literal to output as stringstruct
 * Returns: error reply or NIL if success
 */

IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *tag,char **s,
				    STRING *st)
{
  IMAPPARSEDREPLY *reply;
  unsigned long i = SIZE (st);
  sprintf (*s,"{%ld}",i);	/* write literal count */
  *s += strlen (*s);		/* size of literal count */
				/* send the command */
  reply = imap_sout (stream,tag,LOCAL->tmp,s);
  if (strcmp (reply->tag,"+")) {/* prompt for more data? */
    mail_unlock (stream);	/* no, give up */
    return reply;
  }
  while (i) {			/* dump the text */
    if (!tcp_sout (LOCAL->tcpstream,st->curpos,st->cursize)) {
      mail_unlock (stream);
      return imap_fake (stream,tag,"IMAP connection broken (data)");
    }
    i -= st->cursize;		/* note that we wrote out this much */
    st->curpos += (st->cursize - 1);
    st->cursize = 0;
    (*st->dtb->next) (st);	/* advance to next buffer's worth */
  }
  return NIL;			/* success */
}

/* IMAP send search program
 * Accepts: MAIL stream
 *	    reply tag
 *	    pointer to current position pointer of output bigbuf
 *	    search program to output
 * Returns: error reply or NIL if success
 */

IMAPPARSEDREPLY *imap_send_spgm (MAILSTREAM *stream,char *tag,char **s,
				 SEARCHPGM *pgm)
{
  IMAPPARSEDREPLY *reply;
  SEARCHHEADER *hdr;
  SEARCHOR *pgo;
  SEARCHPGMLIST *pgl;
  char *t = "ALL";
  while (*t) *(*s)++ = *t++;	/* default initial text */
				/* message sequences */
  if (pgm->msgno) imap_send_sset (s,pgm->msgno);
  if (pgm->uid) {		/* UID sequence */
    for (t = " UID"; *t; *(*s)++ = *t++);
    imap_send_sset (s,pgm->msgno);
  }
				/* message sizes */
  if (pgm->larger) {
    sprintf (*s," LARGER %ld",pgm->larger);
    *s += strlen (*s);
  }
  if (pgm->smaller) {
    sprintf (*s," SMALLER %ld",pgm->smaller);
    *s += strlen (*s);
  }
				/* message flags */
  if (pgm->answered) for (t = " ANSWERED"; *t; *(*s)++ = *t++);
  if (pgm->unanswered) for (t =" UNANSWERED"; *t; *(*s)++ = *t++);
  if (pgm->deleted) for (t =" DELETED"; *t; *(*s)++ = *t++);
  if (pgm->undeleted) for (t =" UNDELETED"; *t; *(*s)++ = *t++);
  if (pgm->draft) for (t =" DRAFT"; *t; *(*s)++ = *t++);
  if (pgm->undraft) for (t =" UNDRAFT"; *t; *(*s)++ = *t++);
  if (pgm->flagged) for (t =" FLAGGED"; *t; *(*s)++ = *t++);
  if (pgm->unflagged) for (t =" UNFLAGGED"; *t; *(*s)++ = *t++);
  if (pgm->recent) for (t =" RECENT"; *t; *(*s)++ = *t++);
  if (pgm->old) for (t =" OLD"; *t; *(*s)++ = *t++);
  if (pgm->seen) for (t =" SEEN"; *t; *(*s)++ = *t++);
  if (pgm->unseen) for (t =" UNSEEN"; *t; *(*s)++ = *t++);
  if ((pgm->keyword &&		/* keywords */
       (reply = imap_send_slist (stream,tag,s,"KEYWORD",pgm->keyword))) ||
      (pgm->unkeyword &&
       (reply = imap_send_slist (stream,tag,s,"UNKEYWORD",pgm->unkeyword))))
    return reply;

				/* sent date ranges */
  if (pgm->sentbefore) imap_send_sdate (s,"SENTBEFORE",pgm->sentbefore);
  if (pgm->senton) imap_send_sdate (s,"SENTON",pgm->senton);
  if (pgm->sentsince) imap_send_sdate (s,"SENTSINCE",pgm->sentsince);
				/* internal date ranges */
  if (pgm->before) imap_send_sdate (s,"BEFORE",pgm->before);
  if (pgm->on) imap_send_sdate (s,"ON",pgm->on);
  if (pgm->since) imap_send_sdate (s,"SINCE",pgm->since);
				/* search texts */
  if ((pgm->bcc && (reply = imap_send_slist (stream,tag,s,"BCC",pgm->bcc))) ||
      (pgm->cc && (reply = imap_send_slist (stream,tag,s,"CC",pgm->cc))) ||
      (pgm->from && (reply = imap_send_slist(stream,tag,s,"FROM",pgm->from)))||
      (pgm->to && (reply = imap_send_slist (stream,tag,s,"TO",pgm->to))))
    return reply;
  if ((pgm->subject &&
       (reply = imap_send_slist (stream,tag,s,"SUBJECT",pgm->subject))) ||
      (pgm->body && (reply = imap_send_slist(stream,tag,s,"BODY",pgm->body)))||
      (pgm->text && (reply = imap_send_slist (stream,tag,s,"TEXT",pgm->text))))
    return reply;
  if (hdr = pgm->header) do {
    for (t = " HEADER "; *t; *(*s)++ = *t++);
    for (t = hdr->line; *t; *(*s)++ = *t++);
    if (reply = imap_send_astring (stream,tag,s,hdr->text,
				   (unsigned long) strlen (hdr->text)))
      return reply;
  }
  while (hdr = hdr->next);
  if (pgo = pgm->or) do {
    for (t = " OR ("; *t; *(*s)++ = *t++);
    if (reply = imap_send_spgm (stream,tag,s,pgm->or->first)) return reply;
    for (t = ") ("; *t; *(*s)++ = *t++);
    if (reply = imap_send_spgm (stream,tag,s,pgm->or->second)) return reply;
    *(*s)++ = ')';
  } while (pgo = pgo->next);
  if (pgl = pgm->not) do {
    for (t = " NOT ("; *t; *(*s)++ = *t++);
    if (reply = imap_send_spgm (stream,tag,s,pgl->pgm)) return reply;
    *(*s)++ = ')';
  }
  while (pgl = pgl->next);
  return NIL;			/* search program written OK */
}

/* IMAP send search set
 * Accepts: pointer to current position pointer of output bigbuf
 *	    search set to output
 */

void imap_send_sset (char **s,SEARCHSET *set)
{
  char c = ' ';
  do {				/* run down search set */
    sprintf (*s,set->last ? "%c%ld:%ld" : "%c%ld",c,set->first,set->last);
    *s += strlen (*s);
    c = ',';			/* if there are any more */
  }
  while (set = set->next);
}


/* IMAP send search list
 * Accepts: MAIL stream
 *	    reply tag
 *	    pointer to current position pointer of output bigbuf
 *	    name of search list
 *	    search list to output
 * Returns: NIL if success, error reply if error
 */

IMAPPARSEDREPLY *imap_send_slist (MAILSTREAM *stream,char *tag,char **s,
				  char *name,STRINGLIST *list)
{
  char *t;
  IMAPPARSEDREPLY *reply;
  do {
    *(*s)++ = ' ';		/* output name of search list */
    for (t = name; *t; *(*s)++ = *t++);
    *(*s)++ = ' ';
    reply = imap_send_astring (stream,tag,s,list->text,
			       (unsigned long) strlen (list->text));
  }
  while (!reply && (list = list->next));
  return reply;
}


/* IMAP send search date
 * Accepts: pointer to current position pointer of output bigbuf
 *	    field name
 *	    search date to output
 */

void imap_send_sdate (char **s,char *name,unsigned short date)
{
  sprintf (*s," %s %d-%s-%d",name,date & 0x1f,
	   months[((date >> 5) & 0xf) - 1],BASEYEAR + (date >> 9));
  *s += strlen (*s);
}

/* IMAP send buffered command to sender
 * Accepts: MAIL stream
 *	    reply tag
 *	    string
 *	    pointer to string tail pointer
 * Returns: reply
 */

IMAPPARSEDREPLY *imap_sout (MAILSTREAM *stream,char *tag,char *base,char **s)
{
  IMAPPARSEDREPLY *reply;
  if (stream->debug) {		/* output debugging telemetry */
    **s = '\0';
    mm_dlog (base);
  }
  *(*s)++ = '\015';		/* append CRLF */
  *(*s)++ = '\012';
  **s = '\0';
  reply = tcp_sout (LOCAL->tcpstream,base,*s - base) ?
    imap_reply (stream,tag) :
      imap_fake (stream,tag,"IMAP connection broken (command)");
  *s = base;			/* restart buffer */
  return reply;
}

/* IMAP get reply
 * Accepts: MAIL stream
 *	    tag to search or NIL if want a greeting
 * Returns: parsed reply, never NIL
 */

IMAPPARSEDREPLY *imap_reply (MAILSTREAM *stream,char *tag)
{
  IMAPPARSEDREPLY *reply;
  while (LOCAL->tcpstream) {	/* parse reply from server */
    if (reply = imap_parse_reply (stream,tcp_getline (LOCAL->tcpstream))) {
				/* continuation ready? */
      if (!strcmp (reply->tag,"+")) return reply;
				/* untagged data? */
      else if (!strcmp (reply->tag,"*")) {
	imap_parse_unsolicited (stream,reply);
	if (!tag) return reply;	/* return if just wanted greeting */
      }
      else {			/* tagged data */
	if (tag && !strcmp (tag,reply->tag)) return reply;
				/* report bogon */
	sprintf (LOCAL->tmp,"Unexpected tagged response: %.80s %.80s %.80s",
		 reply->tag,reply->key,reply->text);
	mm_log (LOCAL->tmp,WARN);
      }
    }
  }
  return imap_fake (stream,tag,"IMAP connection broken (server response)");
}

/* IMAP parse reply
 * Accepts: MAIL stream
 *	    text of reply
 * Returns: parsed reply, or NIL if can't parse at least a tag and key
 */


IMAPPARSEDREPLY *imap_parse_reply (MAILSTREAM *stream,char *text)
{
  if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
  if (!(LOCAL->reply.line = text)) {
				/* NIL text means the stream died */
    if (LOCAL->tcpstream) tcp_close (LOCAL->tcpstream);
    LOCAL->tcpstream = NIL;
    return NIL;
  }
  if (stream->debug) mm_dlog (LOCAL->reply.line);
  LOCAL->reply.key = NIL;	/* init fields in case error */
  LOCAL->reply.text = NIL;
  if (!(LOCAL->reply.tag = (char *) strtok (LOCAL->reply.line," "))) {
    mm_log ("IMAP server sent a blank line",WARN);
    return NIL;
  }
				/* non-continuation replies */
  if (strcmp (LOCAL->reply.tag,"+")) {
				/* parse key */
    if (!(LOCAL->reply.key = (char *) strtok (NIL," "))) {
				/* determine what is missing */
      sprintf (LOCAL->tmp,"Missing IMAP reply key: %.80s",LOCAL->reply.tag);
      mm_log (LOCAL->tmp,WARN);	/* pass up the barfage */
      return NIL;		/* can't parse this text */
    }
    ucase (LOCAL->reply.key);	/* make sure key is upper case */
				/* get text as well, allow empty text */
    if (!(LOCAL->reply.text = (char *) strtok (NIL,"\n")))
      LOCAL->reply.text = LOCAL->reply.key + strlen (LOCAL->reply.key);
  }
  else {			/* special handling of continuation */
    LOCAL->reply.key = "BAD";	/* so it barfs if not expecting continuation */
    if (!(LOCAL->reply.text = (char *) strtok (NIL,"\n")))
      LOCAL->reply.text = "Ready for more command";
  }
  return &LOCAL->reply;		/* return parsed reply */
}

/* IMAP fake reply
 * Accepts: MAIL stream
 *	    tag
 *	    text of fake reply
 * Returns: parsed reply
 */

IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text)
{
  mm_notify (stream,text,BYE);	/* send bye alert */
  if (LOCAL->tcpstream) tcp_close (LOCAL->tcpstream);
  LOCAL->tcpstream = NIL;	/* farewell, dear TCP stream... */
				/* build fake reply string */
  sprintf (LOCAL->tmp,"%s NO [CLOSED] %s",tag ? tag : "*",text);
				/* parse and return it */
  return imap_parse_reply (stream,cpystr (LOCAL->tmp));
}


/* IMAP check for OK response in tagged reply
 * Accepts: MAIL stream
 *	    parsed reply
 * Returns: T if OK else NIL
 */

long imap_OK (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
{
				/* OK - operation succeeded */
  if (!strcmp (reply->key,"OK") ||
      (!strcmp (reply->tag,"*") && !strcmp (reply->key,"PREAUTH")))
    return T;
				/* NO - operation failed */
  else if (strcmp (reply->key,"NO")) {
				/* BAD - operation rejected */
    if (!strcmp (reply->key,"BAD"))
      sprintf (LOCAL->tmp,"IMAP error: %.80s",reply->text);
    else sprintf (LOCAL->tmp,"Unexpected IMAP response: %.80s %.80s",
		  reply->key,reply->text);
    mm_log (LOCAL->tmp,WARN);	/* log the sucker */
  }
  return NIL;
}

/* IMAP parse and act upon unsolicited reply
 * Accepts: MAIL stream
 *	    parsed reply
 */

void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
{
  unsigned long i = 0;
  unsigned long msgno;
  char *s,*t;
  char *keyptr,*txtptr;
				/* see if key is a number */
  msgno = strtoul (reply->key,&s,10);
  if (*s) {			/* if non-numeric */
    if (!strcmp (reply->key,"FLAGS")) {
				/* flush old user flags if any */
      while ((i < NUSERFLAGS) && stream->user_flags[i])
	fs_give ((void **) &stream->user_flags[i++]);
      i = 0;			/* add flags */
      if (s = (char *) strtok (reply->text+1," )"))
	do if (*s != '\\') stream->user_flags[i++] = cpystr (s);
      while (s = (char *) strtok (NIL," )"));
    }
    else if (!strcmp (reply->key,"SEARCH")) {
				/* only do something if have text */
      if (reply->text && (t = (char *) strtok (reply->text," "))) do {
	i = atol (t);
	if (!LOCAL->uidsearch) mail_elt (stream,i)->searched = T;
	mm_searched (stream,i);
      }
      while (t = (char *) strtok (NIL," "));
    }

    else if (!strcmp (reply->key,"STATUS")) {
      MAILSTATUS status;
      char *txt;
      switch (*reply->text) {	/* mailbox is an astring */
      case '"':			/* quoted string? */
      case '{':			/* literal? */
	txt = reply->text;	/* status data is in reply */
	t = imap_parse_string (stream,&txt,reply,NIL,NIL);
	break;
      default:			/* must be atom */
	t = reply->text;
	if (txt = strchr (t,' ')) *txt++ = '\0';
	break;
      }
      if (t && txt && (*txt++ == '(') && (s = strchr (txt,')')) && (s - txt) &&
	  !s[1]) {
	*s = '\0';		/* tie off status data */
				/* initialize data block */
	status.flags = status.messages = status.recent = status.unseen =
	status.uidnext = status.uidvalidity = 0;
	ucase (txt);		/* do case-independent match */
	while (*txt && (s = strchr (txt,' '))) {
	  *s++ = '\0';		/* tie off status attribute name */
	  i = strtoul (s,&s,10);/* get attribute value */
	  if (!strcmp (txt,"MESSAGES")) {
	    status.flags |= SA_MESSAGES;
	    status.messages = i;
	  }
	  else if (!strcmp (txt,"RECENT")) {
	    status.flags |= SA_RECENT;
	    status.recent = i;
	  }
	  else if (!strcmp (txt,"UNSEEN")) {
	    status.flags |= SA_UNSEEN;
	    status.unseen = i;
	  }
	  else if (!strcmp (txt,"UID-NEXT")) {
	    status.flags |= SA_UIDNEXT;
	    status.uidnext = i;
	  }
	  else if (!strcmp (txt,"UID-VALIDITY")) {
	    status.flags |= SA_UIDVALIDITY;
	    status.uidvalidity = i;
	  }
				/* next attribute */
	  txt = (*s == ' ') ? s + 1 : s;
	}
	sprintf (LOCAL->tmp,"{%s}%s",imap_host (stream),t);
				/* pass status to main program */
	mm_status (stream,LOCAL->tmp,&status);
      }
    }

    else if ((!strcmp (reply->key,"LIST") || !strcmp (reply->key,"LSUB")) &&
	     (*reply->text == '(') && (s = strchr (reply->text,')')) &&
	     (s[1] == ' ')) {
      char delimiter = '\0';
      *s++ = '\0';		/* tie off attribute list */
				/* parse attribute list */
      if (t = (char *) strtok (reply->text+1," ")) do {
	if (!strcmp (ucase (t),"\\NOINFERIORS")) i |= LATT_NOINFERIORS;
	else if (!strcmp (t,"\\NOSELECT")) i |= LATT_NOSELECT;
	else if (!strcmp (t,"\\MARKED")) i |= LATT_MARKED;
	else if (!strcmp (t,"\\UNMARKED")) i |= LATT_UNMARKED;
				/* ignore extension flags */
      }
      while (t = (char *) strtok (NIL," "));
      switch (*++s) {		/* process delimiter */
      case 'N':			/* NIL */
      case 'n':
	s += 4;			/* skip over NIL<space> */
	break;
      case '"':			/* have a delimiter */
	delimiter = (*++s == '\\') ? *++s : *s;
	s += 3;			/* skip over <delimiter><quote><space> */
      }
      t = ((*s == '"') || (*s == '{')) ?
	imap_parse_string (stream,&s,reply,NIL,NIL) : s;
      if (LOCAL->prefix) {	/* need to prepend a prefix? */
	sprintf (LOCAL->tmp,"%s%s",LOCAL->prefix,t);
	t = LOCAL->tmp;		/* do so then */
      }
      if (reply->key[1] == 'S') mm_lsub (stream,delimiter,t,i);
      else mm_list (stream,delimiter,t,i);
    }
    else if (!strcmp (reply->key,"MAILBOX")) {
      if (LOCAL->prefix)
	sprintf (t = LOCAL->tmp,"%s%s",LOCAL->prefix,reply->text);
      else t = reply->text;

#ifndef WOGGLE 
      if(imap2_findall)
	mm_list(stream,NIL,t,NIL);
      else
	mm_lsub (stream,NIL,t,NIL);
#else /* WOGGLE */


      mm_list (stream,NIL,t,NIL);

#endif /* WOGGLE */


    }

    else if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH")) {
      if ((*reply->text == '[') && (t = strchr (s = reply->text + 1,']')) &&
	  ((i = t - s) < IMAPTMPLEN)) {
				/* get text code */
	strncpy (LOCAL->tmp,s,(size_t) i);
	LOCAL->tmp[i] = '\0';	/* tie off text */
	if (!strcmp (ucase (LOCAL->tmp),"READ-ONLY")) stream->rdonly = T;
	else if (!strcmp (LOCAL->tmp,"READ-WRITE")) stream->rdonly = NIL;
	else if (!strncmp (LOCAL->tmp,"UIDVALIDITY ",12)) {
	  stream->uid_validity = strtoul (LOCAL->tmp+12,NIL,10);
	  return;
	}
	else if (!strncmp (LOCAL->tmp,"PERMANENTFLAGS (",16)) {
	  if (LOCAL->tmp[i-1] == ')') LOCAL->tmp[i-1] = '\0';
	  stream->perm_seen = stream->perm_deleted = stream->perm_answered =
	    stream->perm_draft = stream->kwd_create = NIL;
	  stream->perm_user_flags = NIL;
	  if (s = strtok (LOCAL->tmp+16," ")) do {
	    if (!strcmp (s,"\\SEEN")) stream->perm_seen = T;
	    else if (!strcmp (s,"\\DELETED")) stream->perm_deleted = T;
	    else if (!strcmp (s,"\\FLAGGED")) stream->perm_flagged = T;
	    else if (!strcmp (s,"\\ANSWERED")) stream->perm_answered = T;
	    else if (!strcmp (s,"\\DRAFT")) stream->perm_draft = T;
	    else if (!strcmp (s,"\\*")) stream->kwd_create = T;
	    else stream->perm_user_flags |= imap_parse_user_flag (stream,s);
	  }
	  while (s = strtok (NIL," "));
	  return;
	}
      }
      mm_notify (stream,reply->text,(long) NIL);
    }
    else if (!strcmp (reply->key,"NO")) {
      if (!stream->silent) mm_notify (stream,reply->text,WARN);
    }
    else if (!strcmp (reply->key,"BYE")) {
      if (!stream->silent) mm_notify (stream,reply->text,BYE);
    }
    else if (!strcmp (reply->key,"BAD")) mm_notify (stream,reply->text,ERROR);
    else if (!strcmp (reply->key,"CAPABILITY")) {
				/* only do something if have text */
      if (reply->text && (t = (char *) strtok (reply->text," "))) do {
	if (!strcmp (t,"IMAP4")) LOCAL->imap4 = T;
	else if (!strcmp (t,"STATUS")) LOCAL->use_status = T;
	else if (!strcmp (t,"SCAN")) LOCAL->use_scan = T;
	else if ((!strncmp (t,"AUTH-",5)) || (!strncmp (t,"AUTH=",5))) {
	  AUTHENTICATOR *auth;
	  for (i = 0, auth = authenticators;
	       auth && (i < MAXAUTHENTICATORS) && strcmp (t+5,auth->name);
	       i++, auth = auth->next);
	  if (auth && (i < MAXAUTHENTICATORS)) LOCAL->use_auth |= 1 << i;
	}
				/* ignore other capabilities for now */
      }
      while (t = (char *) strtok (NIL," "));
    }
    else {
      sprintf (LOCAL->tmp,"Unexpected unsolicited message: %.80s",reply->key);
      mm_log (LOCAL->tmp,WARN);
    }
  }

  else {			/* if numeric, a keyword follows */
				/* deposit null at end of keyword */
    keyptr = ucase ((char *) strtok (reply->text," "));
				/* and locate the text after it */
    txtptr = (char *) strtok (NIL,"\n");
				/* now take the action */
				/* change in size of mailbox */
    if (!strcmp (keyptr,"EXISTS")) mail_exists (stream,msgno);
    else if (!strcmp (keyptr,"RECENT")) mail_recent (stream,msgno);
    else if (!strcmp (keyptr,"EXPUNGE")) imap_expunged (stream,msgno);
    else if (!strcmp (keyptr,"FETCH"))
      imap_parse_data (stream,msgno,txtptr,reply);
				/* obsolete alias for FETCH */
    else if (!strcmp (keyptr,"STORE"))
      imap_parse_data (stream,msgno,txtptr,reply);
				/* obsolete response to COPY */
    else if (strcmp (keyptr,"COPY")) {
      sprintf (LOCAL->tmp,"Unknown message data: %ld %.80s",msgno,keyptr);
      mm_log (LOCAL->tmp,WARN);
    }
  }
}

/* IMAP message has been expunged
 * Accepts: MAIL stream
 *	    message number
 *
 * Calls external "mail_expunged" function to notify main program
 */

void imap_expunged (MAILSTREAM *stream,unsigned long msgno)
{
  mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
  MESSAGECACHE *elt = (MESSAGECACHE *) (*mc) (stream,msgno,CH_ELT);
  if (elt && elt->data2) fs_give ((void **) &elt->data2);
  mail_expunged (stream,msgno);	/* notify upper level */
}


/* IMAP parse data
 * Accepts: MAIL stream
 *	    message #
 *	    text to parse
 *	    parsed reply
 *
 *  This code should probably be made a bit more paranoid about malformed
 * S-expressions.
 */

void imap_parse_data (MAILSTREAM *stream,unsigned long msgno,char *text,
		      IMAPPARSEDREPLY *reply)
{
  char *prop;
  MESSAGECACHE *elt = mail_elt (stream,msgno);
  ++text;			/* skip past open parenthesis */
				/* parse Lisp-form property list */
  while (prop = (char *) strtok (text," )")) {
				/* point at value */
    text = (char *) strtok (NIL,"\n");
				/* parse the property and its value */
    imap_parse_prop (stream,elt,ucase (prop),&text,reply);
  }
}

/* IMAP parse property
 * Accepts: MAIL stream
 *	    cache item
 *	    property name
 *	    property value text pointer
 *	    parsed reply
 */

void imap_parse_prop (MAILSTREAM *stream,MESSAGECACHE *elt,char *prop,
		      char **txtptr,IMAPPARSEDREPLY *reply)
{
  char *s;
  unsigned long i;
  ENVELOPE **env;
  BODY **body;
  if (!strcmp (prop,"ENVELOPE")) {
    if (stream->scache) {	/* short cache, flush old stuff */
      mail_free_body (&stream->body);
      stream->msgno =elt->msgno;/* set new current message number */
      env = &stream->env;	/* get pointer to envelope */
    }
    else env = &mail_lelt (stream,elt->msgno)->env;
    imap_parse_envelope (stream,env,txtptr,reply);
  }
  else if (!strcmp (prop,"FLAGS")) imap_parse_flags (stream,elt,txtptr);
  else if (!strcmp (prop,"INTERNALDATE")) {
    if (s = imap_parse_string (stream,txtptr,reply,(long) NIL,NIL)) {
      if (!mail_parse_date (elt,s)) {
	sprintf (LOCAL->tmp,"Bogus date: %.80s",s);
	mm_log (LOCAL->tmp,WARN);
      }
      fs_give ((void **) &s);
    }
  }
  else if (!strcmp (prop,"UID")) elt->uid = strtoul (*txtptr,txtptr,10);

  else if (!strcmp (prop,"RFC822.HEADER")) {
    s = imap_parse_string (stream,txtptr,reply,elt->msgno,&i);
    imap_parse_header (stream,elt->msgno,s,i);
  }
  else if (!strcmp (prop,"RFC822.SIZE"))
    elt->rfc822_size = strtoul (*txtptr,txtptr,10);
  else if (!strcmp (prop,"RFC822.TEXT") || !strcmp (prop,"RFC822")) {
    if (elt->data2) fs_give ((void **) &elt->data2);
    elt->data2 = (unsigned long)
      imap_parse_string (stream,txtptr,reply,elt->msgno,
			 (unsigned long *) &elt->data4);
  }
  else if (prop[0] == 'B' && prop[1] == 'O' && prop[2] == 'D' &&
	   prop[3] == 'Y') {
    s = cpystr (prop+4);	/* copy segment specifier */
    if (stream->scache) {	/* short cache, flush old stuff */
      if (elt->msgno != stream->msgno) {
				/* losing real bad here */
	mail_free_envelope (&stream->env);
	sprintf (LOCAL->tmp,"Body received for %ld when current is %ld",
		 elt->msgno,stream->msgno);
	mm_log (LOCAL->tmp,WARN);
	stream->msgno = elt->msgno;
      }
      body = &stream->body;	/* get pointer to body */
    }
    else body = &mail_lelt (stream,elt->msgno)->body;
    imap_parse_body (stream,elt->msgno,body,s,txtptr,reply);
    fs_give ((void **) &s);
  }
  else {
    sprintf (LOCAL->tmp,"Unknown message property: %.80s",prop);
    mm_log (LOCAL->tmp,WARN);
  }
}

/* Parse RFC822 message header
 * Accepts: MAIL stream
 *	    message number
 *	    header string
 *	    header size
 */

void imap_parse_header (MAILSTREAM *stream,unsigned long msgno,char *header,
			unsigned long size)
{
  ENVELOPE **env,*nenv;
  if (stream->text) fs_give ((void **) &stream->text);
  stream->text = header;	/* set header */
				/* and record its size */
  mail_elt (stream,msgno)->data3 = size;
  if (stream->scache) {		/* short cache, flush old stuff */
    mail_free_body (&stream->body);
    stream->msgno = msgno;	/* set new current message number */
    env = &stream->env;		/* get pointer to envelope */
  }
				/* use envelope from long cache */
  else env = &mail_lelt (stream,msgno)->env;
				/* parse what we can from this header */
  rfc822_parse_msg (&nenv,NIL,header,size,NIL,imap_host (stream),LOCAL->tmp);
  if (*env) {			/* need to merge this header into envelope? */
    (*env)->newsgroups = nenv->newsgroups;
    nenv->newsgroups = NIL;
    (*env)->followup_to = nenv->followup_to;
    nenv->followup_to = NIL;
    (*env)->references = nenv->references;
    nenv->references = NIL;
    mail_free_envelope (&nenv);
  }
  else *env = nenv;		/* otherwise set it to this */
}

/* IMAP parse envelope
 * Accepts: MAIL stream
 *	    pointer to envelope pointer
 *	    current text pointer
 *	    parsed reply
 *
 * Updates text pointer
 */

void imap_parse_envelope (MAILSTREAM *stream,ENVELOPE **env,char **txtptr,
			  IMAPPARSEDREPLY *reply)
{
  ENVELOPE *oenv = *env;
  char c = *((*txtptr)++);	/* grab first character */
				/* ignore leading spaces */
  while (c == ' ') c = *((*txtptr)++);
  switch (c) {			/* dispatch on first character */
  case '(':			/* if envelope S-expression */
    *env = mail_newenvelope ();	/* parse the new envelope */
    (*env)->date = imap_parse_string (stream,txtptr,reply,(long) NIL,NIL);
    (*env)->subject = imap_parse_string (stream,txtptr,reply,(long) NIL,NIL);
    (*env)->from = imap_parse_adrlist (stream,txtptr,reply);
    (*env)->sender = imap_parse_adrlist (stream,txtptr,reply);
    (*env)->reply_to = imap_parse_adrlist (stream,txtptr,reply);
    (*env)->to = imap_parse_adrlist (stream,txtptr,reply);
    (*env)->cc = imap_parse_adrlist (stream,txtptr,reply);
    (*env)->bcc = imap_parse_adrlist (stream,txtptr,reply);
    (*env)->in_reply_to = imap_parse_string(stream,txtptr,reply,(long)NIL,NIL);
    (*env)->message_id = imap_parse_string(stream,txtptr,reply,(long) NIL,NIL);
    if (oenv) {			/* need to merge old envelope? */
      (*env)->newsgroups = oenv->newsgroups;
      oenv->newsgroups = NIL;
      (*env)->followup_to = oenv->followup_to;
      oenv->followup_to = NIL;
      (*env)->references = oenv->references;
      oenv->references = NIL;
      mail_free_envelope(&oenv);/* free old envelope */
    }
    if (**txtptr != ')') {
      sprintf (LOCAL->tmp,"Junk at end of envelope: %.80s",*txtptr);
      mm_log (LOCAL->tmp,WARN);
    }
    else ++*txtptr;		/* skip past delimiter */
    break;
  case 'N':			/* if NIL */
  case 'n':
    ++*txtptr;			/* bump past "I" */
    ++*txtptr;			/* bump past "L" */
    break;
  default:
    sprintf (LOCAL->tmp,"Not an envelope: %.80s",*txtptr);
    mm_log (LOCAL->tmp,WARN);
    break;
  }
}

/* IMAP parse address list
 * Accepts: MAIL stream
 *	    current text pointer
 *	    parsed reply
 * Returns: address list, NIL on failure
 *
 * Updates text pointer
 */

ADDRESS *imap_parse_adrlist (MAILSTREAM *stream,char **txtptr,
			     IMAPPARSEDREPLY *reply)
{
  ADDRESS *adr = NIL;
  char c = **txtptr;		/* sniff at first character */
				/* ignore leading spaces */
  while (c == ' ') c = *++*txtptr;
  ++*txtptr;			/* skip past open paren */
  switch (c) {
  case '(':			/* if envelope S-expression */
    adr = imap_parse_address (stream,txtptr,reply);
    if (**txtptr != ')') {
      sprintf (LOCAL->tmp,"Junk at end of address list: %.80s",*txtptr);
      mm_log (LOCAL->tmp,WARN);
    }
    else ++*txtptr;		/* skip past delimiter */
    break;
  case 'N':			/* if NIL */
  case 'n':
    ++*txtptr;			/* bump past "I" */
    ++*txtptr;			/* bump past "L" */
    break;
  default:
    sprintf (LOCAL->tmp,"Not an address: %.80s",*txtptr);
    mm_log (LOCAL->tmp,WARN);
    break;
  }
  return adr;
}

/* IMAP parse address
 * Accepts: MAIL stream
 *	    current text pointer
 *	    parsed reply
 * Returns: address, NIL on failure
 *
 * Updates text pointer
 */

ADDRESS *imap_parse_address (MAILSTREAM *stream,char **txtptr,
			     IMAPPARSEDREPLY *reply)
{
  ADDRESS *adr = NIL;
  ADDRESS *ret = NIL;
  ADDRESS *prev = NIL;
  char c = **txtptr;		/* sniff at first address character */
  switch (c) {
  case '(':			/* if envelope S-expression */
    while (c == '(') {		/* recursion dies on small stack machines */
      ++*txtptr;		/* skip past open paren */
      if (adr) prev = adr;	/* note previous if any */
      adr = mail_newaddr ();	/* instantiate address and parse its fields */
      adr->personal = imap_parse_string (stream,txtptr,reply,(long) NIL,NIL);
      adr->adl = imap_parse_string (stream,txtptr,reply,(long) NIL,NIL);
      adr->mailbox = imap_parse_string (stream,txtptr,reply,(long) NIL,NIL);
      adr->host = imap_parse_string (stream,txtptr,reply,(long) NIL,NIL);
      if (**txtptr != ')') {	/* handle trailing paren */
	sprintf (LOCAL->tmp,"Junk at end of address: %.80s",*txtptr);
	mm_log (LOCAL->tmp,WARN);
      }
      else ++*txtptr;		/* skip past close paren */
      c = **txtptr;		/* set up for while test */
				/* ignore leading spaces in front of next */
      while (c == ' ') c = *++*txtptr;
      if (!ret) ret = adr;	/* if first time note first adr */
				/* if previous link new block to it */
      if (prev) prev->next = adr;
    }
    break;

  case 'N':			/* if NIL */
  case 'n':
    *txtptr += 3;		/* bump past NIL */
    break;
  default:
    sprintf (LOCAL->tmp,"Not an address: %.80s",*txtptr);
    mm_log (LOCAL->tmp,WARN);
    break;
  }
  return ret;
}

/* IMAP parse flags
 * Accepts: current message cache
 *	    current text pointer
 *
 * Updates text pointer
 */

void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt,char **txtptr)
{
  char *flag;
  char c = '\0';
  elt->valid = T;		/* mark have valid flags now */
  elt->user_flags = NIL;	/* zap old flag values */
  elt->seen = elt->deleted = elt->flagged = elt->answered = elt->recent = NIL;
  while (c != ')') {		/* parse list of flags */
				/* point at a flag */
    while (*(flag = ++*txtptr) == ' ');
				/* scan for end of flag */
    while (**txtptr != ' ' && **txtptr != ')') ++*txtptr;
    c = **txtptr;		/* save delimiter */
    **txtptr = '\0';		/* tie off flag */
    if (!*flag) break;		/* null flag */
				/* if starts with \ must be sys flag */
    else if (*ucase (flag) == '\\') {
      if (!strcmp (flag,"\\SEEN")) elt->seen = T;
      else if (!strcmp (flag,"\\DELETED")) elt->deleted = T;
      else if (!strcmp (flag,"\\FLAGGED")) elt->flagged = T;
      else if (!strcmp (flag,"\\ANSWERED")) elt->answered = T;
      else if (!strcmp (flag,"\\RECENT")) elt->recent = T;
      else if (!strcmp (flag,"\\DRAFT")) elt->draft = T;
    }
				/* otherwise user flag */
    else elt->user_flags |= imap_parse_user_flag (stream,flag);
  }
  ++*txtptr;			/* bump past delimiter */
  if (!elt->seen) {		/* if \Seen went off */
				/* flush any cached text */
    if (elt->data2) fs_give ((void **) &elt->data2);
    if (!stream->scache) imap_gc_body (mail_lelt (stream,elt->msgno)->body);
  }
  mm_flags (stream,elt->msgno);	/* make sure top level knows */
}


/* IMAP parse user flag
 * Accepts: MAIL stream
 *	    flag name
 * Returns: flag bit position
 */

unsigned long imap_parse_user_flag (MAILSTREAM *stream,char *flag)
{
  char tmp[MAILTMPLEN];
  long i;
				/* sniff through all user flags */
  for (i = 0; i < NUSERFLAGS; ++i)
    if (stream->user_flags[i] &&
	!strcmp (flag,ucase (strcpy (tmp,stream->user_flags[i]))))
      return (1 << i);		/* found it! */
  return (unsigned long) 0;	/* not found */
}

/* IMAP parse string
 * Accepts: MAIL stream
 *	    current text pointer
 *	    parsed reply
 *	    flag that it may be kept outside of free storage cache
 *	    returned string length
 * Returns: string
 *
 * Updates text pointer
 */

char *imap_parse_string (MAILSTREAM *stream,char **txtptr,
			 IMAPPARSEDREPLY *reply,long special,
			 unsigned long *len)
{
  char *st;
  char *string = NIL;
  unsigned long i;
  char c = **txtptr;		/* sniff at first character */
  mailgets_t mg = (mailgets_t) mail_parameters (NIL,GET_GETS,NIL);
				/* ignore leading spaces */
  while (c == ' ') c = *++*txtptr;
  st = ++*txtptr;		/* remember start of string */
  switch (c) {
  case '"':			/* if quoted string */
    i = 0;			/* initial byte count */
    while (**txtptr != '"') {	/* search for end of string */
      ++i;			/* bump count */
      ++*txtptr;		/* bump pointer */
    }
    string = (char *) fs_get ((size_t) i + 1);
				/* copy the string */
    strncpy (string,st,(size_t) i);
    string[i] = '\0';		/* tie off string */
    ++*txtptr;			/* bump past delimiter */
    if (len) *len = i;		/* set return value too */
    break;
  case 'N':			/* if NIL */
  case 'n':
    ++*txtptr;			/* bump past "I" */
    ++*txtptr;			/* bump past "L" */
    if (len) *len = 0;
    break;

  case '{':			/* if literal string */
				/* get size of string */
    i = strtoul (*txtptr,txtptr,10);
				/* have special routine to slurp string? */
    if (special && mg) string = (*mg) (tcp_getbuffer,LOCAL->tcpstream,i);
    else {			/* must slurp into free storage */
      string = (char *) fs_get ((size_t) i + 1);
      *string = '\0';		/* init in case getbuffer fails */
				/* get the literal */
      tcp_getbuffer (LOCAL->tcpstream,i,string);
    }
    fs_give ((void **) &reply->line);
    if (len) *len = i;		/* set return value */
				/* get new reply text line */
    reply->line = tcp_getline (LOCAL->tcpstream);
    if (stream->debug) mm_dlog (reply->line);
    *txtptr = reply->line;	/* set text pointer to point at it */
    break;
  default:
    sprintf (LOCAL->tmp,"Not a string: %c%.80s",c,*txtptr);
    mm_log (LOCAL->tmp,WARN);
    if (len) *len = 0;
    break;
  }
  return string;
}

/* IMAP parse body structure or contents
 * Accepts: MAIL stream
 *	    pointer to body pointer
 *	    pointer to segment
 *	    current text pointer
 *	    parsed reply
 *
 * Updates text pointer, stores body
 */

void imap_parse_body (MAILSTREAM *stream,unsigned long msgno,BODY **body,
		      char *seg,char **txtptr,IMAPPARSEDREPLY *reply)
{
  char *s;
  unsigned long i,size;
  BODY *b;
  PART *part;
  switch (*seg++) {		/* dispatch based on type of data */
  case 'S':			/* extensible body structure */
    if (strcmp (seg,"TRUCTURE")) {
      sprintf (LOCAL->tmp,"Bad body fetch: %.80s",seg);
      mm_log (LOCAL->tmp,WARN);
      return;
    }
				/* falls through */
  case '\0':			/* body structure */
    mail_free_body (body);	/* flush any prior body */
				/* instantiate and parse a new body */
    imap_parse_body_structure (stream,*body = mail_newbody (),txtptr,reply);
    break;
  case '[':			/* body section text */
    if ((!(s = strchr (seg,']'))) || s[1]) {
      sprintf (LOCAL->tmp,"Bad body index: %.80s",seg);
      mm_log (LOCAL->tmp,WARN);
      return;
    }
    *s = '\0';			/* tie off section specifier */
				/* get the body section text */
    s = imap_parse_string (stream,txtptr,reply,msgno,&size);
    if (!(b = *body)) {		/* must have structure first */
      mm_log ("Body contents received when body structure unknown",WARN);
      if (s) fs_give ((void **) &s);
      return;
    }
				/* get first section number */
    if (!(seg && *seg && isdigit (*seg))) {
      mm_log ("Bogus section number",WARN);
      if (s) fs_give ((void **) &s);
      return;
    }

				/* non-zero section? */
    if (i = strtoul (seg,&seg,10)) {
      do {			/* multipart content? */
	if (b->type == TYPEMULTIPART) {
				/* yes, find desired part */
	  part = b->contents.part;
	  while (--i && (part = part->next));
	  if (!part || (((b = &part->body)->type == TYPEMULTIPART) &&
			!(s && *s))){
	    mm_log ("Bad section number",WARN);
	    if (s) fs_give ((void **) &s);
	    return;
	  }
	}
	else if (i != 1) {	/* otherwise must be section 1 */
	  mm_log ("Invalid section number",WARN);
	  if (s) fs_give ((void **) &s);
	  return;
	}
				/* need to go down further? */
	if (i = *seg) switch (b->type) {
	case TYPEMESSAGE:	/* embedded message, get body */
	  b = b->contents.msg.body;
	  if (!((*seg++ == '.') && isdigit (*seg))) {
	    mm_log ("Invalid message section",WARN);
	    if (s) fs_give ((void **) &s);
	    return;
	  }
	  if (!(i = strtoul (seg,&seg,10))) {
	    if (b->contents.msg.hdr) fs_give ((void **) &b->contents.msg.hdr);
	    b->contents.msg.hdr = s;
	    b->contents.msg.hdrsize = size;
	    b = NIL;
	  }
	  break;
	case TYPEMULTIPART:	/* multipart, get next section */
	  if ((*seg++ == '.') && (i = strtoul (seg,&seg,10)) > 0) break;
	default:		/* bogus subpart */
	  mm_log ("Invalid multipart section",WARN);
	  if (s) fs_give ((void **) &s);
	  return;
	}
      } while (i);

      if (b) switch (b->type) {	/* decide where the data goes based on type */
      case TYPEMULTIPART:	/* nothing to fetch with these */
	mm_log ("Textual body contents received for MULTIPART body part",WARN);
	if (s) fs_give ((void **) &s);
	return;
      case TYPEMESSAGE:		/* encapsulated message */
	if (b->contents.msg.text) fs_give ((void **) &b->contents.msg.text);
	b->contents.msg.text = s;
	break;
      case TYPETEXT:		/* textual data */
	if (b->contents.text) fs_give ((void **) &b->contents.text);
	b->contents.text = (unsigned char *) s;
	break;
      default:			/* otherwise assume it is binary */
	if (b->contents.binary) fs_give ((void **) &b->contents.binary);
	b->contents.binary = (void *) s;
	break;
      }
    }
    else imap_parse_header (stream,msgno,s,size);
    break;
  default:			/* bogon */
    sprintf (LOCAL->tmp,"Bad body fetch: %.80s",seg);
    mm_log (LOCAL->tmp,WARN);
    return;
  }
}

/* IMAP parse body structure
 * Accepts: MAIL stream
 *	    body structure to write into
 *	    current text pointer
 *	    parsed reply
 *
 * Updates text pointer
 */

void imap_parse_body_structure (MAILSTREAM *stream,BODY *body,char **txtptr,
				IMAPPARSEDREPLY *reply)
{
  int i;
  char *s;
  PART *part = NIL;
  char c = *((*txtptr)++);	/* grab first character */
				/* ignore leading spaces */
  while (c == ' ') c = *((*txtptr)++);
  switch (c) {			/* dispatch on first character */
  case '(':			/* body structure list */
    if (**txtptr == '(') {	/* multipart body? */
      body->type= TYPEMULTIPART;/* yes, set its type */
      do {			/* instantiate new body part */
	if (part) part = part->next = mail_newbody_part ();
	else body->contents.part = part = mail_newbody_part ();
				/* parse it */
	imap_parse_body_structure (stream,&part->body,txtptr,reply);
      } while (**txtptr == '(');/* for each body part */
      if (!(body->subtype = imap_parse_string (stream,txtptr,reply,(long) NIL,
					       NIL)))
	mm_log ("Missing multipart subtype",WARN);

      if (**txtptr == ' ')	/* if extension data */
	body->parameter = imap_parse_body_parameter (stream,txtptr,reply);

      if (**txtptr == ' ')	/* disposition */
	imap_parse_disposition (stream,body,txtptr,reply);
      if (**txtptr == ' ')	/* language */
	(void) imap_parse_language (stream,txtptr,reply);

     while (**txtptr == ' ') imap_parse_extension (stream,body,txtptr,reply);


      if (**txtptr != ')') {	/* validate ending */
	sprintf (LOCAL->tmp,"Junk at end of multipart body: %.80s",*txtptr);
	mm_log (LOCAL->tmp,WARN);
      }
      else ++*txtptr;		/* skip past delimiter */
    }

    else {			/* not multipart, parse type name */
      if (**txtptr == ')') {	/* empty body? */
	++*txtptr;		/* bump past it */
	break;			/* and punt */
      }
      body->type = TYPEOTHER;	/* assume unknown type */
      body->encoding = ENCOTHER;/* and unknown encoding */
				/* parse type */
      if (s = imap_parse_string (stream,txtptr,reply,(long) NIL,NIL)) {
	ucase (s);		/* make parse easier */
	for (i=0;(i<=TYPEMAX) && body_types[i] && strcmp(s,body_types[i]);i++);
	if (i <= TYPEMAX) {	/* only if found a slot */
	  if (body_types[i]) fs_give ((void **) &s);
				/* assign empty slot */
	  else body_types[i] = cpystr (s);
	  body->type = i;	/* set body type */
	}
      }
				/* parse subtype */
      body->subtype = imap_parse_string (stream,txtptr,reply,(long) NIL,NIL);
      body->parameter = imap_parse_body_parameter (stream,txtptr,reply);
      body->id = imap_parse_string (stream,txtptr,reply,(long) NIL,NIL);
      body->description = imap_parse_string(stream,txtptr,reply,(long)NIL,NIL);
      if (s = imap_parse_string (stream,txtptr,reply,(long) NIL,NIL)) {
	ucase (s);		/* make parse easier */
				/* search for body encoding */
	for (i = 0; (i <= ENCMAX) && body_encodings[i] &&
	     strcmp (s,body_encodings[i]); i++);
	if (i > ENCMAX) body->type = ENCOTHER;
	else {			/* only if found a slot */
	  if (body_encodings[i]) fs_give ((void **) &s);
				/* assign empty slot */
	  else body_encodings[i] = cpystr (s);
	  body->encoding = i;	/* set body encoding */
	}
      }
				/* parse size of contents in bytes */
      body->size.bytes = strtoul (*txtptr,txtptr,10);
      switch (body->type) {	/* possible extra stuff */
      case TYPEMESSAGE:		/* message envelope and body */
	if (strcmp (body->subtype,"RFC822")) break;
	imap_parse_envelope (stream,&body->contents.msg.env,txtptr,reply);
	body->contents.msg.body = mail_newbody ();
	imap_parse_body_structure(stream,body->contents.msg.body,txtptr,reply);
				/* drop into text case */
      case TYPETEXT:		/* size in lines */
	body->size.lines = strtoul (*txtptr,txtptr,10);
	break;
      default:			/* otherwise nothing special */
	break;
      }
      if (**txtptr == ' ')	/* if extension data */
	body->md5 = imap_parse_string (stream,txtptr,reply,(long) NIL,NIL);

      if (**txtptr == ' ')	/* disposition */
	imap_parse_disposition (stream,body,txtptr,reply);
      if (**txtptr == ' ')	/* language */
	(void) imap_parse_language (stream,txtptr,reply);

      while (**txtptr == ' ') imap_parse_extension (stream,body,txtptr,reply);
      if (**txtptr != ')') {	/* validate ending */
	sprintf (LOCAL->tmp,"Junk at end of body part: %.80s",*txtptr);
	mm_log (LOCAL->tmp,WARN);
      }
      else ++*txtptr;		/* skip past delimiter */
    }
    break;
  case 'N':			/* if NIL */
  case 'n':
    ++*txtptr;			/* bump past "I" */
    ++*txtptr;			/* bump past "L" */
    break;
  default:			/* otherwise quite bogus */
    sprintf (LOCAL->tmp,"Bogus body structure: %.80s",*txtptr);
    mm_log (LOCAL->tmp,WARN);
    break;
  }
}

/* IMAP parse body parameter
 * Accepts: MAIL stream
 *	    current text pointer
 *	    parsed reply
 * Returns: body parameter
 * Updates text pointer
 */

PARAMETER *imap_parse_body_parameter (MAILSTREAM *stream,char **txtptr,
				      IMAPPARSEDREPLY *reply)
{
  PARAMETER *ret = NIL;
  PARAMETER *par = NIL;
  char c,*s;
				/* ignore leading spaces */
  while ((c = *(*txtptr)++) == ' ');
				/* parse parameter list */
  if (c == '(') while (c != ')') {
				/* append new parameter to tail */
    if (ret) par = par->next = mail_newbody_parameter ();
    else ret = par = mail_newbody_parameter ();
    if(!(par->attribute=imap_parse_string(stream,txtptr,reply,(long)NIL,NIL))){
      mm_log ("Missing parameter attribute",WARN);
      par->attribute = cpystr ("UNKNOWN");
    }
    if (!(par->value = imap_parse_string (stream,txtptr,reply,(long)NIL,NIL))){
      sprintf (LOCAL->tmp,"Missing value for parameter %.80s",par->attribute);
      mm_log (LOCAL->tmp,WARN);
      par->value = cpystr ("UNKNOWN");
    }
    switch (c = **txtptr) {	/* see what comes after */
    case ' ':			/* flush whitespace */
      while ((c = *++*txtptr) == ' ');
      break;
    case ')':			/* end of attribute/value pairs */
      ++*txtptr;		/* skip past closing paren */
      break;
    default:
      sprintf (LOCAL->tmp,"Junk at end of parameter: %.80s",*txtptr);
      mm_log (LOCAL->tmp,WARN);
      break;
    }
  }
				/* empty parameter, must be NIL */
  else if (((c == 'N') || (c == 'n')) &&
	   ((*(s = *txtptr) == 'I') || (*s == 'i')) &&
	   ((s[1] == 'L') || (s[1] == 'l'))) *txtptr += 2;
  else {
    sprintf (LOCAL->tmp,"Bogus body parameter: %c%.80s",c,(*txtptr) - 1);
    mm_log (LOCAL->tmp,WARN);
  }
  return ret;
}

void imap_parse_disposition (MAILSTREAM *stream,BODY *body,char **txtptr,
			     IMAPPARSEDREPLY *reply)
{
  switch (*++*txtptr) {
  case '(':
    ++*txtptr;			/* skip open paren */
    (void) imap_parse_string (stream,txtptr,reply,NIL,NIL); 
    (void) imap_parse_body_parameter (stream,txtptr,reply);
    if (**txtptr != ')') {	/* validate ending */
      sprintf (LOCAL->tmp,"Junk at end of disposition: %.80s",*txtptr);
      mm_log (LOCAL->tmp,WARN);
    }
    else ++*txtptr;		/* skip past delimiter */
    break;
  case 'N':			/* if NIL */
  case 'n':
    ++*txtptr;			/* bump past "N" */
    ++*txtptr;			/* bump past "I" */
    ++*txtptr;			/* bump past "L" */
    break;
  default:
    sprintf (LOCAL->tmp,"Unknown body disposition: %.80s",*txtptr);
    mm_log (LOCAL->tmp,WARN);
				/* try to skip to next space */
    while ((*++*txtptr != ' ') && (**txtptr != ')') && **txtptr);
    break;
  }
}

/* IMAP parse body language
 * Accepts: MAIL stream
 *	    current text pointer
 *	    parsed reply
 * Returns: string list or NIL if empty or error
 */

STRINGLIST *imap_parse_language (MAILSTREAM *stream,char **txtptr,
				 IMAPPARSEDREPLY *reply)
{
  unsigned long i;
  char *s;
  STRINGLIST *ret = NIL;
				/* language is a list */
  if (*++*txtptr == '(') ret = imap_parse_stringlist (stream,txtptr,reply);
  else if (s = imap_parse_string (stream,txtptr,reply,NIL,&i)) {
    ;
  }
  return ret;
}

STRINGLIST *imap_parse_stringlist (MAILSTREAM *stream,char **txtptr,
				   IMAPPARSEDREPLY *reply)
{
  STRINGLIST *stl = NIL;
  STRINGLIST *stc;
  char c,*s;
  char *t = *txtptr;
  unsigned long size;
  char *ptr;
				/* parse the list */
  if (*t++ == '(') while (*t != ')') {
    if (stl) stc = stc->next = mail_newstringlist ();
    else stc = stl = mail_newstringlist ();
				/* atom */
    if ((*t != '{') && (*t != '"') && (s = strpbrk (t," )"))) {
      c = *s;			/* note delimiter */
      *s = '\0';		/* tie off atom and copy it*/
      size = strlen(t);
      if (c == ' ') t = ++s;	/* another atom follows */
      else *(t = s) = c;	/* restore delimiter */
    }
				/* string */
    else if (s = imap_parse_string (stream,&t,reply,NIL,&size)) {
      ;
    }
    else {			/* error */
      sprintf (LOCAL->tmp,"Bogus string list member: %.80s",t);
      mm_log (LOCAL->tmp,WARN);
      mail_free_stringlist (&stl);
      break;
    }
  }
  if (stl) *txtptr = ++t;	/* update return string */
  return stl;
}




/* IMAP parse body structure
 * Accepts: MAIL stream
 *	    current text pointer
 *	    parsed reply
 *	    body structure to write into
 *
 * Updates text pointer
 */

void imap_parse_extension (MAILSTREAM *stream,BODY *body,char **txtptr,
			   IMAPPARSEDREPLY *reply)
{
  unsigned long i,j;
  switch (*++*txtptr) {		/* action depends upon first character */
  case '(':
    while (*++*txtptr != ')') imap_parse_extension (stream,body,txtptr,reply);
    ++*txtptr;			/* bump past closing parenthesis */
    break;
  case '"':			/* if quoted string */
    while (*++*txtptr != '"');
    ++*txtptr;			/* bump past closing quote */
    break;
  case 'N':			/* if NIL */
  case 'n':
    ++*txtptr;			/* bump past "N" */
    ++*txtptr;			/* bump past "I" */
    ++*txtptr;			/* bump past "L" */
    break;
  case '{':			/* get size of literal */
    ++*txtptr;			/* bump past open squiggle */
    if (i = strtoul (*txtptr,txtptr,10)) do
      tcp_getbuffer (LOCAL->tcpstream,j = max (i,(long)IMAPTMPLEN),LOCAL->tmp);
    while (i -= j);
				/* get new reply text line */
    reply->line = tcp_getline (LOCAL->tcpstream);
    if (stream->debug) mm_dlog (reply->line);
    *txtptr = reply->line;	/* set text pointer to point at it */
    break;
  case '0': case '1': case '2': case '3': case '4':
  case '5': case '6': case '7': case '8': case '9':
    strtoul (*txtptr,txtptr,10);
    break;
  default:
    sprintf (LOCAL->tmp,"Unknown extension token: %.80s",*txtptr);
    mm_log (LOCAL->tmp,WARN);
				/* try to skip to next space */
    while ((*++*txtptr != ' ') && (**txtptr != ')') && **txtptr);
    break;
  }
}

/* IMAP return host name
 * Accepts: MAIL stream
 * Returns: host name
 */

char *imap_host (MAILSTREAM *stream)
{
				/* return host name on stream if open */
  return (LOCAL && LOCAL->tcpstream) ? tcp_host (LOCAL->tcpstream) :
    "<closed stream>";
}


/* IMAP return port number
 * Accepts: MAIL stream
 * Returns: port number
 */

unsigned long imap_port (MAILSTREAM *stream)
{
				/* return port number on stream if open */
  return (LOCAL && LOCAL->tcpstream) ? tcp_port (LOCAL->tcpstream) :
    0xffffffff;
}


syntax highlighted by Code2HTML, v. 0.9.1