text: /* 
   * Copyright 2016-2018 Eduardo Chappa
   *
   * Last Edited: May 5, 2018 Eduardo Chappa <alpine.chappa@gmx.com>
   *
   */
  /* ========================================================================
   * Copyright 2008-2011 Mark Crispin   
   * ========================================================================
   */
  /*
   * Program:	Interactive Message Access Protocol 4rev1 (IMAP4R1) routines
   *
   * Author:	Mark Crispin
   *
   * Date:	15 June 1988
   * Last Edited:	3 October 2011
   *
   * Previous versions of this file were
   *
   * Copyright 1988-2008 University of Washington
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * This original version of this file is
   * Copyright 1988 Stanford University
   * and 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.
   */
  
  
  #include <ctype.h>
  #include <stdio.h>
  #include <time.h>
  #include "c-client.h"
  #include "imap4r1.h"
  
  /* Parameters */
  
  #define IMAPLOOKAHEAD 20	/* envelope lookahead */
  #define IMAPUIDLOOKAHEAD 1000	/* UID lookahead */
  #define IMAPTCPPORT (long) 143	/* assigned TCP contact port */
  #define IMAPSSLPORT (long) 993	/* assigned SSL TCP contact port */
  #define MAXCOMMAND 1000		/* RFC 2683 guideline for cmd line length */
  #define IDLETIMEOUT (long) 30	/* defined in RFC 3501 */
  #define MAXSERVERLIT 0x7ffffffe	/* maximum server literal size
  				 * must be smaller than 4294967295
  				 */
  
  
  /* Parsed reply message from imap_reply */
  
  typedef struct imap_parsed_reply {
    unsigned char *line;		/* original reply string pointer */
    unsigned char *tag;		/* command tag this reply is for */
    unsigned char *key;		/* reply keyword */
    unsigned char *text;		/* subsequent text */
  } IMAPPARSEDREPLY;
  
  
  #define IMAPTMPLEN 16*MAILTMPLEN
  
  
  /* IMAP4 I/O stream local data */
  	
  typedef struct imap_local {
    NETSTREAM *netstream;		/* TCP I/O stream */
    IMAPPARSEDREPLY reply;	/* last parsed reply */
    MAILSTATUS *stat;		/* status to fill in */
    IMAPCAP cap;			/* server capabilities */
    char *appendmailbox;		/* mailbox being appended to */
    unsigned int uidsearch : 1;	/* UID searching */
    unsigned int byeseen : 1;	/* saw a BYE response */
  				/* got implicit capabilities */
    unsigned int gotcapability : 1;
    unsigned int setid : 1;	/* set id of app */
    unsigned int sensitive : 1;	/* sensitive data in progress */
    unsigned int tlsflag : 1;	/* TLS session */
    unsigned int tlssslv23 : 1;	/* TLS using SSLv23 client method */
    unsigned int notlsflag : 1;	/* TLS not used in session */
    unsigned int sslflag : 1;	/* SSL session */
    unsigned int tls1 : 1;	/* using TLSv1 over SSL */
    unsigned int tls1_1 : 1;	/* using TLSv1_1 over SSL */
    unsigned int tls1_2 : 1;	/* using TLSv1_2 over SSL */
    unsigned int tls1_3 : 1;	/* using TLSv1_3 over SSL */
    unsigned int novalidate : 1;	/* certificate not validated */
    unsigned int filter : 1;	/* filter SEARCH/SORT/THREAD results */
    unsigned int loser : 1;	/* server is a loser */
    unsigned int saslcancel : 1;	/* SASL cancelled by protocol */
    long authflags;		/* required flags for authenticators */
    unsigned long sortsize;	/* sort return data size */
    unsigned long *sortdata;	/* sort return data */
    struct {
      unsigned long uid;		/* last UID returned */
      unsigned long msgno;	/* last msgno returned */
    } lastuid;
    NAMESPACE **namespace;	/* namespace return data */
    THREADNODE *threaddata;	/* thread return data */
    char *referral;		/* last referral */
    char *prefix;			/* find prefix */
    char *user;			/* logged-in user */
    char *reform;			/* reformed sequence */
    char tmp[IMAPTMPLEN];		/* temporary buffer */
    SEARCHSET *lookahead;		/* fetch lookahead */
    IDLIST *id;			/* id of stream */
  } IMAPLOCAL;
  
  
  /* Convenient access to local data */
  
  #define LOCAL ((IMAPLOCAL *) stream->local)
  
  /* Arguments to imap_send() */
  
  typedef struct imap_argument {
    int type;			/* argument type */
    void *text;			/* argument text */
  } IMAPARG;
  
  
  /* imap_send() argument types */
  
  #define ATOM 0
  #define NUMBER 1
  #define FLAGS 2
  #define ASTRING 3
  #define LITERAL 4
  #define LIST 5
  #define SEARCHPROGRAM 6
  #define SORTPROGRAM 7
  #define BODYTEXT 8
  #define BODYPEEK 9
  #define BODYCLOSE 10
  #define SEQUENCE 11
  #define LISTMAILBOX 12
  #define MULTIAPPEND 13
  #define SNLIST 14
  #define MULTIAPPENDREDO 15
  
  
  /* Append data */
  
  typedef struct append_data {
    append_t af;
    void *data;
    char *flags;
    char *date;
    STRING *message;
  } APPENDDATA;
  
  /* Function prototypes */
  
  DRIVER *imap_valid (char *name);
  void *imap_parameters (long function,void *value);
  void imap_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
  void imap_list (MAILSTREAM *stream,char *ref,char *pat);
  void imap_lsub (MAILSTREAM *stream,char *ref,char *pat);
  void imap_list_work (MAILSTREAM *stream,char *cmd,char *ref,char *pat,
  		     char *contents);
  long imap_subscribe (MAILSTREAM *stream,char *mailbox);
  long imap_unsubscribe (MAILSTREAM *stream,char *mailbox);
  long imap_create (MAILSTREAM *stream,char *mailbox);
  long imap_delete (MAILSTREAM *stream,char *mailbox);
  long imap_rename (MAILSTREAM *stream,char *old,char *newname);
  long imap_manage (MAILSTREAM *stream,char *mailbox,char *command,char *arg2);
  long imap_status (MAILSTREAM *stream,char *mbx,long flags);
  MAILSTREAM *imap_open (MAILSTREAM *stream);
  IMAPPARSEDREPLY *imap_rimap (MAILSTREAM *stream,char *service,NETMBX *mb,
  			     char *usr,char *tmp);
  long imap_anon (MAILSTREAM *stream,char *tmp);
  long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr);
  long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr);
  void *imap_challenge (void *stream,unsigned long *len);
  long imap_response (void *stream,char *s,unsigned long size);
  void imap_close (MAILSTREAM *stream,long options);
  void imap_fast (MAILSTREAM *stream,char *sequence,long flags);
  void imap_flags (MAILSTREAM *stream,char *sequence,long flags);
  long imap_overview (MAILSTREAM *stream,overview_t ofn);
  ENVELOPE *imap_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body,
  			  long flags);
  long imap_msgdata (MAILSTREAM *stream,unsigned long msgno,char *section,
  		   unsigned long first,unsigned long last,STRINGLIST *lines,
  		   long flags);
  unsigned long imap_uid (MAILSTREAM *stream,unsigned long msgno);
  unsigned long imap_msgno (MAILSTREAM *stream,unsigned long uid);
  void imap_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
  long imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags);
  long imap_search_x_gm_ext1 (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags);
  unsigned long *imap_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
  			  SORTPGM *pgm,long flags);
  THREADNODE *imap_thread (MAILSTREAM *stream,char *type,char *charset,
  			 SEARCHPGM *spg,long flags);
  THREADNODE *imap_thread_work (MAILSTREAM *stream,char *type,char *charset,
  			      SEARCHPGM *spg,long flags);
  long imap_ping (MAILSTREAM *stream);
  void imap_check (MAILSTREAM *stream);
  long imap_expunge (MAILSTREAM *stream,char *sequence,long options);
  long imap_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
  long imap_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
  long imap_append_referral (char *mailbox,char *tmp,append_t af,void *data,
  			   char *flags,char *date,STRING *message,
  			   APPENDDATA *map,long options);
  IMAPPARSEDREPLY *imap_append_single (MAILSTREAM *stream,char *mailbox,
  				     char *flags,char *date,STRING *message);
  
  void imap_gc (MAILSTREAM *stream,long gcflags);
  void imap_gc_body (BODY *body);
  void imap_capability (MAILSTREAM *stream);
  long imap_acl_work (MAILSTREAM *stream,char *command,IMAPARG *args[]);
  
  IMAPPARSEDREPLY *imap_send (MAILSTREAM *stream,char *cmd,IMAPARG *args[]);
  IMAPPARSEDREPLY *imap_sout (MAILSTREAM *stream,char *tag,char *base,char **s);
  long imap_soutr (MAILSTREAM *stream,char *string);
  IMAPPARSEDREPLY *imap_send_astring (MAILSTREAM *stream,char *tag,char **s,
  				    SIZEDTEXT *as,long wildok,char *limit);
  IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *tag,char **s,
  				    STRING *st);
  IMAPPARSEDREPLY *imap_send_spgm (MAILSTREAM *stream,char *tag,char *base,
  				 char **s,SEARCHPGM *pgm,char *limit);
  char *imap_send_spgm_trim (char *base,char *s,char *text);
  IMAPPARSEDREPLY *imap_send_sset (MAILSTREAM *stream,char *tag,char *base,
  				 char **s,SEARCHSET *set,char *prefix,
  				 char *limit);
  IMAPPARSEDREPLY *imap_send_slist (MAILSTREAM *stream,char *tag,char *base,
  				  char **s,char *name,STRINGLIST *list,
  				  char *limit);
  void imap_send_sdate (char **s,char *name,unsigned short date);
  IMAPPARSEDREPLY *imap_reply (MAILSTREAM *stream,char *tag);
  IMAPPARSEDREPLY *imap_parse_reply (MAILSTREAM *stream,char *text);
  IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text);
  long imap_OK (MAILSTREAM *stream,IMAPPARSEDREPLY *reply);
  void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply);
  void imap_parse_response (MAILSTREAM *stream,char *text,long errflg,long ntfy);
  NAMESPACE *imap_parse_namespace (MAILSTREAM *stream,unsigned char **txtptr,
  				 IMAPPARSEDREPLY *reply);
  THREADNODE *imap_parse_thread (MAILSTREAM *stream,unsigned char **txtptr);
  void imap_parse_header (MAILSTREAM *stream,ENVELOPE **env,SIZEDTEXT *hdr,
  			STRINGLIST *stl);
  void imap_parse_envelope (MAILSTREAM *stream,ENVELOPE **env,
  			  unsigned char **txtptr,IMAPPARSEDREPLY *reply);
  ADDRESS *imap_parse_adrlist (MAILSTREAM *stream,unsigned char **txtptr,
  			     IMAPPARSEDREPLY *reply);
  ADDRESS *imap_parse_address (MAILSTREAM *stream,unsigned char **txtptr,
  			     IMAPPARSEDREPLY *reply);
  void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt,
  		       unsigned char **txtptr);
  unsigned long imap_parse_user_flag (MAILSTREAM *stream,char *flag);
  unsigned char *imap_parse_astring (MAILSTREAM *stream,unsigned char **txtptr,
  			  IMAPPARSEDREPLY *reply,unsigned long *len);
  unsigned char *imap_parse_string (MAILSTREAM *stream,unsigned char **txtptr,
  				  IMAPPARSEDREPLY *reply,GETS_DATA *md,
  				  unsigned long *len,long flags);
  void imap_parse_body (GETS_DATA *md,char *seg,unsigned char **txtptr,
  		      IMAPPARSEDREPLY *reply);
  void imap_parse_body_structure (MAILSTREAM *stream,BODY *body,
  				unsigned char **txtptr,IMAPPARSEDREPLY *reply);
  PARAMETER *imap_parse_body_parameter (MAILSTREAM *stream,
  				      unsigned char **txtptr,
  				      IMAPPARSEDREPLY *reply);
  void imap_parse_disposition (MAILSTREAM *stream,BODY *body,
  			     unsigned char **txtptr,IMAPPARSEDREPLY *reply);
  STRINGLIST *imap_parse_language (MAILSTREAM *stream,unsigned char **txtptr,
  				 IMAPPARSEDREPLY *reply);
  STRINGLIST *imap_parse_stringlist (MAILSTREAM *stream,unsigned char **txtptr,
  				   IMAPPARSEDREPLY *reply);
  void imap_parse_extension (MAILSTREAM *stream,unsigned char **txtptr,
  			   IMAPPARSEDREPLY *reply);
  void imap_parse_capabilities (MAILSTREAM *stream,char *t);
  IMAPPARSEDREPLY *imap_fetch (MAILSTREAM *stream,char *sequence,long flags);
  char *imap_reform_sequence (MAILSTREAM *stream,char *sequence,long flags);
  long imap_setid(MAILSTREAM *stream, IDLIST *idlist);
  IDLIST *imap_parse_idlist(char *text);
  
  /* Driver dispatch used by MAIL */
  
  DRIVER imapdriver = {
    "imap",			/* driver name */
  				/* driver flags */
    DR_MAIL|DR_NEWS|DR_NAMESPACE|DR_CRLF|DR_RECYCLE|DR_HALFOPEN,
    (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_fast,			/* fetch message "fast" attributes */
    imap_flags,			/* fetch message flags */
    imap_overview,		/* fetch overview */
    imap_structure,		/* fetch message envelopes */
    NIL,				/* fetch message header */
    NIL,				/* fetch message body */
    imap_msgdata,			/* fetch partial message */
    imap_uid,			/* unique identifier */
    imap_msgno,			/* message number */
    imap_flag,			/* modify flags */
    NIL,				/* per-message modify flags */
    imap_search,			/* search for message based on criteria */
    imap_sort,			/* sort messages */
    imap_thread,			/* thread messages */
    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 unsigned long imap_maxlogintrials = MAXLOGINTRIALS;
  static long imap_lookahead = IMAPLOOKAHEAD;
  static long imap_uidlookahead = IMAPUIDLOOKAHEAD;
  static long imap_fetchlookaheadlimit = IMAPLOOKAHEAD;
  static long imap_defaultport = 0;
  static long imap_sslport = 0;
  static long imap_tryssl = NIL;
  static long imap_prefetch = IMAPLOOKAHEAD;
  static long imap_closeonerror = NIL;
  static imapenvelope_t imap_envelope = NIL;
  static imapreferral_t imap_referral = NIL;
  static char *imap_extrahdrs = NIL;
  
  				/* constants */
  static char *hdrheader[] = {
    "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-MD5 Content-Disposition Content-Language Content-Location",
    "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Disposition Content-Language Content-Location",
    "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Language Content-Location",
    "BODY.PEEK[HEADER.FIELDS (Newsgroups Content-Location",
    "BODY.PEEK[HEADER.FIELDS (Newsgroups"
  };
  static char *hdrtrailer ="Followup-To References)]";
  
  /* 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 GET_NAMESPACE:
      if (((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->cap.namespace &&
  	!((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->namespace)
        imap_send (((MAILSTREAM *) value),"NAMESPACE",NIL);
      value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->namespace;
      break;
    case GET_THREADERS:
      value = (void *)
        ((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->cap.threader;
      break;
    case SET_FETCHLOOKAHEAD:	/* must use pointer from GET_FETCHLOOKAHEAD */
      fatal ("SET_FETCHLOOKAHEAD not permitted");
    case GET_FETCHLOOKAHEAD:
      value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->lookahead;
      break;
    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_SSLIMAPPORT:
      imap_sslport = (long) value;
      break;
    case GET_SSLIMAPPORT:
      value = (void *) imap_sslport;
      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;
    case SET_IMAPENVELOPE:
      imap_envelope = (imapenvelope_t) value;
      break;
    case GET_IMAPENVELOPE:
      value = (void *) imap_envelope;
      break;
    case SET_IMAPREFERRAL:
      imap_referral = (imapreferral_t) value;
      break;
    case GET_IMAPREFERRAL:
      value = (void *) imap_referral;
      break;
    case SET_IMAPEXTRAHEADERS:
      imap_extrahdrs = (char *) value;
      break;
    case GET_IMAPEXTRAHEADERS:
      value = (void *) imap_extrahdrs;
      break;
    case SET_IMAPTRYSSL:
      imap_tryssl = (long) value;
      break;
    case GET_IMAPTRYSSL:
      value = (void *) imap_tryssl;
      break;
    case SET_FETCHLOOKAHEADLIMIT:
      imap_fetchlookaheadlimit = (long) value;
      break;
    case GET_FETCHLOOKAHEADLIMIT:
      value = (void *) imap_fetchlookaheadlimit;
      break;
  
    case SET_IDLETIMEOUT:
      fatal ("SET_IDLETIMEOUT not permitted");
    case GET_IDLETIMEOUT:
      value = (void *) IDLETIMEOUT;
      break;
    case SET_IDSTREAM:			/* set IMAP server ID */
      fatal ("SET_IDSTREAM not permitted");
    case GET_IDSTREAM:			/* get IMAP server ID */
      value = (void *) &((IMAPLOCAL *) ((MAILSTREAM *) value)->local)->id;
      break;
    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,"SCAN",ref,pat,contents);
  }
  
  
  /* IMAP list mailboxes
   * Accepts: mail stream
   *	    reference
   *	    pattern to search
   */
  
  void imap_list (MAILSTREAM *stream,char *ref,char *pat)
  {
    imap_list_work (stream,"LIST",ref,pat,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,mbx[MAILTMPLEN],tmp[MAILTMPLEN];
  				/* do it on the server */
    imap_list_work (stream,"LSUB",ref,pat,NIL);
    if (*pat == '{') {		/* if remote pattern, must be IMAP */
      if (!imap_valid (pat)) return;
      ref = NIL;			/* good IMAP pattern, punt reference */
    }
  				/* if remote reference, must be valid IMAP */
    if (ref && (*ref == '{') && !imap_valid (ref)) return;
  				/* kludgy application of reference */
    if (ref && *ref) sprintf (mbx,"%s%s",ref,pat);
    else strcpy (mbx,pat);
  
    if ((s = sm_read (tmp,&sdb)) != NULL) do if (imap_valid (s) && pmatch (s,mbx))
      mm_lsub (stream,NIL,s,NIL);
  				/* until no more subscriptions */
    while ((s = sm_read (tmp,&sdb)) != NULL);
  }
  
  /* IMAP find list of mailboxes
   * Accepts: mail stream
   *	    list command
   *	    reference
   *	    pattern to search
   *	    string to scan
   */
  
  void imap_list_work (MAILSTREAM *stream,char *cmd,char *ref,char *pat,
  		     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->netstream) ||
  	   (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->netstream) ||
  	   (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 (LEVELSCAN (stream)) {	/* make sure permitted */
        args[0] = &aref; args[1] = &apat; args[2] = &acont; args[3] = NIL;
        aref.type = ASTRING; aref.text = (void *) (ref ? ref : "");
        apat.type = LISTMAILBOX; apat.text = (void *) pat;
        acont.type = ASTRING; acont.text = (void *) contents;
        imap_send (stream,cmd,args);
      }
      else mm_log ("Scan not valid on this IMAP server",ERROR);
    }
  
    else if (LEVELIMAP4 (stream)){/* easy if IMAP4 */
      args[0] = &aref; args[1] = &apat; args[2] = NIL;
      aref.type = ASTRING; aref.text = (void *) (ref ? ref : "");
      apat.type = LISTMAILBOX; apat.text = (void *) pat;
  				/* referrals armed? */
      if (LOCAL->cap.mbx_ref && mail_parameters (stream,GET_IMAPREFERRAL,NIL)) {
  				/* yes, convert LIST -> RLIST */
        if (!compare_cstring (cmd,"LIST")) cmd = "RLIST";
  				/* and convert LSUB -> RLSUB */
        else if (!compare_cstring (cmd,"LSUB")) cmd = "RLSUB";
      }
      imap_send (stream,cmd,args);
    }
    else if (LEVEL1176 (stream)) {/* 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 = LISTMAILBOX; apat.text = (void *) mbx;
      if (!(strstr (cmd,"LIST") &&/* 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->cap.rfc1176 = NIL;	/* must be RFC-1064 */
    }
    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)
  {
    MAILSTREAM *st = stream;
    long ret = ((stream && LOCAL && LOCAL->netstream) ||
  	      (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT))) ?
  		imap_manage (stream,mailbox,LEVELIMAP4 (stream) ?
  			     "Subscribe" : "Subscribe Mailbox",NIL) : NIL;
  				/* toss out temporary stream */
    if (st != stream) mail_close (stream);
    return ret;
  }
  
  
  /* 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)
  {
    MAILSTREAM *st = stream;
    long ret = ((stream && LOCAL && LOCAL->netstream) ||
  	      (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT))) ?
  		imap_manage (stream,mailbox,LEVELIMAP4 (stream) ?
  			     "Unsubscribe" : "Unsubscribe Mailbox",NIL) : NIL;
  				/* toss out temporary stream */
    if (st != stream) mail_close (stream);
    return ret;
  }
  
  /* 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;
    imapreferral_t ir =
      (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
    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->netstream) ||
         (stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT)))) {
      if (arg2) args[1] = &amb2;	/* second arg present? */
      if (!(ret = (imap_OK (stream,reply = imap_send (stream,command,args)))) &&
  	ir && LOCAL->referral) {
        long code = -1;
        switch (*command) {	/* which command was it? */
        case 'S': code = REFSUBSCRIBE; break;
        case 'U': code = REFUNSUBSCRIBE; break;
        case 'C': code = REFCREATE; break;
        case 'D': code = REFDELETE; break;
        case 'R': code = REFRENAME; break;
        default:
  	fatal ("impossible referral command");
        }
        if ((code >= 0) && (mailbox = (*ir) (stream,LOCAL->referral,code)))
  	ret = imap_manage (NIL,mailbox,command,(*command == 'R') ?
  			   (mailbox + strlen (mailbox) + 1) : NIL);
      }
      mm_log (reply->text,ret ? 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 = NIL;
  				/* use given stream if (rev1 or halfopen) and
  				   right host */
    if (!((stream && (LEVELIMAP4rev1 (stream) || stream->halfopen) &&
  	 mail_usable_network_stream (stream,mbx)) ||
  	(stream = tstream = mail_open (NIL,mbx,OP_HALFOPEN|OP_SILENT))))
      return NIL;
  				/* parse mailbox name */
    mail_valid_net_parse (mbx,&mb);
    args[0] = &ambx;args[1] = NIL;/* set up first argument as mailbox */
    ambx.type = ASTRING; ambx.text = (void *) mb.mailbox;
    if (LEVELIMAP4rev1 (stream)) {/* have STATUS command? */
      imapreferral_t ir;
      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," UIDNEXT");
      if (flags & SA_UIDVALIDITY) strcat (tmp," UIDVALIDITY");
      tmp[0] = '(';
      strcat (tmp,")");
  				/* send "STATUS mailbox flag" */
      if (imap_OK (stream,imap_send (stream,"STATUS",args))) ret = T;
      else if ((ir = (imapreferral_t)
  	      mail_parameters (stream,GET_IMAPREFERRAL,NIL)) &&
  	     LOCAL->referral &&
  	     (mbx = (*ir) (stream,LOCAL->referral,REFSTATUS)))
        ret = imap_status (NIL,mbx,flags | (stream->debug ? SA_DEBUG : NIL));
    }
  
  				/* 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++;
      }
      strcpy (strchr (strcpy (tmp,stream->mailbox),'}') + 1,mb.mailbox);
  				/* pass status to main program */
      mm_status (stream,tmp,&status);
      ret = T;			/* note success */
    }
    if (tstream) mail_close (tstream);
    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 *s,tmp[MAILTMPLEN],usr[MAILTMPLEN];
    NETMBX mb;
    IMAPPARSEDREPLY *reply = NIL;
    imapreferral_t ir =
      (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
  				/* return prototype for OP_PROTOTYPE call */
    if (!stream) return &imapproto;
    mail_valid_net_parse (stream->mailbox,&mb);
    usr[0] = '\0';		/* initially no user name */
    if (LOCAL) {			/* if stream opened earlier by us */
  				/* recycle if still alive */
      if (LOCAL->netstream && (!stream->halfopen || LOCAL->cap.unselect)) {
        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",net_host (LOCAL->netstream));
  	if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user=\"%s\"",
  				  LOCAL->user);
  	if (!stream->silent) mm_log (tmp,(long) NIL);
  				/* unselect if now want halfopen */
  	if (stream->halfopen) imap_send (stream,"UNSELECT",NIL);
        }
        else imap_close (stream,NIL);
      }
      else imap_close (stream,NIL);
    }
  				/* copy flags from name */
    if (mb.dbgflag) stream->debug = T;
    if (mb.readonlyflag) stream->rdonly = T;
    if (mb.anoflag) stream->anonymous = T;
    if (mb.secflag) stream->secure = T;
    if (mb.trysslflag || imap_tryssl) stream->tryssl = T;
  
    if (!LOCAL) {			/* open new connection if no recycle */
      NETDRIVER *ssld = (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL);
      unsigned long defprt = imap_defaultport ? imap_defaultport : IMAPTCPPORT;
      unsigned long sslport = imap_sslport ? imap_sslport : IMAPSSLPORT;
      stream->local =		/* instantiate localdata */
        (void *) memset (fs_get (sizeof (IMAPLOCAL)),0,sizeof (IMAPLOCAL));
  				/* assume IMAP2bis server */
      LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
  				/* in case server is a loser */
      if (mb.loser) LOCAL->loser = T;
  				/* desirable authenticators */
      LOCAL->authflags = (stream->secure ? AU_SECURE : NIL) |
        (mb.authuser[0] ? AU_AUTHUSER : NIL);
      /* IMAP connection open logic is more complex than net_open() normally
       * deals with, because of the simap and rimap hacks.
       * If the session is anonymous, a specific port is given, or if /ssl or
       * /tls is set, do net_open() since those conditions override everything
       * else.
       */
      if (stream->anonymous || mb.port || mb.sslflag || mb.tlsflag)
        reply = (LOCAL->netstream = net_open (&mb,NIL,defprt,ssld,"*imaps",
  					    sslport)) ?
  	imap_reply (stream,NIL) : NIL;
      /* 
       * No overriding conditions, so get the best connection that we can.  In
       * order, attempt to open via simap, tryssl, rimap, and finally TCP.
       */
  				/* try simap */
      else if ((reply = imap_rimap (stream,"*imap",&mb,usr,tmp)) != NULL);
      else if (ssld &&		/* try tryssl if enabled */
  	     (stream->tryssl || mail_parameters (NIL,GET_TRYSSLFIRST,NIL)) &&
  	     (LOCAL->netstream =
  	      net_open_work (ssld,mb.host,"*imaps",sslport,mb.port,
  			     (mb.novalidate ? NET_NOVALIDATECERT : 0) |
  			     NET_SILENT | NET_TRYSSL))) {
        if (net_sout (LOCAL->netstream,"",0)) {
  	mb.sslflag = T;
  	reply = imap_reply (stream,NIL);
        }
        else {			/* flush fake SSL stream */
  	net_close (LOCAL->netstream);
  	LOCAL->netstream = NIL;
        }
      }
  				/* try rimap first, then TCP */
      else if (!(reply = imap_rimap (stream,"imap",&mb,usr,tmp)) &&
  	     (LOCAL->netstream = net_open (&mb,NIL,defprt,NIL,NIL,NIL)))
        reply = imap_reply (stream,NIL);
  				/* make sure greeting is good */
      if (!reply || strcmp (reply->tag,"*") ||
  	(strcmp (reply->key,"OK") && strcmp (reply->key,"PREAUTH"))) {
        if (reply) mm_log (reply->text,ERROR);
        return NIL;		/* lost during greeting */
      }
  
  				/* if connected and not preauthenticated */
      if (LOCAL->netstream && strcmp (reply->key,"PREAUTH")) {
        sslstart_t stls = (sslstart_t) mail_parameters (NIL,GET_SSLSTART,NIL);
  				/* get server capabilities */
        if (!LOCAL->gotcapability) imap_capability (stream);
        if (LOCAL->netstream &&	/* does server support STARTTLS? */
  	  stls && LOCAL->cap.starttls && !mb.sslflag && !mb.notlsflag &&
  	  imap_OK (stream,imap_send (stream,"STARTTLS",NIL))) {
  	mb.tlsflag = T;		/* TLS OK, get into TLS at this end */
  	LOCAL->netstream->dtb = ssld;
  	if (!(LOCAL->netstream->stream =
  	      (*stls) (LOCAL->netstream->stream,mb.host,
  		       SSL_MTHD(mb) | (mb.novalidate ? NET_NOVALIDATECERT : NIL)))) {
  				/* drat, drop this connection */
  	  if (LOCAL->netstream) net_close (LOCAL->netstream);
  	  LOCAL->netstream = NIL;
  	}
  				/* get capabilities now that TLS in effect */
  	if (LOCAL->netstream) imap_capability (stream);
        }
        else if (mb.tlsflag) {	/* user specified /tls but can't do it */
  	mm_log ("Unable to negotiate TLS with this server",ERROR);
  	return NIL;
        }
        if (LOCAL->netstream) {	/* still in the land of the living? */
  	if ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL)) {
  				/* remote name for authentication */
  	  strncpy (mb.host,(long) mail_parameters(NIL,GET_SASLUSESPTRNAME,NIL)?
  		   net_remotehost (LOCAL->netstream) :
  		   net_host (LOCAL->netstream),NETMAXHOST-1);
  	  mb.host[NETMAXHOST-1] = '\0';
  	}
  				/* need new capabilities after login */
  	LOCAL->gotcapability = NIL;
  	if (!(stream->anonymous ? imap_anon (stream,tmp) :
  	      (LOCAL->cap.auth ? imap_auth (stream,&mb,tmp,usr) :
  	       imap_login (stream,&mb,tmp,usr)))) {
  				/* failed, is there a referral? */
      if (mb.tlsflag) LOCAL->tlsflag = T;
  	  if (ir && LOCAL->referral &&
  	      (s = (*ir) (stream,LOCAL->referral,REFAUTHFAILED))) {
  	    imap_close (stream,NIL);
  	    fs_give ((void **) &stream->mailbox);
  				/* set as new mailbox name to open */
  	    stream->mailbox = s;
  	    return imap_open (stream);
  	  }
  	  return NIL;		/* authentication failed */
  	}
  	else if (ir && LOCAL->referral &&
  		 (s = (*ir) (stream,LOCAL->referral,REFAUTH))) {
  	  imap_close (stream,NIL);
  	  fs_give ((void **) &stream->mailbox);
  	  stream->mailbox = s;	/* set as new mailbox name to open */
  				/* recurse to log in on real site */
  	  return imap_open (stream);
  	}
        }
      }
  				/* get server capabilities again */
      if (LOCAL->netstream && !LOCAL->gotcapability) imap_capability (stream);
  				/* save state for future recycling */
      if (mb.tlsflag) LOCAL->tlsflag = T;
      if (mb.tls1) LOCAL->tls1 = T;
      if (mb.tls1_1) LOCAL->tls1_1 = T;
      if (mb.tls1_2) LOCAL->tls1_2 = T;
      if (mb.tls1_3) LOCAL->tls1_3 = T;
      if (mb.tlssslv23) LOCAL->tlssslv23 = T;
      if (mb.notlsflag) LOCAL->notlsflag = T;
      if (mb.sslflag) LOCAL->sslflag = T;
      if (mb.novalidate) LOCAL->novalidate = T;
      if (mb.loser) LOCAL->loser = T;
    }
  
    if (LOCAL->netstream) {	/* still have a connection? */
      stream->perm_seen = stream->perm_deleted = stream->perm_answered =
        stream->perm_draft = LEVELIMAP4 (stream) ? NIL : T;
      stream->perm_user_flags = LEVELIMAP4 (stream) ? NIL : 0xffffffff;
      stream->sequence++;		/* bump sequence number */
      sprintf (tmp,"{%s",(long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ?
  	     net_host (LOCAL->netstream) : mb.host);
      if (!((i = net_port (LOCAL->netstream)) & 0xffff0000))
        sprintf (tmp + strlen (tmp),":%lu",i);
      strcat (tmp,"/imap");
      if (LOCAL->tlsflag) strcat (tmp,"/tls");
      if (LOCAL->tls1) strcat (tmp,"/tls1");
      if (LOCAL->tls1_1) strcat (tmp,"/tls1_1");
      if (LOCAL->tls1_2) strcat (tmp,"/tls1_2");
      if (LOCAL->tls1_3) strcat (tmp,"/tls1_3");
      if (LOCAL->tlssslv23) strcat (tmp,"/tls-sslv23");
      if (LOCAL->notlsflag) strcat (tmp,"/notls");
      if (LOCAL->sslflag) strcat (tmp,"/ssl");
      if (LOCAL->novalidate) strcat (tmp,"/novalidate-cert");
      if (LOCAL->loser) strcat (tmp,"/loser");
      if (stream->secure) strcat (tmp,"/secure");
      if (stream->rdonly) strcat (tmp,"/readonly");
      if (stream->anonymous) strcat (tmp,"/anonymous");
      else {			/* record user name */
        if (!LOCAL->user && usr[0]) LOCAL->user = cpystr (usr);
        if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user=\"%s\"",
  				LOCAL->user);
      }
      strcat (tmp,"}");
  
      if(LEVELID(stream)){	/* Set ID of app */
         IDLIST *idapp = (IDLIST *) mail_parameters(NIL, GET_IDPARAMS, NIL);
         if(idapp && !LOCAL->setid){
  	  imap_setid(stream, idapp);
  	  LOCAL->setid++;
         }
      }
      if (!stream->halfopen) {	/* wants to open a mailbox? */
        IMAPARG *args[2];
        IMAPARG ambx;
        ambx.type = ASTRING;
        ambx.text = (void *) mb.mailbox;
        args[0] = &ambx; args[1] = NIL;
        stream->nmsgs = 0;
        if (imap_OK (stream,reply = imap_send (stream,stream->rdonly ?
  					     "EXAMINE": "SELECT",args))) {
  	strcat (tmp,mb.mailbox);/* mailbox name */
  	if (!stream->nmsgs && !stream->silent)
  	  mm_log ("Mailbox is empty",(long) NIL);
  				/* note if an INBOX or not */
  	stream->inbox = !compare_cstring (mb.mailbox,"INBOX");
        }
        else if (ir && LOCAL->referral &&
  	       (s = (*ir) (stream,LOCAL->referral,REFSELECT))) {
  	imap_close (stream,NIL);
  	fs_give ((void **) &stream->mailbox);
  	stream->mailbox = s;	/* set as new mailbox name to open */
  	return imap_open (stream);
        }
        else {
  	mm_log (reply->text,ERROR);
  	if (imap_closeonerror) return NIL;
  	stream->halfopen = T;	/* let him keep it half-open */
        }
      }
      if (stream->halfopen) {	/* half-open connection? */
        strcat (tmp,"<no_mailbox>");
  				/* make sure dummy message counts */
        mail_exists (stream,(long) 0);
        mail_recent (stream,(long) 0);
      }
      fs_give ((void **) &stream->mailbox);
      stream->mailbox = cpystr (tmp);
    }
  				/* success if stream open */
    return LOCAL->netstream ? stream : NIL;
  }
  
  /* IMAP rimap connect
   * Accepts: MAIL stream
   *	    NETMBX specification
   *	    service to use
   *	    user name
   *	    scratch buffer
   * Returns: parsed reply if success, else NIL
   */
  
  IMAPPARSEDREPLY *imap_rimap (MAILSTREAM *stream,char *service,NETMBX *mb,
  			     char *usr,char *tmp)
  {
    unsigned long i;
    char c[2];
    NETSTREAM *tstream;
    IMAPPARSEDREPLY *reply = NIL;
  				/* try rimap open */
    if (!mb->norsh && (tstream = net_aopen (NIL,mb,service,usr))) {
  				/* if success, see if reasonable banner */
      if (net_getbuffer (tstream,(long) 1,c) && (*c == '*')) {
        i = 0;			/* copy to buffer */
        do tmp[i++] = *c;
        while (net_getbuffer (tstream,(long) 1,c) && (*c != '\015') &&
  	     (*c != '\012') && (i < (MAILTMPLEN-1)));
        tmp[i] = '\0';		/* tie off */
  				/* snarfed a valid greeting? */
        if ((*c == '\015') && net_getbuffer (tstream,(long) 1,c) &&
  	  (*c == '\012') &&
  	  !strcmp ((reply = imap_parse_reply (stream,cpystr (tmp)))->tag,"*")){
  				/* parse line as IMAP */
  	imap_parse_unsolicited (stream,reply);
  				/* make sure greeting is good */
  	if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH")) {
  	  LOCAL->netstream = tstream;
  	  return reply;		/* return success */
  	}
        }
      }
      net_close (tstream);	/* failed, punt the temporary netstream */
    }
    return NIL;
  }
  
  /* IMAP log in as anonymous
   * Accepts: stream to authenticate
   *	    scratch buffer
   * Returns: T on success, NIL on failure
   */
  
  long imap_anon (MAILSTREAM *stream,char *tmp)
  {
    IMAPPARSEDREPLY *reply;
    char *s = net_localhost (LOCAL->netstream);
    if (LOCAL->cap.authanon) {
      char tag[16];
      unsigned long i;
      char *broken = "[CLOSED] IMAP connection broken (anonymous auth)";
      sprintf (tag,"%08lx",0xffffffff & (stream->gensym++));
  				/* build command */
      sprintf (tmp,"%s AUTHENTICATE ANONYMOUS",tag);
      if (!imap_soutr (stream,tmp)) {
        mm_log (broken,ERROR);
        return NIL;
      }
      if (imap_challenge (stream,&i)) imap_response (stream,s,strlen (s));
  				/* get response */
      if (!(reply = &LOCAL->reply)->tag) reply = imap_fake (stream,tag,broken);
  				/* what we wanted? */
      if (compare_cstring (reply->tag,tag)) {
  				/* abort if don't have tagged response */
        while (compare_cstring ((reply = imap_reply (stream,tag))->tag,tag))
  	imap_soutr (stream,"*");
      }
    }
    else {
      IMAPARG *args[2];
      IMAPARG ausr;
      ausr.type = ASTRING;
      ausr.text = (void *) s;
      args[0] = &ausr; args[1] = NIL;
  				/* send "LOGIN anonymous <host>" */
      reply = imap_send (stream,"LOGIN ANONYMOUS",args);
    }
  				/* success if reply OK */
    if (imap_OK (stream,reply)) return T;
    mm_log (reply->text,ERROR);
    return NIL;
  }
  
  /* IMAP authenticate
   * Accepts: stream to authenticate
   *	    parsed network mailbox structure
   *	    scratch buffer
   *	    place to return user name
   * Returns: T on success, NIL on failure
   */
  
  long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr)
  {
    unsigned long trial,ua;
    int ok;
    char tag[16];
    char *lsterr = NIL;
    AUTHENTICATOR *at;
    IMAPPARSEDREPLY *reply;
    for (ua = LOCAL->cap.auth, LOCAL->saslcancel = NIL; LOCAL->netstream && ua &&
         (at = mail_lookup_auth (find_rightmost_bit (&ua) + 1));) {
      if (lsterr) {		/* previous authenticator failed? */
        sprintf (tmp,"Retrying using %s authentication after %.80s",
  	       at->name,lsterr);
        mm_log (tmp,NIL);
        fs_give ((void **) &lsterr);
      }
      trial = 0;			/* initial trial count */
      tmp[0] = '\0';		/* no error */
      do {			/* gensym a new tag */
        if (lsterr) {		/* previous attempt with this one failed? */
  	sprintf (tmp,"Retrying %s authentication after %.80s",at->name,lsterr);
  	mm_log (tmp,WARN);
  	fs_give ((void **) &lsterr);
        }
        LOCAL->saslcancel = NIL;
        sprintf (tag,"%08lx",0xffffffff & (stream->gensym++));
  				/* build command */
        sprintf (tmp,"%s AUTHENTICATE %s",tag,at->name);
        if (imap_soutr (stream,tmp)) {
  				/* hide client authentication responses */
  	if (!(at->flags & AU_SECURE)) LOCAL->sensitive = T;
  	ok = (*at->client) (imap_challenge,imap_response,"imap",mb,stream,
  			    &trial,usr);
  	LOCAL->sensitive = NIL;	/* unhide */
  				/* make sure have a response */
  	if (!(reply = &LOCAL->reply)->tag)
  	  reply = imap_fake (stream,tag,
  			     "[CLOSED] IMAP connection broken (authenticate)");
  	else if (compare_cstring (reply->tag,tag))
  	  while (compare_cstring ((reply = imap_reply (stream,tag))->tag,tag))
  	    imap_soutr (stream,"*");
  				/* good if SASL ok and success response */
  	if (ok && imap_OK (stream,reply)) return T;
  	if (!trial) {		/* if main program requested cancellation */
  	  mm_log ("IMAP Authentication cancelled",ERROR);
  	  return NIL;
  	}
  				/* no error if protocol-initiated cancel */
  	lsterr = cpystr (reply->text);
        }
      }
      while (LOCAL->netstream && !LOCAL->byeseen && trial &&
  	   (trial < imap_maxlogintrials));
    }
    if (lsterr) {			/* previous authenticator failed? */
      if (!LOCAL->saslcancel) {	/* don't do this if a cancel */
        sprintf (tmp,"Can not authenticate to IMAP server: %.80s",lsterr);
        mm_log (tmp,ERROR);
      }
      fs_give ((void **) &lsterr);
    }
    return NIL;			/* ran out of authenticators */
  }
  
  /* IMAP login
   * Accepts: stream to login
   *	    parsed network mailbox structure
   *	    scratch buffer of length MAILTMPLEN
   *	    place to return user name
   * Returns: T on success, NIL on failure
   */
  
  long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr)
  {
    unsigned long trial = 0;
    IMAPPARSEDREPLY *reply;
    IMAPARG *args[3];
    IMAPARG ausr,apwd;
    long ret = NIL;
    if (stream->secure)		/* never do LOGIN if want security */
      mm_log ("Can't do secure authentication with this server",ERROR);
  				/* never do LOGIN if server disabled it */
    else if (LOCAL->cap.logindisabled)
      mm_log ("Server disables LOGIN, no recognized SASL authenticator",ERROR);
    else if (mb->authuser[0])	/* never do LOGIN with /authuser */
      mm_log ("Can't do /authuser with this server",ERROR);
    else {			/* OK to try login */
      ausr.type = apwd.type = ASTRING;
      ausr.text = (void *) usr;
      apwd.text = (void *) pwd;
      args[0] = &ausr; args[1] = &apwd; args[2] = NIL;
      do {
        pwd[0] = 0;		/* prompt user for password */
        mm_login (mb,usr,pwd,trial++);
        if (pwd[0]) {		/* send login command if have password */
  	LOCAL->sensitive = T;	/* hide this command */
  				/* send "LOGIN usr pwd" */
  	if (imap_OK (stream,reply = imap_send (stream,"LOGIN",args)))
  	  ret = LONGT;		/* success */
  	else {
  	  mm_log (reply->text,WARN);
  	  if (!LOCAL->referral && (trial == imap_maxlogintrials))
  	    mm_log ("Too many login failures",ERROR);
  	}
  	LOCAL->sensitive = NIL;	/* unhide */
        }
  				/* user refused to give password */
        else mm_log ("Login aborted",ERROR);
      } while (!ret && pwd[0] && (trial < imap_maxlogintrials) &&
  	     LOCAL->netstream && !LOCAL->byeseen && !LOCAL->referral);
    }
    memset (pwd,0,MAILTMPLEN);	/* erase password */
    return ret;
  }
  
  /* 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)
  {
    char tmp[MAILTMPLEN];
    void *ret = NIL;
    MAILSTREAM *stream = (MAILSTREAM *) s;
    IMAPPARSEDREPLY *reply = NIL;
  				/* get tagged response or challenge */
    while (stream && LOCAL->netstream &&
  	 (reply = imap_parse_reply (stream,net_getline (LOCAL->netstream))) &&
  	 !strcmp (reply->tag,"*")) imap_parse_unsolicited (stream,reply);
  				/* parse challenge if have one */
    if (stream && LOCAL->netstream && reply && reply->tag &&
        (*reply->tag == '+') && !reply->tag[1] && reply->text &&
        !(ret = rfc822_base64 ((unsigned char *) reply->text,
  			     strlen (reply->text),len))) {
      sprintf (tmp,"IMAP SERVER BUG (invalid challenge): %.80s",
  	     (char *) reply->text);
      mm_log (tmp,ERROR);
    }
    return ret;
  }
  
  
  /* 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 i,j,ret;
    char *t,*u;
    if (response) {		/* make CRLFless BASE64 string */
      if (size) {
        for (t = (char *) rfc822_binary ((void *) response,size,&i),u = t,j = 0;
  	   j < i; j++) if (t[j] > ' ') *u++ = t[j];
        *u = '\0';		/* tie off string for mm_dlog() */
        if (stream->debug) mail_dlog (t,LOCAL->sensitive);
  				/* append CRLF */
        *u++ = '\015'; *u++ = '\012';
        ret = net_sout (LOCAL->netstream,t,u - t);
        fs_give ((void **) &t);
      }
      else ret = imap_soutr (stream,"");
    }
    else {			/* abort requested */
      ret = imap_soutr (stream,"*");
      LOCAL->saslcancel = T;	/* mark protocol-requested SASL cancel */
    }
    return ret;
  }
  
  /* IMAP close
   * Accepts: MAIL stream
   *	    option flags
   */
  
  void imap_close (MAILSTREAM *stream,long options)
  {
    THREADER *thr,*t;
    IMAPPARSEDREPLY *reply;
    if (stream && LOCAL) {	/* send "LOGOUT" */
      if (!LOCAL->byeseen) {	/* don't even think of doing it if saw a BYE */
  				/* expunge silently if requested */
        if (options & CL_EXPUNGE)
  	imap_send (stream,LEVELIMAP4 (stream) ? "CLOSE" : "EXPUNGE",NIL);
        if (LOCAL->netstream &&
  	  !imap_OK (stream,reply = imap_send (stream,"LOGOUT",NIL)))
  	mm_log (reply->text,WARN);
      }
  				/* close NET connection if still open */
      if (LOCAL->netstream) net_close (LOCAL->netstream);
      LOCAL->netstream = NIL;
  				/* free up memory */
      if (LOCAL->sortdata) fs_give ((void **) &LOCAL->sortdata);
      if (LOCAL->namespace) {
        mail_free_namespace (&LOCAL->namespace[0]);
        mail_free_namespace (&LOCAL->namespace[1]);
        mail_free_namespace (&LOCAL->namespace[2]);
        fs_give ((void **) &LOCAL->namespace);
      }
      if (LOCAL->threaddata) mail_free_threadnode (&LOCAL->threaddata);
  				/* flush threaders */
      if ((thr = LOCAL->cap.threader) != NULL) while ((t = thr) != NULL) {
        fs_give ((void **) &t->name);
        thr = t->next;
        fs_give ((void **) &t);
      }
      if (LOCAL->referral) fs_give ((void **) &LOCAL->referral);
      if (LOCAL->user) fs_give ((void **) &LOCAL->user);
      if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
      if (LOCAL->reform) fs_give ((void **) &LOCAL->reform);
      if (LOCAL->id) mail_free_idlist(&LOCAL->id);
  				/* nuke the local data */
      fs_give ((void **) &stream->local);
    }
  }
  
  /* IMAP fetch fast information
   * Accepts: MAIL stream
   *	    sequence
   *	    option flags
   *
   * Generally, imap_structure is preferred
   */
  
  void imap_fast (MAILSTREAM *stream,char *sequence,long flags)
  {
    IMAPPARSEDREPLY *reply = imap_fetch (stream,sequence,flags & FT_UID);
    if (!imap_OK (stream,reply)) mm_log (reply->text,ERROR);
  }
  
  
  /* IMAP fetch flags
   * Accepts: MAIL stream
   *	    sequence
   *	    option flags
   */
  
  void imap_flags (MAILSTREAM *stream,char *sequence,long flags)
  {				/* send "FETCH sequence FLAGS" */
    char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
    IMAPPARSEDREPLY *reply;
    IMAPARG *args[3],aseq,aatt;
    if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
  						     flags & FT_UID);
    aseq.type = SEQUENCE; aseq.text = (void *) sequence;
    aatt.type = ATOM; aatt.text = (void *) "FLAGS";
    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 overview
   * Accepts: MAIL stream, sequence bits set
   *	    pointer to overview return function
   * Returns: T if successful, NIL otherwise
   */
  
  long imap_overview (MAILSTREAM *stream,overview_t ofn)
  {
    MESSAGECACHE *elt;
    ENVELOPE *env;
    OVERVIEW ov;
    char *s,*t;
    unsigned long i,start,last,len,slen;
    if (!LOCAL->netstream) return NIL;
  				/* build overview sequence */
    for (i = 1,len = start = last = 0,s = t = NIL; i <= stream->nmsgs; ++i)
      if ((elt = mail_elt (stream,i))->sequence) {
        if (!elt->private.msg.env) {
  	if (s) {		/* continuing a sequence */
  	  if (i == last + 1) last = i;
  	  else {		/* end of range */
  	    if (last != start) sprintf (t,":%lu,%lu",last,i);
  	    else sprintf (t,",%lu",i);
  	    if ((len - (slen = (t += strlen (t)) - s)) < 20) {
  	      fs_resize ((void **) &s,len += MAILTMPLEN);
  	      t = s + slen;	/* relocate current pointer */
  	    }
  	    start = last = i;	/* begin a new range */
  	  }
  	}
  	else {			/* first time, start new buffer */
  	  s = (char *) fs_get (len = MAILTMPLEN);
  	  sprintf (s,"%lu",start = last = i);
  	  t = s + strlen (s);	/* end of buffer */
  	}
        }
      }
  				/* last sequence */
    if (last != start) sprintf (t,":%lu",last);
    if (s) {			/* prefetch as needed */
      imap_fetch (stream,s,FT_NEEDENV);
      fs_give ((void **) &s);
    }
    ov.optional.lines = 0;	/* now overview each message */
    ov.optional.xref = NIL;
    if (ofn) for (i = 1; i <= stream->nmsgs; i++)
      if (((elt = mail_elt (stream,i))->sequence) &&
  	(env = mail_fetch_structure (stream,i,NIL,NIL)) && ofn) {
        ov.subject = env->subject;
        ov.from = env->from;
        ov.date = env->date;
        ov.message_id = env->message_id;
        ov.references = env->references;
        ov.optional.octets = elt->rfc822_size;
        (*ofn) (stream,mail_uid (stream,i),&ov,i);
      }
    return LONGT;
  }
  
  /* 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_structure (MAILSTREAM *stream,unsigned long msgno,BODY **body,
  			  long flags)
  {
    unsigned long i,j,k,x;
    char *s,seq[MAILTMPLEN],tmp[MAILTMPLEN];
    MESSAGECACHE *elt;
    ENVELOPE **env;
    BODY **b;
    IMAPPARSEDREPLY *reply = NIL;
    IMAPARG *args[3],aseq,aatt;
    SEARCHSET *set = LOCAL->lookahead;
    LOCAL->lookahead = NIL;
    args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
    aseq.type = SEQUENCE; aseq.text = (void *) seq;
    aatt.type = ATOM; aatt.text = NIL;
    if (flags & FT_UID)		/* see if can find msgno from UID */
      for (i = 1; i <= stream->nmsgs; i++)
        if ((elt = mail_elt (stream,i))->private.uid == msgno) {
  	msgno = i;		/* found msgno, use it from now on */
  	flags &= ~FT_UID;	/* no longer a UID fetch */
        }
    sprintf (s = seq,"%lu",msgno);/* initial sequence */
    if (LEVELIMAP4 (stream) && (flags & FT_UID)) {
      /* UID fetching is requested and we can't map the UID to a message sequence
       * number.  Assume that the message isn't cached at all.
       */
      if (!imap_OK (stream,reply = imap_fetch (stream,seq,FT_NEEDENV +
  					     (body ? FT_NEEDBODY : NIL) +
  					     (flags & (FT_UID + FT_NOHDRS)))))
        mm_log (reply->text,ERROR);
  				/* now hunt for this UID */
      for (i = 1; i <= stream->nmsgs; i++)
        if ((elt = mail_elt (stream,i))->private.uid == msgno) {
  	if (body) *body = elt->private.msg.body;
  	return elt->private.msg.env;
        }
      if (body) *body = NIL;	/* can't find the UID */
      return NIL;
    }
    elt = mail_elt (stream,msgno);/* get cache pointer */
    if (stream->scache) {		/* short caching? */
      env = &stream->env;		/* use temporaries on the stream */
      b = &stream->body;
      if (msgno != stream->msgno){/* flush old poop if a different message */
        mail_free_envelope (env);
        mail_free_body (b);
        stream->msgno = msgno;	/* this is now the current short cache msg */
      }
    }
  
    else {			/* normal cache */
      env = &elt->private.msg.env;/* get envelope and body pointers */
      b = &elt->private.msg.body;
  				/* prefetch if don't have envelope */
      if (!(flags & FT_NOLOOKAHEAD) &&
  	((!*env || (*env)->incomplete) ||
  	 (body && !*b && LEVELIMAP2bis (stream)))) {
        if (set) {		/* have a lookahead list? */
  	MESSAGE *msg;
  	for (k = imap_fetchlookaheadlimit;
  	     k && set && (((s += strlen (s)) - seq) < (MAXCOMMAND - 30));
  	     set = set->next) {
  	  i = (set->first == 0xffffffff) ? stream->nmsgs :
  	    min (set->first,stream->nmsgs);
  	  if ((j = (set->last == 0xffffffff) ? stream->nmsgs :
  	      min (set->last,stream->nmsgs)) != 0L) {
  	    if (i > j) {	/* swap the range if backwards */
  	      x = i; i = j; j = x;
  	    }
  				/* find first message not msgno or in cache */
  	    while (((i == msgno) ||
  		    ((msg = &(mail_elt (stream,i)->private.msg))->env &&
  		     (!body || msg->body))) && (i++ < j));
  				/* until range or lookahead finished */
  	    while (k && (i <= j)) {
  				/* find first cached message in range */
  	      for (x = i + 1; (x <= j) &&
  		     !((msg = &(mail_elt (stream,x)->private.msg))->env &&
  		       (!body || msg->body)); x++);
  	      if (i == --x) {	/* only one message? */
  		sprintf (s += strlen (s),",%lu",i++);
  		k--;		/* prefetching one message */
  	      }
  	      else {		/* a range to prefetch */
  		sprintf (s += strlen (s),",%lu:%lu",i,x);
  		i = 1 + x - i;	/* number of messages in this range */
  				/* still can look ahead some more? */
  		if ((k = (k > i) ? k - i : 0) != 0)
  				/* yes, scan further in this range */
  		  for (i = x + 2; (i <= j) &&
  			 ((i == msgno) || 
  			  ((msg = &(mail_elt (stream,i)->private.msg))->env &&
  			   (!body || msg->body)));
  		       i++);
  	      }
  	    }
  	  }
  	  else if ((i != msgno) && !mail_elt (stream,i)->private.msg.env) {
  	    sprintf (s += strlen (s),",%lu",i);
  	    k--;		/* prefetching one message */
  	  }
        }
        }
  				/* build message number list */
        else for (i = msgno+1,k = imap_lookahead; k && (i <= stream->nmsgs); i++)
  	if (!mail_elt (stream,i)->private.msg.env) {
  	  s += strlen (s);	/* find string end, see if nearing end */
  	  if ((s - seq) > (MAILTMPLEN - 20)) break;
  	  sprintf (s,",%lu",i);	/* append message */
   	  for (j = i + 1, k--;	/* hunt for last message without an envelope */
  	       k && (j <= stream->nmsgs) &&
  	       !mail_elt (stream,j)->private.msg.env; j++, k--);
  				/* if different, make a range */
  	  if (i != --j) sprintf (s + strlen (s),":%lu",i = j);
  	}
      }
    }
  
    if (!stream->lock) {		/* no-op if stream locked */
      /* Build the fetch attributes.  Unlike imap_fetch(), this tries not to
       * fetch data that is already cached.  However, since it is based on the
       * message requested and not on any of the prefetched messages, it can
       * goof, either by fetching data already cached or not prefetching data
       * that isn't cached (but was cached in the message requested).
       * Fortunately, no great harm is done.  If it doesn't prefetch the data,
       * it will get it when the affected message(s) are requested.
       */
      if (!elt->private.uid && LEVELIMAP4 (stream)) strcpy (tmp," UID");
      else tmp[0] = '\0';		/* initialize command */
  				/* need envelope? */
      if (!*env || (*env)->incomplete) {
        strcat (tmp," ENVELOPE");	/* yes, get it and possible extra poop */
        if (!(flags & FT_NOHDRS) && LEVELIMAP4rev1 (stream)) {
  	if (imap_extrahdrs) sprintf (tmp + strlen (tmp)," %s %s %s",
  				     hdrheader[LOCAL->cap.extlevel],
  				     imap_extrahdrs,hdrtrailer);
  	else sprintf (tmp + strlen (tmp)," %s %s",
  		      hdrheader[LOCAL->cap.extlevel],hdrtrailer);
        }
      }
  				/* need body? */
      if (body && !*b && LEVELIMAP2bis (stream))
        strcat (tmp,LEVELIMAP4 (stream) ? " BODYSTRUCTURE" : " BODY");
      if (!elt->day) strcat (tmp," INTERNALDATE");
      if (!elt->rfc822_size) strcat (tmp," RFC822.SIZE");
      if (tmp[0]) {		/* anything to do? */
        tmp[0] = '(';		/* make into a list */
        strcat (tmp," FLAGS)");	/* always get current flags */
        aatt.text = (void *) tmp;	/* do the built command */
        if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args))) {
  				/* failed, probably RFC-1176 server */
  	if (!LEVELIMAP4 (stream) && LEVELIMAP2bis (stream) && body && !*b){
  	  aatt.text = (void *) "ALL";
  	  if (imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
  				/* doesn't have body capabilities */
  	    LOCAL->cap.imap2bis = NIL;
  	  else mm_log (reply->text,ERROR);
  	}
  	else mm_log (reply->text,ERROR);
        }
      }
    }
    if (body) {			/* wants to return body */
      if (!*b && !LEVELIMAP2bis (stream)) {
  				/* simulate body structure fetch for IMAP2 */
        *b = mail_initbody (mail_newbody ());
        (*b)->subtype = cpystr (rfc822_default_subtype ((*b)->type));
        ((*b)->parameter = mail_newbody_parameter ())->attribute =
  	cpystr ("CHARSET");
        (*b)->parameter->value = cpystr ("US-ASCII");
        s = mail_fetch_text (stream,msgno,NIL,&i,flags);
        (*b)->size.bytes = i;
        while (i--) if (*s++ == '\n') (*b)->size.lines++;
      }
      *body = *b;			/* return the body */
    }
    return *env;			/* return the envelope */
  }
  
  /* IMAP fetch message data
   * Accepts: MAIL stream
   *	    message number
   *	    section specifier
   *	    offset of first designated byte or 0 to start at beginning
   *	    maximum number of bytes or 0 for all bytes
   *	    lines to fetch if header
   *	    flags
   * Returns: T on success, NIL on failure
   */
  
  long imap_msgdata (MAILSTREAM *stream,unsigned long msgno,char *section,
  		   unsigned long first,unsigned long last,STRINGLIST *lines,
  		   long flags)
  {
    int i;
    char *t,tmp[MAILTMPLEN],partial[40],seq[40];
    char *noextend,*nopartial,*nolines,*nopeek,*nononpeek;
    char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
    IMAPPARSEDREPLY *reply;
    IMAPARG *args[5],*auxargs[3],aseq,aatt,alns,acls,aflg;
    noextend = nopartial = nolines = nopeek = nononpeek = NIL;
  				/* does searching desire a lookahead? */
    if ((flags & FT_SEARCHLOOKAHEAD) && (msgno < stream->nmsgs) &&
        !stream->scache) {
      sprintf (seq,"%lu:%lu",msgno,
  	     (unsigned long) min (msgno + IMAPLOOKAHEAD,stream->nmsgs));
      aseq.type = SEQUENCE;
      aseq.text = (void *) seq;
    }
    else {			/* no, do it the easy way */
      aseq.type = NUMBER;
      aseq.text = (void *) msgno;
    }
    aatt.type = ATOM;		/* assume atomic attribute */
    alns.type = LIST; alns.text = (void *) lines;
    acls.type = BODYCLOSE; acls.text = (void *) partial;
    aflg.type = ATOM; aflg.text = (void *) "FLAGS";
    args[0] = &aseq; args[1] = &aatt; args[2] = args[3] = args[4] = NIL;
    auxargs[0] = &aseq; auxargs[1] = &aflg; auxargs[2] = NIL;
    partial[0] = '\0';		/* initially no partial specifier */
    if (LEVELIMAP4rev1 (stream)) {/* easy if IMAP4rev1 server */
  				/* HEADER fetching with special handling? */
      if (!strcmp (section,"HEADER") && (lines || (flags & FT_PREFETCHTEXT))) {
        if (lines) {		/* want specific header lines? */
  	aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
  	aatt.text = (void *) ((flags & FT_NOT) ?
  			      "HEADER.FIELDS.NOT" : "HEADER.FIELDS");
  	args[2] = &alns; args[3] = &acls;
        }
  				/* must be prefetching */
        else aatt.text = (void *) ((flags & FT_PEEK) ?
  				 "(BODY.PEEK[HEADER] BODY.PEEK[TEXT])" :
  				 "(BODY[HEADER] BODY[TEXT])");
      }
      else {			/* simple case */
        aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
        aatt.text = (void *) section;
        args[2] = &acls;
      }
      if (first || last) sprintf (partial,"<%lu.%lu>",first,last ? last:-1);
    }
  
    /* IMAP4 did not have:
     * . HEADER body part (can simulate with BODY[0] or BODY.PEEK[0])
     * . TEXT body part (can simulate top-level with RFC822.TEXT or
     *			RFC822.TEXT.PEEK)
     * . MIME body part
     * . (usable) partial fetching
     * . (usable) selective header line fetching
     */
    else if (LEVEL1730 (stream)) {/* IMAP4 (RFC 1730) compatibility */
  				/* BODY[HEADER] becomes BODY.PEEK[0] */
      if (!strcmp (section,"HEADER"))
        aatt.text = (void *)
  	((flags & FT_PREFETCHTEXT) ?
  	 ((flags & FT_PEEK) ? "(BODY.PEEK[0] RFC822.TEXT.PEEK)" :
  	  "(BODY[0] RFC822.TEXT)") :
  	 ((flags & FT_PEEK) ? "BODY.PEEK[0]" : "BODY[0]"));
  				/* BODY[TEXT] becomes RFC822.TEXT */
      else if (!strcmp (section,"TEXT"))
        aatt.text = (void *) ((flags & FT_PEEK) ? "RFC822.TEXT.PEEK" :
  			    "RFC822.TEXT");
      else if (!section[0])	/* BODY[] becomes RFC822 */
        aatt.text = (void *) ((flags & FT_PEEK) ? "RFC822.PEEK" : "RFC822");
  				/* nested header */
      else if ((t = strstr (section,".HEADER")) != NULL) {
        aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
        args[2] = &acls;		/* will need to close section */
        aatt.text = (void *) tmp;	/* convert .HEADER to .0 */
        strncpy (tmp,section,t-section);
        strcpy (tmp+(t-section),".0");
      }
      else {			/* IMAP4 body part */
        aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
        args[2] = &acls;		/* will need to close section */
        aatt.text = (void *) section;
      }
      if (strstr (section,".MIME") || strstr (section,".TEXT")) noextend = "4";
      if (first || last) nopartial = "4";
      if (lines) nolines = "4";
    }
  
    /* IMAP2bis did not have:
     * . HEADER body part (can simulate peeking top-level with RFC822.HEADER)
     * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT)
     * . MIME body part
     * . partial fetching
     * . selective header line fetching
     * . non-peeking header fetching
     * . peeking body fetching
     */
  				/* IMAP2bis compatibility */
    else if (LEVELIMAP2bis (stream)) {
  				/* BODY[HEADER] becomes RFC822.HEADER */
      if (!strcmp (section,"HEADER")) {
        aatt.text = (void *)
  	((flags & FT_PREFETCHTEXT) ?
  	 "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER");
        if (flags & FT_PEEK) flags &= ~FT_PEEK;
        else nononpeek = "2bis";
      }
  				/* BODY[TEXT] becomes RFC822.TEXT */
      else if (!strcmp (section,"TEXT")) aatt.text = (void *) "RFC822.TEXT";
  				/* BODY[] becomes RFC822 */
      else if (!section[0]) aatt.text = (void *) "RFC822";
      else {			/* IMAP2bis body part */
        aatt.type = BODYTEXT;
        args[2] = &acls;		/* will need to close section */
        aatt.text = (void *) section;
      }
      if (strstr (section,".HEADER") || strstr (section,".MIME") ||
  	     strstr (section,".TEXT")) noextend = "2bis";
      if (first || last) nopartial = "2bis";
      if (lines) nolines = "2bis";
      if (flags & FT_PEEK) nopeek = "2bis";
    }
  
    /* IMAP2 did not have:
     * . HEADER body part (can simulate peeking top-level with RFC822.HEADER)
     * . TEXT body part (can simulate non-peeking top-level with RFC822.TEXT)
     * . MIME body part
     * . multiple body parts (can simulate BODY[1] with RFC822.TEXT)
     * . partial fetching
     * . selective header line fetching
     * . non-peeking header fetching
     * . peeking body fetching
     */
    else {			/* IMAP2 (RFC 1176/1064) compatibility */
  				/* BODY[HEADER] */
      if (!strcmp (section,"HEADER")) {
        aatt.text = (void *) ((flags & FT_PREFETCHTEXT) ?
  			    "(RFC822.HEADER RFC822.TEXT)" : "RFC822.HEADER");
        if (flags & FT_PEEK) flags &= ~FT_PEEK;
        nononpeek = "2";
      }
  				/* BODY[TEXT] becomes RFC822.TEXT */
      else if (!strcmp (section,"TEXT")) aatt.text = (void *) "RFC822.TEXT";
  				/* BODY[1] treated like RFC822.TEXT */
      else if (!strcmp (section,"1")) {
        SIZEDTEXT text;
        MESSAGECACHE *elt = mail_elt (stream,msgno);
  				/* have a cached RFC822.TEXT? */
        if (elt->private.msg.text.text.data) {
  	text.size = elt->private.msg.text.text.size;
  				/* should move instead of copy */
  	text.data = memcpy (fs_get (text.size+1),
  			    elt->private.msg.text.text.data,text.size);
  	(t = (char *) text.data)[text.size] = '\0';
  	imap_cache (stream,msgno,"1",NIL,&text);
  	return LONGT;		/* don't have to do any fetches */
        }
  				/* otherwise do RFC822.TEXT */
        aatt.text = (void *) "RFC822.TEXT";
      }
  				/* BODY[] becomes RFC822 */
      else if (!section[0]) aatt.text = (void *) "RFC822";
      else noextend = "2";	/* how did we get here? */
      if (flags & FT_PEEK) nopeek = "2";
      if (first || last) nopartial = "2";
      if (lines) nolines = "2";
    }
  
    /* Report unavailable functionalities.  The application can use the helpful
     * LEVELIMAPREV1, LEVELIMAP4, and LEVELIMAP2bis operations provided in
     * imap4r1.h to avoid triggering these errors.  There aren't any workarounds
     * for these restrictions.
     */
    if (noextend) {
      sprintf (tmp,"[NOTIMAP4REV1] IMAP%s server can't do extended body fetch",
  	     noextend);
      mm_log (tmp,ERROR);
      return NIL;			/* can't do anything close either */
    }
    if (nopartial) {
      sprintf (tmp,"[NOTIMAP4REV1] IMAP%s server can't do partial fetch",
  	     nopartial);
      mm_notify (stream,tmp,WARN);
    }
    if (nolines) {
      sprintf(tmp,"[NOTIMAP4REV1] IMAP%s server can't do selective header fetch",
  	    nolines);
      mm_notify (stream,tmp,WARN);
    }
  
  				/* trying to do unsupported peek behavior? */
    if ((t = nopeek) || (t = nononpeek)) {
  				/* get most recent \Seen setting */
      if (!imap_OK (stream,reply = imap_send (stream,cmd,auxargs)))
        mm_log (reply->text,WARN);
  				/* note current setting of \Seen flag */
      if (!(i = mail_elt (stream,msgno)->seen)) {
        sprintf (tmp,nopeek ?	/* only babble if \Seen not set */
  	       "[NOTIMAP4] Simulating peeking fetch in IMAP%s" :
  	       "[NOTIMAP4] Simulating non-peeking header fetch in IMAP%s",t);
        mm_notify (stream,tmp,NIL);
      }
  				/* send the fetch command */
      if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) {
        mm_log (reply->text,ERROR);
        return NIL;		/* failure */
      }
  				/* send command if need to reset \Seen */
      if (((nopeek && !i && mail_elt (stream,msgno)->seen &&
  	  (aflg.text = "-FLAGS \\Seen")) ||
  	 ((nononpeek && !mail_elt (stream,msgno)->seen) &&
  	  (aflg.text = "+FLAGS \\Seen"))) &&
  	!imap_OK (stream,reply = imap_send (stream,"STORE",auxargs)))
        mm_log (reply->text,WARN);
    }
  				/* simple case if traditional behavior */
    else if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) {
      mm_log (reply->text,ERROR);
      return NIL;			/* failure */
    }
  				/* simulate BODY[1] return for RFC 1064/1176 */
    if (!LEVELIMAP2bis (stream) && !strcmp (section,"1")) {
      SIZEDTEXT text;
      MESSAGECACHE *elt = mail_elt (stream,msgno);
      text.size = elt->private.msg.text.text.size;
  				/* should move instead of copy */
      text.data = memcpy (fs_get (text.size+1),elt->private.msg.text.text.data,
  			text.size);
      (t = (char *) text.data)[text.size] = '\0';
      imap_cache (stream,msgno,"1",NIL,&text);
    }
    return LONGT;
  }
  
  /* 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 (!LEVELIMAP4 (stream)) return msgno;
  				/* do we know its UID yet? */
    if (!(elt = mail_elt (stream,msgno))->private.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,"%lu",msgno);
      if ((k = imap_uidlookahead) != 0L) {/* build UID list */
        for (i = msgno + 1, s = seq; k && (i <= stream->nmsgs); i++)
  	if (!mail_elt (stream,i)->private.uid) {
  	  s += strlen (s);	/* find string end, see if nearing end */
  	  if ((s - seq) > (MAILTMPLEN - 20)) break;
  	  sprintf (s,",%lu",i);	/* append message */
  	  for (j = i + 1, k--;	/* hunt for last message without a UID */
  	       k && (j <= stream->nmsgs) && !mail_elt (stream,j)->private.uid;
  	       j++, k--);
  				/* if different, make a range */
  	  if (i != --j) sprintf (s + strlen (s),":%lu",i = j);
  	}
      }
  				/* send "FETCH msgno UID" */
      if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
        mm_log (reply->text,ERROR);
    }
    return elt->private.uid;	/* return our UID now */
  }
  
  /* IMAP fetch message number from UID
   * Accepts: MAIL stream
   *	    UID
   * Returns: message number
   */
  
  unsigned long imap_msgno (MAILSTREAM *stream,unsigned long uid)
  {
    IMAPPARSEDREPLY *reply;
    IMAPARG *args[3],aseq,aatt;
    char seq[MAILTMPLEN];
    int holes = 0;
    unsigned long i,msgno;
  				/* IMAP2 didn't have UIDs */
    if (!LEVELIMAP4 (stream)) return uid;
    /* This really should be a binary search, but since there are likely to be
     * holes in the msgno->UID map it's hard to do.
     */
    for (msgno = 1; msgno <= stream->nmsgs; msgno++) {
      if (!(i = mail_elt (stream,msgno)->private.uid)) holes = T;
      else if (i == uid) return msgno;
    }
    if (holes) {			/* have holes in cache? */
  				/* yes, have server hunt for UID */
      LOCAL->lastuid.uid = LOCAL->lastuid.msgno = 0;
      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,"%lu",uid);
  				/* send "UID FETCH uid UID" */
      if (!imap_OK (stream,reply = imap_send (stream,"UID FETCH",args)))
        mm_log (reply->text,ERROR);
      if (LOCAL->lastuid.uid) {	/* got any results from FETCH? */
        if ((LOCAL->lastuid.uid == uid) &&
  				/* what, me paranoid? */
  	  (LOCAL->lastuid.msgno <= stream->nmsgs) &&
  	  (mail_elt (stream,LOCAL->lastuid.msgno)->private.uid == uid))
  				/* got it the easy way */
  	return LOCAL->lastuid.msgno;
  				/* sigh, do another linear search... */
        for (msgno = 1; msgno <= stream->nmsgs; msgno++)
  	if (mail_elt (stream,msgno)->private.uid == uid) return msgno;
      }
    }
    return 0;			/* didn't find the UID anywhere */
  }
  
  /* IMAP modify flags
   * Accepts: MAIL stream
   *	    sequence
   *	    flag(s)
   *	    option flags
   */
  
  void imap_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
  {
    char *cmd = (LEVELIMAP4 (stream) && (flags & ST_UID)) ? "UID STORE":"STORE";
    IMAPPARSEDREPLY *reply;
    IMAPARG *args[4],aseq,ascm,aflg;
    if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
  						     flags & ST_UID);
    aseq.type = SEQUENCE; aseq.text = (void *) sequence;
    ascm.type = ATOM; ascm.text = (void *)
      ((flags & ST_SET) ?
       ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ?
        "+Flags.silent" : "+Flags") :
       ((LEVELIMAP4 (stream) && (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
   * Returns: T on success, NIL on failure
   */
  
  long imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags)
  {
    unsigned long i,j,k;
    char *s;
    IMAPPARSEDREPLY *reply;
    MESSAGECACHE *elt;
  
    if(LOCAL->cap.x_gm_ext1 && pgm && pgm->x_gm_ext1)
      return imap_search_x_gm_ext1(stream, charset, pgm, flags);
  
    if ((flags & SE_NOSERVER) ||	/* if want to do local search */
        LOCAL->loser ||		/* or loser */
        (!LEVELIMAP4 (stream) &&	/* or old server but new functions... */
         (charset || (flags & SE_UID) || pgm->msgno || pgm->uid || pgm->or ||
  	pgm->not || pgm->header || pgm->larger || pgm->smaller ||
  	pgm->sentbefore || pgm->senton || pgm->sentsince || pgm->draft ||
  	pgm->undraft || pgm->return_path || pgm->sender || pgm->reply_to ||
  	pgm->message_id || pgm->in_reply_to || pgm->newsgroups ||
  	pgm->followup_to || pgm->references)) ||
        (!LEVELWITHIN (stream) && (pgm->older || pgm->younger))) {
      if ((flags & SE_NOLOCAL) ||
  	!mail_search_default (stream,charset,pgm,flags | SE_NOSERVER))
        return NIL;
    }
  				/* do silly ALL or seq-only search locally */
    else if (!(flags & (SE_NOLOCAL|SE_SILLYOK)) &&
  	   !(pgm->uid || pgm->or || pgm->not ||
  	     pgm->header || pgm->from || pgm->to || pgm->cc || pgm->bcc ||
  	     pgm->subject || pgm->body || pgm->text ||
  	     pgm->larger || pgm->smaller ||
  	     pgm->sentbefore || pgm->senton || pgm->sentsince ||
  	     pgm->before || pgm->on || pgm->since ||
  	     pgm->answered || pgm->unanswered ||
  	     pgm->deleted || pgm->undeleted || pgm->draft || pgm->undraft ||
  	     pgm->flagged || pgm->unflagged || pgm->recent || pgm->old ||
  	     pgm->seen || pgm->unseen ||
  	     pgm->keyword || pgm->unkeyword ||
  	     pgm->return_path || pgm->sender ||
  	     pgm->reply_to || pgm->in_reply_to || pgm->message_id ||
  	     pgm->newsgroups || pgm->followup_to || pgm->references)) {
      if (!mail_search_default (stream,NIL,pgm,flags | SE_NOSERVER))
        fatal ("impossible mail_search_default() failure");
    }
  
    else {			/* do server-based SEARCH */
      char *cmd = (flags & SE_UID) ? "UID SEARCH" : "SEARCH";
      IMAPARG *args[4],apgm,aatt,achs;
      SEARCHSET *ss,*set;
      args[1] = args[2] = args[3] = NIL;
      apgm.type = SEARCHPROGRAM; apgm.text = (void *) pgm;
      if (charset) {		/* optional charset argument requested */
        args[0] = &aatt; args[1] = &achs; args[2] = &apgm;
        aatt.type = ATOM; aatt.text = (void *) "CHARSET";
        achs.type = ASTRING; achs.text = (void *) charset;
      }
      else args[0] = &apgm;	/* no charset argument */
  				/* tell receiver that these will be UIDs */
      LOCAL->uidsearch = (flags & SE_UID) ? T : NIL;
      reply = imap_send (stream,cmd,args);
  				/* did server barf with that searchpgm? */
      if (!(flags & SE_UID) && pgm && (ss = pgm->msgno) &&
  	!strcmp (reply->key,"BAD")) {
        LOCAL->filter = T;	/* retry, filtering SEARCH results */
        for (i = 1; i <= stream->nmsgs; i++)
  	mail_elt (stream,i)->private.filter = NIL;
        for (set = ss; set; set = set->next) if ((i = set->first) != 0L) {
  				/* single message becomes one-message range */
  	if (!(j = set->last)) j = i;
  	else if (j < i) {	/* swap reversed range */
  	  i = set->last; j = set->first;
  	}
  	while (i <= j) mail_elt (stream,i++)->private.filter = T;
        }      
        pgm->msgno = NIL;		/* and without the searchset */
        reply = imap_send (stream,cmd,args);
        pgm->msgno = ss;		/* restore searchset */
        LOCAL->filter = NIL;	/* turn off filtering */
      }
      LOCAL->uidsearch = NIL;
  				/* do locally if server won't grok */
      if (!strcmp (reply->key,"BAD")) {
        if ((flags & SE_NOLOCAL) ||
  	  !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER))
  	return NIL;
      }
      else if (!imap_OK (stream,reply)) {
        mm_log (reply->text,ERROR);
        return NIL;
      }
    }
  
  				/* can never pre-fetch with a short cache */
    if ((k = imap_prefetch) && !(flags & (SE_NOPREFETCH | SE_UID)) &&
        !stream->scache) {	/* only if prefetching permitted */
      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_elt (stream,i)->private.msg.env) {
  				/* prepend with comma if not first time */
  	if (LOCAL->tmp[0]) *s++ = ',';
  	sprintf (s,"%lu",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))->searched &&
  	       !elt->private.msg.env) i++,k--;
  	if (i != j) {		/* if a range */
  	  sprintf (s,":%lu",i);	/* output delimiter and end of range */
  	  s += strlen (s);	/* point at end of string */
  	}
  	if ((s - LOCAL->tmp) > (IMAPTMPLEN - 50)) break;
        }
      if (LOCAL->tmp[0]) {	/* anything to pre-fetch? */
        /* pre-fetch envelopes for the first imap_prefetch number of messages */
        if (!imap_OK (stream,reply =
  		    imap_fetch (stream,s = cpystr (LOCAL->tmp),FT_NEEDENV +
  				((flags & SE_NOHDRS) ? FT_NOHDRS : NIL) +
  				((flags & SE_NEEDBODY) ? FT_NEEDBODY : NIL))))
  	mm_log (reply->text,ERROR);
        fs_give ((void **) &s);	/* flush copy of sequence */
      }
    }
    return LONGT;
  }
  
  /* IMAP sort messages
   * Accepts: mail stream
   *	    character set
   *	    search program
   *	    sort program
   *	    option flags
   * Returns: vector of sorted message sequences or NIL if error
   */
  
  unsigned long *imap_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
  			  SORTPGM *pgm,long flags)
  {
    unsigned long i,j,start,last;
    unsigned long *ret = NIL;
    pgm->nmsgs = 0;		/* start off with no messages */
  				/* can use server-based sort? */
    if (LEVELSORT (stream) && !(flags & SE_NOSERVER) &&
        (!spg || (LEVELWITHIN (stream) || !(spg->older || spg->younger)))) {
      char *cmd = (flags & SE_UID) ? "UID SORT" : "SORT";
      IMAPARG *args[4],apgm,achs,aspg;
      IMAPPARSEDREPLY *reply;
      SEARCHSET *ss = NIL;
      SEARCHPGM *tsp = NIL;
      apgm.type = SORTPROGRAM; apgm.text = (void *) pgm;
      achs.type = ASTRING; achs.text = (void *) (charset ? charset : "US-ASCII");
      aspg.type = SEARCHPROGRAM;
  				/* did he provide a searchpgm? */
      if (!(aspg.text = (void *) spg)) {
        for (i = 1,start = last = 0; i <= stream->nmsgs; ++i)
  	if (mail_elt (stream,i)->searched) {
  	  if (ss) {		/* continuing a sequence */
  	    if (i == last + 1) last = i;
  	    else {		/* end of range */
  	      if (last != start) ss->last = last;
  	      (ss = ss->next = mail_newsearchset ())->first = i;
  	      start = last = i;	/* begin a new range */
  	    }
  	  }
  	  else {		/* first time, start new searchpgm */
  	    (tsp = mail_newsearchpgm ())->msgno = ss = mail_newsearchset ();
  	    ss->first = start = last = i;
  	  }
  	}
  				/* nothing to sort if no messages */
        if (!(aspg.text = (void *) tsp)) return NIL;
  				/* else install last sequence */
        if (last != start) ss->last = last;
      }
  
      args[0] = &apgm; args[1] = &achs; args[2] = &aspg; args[3] = NIL;
  				/* ask server to do it */
      reply = imap_send (stream,cmd,args);
      if (tsp) {			/* was there a temporary searchpgm? */
        aspg.text = NIL;		/* yes, flush it */
        mail_free_searchpgm (&tsp);
  				/* did server barf with that searchpgm? */
        if (!(flags & SE_UID) && !strcmp (reply->key,"BAD")) {
  	LOCAL->filter = T;	/* retry, filtering SORT/THREAD results */
  	reply = imap_send (stream,cmd,args);
  	LOCAL->filter = NIL;	/* turn off filtering */
        }
      }
  				/* do locally if server barfs */
      if (!strcmp (reply->key,"BAD"))
        return (flags & SE_NOLOCAL) ? NIL :
  	imap_sort (stream,charset,spg,pgm,flags | SE_NOSERVER);
  				/* server sorted OK? */
      else if (imap_OK (stream,reply)) {
        pgm->nmsgs = LOCAL->sortsize;
        ret = LOCAL->sortdata;
        LOCAL->sortdata = NIL;	/* mail program is responsible for flushing */
      }
      else mm_log (reply->text,ERROR);
    }
  
  				/* not much can do if short caching */
    else if (stream->scache) ret = mail_sort_msgs (stream,charset,spg,pgm,flags);
    else {			/* try to be a bit more clever */
      char *s,*t;
      unsigned long len;
      MESSAGECACHE *elt;
      SORTCACHE **sc;
      SORTPGM *sp;
      long ftflags = 0;
  				/* see if need envelopes */
      for (sp = pgm; sp && !ftflags; sp = sp->next) switch (sp->function) {
      case SORTDATE: case SORTFROM: case SORTSUBJECT: case SORTTO: case SORTCC:
        ftflags = FT_NEEDENV + ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL);
      }
      if (spg) {			/* only if a search needs to be done */
        int silent = stream->silent;
        stream->silent = T;	/* don't pass up mm_searched() events */
  				/* search for messages */
        mail_search_full (stream,charset,spg,flags & SE_NOSERVER);
        stream->silent = silent;	/* restore silence state */
      }
  				/* initialize progress counters */
      pgm->nmsgs = pgm->progress.cached = 0;
  				/* pass 1: count messages to sort */
      for (i = 1,len = start = last = 0,s = t = NIL; i <= stream->nmsgs; ++i)
        if ((elt = mail_elt (stream,i))->searched) {
  	pgm->nmsgs++;
  	if (ftflags ? !elt->private.msg.env : !elt->day) {
  	  if (s) {		/* continuing a sequence */
  	    if (i == last + 1) last = i;
  	    else {		/* end of range */
  	      if (last != start) sprintf (t,":%lu,%lu",last,i);
  	      else sprintf (t,",%lu",i);
  	      start = last = i;	/* begin a new range */
  	      if ((len - (j = ((t += strlen (t)) - s)) < 20)) {
  		fs_resize ((void **) &s,len += MAILTMPLEN);
  		t = s + j;	/* relocate current pointer */
  	      }
  	    }
  	  }
  	  else {		/* first time, start new buffer */
  	    s = (char *) fs_get (len = MAILTMPLEN);
  	    sprintf (s,"%lu",start = last = i);
  	    t = s + strlen (s);	/* end of buffer */
  	  }
  	}
        }
  				/* last sequence */
      if (last != start) sprintf (t,":%lu",last);
      if (s) {			/* load cache for all messages being sorted */
        imap_fetch (stream,s,ftflags);
        fs_give ((void **) &s);
      }
      if (pgm->nmsgs) {		/* pass 2: sort cache */
        sortresults_t sr = (sortresults_t)
  	mail_parameters (NIL,GET_SORTRESULTS,NIL);
        sc = mail_sort_loadcache (stream,pgm);
  				/* pass 3: sort messages */
        if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags);
        fs_give ((void **) &sc);	/* don't need sort vector any more */
  				/* also return via callback if requested */
        if (sr) (*sr) (stream,ret,pgm->nmsgs);
      }
    }
    return ret;
  }
  
  /* IMAP thread messages
   * Accepts: mail stream
   *	    thread type
   *	    character set
   *	    search program
   *	    option flags
   * Returns: thread node tree or NIL if error
   */
  
  THREADNODE *imap_thread (MAILSTREAM *stream,char *type,char *charset,
  			 SEARCHPGM *spg,long flags)
  {
    THREADER *thr;
    if (!(flags & SE_NOSERVER) &&
        (!spg || (LEVELWITHIN (stream) || !(spg->older || spg->younger))))
  				/* does server have this threader type? */
      for (thr = LOCAL->cap.threader; thr; thr = thr->next)
        if (!compare_cstring (thr->name,type)) 
  	return imap_thread_work (stream,type,charset,spg,flags);
  				/* server doesn't support it, do locally */
    return (flags & SE_NOLOCAL) ? NIL: 
      mail_thread_msgs (stream,type,charset,spg,flags | SE_NOSERVER,imap_sort);
  }
  
  /* IMAP thread messages worker routine
   * Accepts: mail stream
   *	    thread type
   *	    character set
   *	    search program
   *	    option flags
   * Returns: thread node tree
   */
  
  THREADNODE *imap_thread_work (MAILSTREAM *stream,char *type,char *charset,
  			      SEARCHPGM *spg,long flags)
  {
    unsigned long i,start,last;
    char *cmd = (flags & SE_UID) ? "UID THREAD" : "THREAD";
    IMAPARG *args[4],apgm,achs,aspg;
    IMAPPARSEDREPLY *reply;
    THREADNODE *ret = NIL;
    SEARCHSET *ss = NIL;
    SEARCHPGM *tsp = NIL;
    apgm.type = ATOM; apgm.text = (void *) type;
    achs.type = ASTRING;
    achs.text = (void *) (charset ? charset : "US-ASCII");
    aspg.type = SEARCHPROGRAM;
  				/* did he provide a searchpgm? */
    if (!(aspg.text = (void *) spg)) {
      for (i = 1,start = last = 0; i <= stream->nmsgs; ++i)
        if (mail_elt (stream,i)->searched) {
  	if (ss) {		/* continuing a sequence */
  	  if (i == last + 1) last = i;
  	  else {		/* end of range */
  	    if (last != start) ss->last = last;
  	    (ss = ss->next = mail_newsearchset ())->first = i;
  	    start = last =i;	/* begin a new range */
  	  }
  	}
  	else {			/* first time, start new searchpgm */
  	  (tsp = mail_newsearchpgm ())->msgno = ss = mail_newsearchset ();
  	  ss->first = start = last = i;
  	}
        }
  				/* nothing to sort if no messages */
      if (!(aspg.text = (void *) tsp)) return NIL;
  				/* else install last sequence */
      if (last != start) ss->last = last;
    }
  
    args[0] = &apgm; args[1] = &achs; args[2] = &aspg; args[3] = NIL;
  				/* ask server to do it */
    reply = imap_send (stream,cmd,args);
    if (tsp) {			/* was there a temporary searchpgm? */
      aspg.text = NIL;		/* yes, flush it */
      mail_free_searchpgm (&tsp);
  				/* did server barf with that searchpgm? */
      if (!(flags & SE_UID) && !strcmp (reply->key,"BAD")) {
        LOCAL->filter = T;	/* retry, filtering SORT/THREAD results */
        reply = imap_send (stream,cmd,args);
        LOCAL->filter = NIL;	/* turn off filtering */
      }
    }
  				/* do locally if server barfs */
    if (!strcmp (reply->key,"BAD"))
      ret = (flags & SE_NOLOCAL) ? NIL: 
      mail_thread_msgs (stream,type,charset,spg,flags | SE_NOSERVER,imap_sort);
  				/* server threaded OK? */
    else if (imap_OK (stream,reply)) {
      ret = LOCAL->threaddata;
      LOCAL->threaddata = NIL;	/* mail program is responsible for flushing */
    }
    else mm_log (reply->text,ERROR);
    return ret;
  }
  
  /* IMAP ping mailbox
   * Accepts: MAIL stream
   * Returns: T if stream still alive, else NIL
   */
  
  long imap_ping (MAILSTREAM *stream)
  {
    return (LOCAL->netstream &&	/* 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
   *	    sequence to expunge if non-NIL
   *	    expunge options
   * Returns: T if success, NIL if failure
   */
  
  long imap_expunge (MAILSTREAM *stream,char *sequence,long options)
  {
    long ret = NIL;
    IMAPPARSEDREPLY *reply = NIL;
    if (sequence) {		/* wants selective expunging? */
      if (options & EX_UID) {	/* UID EXPUNGE form? */
        if (LEVELUIDPLUS (stream)) {/* server support UIDPLUS? */
  	IMAPARG *args[2],aseq;
  	aseq.type = SEQUENCE; aseq.text = (void *) sequence;
  	args[0] = &aseq; args[1] = NIL;
  	ret = imap_OK (stream,reply = imap_send (stream,"UID EXPUNGE",args));
        }
        else mm_log ("[NOTUIDPLUS] Can't do UID EXPUNGE with this server",ERROR);
      }
  				/* otherwise try to make into UID EXPUNGE */
      else if (mail_sequence (stream,sequence)) {
        unsigned long i,j;
        char *t = (char *) fs_get (IMAPTMPLEN);
        char *s = t;
  				/* search through mailbox */
        for (*s = '\0', i = 1; i <= stream->nmsgs; ++i)
  	if (mail_elt (stream,i)->sequence) {
  	  if (t[0]) *s++ = ',';	/* prepend with comma if not first time */
  	  sprintf (s,"%lu",mail_uid (stream,j = i));
  	  s += strlen (s);	/* point at end of string */
  				/* search for possible end of range */
  	  while ((i < stream->nmsgs) && mail_elt (stream,i+1)->sequence) i++;
  	  if (i != j) {		/* output end of range */
  	    sprintf (s,":%lu",mail_uid (stream,i));
  	    s += strlen (s);	/* point at end of string */
  	  }
  	  if ((s - t) > (IMAPTMPLEN - 50)) {
  	    mm_log ("Excessively complex sequence",ERROR);
  	    return NIL;
  	  }
  	}
  				/* now do as UID EXPUNGE */
        ret = imap_expunge (stream,t,EX_UID);
        fs_give ((void **) &t);
      }
    }
  				/* ordinary EXPUNGE */
    else ret = imap_OK (stream,reply = imap_send (stream,"EXPUNGE",NIL));
    if (reply) mm_log (reply->text,ret ? (long) NIL : ERROR);
    return ret;
  }
  
  /* 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 = (LEVELIMAP4 (stream) && (flags & CP_UID)) ? "UID COPY" : "COPY";
    char *s;
    long ret = NIL;
    IMAPPARSEDREPLY *reply;
    IMAPARG *args[3],aseq,ambx;
    imapreferral_t ir =
      (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
    mailproxycopy_t pc =
      (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
    if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
  						     flags & CP_UID);
    aseq.type = SEQUENCE; aseq.text = (void *) sequence;
    ambx.type = ASTRING; ambx.text = (void *) mailbox;
    args[0] = &aseq; args[1] = &ambx; args[2] = NIL;
  				/* note mailbox in case APPENDUID */
    LOCAL->appendmailbox = mailbox;
  				/* send "COPY sequence mailbox" */
    ret = imap_OK (stream,reply = imap_send (stream,cmd,args));
    LOCAL->appendmailbox = NIL;	/* no longer appending */
    if (ret) {			/* success, delete messages if move */
      if (flags & CP_MOVE) imap_flag (stream,sequence,"\\Deleted",
  				    ST_SET + ((flags&CP_UID) ? ST_UID : NIL));
    }
  				/* failed, do referral action if any */
    else if (ir && pc && LOCAL->referral && mail_sequence (stream,sequence) &&
  	   (s = (*ir) (stream,LOCAL->referral,REFCOPY)))
      ret = (*pc) (stream,sequence,s,flags | (stream->debug ? CP_DEBUG : NIL));
  				/* otherwise issue error message */
    else mm_log (reply->text,ERROR);
    return ret;
  }
  
  /* IMAP mail append message from stringstruct
   * Accepts: MAIL stream
   *	    destination mailbox
   *	    append callback
   *	    data for callback
   * Returns: T if append successful, else NIL
   */
  
  long imap_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
  {
    MAILSTREAM *st = stream;
    IMAPARG *args[3],ambx,amap;
    IMAPPARSEDREPLY *reply = NIL;
    APPENDDATA map;
    char tmp[MAILTMPLEN];
    long debug = stream ? stream->debug : NIL;
    long ret = NIL;
    imapreferral_t ir =
      (imapreferral_t) mail_parameters (stream,GET_IMAPREFERRAL,NIL);
  				/* mailbox must be good */
    if (mail_valid_net (mailbox,&imapdriver,NIL,tmp)) {
  				/* create a stream if given one no good */
      if ((stream && LOCAL && LOCAL->netstream) ||
  	(stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT |
  			     (debug ? OP_DEBUG : NIL)))) {
  				/* note mailbox in case APPENDUID */
        LOCAL->appendmailbox = mailbox;
  				/* use multi-append? */
        if (LEVELMULTIAPPEND (stream)) {
  	ambx.type = ASTRING; ambx.text = (void *) tmp;
  	amap.type = MULTIAPPEND; amap.text = (void *) &map;
  	map.af = af; map.data = data;
  	args[0] = &ambx; args[1] = &amap; args[2] = NIL;
  				/* success if OK */
  	ret = imap_OK (stream,reply = imap_send (stream,"APPEND",args));
  	LOCAL->appendmailbox = NIL;
        }
  				/* do succession of single appends */
        else while ((*af) (stream,data,&map.flags,&map.date,&map.message) &&
  		  map.message &&
  		  (ret = imap_OK (stream,reply =
  				  imap_append_single (stream,tmp,map.flags,
  						      map.date,map.message))));
        LOCAL->appendmailbox = NIL;
  				/* don't do referrals if success or no reply */
        if (ret || !reply) mailbox = NIL;
  				/* otherwise generate referral */
        else if (!(mailbox = (ir && LOCAL->referral) ?
  		 (*ir) (stream,LOCAL->referral,REFAPPEND) : NIL))
  	mm_log (reply->text,ERROR);
  				/* close temporary stream */
        if (st != stream) stream = mail_close (stream);
        if (mailbox)		/* chase referral if any */
  	ret = imap_append_referral (mailbox,tmp,af,data,map.flags,map.date,
  				    map.message,&map,debug);
      }
      else mm_log ("Can't access server for append",ERROR);
    }
    return ret;			/* return */
  }
  
  /* IMAP mail append message referral retry
   * Accepts: destination mailbox
   *	    temporary buffer
   *	    append callback
   *	    data for callback
   *	    flags from previous attempt
   *	    date from previous attempt
   *	    message stringstruct from previous attempt
   *	    options (currently non-zero to set OP_DEBUG)
   * Returns: T if append successful, else NIL
   */
  
  long imap_append_referral (char *mailbox,char *tmp,append_t af,void *data,
  			   char *flags,char *date,STRING *message,
  			   APPENDDATA *map,long options)
  {
    MAILSTREAM *stream;
    IMAPARG *args[3],ambx,amap;
    IMAPPARSEDREPLY *reply;
    imapreferral_t ir =
      (imapreferral_t) mail_parameters (NIL,GET_IMAPREFERRAL,NIL);
  				/* barf if bad mailbox */
    while (mailbox && mail_valid_net (mailbox,&imapdriver,NIL,tmp)) {
  				/* create a stream if given one no good */
      if (!(stream = mail_open (NIL,mailbox,OP_HALFOPEN|OP_SILENT |
  			      (options ? OP_DEBUG : NIL)))) {
        sprintf (tmp,"Can't access referral server: %.80s",mailbox);
        mm_log (tmp,ERROR);
        return NIL;
      }
  				/* got referral server, use multi-append? */
      if (LEVELMULTIAPPEND (stream)) {
        ambx.type = ASTRING; ambx.text = (void *) tmp;
        amap.type = MULTIAPPENDREDO; amap.text = (void *) map;
        args[0] = &ambx; args[1] = &amap; args[2] = NIL;
  				/* do multiappend on referral site */
        if (imap_OK (stream,reply = imap_send (stream,"APPEND",args))) {
  	mail_close (stream);	/* multiappend OK, close stream */
  	return LONGT;		/* all done */
        }
      }
  				/* do multiple single appends */
      else while (imap_OK (stream,reply =
  			 imap_append_single (stream,tmp,flags,date,message)))
        if (!((*af) (stream,data,&flags,&date,&message) && message)) {
  	mail_close (stream);	/* last message, close stream */
  	return LONGT;		/* all done */
        }
  				/* generate error if no nested referral */
      if (!(mailbox = (ir && LOCAL->referral) ?
  	  (*ir) (stream,LOCAL->referral,REFAPPEND) : NIL))
        mm_log (reply->text,ERROR);
      mail_close (stream);	/* close previous referral stream */
    }
    return NIL;			/* bogus mailbox */
  }
  
  /* IMAP append single message
   * Accepts: mail stream
   *	    destination mailbox
   *	    initial flags
   *	    internal date
   *	    stringstruct of message to append
   * Returns: reply from append
   */
  
  IMAPPARSEDREPLY *imap_append_single (MAILSTREAM *stream,char *mailbox,
  				     char *flags,char *date,STRING *message)
  {
    MESSAGECACHE elt;
    IMAPARG *args[5],ambx,aflg,adat,amsg;
    IMAPPARSEDREPLY *reply;
    char tmp[MAILTMPLEN];
    int i;
    ambx.type = ASTRING; ambx.text = (void *) mailbox;
    args[i = 0] = &ambx;
    if (flags) {
      aflg.type = FLAGS; aflg.text = (void *) flags;
      args[++i] = &aflg;
    }
    if (date) {			/* ensure date in INTERNALDATE format */
      if (!mail_parse_date (&elt,date)) {
  				/* flush previous reply */
        if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
  				/* build new fake reply */
        LOCAL->reply.tag = LOCAL->reply.line = cpystr ("*");
        LOCAL->reply.key = "BAD";
        LOCAL->reply.text = "Bad date in append";
        return &LOCAL->reply;
      }
      adat.type = ASTRING;
      adat.text = (void *) (date = mail_date (tmp,&elt));
      args[++i] = &adat;
    }
    amsg.type = LITERAL; amsg.text = (void *) message;
    args[++i] = &amsg;
    args[++i] = NIL;
  				/* easy if IMAP4[rev1] */
    if (LEVELIMAP4 (stream)) reply = imap_send (stream,"APPEND",args);
    else {			/* try the IMAP2bis way */
      args[1] = &amsg; args[2] = NIL;
      reply = imap_send (stream,"APPEND",args);
    }
    return reply;
  }
  
  /* 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? */
      if (!stream->scache) for (i = 1; i <= stream->nmsgs; ++i)
        if ((elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) != NULL)
  	imap_gc_body (elt->private.msg.body);
      imap_gc_body (stream->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) {			/* have a body? */
      if (body->mime.text.data)	/* flush MIME data */
        fs_give ((void **) &body->mime.text.data);
  				/* flush text contents */
      if (body->contents.text.data)
        fs_give ((void **) &body->contents.text.data);
      body->mime.text.size = body->contents.text.size = 0;
  				/* multipart? */
      if (body->type == TYPEMULTIPART)
        for (part = body->nested.part; part; part = part->next)
  	imap_gc_body (&part->body);
  				/* MESSAGE/RFC822? */
      else if ((body->type == TYPEMESSAGE) && !strcmp (body->subtype,"RFC822")) {
        imap_gc_body (body->nested.msg->body);
        if (body->nested.msg->full.text.data)
  	fs_give ((void **) &body->nested.msg->full.text.data);
        if (body->nested.msg->header.text.data)
  	fs_give ((void **) &body->nested.msg->header.text.data);
        if (body->nested.msg->text.text.data)
  	fs_give ((void **) &body->nested.msg->text.text.data);
        body->nested.msg->full.text.size = body->nested.msg->header.text.size =
  	body->nested.msg->text.text.size = 0;
      }
    }
  }
  
  /* IMAP get capabilities
   * Accepts: mail stream
   */
  
  void imap_capability (MAILSTREAM *stream)
  {
    THREADER *thr,*t;
    LOCAL->gotcapability = NIL;	/* flush any previous capabilities */
  				/* request new capabilities */
    imap_send (stream,"CAPABILITY",NIL);
    if (!LOCAL->gotcapability) {	/* did server get any? */
  				/* no, flush threaders just in case */
      if ((thr = LOCAL->cap.threader) != NULL) while ((t = thr) != NULL) {
        fs_give ((void **) &t->name);
        thr = t->next;
        fs_give ((void **) &t);
      }
  				/* zap most capabilities */
      memset (&LOCAL->cap,0,sizeof (LOCAL->cap));
  				/* assume IMAP2bis server if failure */
      LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
    }
  }
  
  /* IMAP set ACL
   * Accepts: mail stream
   *	    mailbox name
   *	    authentication identifer
   *	    new access rights
   * Returns: T on success, NIL on failure
   */
  
  long imap_setacl (MAILSTREAM *stream,char *mailbox,char *id,char *rights)
  {
    IMAPARG *args[4],ambx,aid,art;
    ambx.type = aid.type = art.type = ASTRING;
    ambx.text = (void *) mailbox; aid.text = (void *) id;
    art.text = (void *) rights;
    args[0] = &ambx; args[1] = &aid; args[2] = &art; args[3] = NIL;
    return imap_acl_work (stream,"SETACL",args);
  }
  
  
  /* IMAP delete ACL
   * Accepts: mail stream
   *	    mailbox name
   *	    authentication identifer
   * Returns: T on success, NIL on failure
   */
  
  long imap_deleteacl (MAILSTREAM *stream,char *mailbox,char *id)
  {
    IMAPARG *args[3],ambx,aid;
    ambx.type = aid.type = ASTRING;
    ambx.text = (void *) mailbox; aid.text = (void *) id;
    args[0] = &ambx; args[1] = &aid; args[2] = NIL;
    return imap_acl_work (stream,"DELETEACL",args);
  }
  
  
  /* IMAP get ACL
   * Accepts: mail stream
   *	    mailbox name
   * Returns: T on success with data returned via callback, NIL on failure
   */
  
  long imap_getacl (MAILSTREAM *stream,char *mailbox)
  {
    IMAPARG *args[2],ambx;
    ambx.type = ASTRING; ambx.text = (void *) mailbox;
      args[0] = &ambx; args[1] = NIL;
    return imap_acl_work (stream,"GETACL",args);
  }
  
  /* IMAP list rights
   * Accepts: mail stream
   *	    mailbox name
   *	    authentication identifer
   * Returns: T on success with data returned via callback, NIL on failure
   */
  
  long imap_listrights (MAILSTREAM *stream,char *mailbox,char *id)
  {
    IMAPARG *args[3],ambx,aid;
    ambx.type = aid.type = ASTRING;
    ambx.text = (void *) mailbox; aid.text = (void *) id;
    args[0] = &ambx; args[1] = &aid; args[2] = NIL;
    return imap_acl_work (stream,"LISTRIGHTS",args);
  }
  
  
  /* IMAP my rights
   * Accepts: mail stream
   *	    mailbox name
   * Returns: T on success with data returned via callback, NIL on failure
   */
  
  long imap_myrights (MAILSTREAM *stream,char *mailbox)
  {
    IMAPARG *args[2],ambx;
    ambx.type = ASTRING; ambx.text = (void *) mailbox;
    args[0] = &ambx; args[1] = NIL;
    return imap_acl_work (stream,"MYRIGHTS",args);
  }
  
  
  /* IMAP ACL worker routine
   * Accepts: mail stream
   *	    command
   *	    command arguments
   * Returns: T on success, NIL on failure
   */
  
  long imap_acl_work (MAILSTREAM *stream,char *command,IMAPARG *args[])
  {
    long ret = NIL;
    if (LEVELACL (stream)) {	/* send command */
      IMAPPARSEDREPLY *reply;
      if (imap_OK (stream,reply = imap_send (stream,command,args)))
        ret = LONGT;
      else mm_log (reply->text,ERROR);
    }
    else mm_log ("ACL not available on this IMAP server",ERROR);
    return ret;
  }
  
  /* IMAP set quota
   * Accepts: mail stream
   *	    quota root name
   *	    resource limit list as a stringlist
   * Returns: T on success with data returned via callback, NIL on failure
   */
  
  long imap_setquota (MAILSTREAM *stream,char *qroot,STRINGLIST *limits)
  {
    long ret = NIL;
    if (LEVELQUOTA (stream)) {	/* send "SETQUOTA" */
      IMAPPARSEDREPLY *reply;
      IMAPARG *args[3],aqrt,alim;
      aqrt.type = ASTRING; aqrt.text = (void *) qroot;
      alim.type = SNLIST; alim.text = (void *) limits;
      args[0] = &aqrt; args[1] = &alim; args[2] = NIL;
      if (imap_OK (stream,reply = imap_send (stream,"SETQUOTA",args)))
        ret = LONGT;
      else mm_log (reply->text,ERROR);
    }
    else mm_log ("Quota not available on this IMAP server",ERROR);
    return ret;
  }
  
  IDLIST *imap_parse_idlist (char *text)
  {
    IDLIST *ret = NULL;
    char *s;
    char tmp[MAILTMPLEN];
  
    if(text == NULL) return NULL;
    for(s = text; *s == ' '; s++);	/* move past spaces */
    if(*s == '(') s++;
    if(*s++ == '"'){
       char *t;
       for(t = s; *t && *t != '"'; t++);
       if(*t == '"'){
  	ret = fs_get(sizeof(IDLIST));
  	*t = '\0';
  	ret->name = cpystr(s);
  	*t = '"';
  	for(s = t+1; *s == ' '; s++);	/* move past spaces */
  	if(*s++ == '"'){
  	   for(t = s; *t && *t != '"'; t++);
  	   if(*t == '"'){
  	      *t = '\0';
  	      ret->value = cpystr(s);
  	      *t++ = '"';
  	      ret->next = imap_parse_idlist(t);
  	   }
  	   else {
  	     sprintf(tmp,"ID value not found for name %.80s, at %.80s", ret->name, s);
  	     fs_give((void **)&ret->name);
  	     fs_give((void **)&ret);
  	     mm_log (tmp, NIL);	/* this is an technically an error */
  	   }
  	}
  	else {	/* failed!, quit */
  	  sprintf(tmp,"ID name \"%.80s\" has no value", ret->name);
  	  fs_give((void **)&ret->name);
  	  fs_give((void **)&ret);
  	  mm_log (tmp, NIL);	/* this is an technically an error */
  	}
       }
    }
    return ret;
  }
  
  long imap_setid (MAILSTREAM *stream, IDLIST *idlist)
  {
    long ret = NIL;
    if (LEVELID (stream)) {	/* send "ID (params)" */
      IMAPPARSEDREPLY *reply;
      IMAPARG *args[2],aqrt;
      IDLIST *list;
      char *qroot, *p;
      long len = 0L;
  
      if(idlist == NULL) return ret;
      for (list = idlist; list != NULL; list = list->next)
  	len += strlen(list->name)  + strlen(list->value) + 6;
      if(len > 0){
  	len += 1L;	/* in case there is only one field */
  	qroot = fs_get(len+1);
  	memset((void *)&qroot[0], 0, len+1);
  	p = qroot;
  	for (list = idlist; list != NULL; list = list->next){
  	    sprintf(p, " \"%s\" \"%s\"", list->name, list->value);
  	    p += strlen(p);
  	}
  	*p = ')';
  	qroot[0] = '(';
  	aqrt.type = ATOM; aqrt.text = (void *) qroot;
  	args[0] = &aqrt; args[1] = NIL;
  	if (imap_OK (stream,reply = imap_send (stream,"ID",args)))
  	   ret = LONGT;
  	else mm_log (reply->text,ERROR);
  	if(qroot) fs_give((void **) &qroot);
      } else mm_log("Empty or malformed ID list", ERROR);
    }
    else mm_log ("ID capability not available on this IMAP server",ERROR);
    return ret;
  }
  
  /* IMAP get quota
   * Accepts: mail stream
   *	    quota root name
   * Returns: T on success with data returned via callback, NIL on failure
   */
  
  long imap_getquota (MAILSTREAM *stream,char *qroot)
  {
    long ret = NIL;
    if (LEVELQUOTA (stream)) {	/* send "GETQUOTA" */
      IMAPPARSEDREPLY *reply;
      IMAPARG *args[2],aqrt;
      aqrt.type = ASTRING; aqrt.text = (void *) qroot;
      args[0] = &aqrt; args[1] = NIL;
      if (imap_OK (stream,reply = imap_send (stream,"GETQUOTA",args)))
        ret = LONGT;
      else mm_log (reply->text,ERROR);
    }
    else mm_log ("Quota not available on this IMAP server",ERROR);
    return ret;
  }
  
  
  /* IMAP get quota root
   * Accepts: mail stream
   *	    mailbox name
   * Returns: T on success with data returned via callback, NIL on failure
   */
  
  long imap_getquotaroot (MAILSTREAM *stream,char *mailbox)
  {
    long ret = NIL;
    if (LEVELQUOTA (stream)) {	/* send "GETQUOTAROOT" */
      IMAPPARSEDREPLY *reply;
      IMAPARG *args[2],ambx;
      ambx.type = ASTRING; ambx.text = (void *) mailbox;
      args[0] = &ambx; args[1] = NIL;
      if (imap_OK (stream,reply = imap_send (stream,"GETQUOTAROOT",args)))
        ret = LONGT;
      else mm_log (reply->text,ERROR);
    }
    else mm_log ("Quota not available on this IMAP server",ERROR);
    return ret;
  }
  
  /* Internal routines */
  
  
  /* IMAP send command
   * Accepts: MAIL stream
   *	    command
   *	    argument list
   * Returns: parsed reply
   */
  
  #define CMDBASE LOCAL->tmp	/* command base */
  
  IMAPPARSEDREPLY *imap_send (MAILSTREAM *stream,char *cmd,IMAPARG *args[])
  {
    IMAPPARSEDREPLY *reply;
    IMAPARG *arg,**arglst;
    SORTPGM *spg;
    STRINGLIST *list;
    SIZEDTEXT st;
    APPENDDATA *map;
    sendcommand_t sc = (sendcommand_t) mail_parameters (NIL,GET_SENDCOMMAND,NIL);
    size_t i;
    void *a;
    char c,*s,*t,tag[10];
    stream->unhealthy = NIL;	/* make stream healthy again */
    				/* gensym a new tag */
    sprintf (tag,"%08lx",0xffffffff & (stream->gensym++));
    if (!LOCAL->netstream)	/* make sure have a session */
      return imap_fake (stream,tag,"[CLOSED] IMAP connection lost");
    mail_lock (stream);		/* lock up the stream */
    if (sc)			/* tell client sending a command */
      (*sc) (stream,cmd,((compare_cstring (cmd,"FETCH") &&
  			compare_cstring (cmd,"STORE") &&
  			compare_cstring (cmd,"SEARCH")) ? 
  		       NIL : SC_EXPUNGEDEFERRED));
  				/* ignore referral from previous command */
    if (LOCAL->referral) fs_give ((void **) &LOCAL->referral);
    sprintf (CMDBASE,"%s %s",tag,cmd);
    s = CMDBASE + strlen (CMDBASE);
    if ((arglst = args) != NULL) while ((arg = *arglst++) != NULL) {
      *s++ = ' ';			/* delimit argument with space */
      switch (arg->type) {
      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? */
        st.size = strlen ((char *) (st.data = (unsigned char *) arg->text));
        if ((reply = imap_send_astring (stream,tag,&s,&st,NIL,CMDBASE+MAXCOMMAND)) != NULL)
  	return reply;
        break;
      case LITERAL:		/* literal, as a stringstruct */
        if ((reply = imap_send_literal (stream,tag,&s,arg->text)) != NULL) 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,NIL,
  				       CMDBASE+MAXCOMMAND)) != NULL) return reply;
  	c = ' ';		/* prefix character for subsequent strings */
        }
        while ((list = list->next) != NULL); 
        *s++ = ')';		/* close list */
        break;
      case SEARCHPROGRAM:		/* search program */
        if ((reply = imap_send_spgm (stream,tag,CMDBASE,&s,arg->text,
  				  CMDBASE+MAXCOMMAND)) != NULL)
  	return reply;
        break;
      case SORTPROGRAM:		/* search program */
        c = '(';			/* open paren */
        for (spg = (SORTPGM *) arg->text; spg; spg = spg->next) {
  	*s++ = c;		/* write prefix */
  	if (spg->reverse) for (t = "REVERSE "; *t; *s++ = *t++);
  	switch (spg->function) {
  	case SORTDATE:
  	  for (t = "DATE"; *t; *s++ = *t++);
  	  break;
  	case SORTARRIVAL:
  	  for (t = "ARRIVAL"; *t; *s++ = *t++);
  	  break;
  	case SORTFROM:
  	  for (t = "FROM"; *t; *s++ = *t++);
  	  break;
  	case SORTSUBJECT:
  	  for (t = "SUBJECT"; *t; *s++ = *t++);
  	  break;
  	case SORTTO:
  	  for (t = "TO"; *t; *s++ = *t++);
  	  break;
  	case SORTCC:
  	  for (t = "CC"; *t; *s++ = *t++);
  	  break;
  	case SORTSIZE:
  	  for (t = "SIZE"; *t; *s++ = *t++);
  	  break;
  	default:
  	  fatal ("Unknown sort program function in imap_send()!");
  	}
  	c = ' ';		/* prefix character for subsequent items */
        }
        *s++ = ')';		/* close list */
        break;
  
      case BODYTEXT:		/* body section */
        for (t = "BODY["; *t; *s++ = *t++);
        for (t = (char *) arg->text; *t; *s++ = *t++);
        break;
      case BODYPEEK:		/* body section */
        for (t = "BODY.PEEK["; *t; *s++ = *t++);
        for (t = (char *) arg->text; *t; *s++ = *t++);
        break;
      case BODYCLOSE:		/* close bracket and possible length */
        s[-1] = ']';		/* no leading space */
        for (t = (char *) arg->text; *t; *s++ = *t++);
        break;
      case SEQUENCE:		/* sequence */
        if ((i = strlen (t = (char *) arg->text)) <= (size_t) MAXCOMMAND)
  	while (*t) *s++ = *t++;	/* easy case */
        else {
  	mail_unlock (stream);	/* unlock stream */
  	a = arg->text;		/* save original sequence pointer */
  	arg->type = ATOM;	/* make recursive call be faster */
  	do {			/* break up into multiple commands */
  	  if (i <= MAXCOMMAND) {/* final part? */
  	    reply = imap_send (stream,cmd,args);
  	    i = 0;		/* and mark as done */
  	  }
  	  else {		/* still needs to be split further */
  	    if (!(t = strchr (t + MAXCOMMAND - 30,',')) ||
  		((t - (char *) arg->text) > MAXCOMMAND))
  	      fatal ("impossible over-long sequence");
  	    *t = '\0';		/* tie off sequence at point of split*/
  				/* recurse to do this part */
  	    reply = imap_send (stream,cmd,args);
  	    *t++ = ',';		/* restore the comma in case something cares */
  				/* punt if error */
  	    if (!imap_OK (stream,reply)) break;
  				/* calculate size of remaining sequence */
  	    i -= (t - (char *) arg->text);
  				/* point to new remaining sequence */
  	    arg->text = (void *) t;
  	  }
  	} while (i);
  	arg->type = SEQUENCE;	/* restore in case something cares */
  	arg->text = a;
  	return reply;		/* return result */
        }
        break;
      case LISTMAILBOX:		/* astring with wildcards */
        st.size = strlen ((char *) (st.data = (unsigned char *) arg->text));
        if ((reply = imap_send_astring (stream,tag,&s,&st,T,CMDBASE+MAXCOMMAND)) != NULL)
  	return reply;
        break;
  
      case MULTIAPPEND:		/* append multiple messages */
  				/* get package pointer */
        map = (APPENDDATA *) arg->text;
        if (!(*map->af) (stream,map->data,&map->flags,&map->date,&map->message)||
  	  !map->message) {
  	STRING es;
  	INIT (&es,mail_string,"",0);
  	return (reply = imap_send_literal (stream,tag,&s,&es)) ?
  	  reply : imap_fake (stream,tag,"Server zero-length literal error");
        }
      case MULTIAPPENDREDO:	/* redo multiappend */
  				/* get package pointer */
        map = (APPENDDATA *) arg->text;
        do {			/* make sure date valid if given */
  	char datetmp[MAILTMPLEN];
  	MESSAGECACHE elt;
  	STRING es;
  	if (!map->date || mail_parse_date (&elt,map->date)) {
  	  if ((t = map->flags) != NULL) {	/* flags given? */
  	    if (*t != '(') {
  	      *s++ = '(';	/* wrap parens around string */
  	      while (*t) *s++ = *t++;
  	      *s++ = ')';	/* wrap parens around string */
  	    }
  	    else while (*t) *s++ = *t++;
  	    *s++ = ' ';		/* delimit with space */
  	  }
  	  if (map->date) {	/* date given? */
  	    st.size = strlen ((char *) (st.data = (unsigned char *)
  					mail_date (datetmp,&elt)));
  	    if ((reply = imap_send_astring (stream,tag,&s,&st,NIL,
  				   CMDBASE+MAXCOMMAND)) != NULL) return reply;
  	    *s++ = ' ';		/* delimit with space */
  	  }
  	  if ((reply = imap_send_literal (stream,tag,&s,map->message)) != NULL)
  	    return reply;
  				/* get next message */
  	  if ((*map->af) (stream,map->data,&map->flags,&map->date,
  			  &map->message)) {
  				/* have a message, delete next in command */
  	    if (map->message) *s++ = ' ';
  	    continue;		/* loop back for next message */
  	  }
  	}
  				/* bad date or need to abort */
  	INIT (&es,mail_string,"",0);
  	return (reply = imap_send_literal (stream,tag,&s,&es)) ?
  	  reply : imap_fake (stream,tag,"Server zero-length literal error");
  	break;			/* exit the loop */
        } while (map->message);
        break;
  
      case SNLIST:		/* list of string/number pairs */
        list = (STRINGLIST *) arg->text;
        c = '(';			/* open paren */
        do {			/* for each list item */
  	*s++ = c;		/* write prefix character */
  	if (list) {		/* sigh, QUOTA has bizarre syntax! */
  	  for (t = (char *) list->text.data; *t; *s++ = *t++);
  	  sprintf (s," %lu",list->text.size);
  	  s += strlen (s);
  	  c = ' ';		/* prefix character for subsequent strings */
  	}
        }
        while ((list = list->next) != NULL);
        *s++ = ')';		/* close list */
        break;
      default:
        fatal ("Unknown argument type in imap_send()!");
      }
    }
  				/* send the command */
    reply = imap_sout (stream,tag,CMDBASE,&s);
    mail_unlock (stream);		/* unlock stream */
    return reply;
  }
  
  /* IMAP send atom-string
   * Accepts: MAIL stream
   *	    reply tag
   *	    pointer to current position pointer of output bigbuf
   *	    atom-string to output
   *	    flag if list_wildcards allowed
   *	    maximum to write as atom or qstring
   * Returns: error reply or NIL if success
   */
  
  IMAPPARSEDREPLY *imap_send_astring (MAILSTREAM *stream,char *tag,char **s,
  				    SIZEDTEXT *as,long wildok,char *limit)
  {
    unsigned long j;
    char c;
    STRING st;
  				/* default to atom unless empty or loser */
    int qflag = (as->size && !LOCAL->loser) ? NIL : T;
  				/* in case needed */
    INIT (&st,mail_string,(void *) as->data,as->size);
  				/* always write literal if no space */
    if ((*s + as->size) > limit) return imap_send_literal (stream,tag,s,&st);
    for (j = 0; j < as->size; j++) switch (c = as->data[j]) {
    default:			/* all other characters */
      if (!(c & 0x80)) {		/* must not be 8bit */
        if (c <= ' ') qflag = T;	/* must quote if a CTL */
        break;
      }
    case '\0':			/* not a CHAR */
    case '\012': case '\015':	/* not a TEXT-CHAR */
    case '"': case '\\':		/* quoted-specials (IMAP2 required this) */
      return imap_send_literal (stream,tag,s,&st);
    case '*': case '%':		/* list_wildcards */
      if (wildok) break;		/* allowed if doing the wild thing */
  				/* atom_specials */
    case '(': case ')': case '{': case ' ': case 0x7f:
  #if 0
    case '"': case '\\':		/* quoted-specials (could work in IMAP4) */
  #endif
      qflag = T;			/* must use quoted string format */
      break;
    }
    if (qflag) *(*s)++ = '"';	/* write open quote */
    for (j = 0; j < as->size; j++) *(*s)++ = as->data[j];
    if (qflag) *(*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);
    unsigned long j;
    sprintf (*s,"{%lu}",i);	/* write literal count */
    *s += strlen (*s);		/* size of literal count */
  				/* send the command */
    reply = imap_sout (stream,tag,CMDBASE,s);
    if (strcmp (reply->tag,"+")) {/* prompt for more data? */
      mail_unlock (stream);	/* no, give up */
      return reply;
    }
    while (i) {			/* dump the text */
      if (st->cursize) {		/* if text to do in this chunk */
        /* RFC 3501 technically forbids NULs in literals.  Normally, the
         * delivering MTA would take care of MIME converting the message text
         * so that it is NUL-free.  If it doesn't, then we have the choice of
         * either violating IMAP by sending NULs, corrupting the data, or going
         * to lots of work to do MIME conversion in the IMAP server.
         *
         * No current stringstruct driver objects to having its buffer patched.
         * If this ever changes, it will be necessary to change this kludge.
         */
  				/* patch NULs to C1 control */
        for (j = 0; j < st->cursize; ++j)
  	if (!st->curpos[j]) st->curpos[j] = 0x80;
        if (!net_sout (LOCAL->netstream,st->curpos,st->cursize)) {
  	mail_unlock (stream);
  	return imap_fake (stream,tag,"[CLOSED] 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 */
  }
  
  #define ADD_STRING(X, Y)	{		\
  	    if(remain > 0){			\
  	       sprintf (u, (X), (Y));		\
  	       len = strlen(u);			\
  	       if(len < remain){		\
  		 remain -= len;			\
  		 strncpy(t, u, strlen(u));	\
  		 t[strlen(u)] = '\0';		\
  		 t += strlen (t);		\
  	       }				\
  	    }					\
  	}
  
  
  long imap_search_x_gm_ext1 (MAILSTREAM *stream, char *charset, SEARCHPGM *pgm, long flags)
  {
    char *cmd = (flags & SE_UID) ? "UID SEARCH X-GM-RAW" : "SEARCH X-GM-RAW";
    char *t, s[MAILTMPLEN+1], u[MAILTMPLEN];
    IMAPARG *args[4],apgm;
    IMAPPARSEDREPLY *reply;
    unsigned long i,j,k;
    MESSAGECACHE *elt;
    size_t remain = sizeof(s) - 1, len;
  	
    u[0] = s[0] = '\0';
    t = s;
    args[1] = args[2] = args[3] = NIL;
  
    if(pgm->x_gm_ext1)
       ADD_STRING(" %s", pgm->x_gm_ext1->text.data);
  
  #if 0 /* maybe later */
    if (pgm->larger)
       ADD_STRING(" larger:%lu", pgm->larger);
  
    if (pgm->smaller)
       ADD_STRING(" smaller:%lu", pgm->smaller);
  
    if (pgm->deleted)
       ADD_STRING(" %s", "in:trash");
  
    if (pgm->undeleted)
       ADD_STRING(" %s", "-in:trash");
  
    if (pgm->draft)
       ADD_STRING(" %s", "in:drafts");
  
    if (pgm->undraft)
       ADD_STRING(" %s", "-in:drafts");
  
    if (pgm->flagged)
       ADD_STRING(" %s", "is:starred");
  
    if (pgm->unflagged)
       ADD_STRING(" %s", "-is:starred");
  
    if (pgm->seen)
       ADD_STRING(" %s", "-is:unread");
  
    if (pgm->unseen)
       ADD_STRING(" %s", "is:unread");
  
    if (pgm->keyword){
      STRINGLIST *sl;
      for(sl = pgm->keyword; remain > 0 && sl; sl = sl->next)
        ADD_STRING(" label:%s", sl->text.data);
    }
  
    if (pgm->unkeyword){
      STRINGLIST *sl;
      for(sl = pgm->unkeyword; remain > 0 && sl; sl = sl->next)
        ADD_STRING(" -label:%s", sl->text.data);
    }
  
    if (pgm->sentbefore){
      unsigned short date = pgm->sentbefore;
      sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9),
  			(date >> 5) & 0xf, date & 0x1f);
      ADD_STRING(" before:%s", v);
    }
  
    if (pgm->sentsince){
      unsigned short date = pgm->sentsince;
      sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9),
  			(date >> 5) & 0xf, date & 0x1f);
      ADD_STRING(" older:%s", v);
    }
  
    if (pgm->before){
      unsigned short date = pgm->before;
      sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9),
  			(date >> 5) & 0xf, date & 0x1f);
      ADD_STRING(" before:%s", v);
    }
  
    if (pgm->since){
      unsigned short date = pgm->since;
      sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9),
  			(date >> 5) & 0xf, date & 0x1f);
      ADD_STRING(" before:%s", v);
    }
  
    if (pgm->older){
      sprintf(v, "%dd", pgm->older/86400);
      ADD_STRING(" older_than:%s", v);
    }
  
    if (pgm->younger){
      sprintf(v, "%dd", pgm->younger/86400);
      ADD_STRING(" newer_than:%s", v);
    }
  
    if(pgm->bcc){
      STRINGLIST *sl;
      ADD_STRING("%s", pgm->bcc->next ? " {" : " ");
      for(sl = pgm->bcc; remain > 0 && sl; sl = sl->next){
        ADD_STRING("bcc:%s", sl->text.data);
        ADD_STRING("%s", sl->next ? " " : "}");
      }
    }
  
    if(pgm->cc){
      STRINGLIST *sl;
      ADD_STRING("%s", pgm->cc->next ? " {" : " ");
      for(sl = pgm->cc; remain > 0 && sl; sl = sl->next){
        ADD_STRING("cc:%s", sl->text.data);
        ADD_STRING("%s", sl->next ? " " : "}");
      }
    }
  
    if(pgm->from){
      STRINGLIST *sl;
      ADD_STRING("%s", pgm->from->next ? " {" : " ");
      for(sl = pgm->from; remain > 0 && sl; sl = sl->next){
        ADD_STRING("from:%s", sl->text.data);
        ADD_STRING("%s", sl->next ? " " : (pgm->from->next ? "}" : ""));
      }
    }
  
    if(pgm->to){
      STRINGLIST *sl;
      ADD_STRING("%s", pgm->to->next ? " {" : " ");
      for(sl = pgm->to; remain > 0 && sl; sl = sl->next){
        ADD_STRING("to:%s", sl->text.data);
        ADD_STRING("%s", sl->next ? " " : (pgm->to->next ? "}" : ""));
      }
    }
  
    if(pgm->subject){
      STRINGLIST *sl;
      ADD_STRING("%s", pgm->subject->next ? " {" : " ");
      for(sl = pgm->subject; remain > 0 && sl; sl = sl->next){
        ADD_STRING("subject:(%s)", sl->text.data);
        ADD_STRING("%s", sl->next ? " " : (pgm->subject->next ? "}" : ""));
      }
    }
  
    if(pgm->body){
      STRINGLIST *sl;
      ADD_STRING("%s", pgm->body->next ? " {" : " ");
      for(sl = pgm->body; remain > 0 && sl; sl = sl->next){
        ADD_STRING(" %s", sl->text.data);
        ADD_STRING("%s", sl->next ? " " : (pgm->body->next ? "}" : ""));
      }
    }
  
    if (mail_valid_net_parse (stream->mailbox,&mb)){
       p = strchr(mb.mailbox, '/');
       ADD_STRING(" in:%s", p ? p+1 : "inbox");
    }
  
  #endif /* maybe later */
        
    s[0] = '\"';
    strcat(t, "\"");
  
    apgm.type = ATOM; apgm.text = (void *) s;
    args[0] = &apgm;
    args[1] = NIL;
    LOCAL->uidsearch = (flags & SE_UID) ? T : NIL;
    reply = imap_send (stream,cmd,args);
    LOCAL->uidsearch = NIL;
                                  /* do locally if server won't grok */
    if (!strcmp (reply->key,"BAD")) {
       if ((flags & SE_NOLOCAL) ||
          !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER))
        return NIL;
    }
    else if (!imap_OK (stream,reply)) {
        mm_log (reply->text,ERROR);
        return NIL;
    }
  
                                  /* can never pre-fetch with a short cache */
    if ((k = imap_prefetch) && !(flags & (SE_NOPREFETCH | SE_UID)) &&
        !stream->scache) {        /* only if prefetching permitted */
       t = LOCAL->tmp;             /* build sequence in temporary buffer */
      *t = '\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_elt (stream,i)->private.msg.env) {
                                  /* prepend with comma if not first time */
          if (LOCAL->tmp[0]) *t++ = ',';
          sprintf (t,"%lu",j = i);/* output message number */
          t += strlen (t);        /* 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))->searched &&
                 !elt->private.msg.env) i++,k--;
          if (i != j) {           /* if a range */
            sprintf (t,":%lu",i); /* output delimiter and end of range */
            t += strlen (t);      /* point at end of string */
          }
          if ((t - LOCAL->tmp) > (IMAPTMPLEN - 50)) break;
        }
      if (LOCAL->tmp[0]) {        /* anything to pre-fetch? */
        /* pre-fetch envelopes for the first imap_prefetch number of messages */
        if (!imap_OK (stream,reply =
                      imap_fetch (stream,t = cpystr (LOCAL->tmp),FT_NEEDENV +
                                  ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL) +
                                  ((flags & SE_NEEDBODY) ? FT_NEEDBODY : NIL))))
          mm_log (reply->text,ERROR);
        fs_give ((void **) &t);   /* flush copy of sequence */
      }
    }
    return LONGT;
  }
  
  /* IMAP send search program
   * Accepts: MAIL stream
   *	    reply tag
   *	    base pointer if trimming needed
   *	    pointer to current position pointer of output bigbuf
   *	    search program to output
   *	    pointer to limit guideline
   * Returns: error reply or NIL if success
   */
  
  
  IMAPPARSEDREPLY *imap_send_spgm (MAILSTREAM *stream,char *tag,char *base,
  				 char **s,SEARCHPGM *pgm,char *limit)
  {
    IMAPPARSEDREPLY *reply;
    SEARCHHEADER *hdr;
    SEARCHOR *pgo;
    SEARCHPGMLIST *pgl;
    char *t;
  				/* trim if called recursively */
    if (base) *s = imap_send_spgm_trim (base,*s,NIL);
    base = *s;			/* this is the new base */
  				/* default searchpgm */
    for (t = "ALL"; *t; *(*s)++ = *t++);
    if (!pgm) return NIL;		/* done if NIL searchpgm */
    if ((pgm->msgno &&		/* message sequences */
         (pgm->msgno->next ||	/* trim away first:last */
  	(pgm->msgno->first != 1) || (pgm->msgno->last != stream->nmsgs)) &&
         (reply = imap_send_sset (stream,tag,base,s,pgm->msgno," ",limit))) ||
        (pgm->uid &&
         (reply = imap_send_sset (stream,tag,base,s,pgm->uid," UID ",limit))))
      return reply;
  				/* message sizes */
    if (pgm->larger) {
      sprintf (*s," LARGER %lu",pgm->larger);
      *s += strlen (*s);
    }
    if (pgm->smaller) {
      sprintf (*s," SMALLER %lu",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,base,s," KEYWORD ",pgm->keyword,
  				 limit))) ||
        (pgm->unkeyword &&
         (reply = imap_send_slist (stream,tag,base,s," UNKEYWORD ",
  				 pgm->unkeyword,limit))))
      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);
    if (pgm->older) {
      sprintf (*s," OLDER %lu",pgm->older);
      *s += strlen (*s);
    }
    if (pgm->younger) {
      sprintf (*s," YOUNGER %lu",pgm->younger);
      *s += strlen (*s);
    }
  				/* search texts */
    if ((pgm->bcc && (reply = imap_send_slist (stream,tag,base,s," BCC ",
  					     pgm->bcc,limit))) ||
        (pgm->cc && (reply = imap_send_slist (stream,tag,base,s," CC ",pgm->cc,
  					    limit))) ||
        (pgm->from && (reply = imap_send_slist (stream,tag,base,s," FROM ",
  					      pgm->from,limit))) ||
        (pgm->to && (reply = imap_send_slist (stream,tag,base,s," TO ",pgm->to,
  					    limit))))
      return reply;
    if ((pgm->subject && (reply = imap_send_slist (stream,tag,base,s," SUBJECT ",
  						 pgm->subject,limit))) ||
        (pgm->body && (reply = imap_send_slist (stream,tag,base,s," BODY ",
  					      pgm->body,limit))) ||
        (pgm->text && (reply = imap_send_slist (stream,tag,base,s," TEXT ",
  					      pgm->text,limit))))
      return reply;
  
    /* Note that these criteria are not supported by IMAP and have to be
       emulated */
    if ((pgm->return_path &&
         (reply = imap_send_slist (stream,tag,base,s," HEADER Return-Path ",
  				 pgm->return_path,limit))) ||
        (pgm->sender &&
         (reply = imap_send_slist (stream,tag,base,s," HEADER Sender ",
  				 pgm->sender,limit))) ||
        (pgm->reply_to &&
         (reply = imap_send_slist (stream,tag,base,s," HEADER Reply-To ",
  				 pgm->reply_to,limit))) ||
        (pgm->in_reply_to &&
         (reply = imap_send_slist (stream,tag,base,s," HEADER In-Reply-To ",
  				 pgm->in_reply_to,limit))) ||
        (pgm->message_id &&
         (reply = imap_send_slist (stream,tag,base,s," HEADER Message-ID ",
  				 pgm->message_id,limit))) ||
        (pgm->newsgroups &&
         (reply = imap_send_slist (stream,tag,base,s," HEADER Newsgroups ",
  				 pgm->newsgroups,limit))) ||
        (pgm->followup_to &&
         (reply = imap_send_slist (stream,tag,base,s," HEADER Followup-To ",
  				 pgm->followup_to,limit))) ||
        (pgm->references &&
         (reply = imap_send_slist (stream,tag,base,s," HEADER References ",
  				 pgm->references,limit)))) return reply;
  
  				/* all other headers */
    if ((hdr = pgm->header) != NULL) do {
      *s = imap_send_spgm_trim (base,*s," HEADER ");
      if ((reply = imap_send_astring (stream,tag,s,&hdr->line,NIL,limit)) != NULL)
        return reply;
      *(*s)++ = ' ';
      if ((reply = imap_send_astring (stream,tag,s,&hdr->text,NIL,limit)) != NULL)
        return reply;
    } while ((hdr = hdr->next) != NULL);
    for (pgo = pgm->or; pgo; pgo = pgo->next) {
      *s = imap_send_spgm_trim (base,*s," OR (");
      if ((reply = imap_send_spgm (stream,tag,base,s,pgo->first,limit)) != NULL)
        return reply;
      for (t = ") ("; *t; *(*s)++ = *t++);
      if ((reply = imap_send_spgm (stream,tag,base,s,pgo->second,limit)) != NULL)
        return reply;
      *(*s)++ = ')';
    }
    for (pgl = pgm->not; pgl; pgl = pgl->next) {
      *s = imap_send_spgm_trim (base,*s," NOT (");
      if ((reply = imap_send_spgm (stream,tag,base,s,pgl->pgm,limit)) != NULL)
        return reply;
      *(*s)++ = ')';
    }
  				/* trim if needed */
    *s = imap_send_spgm_trim (base,*s,NIL);
    return NIL;			/* search program written OK */
  }
  
  
  /* Write new text and trim extraneous "ALL" from searchpgm
   * Accepts: pointer to start of searchpgm or NIL
   *	    current end pointer
   *	    new text to write or NIL
   * Returns: new end pointer, trimmed if needed
   */
  
  char *imap_send_spgm_trim (char *base,char *s,char *text)
  {
    char *t;
  				/* write new text */
    if (text) for (t = text; *t; *s++ = *t++);
  				/* need to trim? */
    if (base && (s > (t = (base + 4))) && (*base == 'A') && (base[1] == 'L') &&
        (base[2] == 'L') && (base[3] == ' ')) {
      memmove (base,t,s - t);	/* yes, blat down remaining text */
      s -= 4;			/* and reduce current pointer */
    }
    return s;			/* return new end pointer */
  }
  
  /* IMAP send search set
   * Accepts: MAIL stream
   *	    current command tag
   *	    base pointer if trimming needed
   *	    pointer to current position pointer of output bigbuf
   *	    search set to output
   *	    message prefix
   *	    maximum output pointer
   * Returns: NIL if success, error reply if error
   */
  
  IMAPPARSEDREPLY *imap_send_sset (MAILSTREAM *stream,char *tag,char *base,
  				 char **s,SEARCHSET *set,char *prefix,
  				 char *limit)
  {
    IMAPPARSEDREPLY *reply;
    STRING st;
    char c,*t;
    char *start = *s;
  				/* trim and write prefix */
    *s = imap_send_spgm_trim (base,*s,prefix);
  				/* run down search list */
    for (c = NIL; set && (*s < limit); set = set->next, c = ',') {
      if (c) *(*s)++ = c;		/* write delimiter and first value */
      if (set->first == 0xffffffff) *(*s)++ = '*';
      else {
        sprintf (*s,"%lu",set->first);
        *s += strlen (*s);
      }
  				/* have a second value? */
      if (set->last && (set->first != set->last)) {
        *(*s)++ = ':';		/* write delimiter and second value */
        if (set->last == 0xffffffff) *(*s)++ = '*';
        else {
  	sprintf (*s,"%lu",set->last);
  	*s += strlen (*s);
        }
      }
    }
    if (set) {			/* insert "OR" in front of incomplete set */
      memmove (start + 3,start,*s - start);
      memcpy (start," OR",3);
      *s += 3;			/* point to end of buffer */
  				/* write glue that is equivalent to ALL */
      for (t =" ((OR BCC FOO NOT BCC "; *t; *(*s)++ = *t++);
  				/* but broken by a literal */
      INIT (&st,mail_string,(void *) "FOO",3);
      if ((reply = imap_send_literal (stream,tag,s,&st)) != NULL) return reply;
      *(*s)++ = ')';		/* close glue */
      if ((reply = imap_send_sset (stream,tag,NIL,s,set,prefix,limit)) != NULL)
        return reply;
      *(*s)++ = ')';		/* close second OR argument */
    }
    return NIL;
  }
  
  /* IMAP send search list
   * Accepts: MAIL stream
   *	    reply tag
   *	    base pointer if trimming needed
   *	    pointer to current position pointer of output bigbuf
   *	    name of search list
   *	    search list to output
   *	    maximum output pointer
   * Returns: NIL if success, error reply if error
   */
  
  IMAPPARSEDREPLY *imap_send_slist (MAILSTREAM *stream,char *tag,char *base,
  				  char **s,char *name,STRINGLIST *list,
  				  char *limit)
  {
    IMAPPARSEDREPLY *reply;
    do {
      *s = imap_send_spgm_trim (base,*s,name);
      base = NIL;			/* no longer need trimming */
      reply = imap_send_astring (stream,tag,s,&list->text,NIL,limit);
    }
    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';
      mail_dlog (base,LOCAL->sensitive);
    }
    *(*s)++ = '\015';		/* append CRLF */
    *(*s)++ = '\012';
    **s = '\0';
    reply = net_sout (LOCAL->netstream,base,*s - base) ?
      imap_reply (stream,tag) :
        imap_fake (stream,tag,"[CLOSED] IMAP connection broken (command)");
    *s = base;			/* restart buffer */
    return reply;
  }
  
  
  /* IMAP send null-terminated string to sender
   * Accepts: MAIL stream
   *	    string
   * Returns: T if success, else NIL
   */
  
  long imap_soutr (MAILSTREAM *stream,char *string)
  {
    long ret;
    unsigned long i;
    char *s;
    if (stream->debug) mm_dlog (string);
    sprintf (s = (char *) fs_get ((i = strlen (string) + 2) + 1),
  	   "%s\015\012",string);
    ret = net_sout (LOCAL->netstream,s,i);
    fs_give ((void **) &s);
    return ret;
  }
  
  /* 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->netstream) {	/* parse reply from server */
      if ((reply = imap_parse_reply (stream,net_getline (LOCAL->netstream))) != NULL) {
  				/* 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 && !compare_cstring (tag,reply->tag)) return reply;
  				/* report bogon */
  	sprintf (LOCAL->tmp,"Unexpected tagged response: %.80s %.80s %.80s",
  		 (char *) reply->tag,(char *) reply->key,(char *) reply->text);
  	mm_notify (stream,LOCAL->tmp,WARN);
  	stream->unhealthy = T;
        }
      }
    }
    return imap_fake (stream,tag,
  		    "[CLOSED] 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)
  {
    char *r;
    if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
  				/* init fields in case error */
    LOCAL->reply.key = LOCAL->reply.text = LOCAL->reply.tag = NIL;
    if (!(LOCAL->reply.line = text)) {
  				/* NIL text means the stream died */
      if (LOCAL->netstream) net_close (LOCAL->netstream);
      LOCAL->netstream = NIL;
      return NIL;
    }
    if (stream->debug) mm_dlog (LOCAL->reply.line);
    if (!(LOCAL->reply.tag = strtok_r (LOCAL->reply.line," ",&r))) {
      mm_notify (stream,"IMAP server sent a blank line",WARN);
      stream->unhealthy = T;
      return NIL;
    }
  				/* non-continuation replies */
    if (strcmp (LOCAL->reply.tag,"+")) {
  				/* parse key */
      if (!(LOCAL->reply.key = strtok_r (NIL," ",&r))) {
  				/* determine what is missing */
        sprintf (LOCAL->tmp,"Missing IMAP reply key: %.80s",
  	       (char *) LOCAL->reply.tag);
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;
        return NIL;		/* can't parse this text */
      }
      ucase (LOCAL->reply.key);	/* canonicalize key to upper */
  				/* get text as well, allow empty text */
      if (!(LOCAL->reply.text = strtok_r (NIL,"\n",&r)))
        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 = strtok_r (NIL,"\n",&r)))
        LOCAL->reply.text = "";
    }
    return &LOCAL->reply;		/* return parsed reply */
  }
  
  /* IMAP fake reply when stream determined to be dead
   * Accepts: MAIL stream
   *	    tag
   *	    text of fake reply (must start with "[CLOSED]")
   * Returns: parsed reply
   */
  
  IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text)
  {
    mm_notify (stream,text,BYE);	/* send bye alert */
    if (LOCAL->netstream) net_close (LOCAL->netstream);
    LOCAL->netstream = NIL;	/* farewell, dear NET stream... */
  				/* flush previous reply */
    if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
  				/* build new fake reply */
    LOCAL->reply.tag = LOCAL->reply.line = cpystr (tag ? tag : "*");
    LOCAL->reply.key = "NO";
    LOCAL->reply.text = text;
    return &LOCAL->reply;		/* return parsed reply */
  }
  
  
  /* 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)
  {
    long ret = NIL;
  				/* OK - operation succeeded */
    if (!strcmp (reply->key,"OK")) {
      imap_parse_response (stream,reply->text,NIL,NIL);
      ret = T;
    }
  				/* NO - operation failed */
    else if (!strcmp (reply->key,"NO"))
      imap_parse_response (stream,reply->text,WARN,NIL);
    else {			/* BAD - operation rejected */
      if (!strcmp (reply->key,"BAD")) {
        imap_parse_response (stream,reply->text,ERROR,NIL);
        sprintf (LOCAL->tmp,"IMAP protocol error: %.80s",(char *) reply->text);
      }
  				/* bad protocol received */
      else sprintf (LOCAL->tmp,"Unexpected IMAP response: %.80s %.80s",
  		  (char *) reply->key,(char *) reply->text);
      mm_log (LOCAL->tmp,ERROR);	/* either way, this is not good */
    }
    return ret;
  }
  
  /* 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 j,msgno;
    unsigned char *s,*t;
    char *r;
  				/* see if key is a number */
    if (isdigit (*reply->key)) {
      msgno = strtoul (reply->key,(char **) &s,10);
      if (*s) {			/* better be nothing after number */
        sprintf (LOCAL->tmp,"Unexpected untagged message: %.80s",
  	       (char *) reply->key);
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;
        return;
      }
      if (!reply->text) {		/* better be some data */
        mm_notify (stream,"Missing message data",WARN);
        stream->unhealthy = T;
        return;
      }
  				/* get message data type, canonicalize upper */
      s = ucase (strtok_r (reply->text," ",&r));
      t = strtok_r (NIL,"\n",&r);	/* and locate the text after it */
  				/* now take the action */
  				/* change in size of mailbox */
      if (!strcmp (s,"EXISTS") && (msgno >= stream->nmsgs))
        mail_exists (stream,msgno);
      else if (!strcmp (s,"RECENT") && (msgno <= stream->nmsgs))
        mail_recent (stream,msgno);
      else if (!strcmp (s,"EXPUNGE") && msgno && (msgno <= stream->nmsgs)) {
        mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
        MESSAGECACHE *elt = (MESSAGECACHE *) (*mc) (stream,msgno,CH_ELT);
        if (elt) imap_gc_body (elt->private.msg.body);
  				/* notify upper level */
        mail_expunged (stream,msgno);
      }
  
      else if (t && (!strcmp (s,"FETCH") || !strcmp (s,"STORE")) &&
  	     msgno && (msgno <= stream->nmsgs)) {
        char *prop;
        GETS_DATA md;
        ENVELOPE **e;
        MESSAGECACHE *elt = mail_elt (stream,msgno);
        ENVELOPE *env = NIL;
        imapenvelope_t ie =
  	(imapenvelope_t) mail_parameters (stream,GET_IMAPENVELOPE,NIL);
        ++t;			/* skip past open parenthesis */
  				/* parse Lisp-form property list */
        while ((prop = (strtok_r (t," )",&r))) && (t = strtok_r (NIL,"\n",&r))) {
  	INIT_GETS (md,stream,elt->msgno,NIL,0,0);
  	e = NIL;		/* not pointing at any envelope yet */
  				/* canonicalize property, parse it */
  	if (!strcmp (ucase (prop),"FLAGS")) imap_parse_flags (stream,elt,&t);
  	else if (!strcmp (prop,"INTERNALDATE") &&
  		 (s = imap_parse_string (stream,&t,reply,NIL,NIL,LONGT))) {
  	  if (!mail_parse_date (elt,s)) {
  	    sprintf (LOCAL->tmp,"Bogus date: %.80s",(char *) s);
  	    mm_notify (stream,LOCAL->tmp,WARN);
  	    stream->unhealthy = T;
  				/* slam in default so we don't try again */
  	    mail_parse_date (elt,"01-Jan-1970 00:00:00 +0000");
  	  }
  	  fs_give ((void **) &s);
  	}
  				/* unique identifier */
  	else if (!strcmp (prop,"UID")) {
  	  LOCAL->lastuid.uid = elt->private.uid = strtoul (t,(char **) &t,10);
  	  LOCAL->lastuid.msgno = elt->msgno;
  	}
  	else if (!strcmp (prop,"ENVELOPE")) {
  	  if (stream->scache) {	/* short cache, flush old stuff */
  	    mail_free_body (&stream->body);
  	    stream->msgno = elt->msgno;
  	    e = &stream->env;	/* get pointer to envelope */
  	  }
  	  else e = &elt->private.msg.env;
  	  imap_parse_envelope (stream,e,&t,reply);
  	}
  	else if (!strncmp (prop,"BODY",4)) {
  	  if (!prop[4] || !strcmp (prop+4,"STRUCTURE")) {
  	    BODY **body;
  	    if (stream->scache){/* short cache, flush old stuff */
  	      if (stream->msgno != msgno) {
  		mail_free_envelope (&stream->env);
  		sprintf (LOCAL->tmp,"Body received for %lu but current is %lu",
  			 msgno,stream->msgno);
  		stream->msgno = msgno;
  	      }
  				/* get pointer to body */
  	      body = &stream->body;
  	    }
  	    else body = &elt->private.msg.body;
  				/* flush any prior body */
  	    mail_free_body (body);
  				/* instantiate and parse a new body */
  	    imap_parse_body_structure (stream,*body = mail_newbody(),&t,reply);
  	  }
  
  	  else if (prop[4] == '[') {
  	    STRINGLIST *stl = NIL;
  	    SIZEDTEXT text;
  				/* will want to return envelope data */
  	    if (!strcmp (md.what = cpystr (prop + 5),"HEADER]") ||
  		!strcmp (md.what,"0]"))
  	      e = stream->scache ? &stream->env : &elt->private.msg.env;
  	    LOCAL->tmp[0] ='\0';/* no errors yet */
  				/* found end of section? */
  	    if (!(s = strchr (md.what,']'))) {
  				/* skip leading nesting */
  	      for (s = md.what; *s && (isdigit (*s) || (*s == '.')); s++);
  				/* better be one of these */
  	      if (strncmp (s,"HEADER.FIELDS",13) &&
  		  (!s[13] || strcmp (s+13,".NOT")))
  		sprintf (LOCAL->tmp,"Unterminated section: %.80s",md.what);
  				/* get list of headers */
  	      else if (!(stl = imap_parse_stringlist (stream,&t,reply)))
  		sprintf (LOCAL->tmp,"Bogus header field list: %.80s",
  			 (char *) t);
  	      else if (*t != ']')
  		sprintf (LOCAL->tmp,"Unterminated header section: %.80s",
  			 (char *) t);
  				/* point after the text */
  	      else if ((t = strchr (s = t,' ')) != NULL) *t++ = '\0';
  	    }
  	    if (s && !LOCAL->tmp[0]) {
  	      *s++ = '\0';	/* tie off section specifier */
  	      if (*s == '<') {	/* partial specifier? */
  		md.first = strtoul (s+1,(char **) &s,10) + 1;
  		if (*s++ != '>')	/* make sure properly terminated */
  		  sprintf (LOCAL->tmp,"Unterminated partial data: %.80s",
  			   (char *) s-1);
  	      }
  	      if (!LOCAL->tmp[0] && *s)
  		sprintf (LOCAL->tmp,"Junk after section: %.80s",(char *) s);
  	    }
  	    if (LOCAL->tmp[0]) { /* got any errors? */
  	      mm_notify (stream,LOCAL->tmp,WARN);
  	      stream->unhealthy = T;
  	      mail_free_stringlist (&stl);
  	    }
  	    else {		/* parse text from server */
  	      text.data = (unsigned char *)
  		imap_parse_string (stream,&t,reply,
  				   ((md.what[0] && (md.what[0] != 'H')) ||
  				    md.first || md.last) ? &md : NIL,
  				   &text.size,NIL);
  				/* all done if partial */
  	      if (md.first || md.last) mail_free_stringlist (&stl);
  				/* otherwise register it in the cache */
  	      else imap_cache (stream,msgno,md.what,stl,&text);
  	    }
  	    fs_give ((void **) &md.what);
  	  }
  	  else {
  	    sprintf (LOCAL->tmp,"Unknown body message property: %.80s",prop);
  	    mm_notify (stream,LOCAL->tmp,WARN);
  	    stream->unhealthy = T;
  	  }
  	}
  
  				/* one of the RFC822 props? */
  	else if (!strncmp (prop,"RFC822",6) && (!prop[6] || (prop[6] == '.'))){
  	  SIZEDTEXT text;
  	  if (!prop[6]) {	/* cache full message */
  	    md.what = "";
  	    text.data = (unsigned char *)
  	      imap_parse_string (stream,&t,reply,&md,&text.size,NIL);
  	    imap_cache (stream,msgno,md.what,NIL,&text);
  	  }
  	  else if (!strcmp (prop+7,"SIZE"))
  	    elt->rfc822_size = strtoul (t,(char **) &t,10);
  				/* legacy properties */
  	  else if (!strcmp (prop+7,"HEADER")) {
  	    text.data = (unsigned char *)
  	      imap_parse_string (stream,&t,reply,NIL,&text.size,NIL);
  	    imap_cache (stream,msgno,"HEADER",NIL,&text);
  	    e = stream->scache ? &stream->env : &elt->private.msg.env;
  	  }
  	  else if (!strcmp (prop+7,"TEXT")) {
  	    md.what = "TEXT";
  	    text.data = (unsigned char *)
  	      imap_parse_string (stream,&t,reply,&md,&text.size,NIL);
  	    imap_cache (stream,msgno,md.what,NIL,&text);
  	  }
  	  else {
  	    sprintf (LOCAL->tmp,"Unknown RFC822 message property: %.80s",prop);
  	    mm_notify (stream,LOCAL->tmp,WARN);
  	    stream->unhealthy = T;
  	  }
  	}
  	else {
  	  sprintf (LOCAL->tmp,"Unknown message property: %.80s",prop);
  	  mm_notify (stream,LOCAL->tmp,WARN);
  	  stream->unhealthy = T;
  	}
  	if (e && *e) env = *e;	/* note envelope if we got one */
        }
        if (prop) {
  	sprintf (LOCAL->tmp,"Missing data for property: %.80s",prop);
  	mm_notify (stream,LOCAL->tmp,WARN);
  	stream->unhealthy = T;
        }
  				/* do callback if requested */
        if (ie && env) (*ie) (stream,msgno,env);
      }
  				/* obsolete response to COPY */
      else if (strcmp (s,"COPY")) {
        sprintf (LOCAL->tmp,"Unknown message data: %lu %.80s",msgno,(char *) s);
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;
      }
    }
  
    else if (!strcmp (reply->key,"FLAGS") && reply->text &&
  	   (*reply->text == '(') &&
  	   (s = strtok_r (reply->text+1," )",&r)))
      do if (*s != '\\') {
        for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i] &&
  	     compare_cstring (s,stream->user_flags[i]); i++);
        if (i > NUSERFLAGS) {
  	sprintf (LOCAL->tmp,"Too many server flags, discarding: %.80s",
  		 (char *) s);
  	mm_notify (stream,LOCAL->tmp,WARN);
        }
        else if (!stream->user_flags[i]) stream->user_flags[i++] = cpystr (s);
      }
      while ((s = strtok_r (NIL," )",&r)) != NULL);
    else if (!strcmp (reply->key,"SEARCH")) {
  				/* only do something if have text */
      if (reply->text && (t = strtok_r (reply->text," ",&r))) do
        if ((i = strtoul (t,NIL,10)) != 0L) {
  				/* UIDs always passed to main program */
  	if (LOCAL->uidsearch) mm_searched (stream,i);
  				/* should be a msgno then */
  	else if ((i <= stream->nmsgs) &&
  		 (!LOCAL->filter || mail_elt (stream,i)->private.filter)) {
  	  mail_elt (stream,i)->searched = T;
  	  if (!stream->silent) mm_searched (stream,i);
  	}
        } while ((t = strtok_r (NIL," ",&r)) != NULL);
    }
    else if (!strcmp (reply->key,"SORT")) {
      sortresults_t sr = (sortresults_t)
        mail_parameters (NIL,GET_SORTRESULTS,NIL);
      LOCAL->sortsize = 0;	/* initialize sort data */
      if (LOCAL->sortdata) fs_give ((void **) &LOCAL->sortdata);
      LOCAL->sortdata = (unsigned long *)
        fs_get ((stream->nmsgs + 1) * sizeof (unsigned long));
  				/* only do something if have text */
      if (reply->text && (t = strtok_r (reply->text," ",&r))) {
        do if ((i = atol (t)) && (LOCAL->filter ?
  				mail_elt (stream,i)->searched : T))
  	LOCAL->sortdata[LOCAL->sortsize++] = i;
        while ((t = strtok_r (NIL," ",&r)) && (LOCAL->sortsize < stream->nmsgs));
      }
      LOCAL->sortdata[LOCAL->sortsize] = 0;
  				/* also return via callback if requested */
      if (sr) (*sr) (stream,LOCAL->sortdata,LOCAL->sortsize);
    }
    else if (!strcmp (reply->key,"THREAD")) {
      threadresults_t tr = (threadresults_t)
        mail_parameters (NIL,GET_THREADRESULTS,NIL);
      if (LOCAL->threaddata) mail_free_threadnode (&LOCAL->threaddata);
      if ((s = reply->text) != NULL) {
        LOCAL->threaddata = imap_parse_thread (stream,&s);
        if (tr) (*tr) (stream,LOCAL->threaddata);
        if (s && *s) {
  	sprintf (LOCAL->tmp,"Junk at end of thread: %.80s",(char *) s);
  	mm_notify (stream,LOCAL->tmp,WARN);
  	stream->unhealthy = T;
        }
      }
    }
  
    else if (!strcmp (reply->key,"STATUS") && reply->text) {
      MAILSTATUS status;
      unsigned char *txt = reply->text;
      if ((t = imap_parse_astring (stream,&txt,reply,&j)) && txt &&
  	(*txt++ == ' ') && (*txt++ == '(') && (s = strchr (txt,')')) &&
  	(s - txt)) {
        *s = '\0';		/* tie off status data */
  				/* initialize data block */
        status.flags = status.messages = status.recent = status.unseen =
  	status.uidnext = status.uidvalidity = 0;
        while (*txt && (s = strchr (txt,' '))) {
  	*s++ = '\0';		/* tie off status attribute name */
  				/* get attribute value */
  	i = strtoul (s,(char **) &s,10);
  	if (!compare_cstring (txt,"MESSAGES")) {
  	  status.flags |= SA_MESSAGES;
  	  status.messages = i;
  	}
  	else if (!compare_cstring (txt,"RECENT")) {
  	  status.flags |= SA_RECENT;
  	  status.recent = i;
  	}
  	else if (!compare_cstring (txt,"UNSEEN")) {
  	  status.flags |= SA_UNSEEN;
  	  status.unseen = i;
  	}
  	else if (!compare_cstring (txt,"UIDNEXT")) {
  	  status.flags |= SA_UIDNEXT;
  	  status.uidnext = i;
  	}
  	else if (!compare_cstring (txt,"UIDVALIDITY")) {
  	  status.flags |= SA_UIDVALIDITY;
  	  status.uidvalidity = i;
  	}
  				/* next attribute */
  	txt = (*s == ' ') ? s + 1 : s;
        }
        if (((i = 1 + strchr (stream->mailbox,'}') - stream->mailbox) + j) <
  	  IMAPTMPLEN) {
  	strcpy (strncpy (LOCAL->tmp,stream->mailbox,i) + i,t);
  				/* pass status to main program */
  	mm_status (stream,LOCAL->tmp,&status);
        }
      }
      if (t) fs_give ((void **) &t);
    }
  
    else if ((!strcmp (reply->key,"LIST") || !strcmp (reply->key,"LSUB")) &&
  	   reply->text && (*reply->text == '(') &&
  	   (s = strchr (reply->text,')')) && (s[1] == ' ')) {
      char delimiter = '\0';
      *s++ = '\0';		/* tie off attribute list */
  				/* parse attribute list */
      if ((t = strtok_r (reply->text+1," ",&r)) != NULL) do {
        if (!compare_cstring (t,"\\NoInferiors")) i |= LATT_NOINFERIORS;
        else if (!compare_cstring (t,"\\NoSelect")) i |= LATT_NOSELECT;
        else if (!compare_cstring (t,"\\Marked")) i |= LATT_MARKED;
        else if (!compare_cstring (t,"\\Unmarked")) i |= LATT_UNMARKED;
        else if (!compare_cstring (t,"\\HasChildren")) i |= LATT_HASCHILDREN;
        else if (!compare_cstring (t,"\\HasNoChildren")) i |= LATT_HASNOCHILDREN;
        else if (!compare_cstring (t,"\\All")) i |= LATT_ALL;
        else if (!compare_cstring (t,"\\Archive")) i |= LATT_ARCHIVE;
        else if (!compare_cstring (t,"\\Drafts")) i |= LATT_DRAFTS;
        else if (!compare_cstring (t,"\\Flagged")) i |= LATT_FLAGGED;
        else if (!compare_cstring (t,"\\Junk")) i |= LATT_JUNK;
        else if (!compare_cstring (t,"\\Sent")) i |= LATT_SENT;
        else if (!compare_cstring (t,"\\Trash")) i |= LATT_TRASH;
  				/* ignore extension flags */
      }
      while ((t = strtok_r (NIL," ",&r)) != NULL);
      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> */
      }
  				/* parse the mailbox name */
      if ((t = imap_parse_astring (stream,&s,reply,&j)) != NULL) {
  				/* prepend prefix if requested */
        if (LOCAL->prefix && ((strlen (LOCAL->prefix) + j) < IMAPTMPLEN))
  	sprintf (s = LOCAL->tmp,"%s%s",LOCAL->prefix,(char *) t);
        else s = t;		/* otherwise just mailbox name */
  				/* pass data to main program */
        if (reply->key[1] == 'S') mm_lsub (stream,delimiter,s,i);
        else mm_list (stream,delimiter,s,i);
        fs_give ((void **) &t);	/* flush mailbox name */
      }
    }
    else if (!strcmp (reply->key,"NAMESPACE")) {
      if (LOCAL->namespace) {
        mail_free_namespace (&LOCAL->namespace[0]);
        mail_free_namespace (&LOCAL->namespace[1]);
        mail_free_namespace (&LOCAL->namespace[2]);
      }
      else LOCAL->namespace = (NAMESPACE **) fs_get (3 * sizeof (NAMESPACE *));
      if ((s = reply->text) != NULL) {	/* parse namespace results */
        LOCAL->namespace[0] = imap_parse_namespace (stream,&s,reply);
        LOCAL->namespace[1] = imap_parse_namespace (stream,&s,reply);
        LOCAL->namespace[2] = imap_parse_namespace (stream,&s,reply);
        if (s && *s) {
  	sprintf (LOCAL->tmp,"Junk after namespace list: %.80s",(char *) s);
  	mm_notify (stream,LOCAL->tmp,WARN);
  	stream->unhealthy = T;
        }
      }
      else {
        mm_notify (stream,"Missing namespace list",WARN);
        stream->unhealthy = T;
      }
    }
  
    else if (!strcmp (reply->key,"ACL") && (s = reply->text) &&
  	   (t = imap_parse_astring (stream,&s,reply,NIL))) {
      getacl_t ar = (getacl_t) mail_parameters (NIL,GET_ACL,NIL);
      if (s && (*s++ == ' ')) {
        ACLLIST *al = mail_newacllist ();
        ACLLIST *ac = al;
        do if ((ac->identifier = imap_parse_astring (stream,&s,reply,NIL)) &&
  	     s && (*s++ == ' '))
  	ac->rights = imap_parse_astring (stream,&s,reply,NIL);
        while (ac->rights && s && (*s == ' ') && s++ &&
  	     (ac = ac->next = mail_newacllist ()));
        if (!ac->rights || (s && *s)) {
  	sprintf (LOCAL->tmp,"Invalid ACL identifer/rights for %.80s",
  		 (char *) t);
  	mm_notify (stream,LOCAL->tmp,WARN);
  	stream->unhealthy = T;
        }
        else if (ar) (*ar) (stream,t,al);
        mail_free_acllist (&al);	/* clean up */
      }
  				/* no optional rights */
      else if (ar) (*ar) (stream,t,NIL);
      fs_give ((void **) &t);	/* free mailbox name */
    }
  
    else if (!strcmp (reply->key,"LISTRIGHTS") && (s = reply->text) &&
  	   (t = imap_parse_astring (stream,&s,reply,NIL))) {
      listrights_t lr = (listrights_t) mail_parameters (NIL,GET_LISTRIGHTS,NIL);
      char *id,*r;
      if (s && (*s++ == ' ') && (id = imap_parse_astring (stream,&s,reply,NIL))){
        if (s && (*s++ == ' ') &&
  	  (r = imap_parse_astring (stream,&s,reply,NIL))) {
  	if (s && (*s++ == ' ')) {
  	  STRINGLIST *rl = mail_newstringlist ();
  	  STRINGLIST *rc = rl;
  	  do rc->text.data = (unsigned char *)
  	    imap_parse_astring (stream,&s,reply,&rc->text.size);
  	  while (rc->text.data && s && (*s == ' ') && s++ &&
  		 (rc = rc->next = mail_newstringlist ()));
  	  if (!rc->text.data || (s && *s)) {
  	    sprintf (LOCAL->tmp,"Invalid optional LISTRIGHTS for %.80s",
  		     (char *) t);
  	    mm_notify (stream,LOCAL->tmp,WARN);
  	    stream->unhealthy = T;
  	  }
  	  else if (lr) (*lr) (stream,t,id,r,rl);
  				/* clean up */
  	  mail_free_stringlist (&rl);
  	}
  				/* no optional rights */
  	else if (lr) (*lr) (stream,t,id,r,NIL);
  	fs_give ((void **) &r);	/* free rights */
        }
        else {
  	sprintf (LOCAL->tmp,"Missing LISTRIGHTS rights for %.80s",(char *) t);
  	mm_notify (stream,LOCAL->tmp,WARN);
  	stream->unhealthy = T;
        }
        fs_give ((void **) &id);	/* free identifier */
      }
      else {
        sprintf (LOCAL->tmp,"Missing LISTRIGHTS identifer for %.80s",(char *) t);
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;
      }
      fs_give ((void **) &t);	/* free mailbox name */
    }
  
    else if (!strcmp (reply->key,"MYRIGHTS") && (s = reply->text) &&
  	   (t = imap_parse_astring (stream,&s,reply,NIL))) {
      myrights_t mr = (myrights_t) mail_parameters (NIL,GET_MYRIGHTS,NIL);
      char *r;
      if (s && (*s++ == ' ') && (r = imap_parse_astring (stream,&s,reply,NIL))) {
        if (s && *s) {
  	sprintf (LOCAL->tmp,"Junk after MYRIGHTS for %.80s",(char *) t);
  	mm_notify (stream,LOCAL->tmp,WARN);
  	stream->unhealthy = T;
        }
        else if (mr) (*mr) (stream,t,r);
        fs_give ((void **) &r);	/* free rights */
      }
      else {
        sprintf (LOCAL->tmp,"Missing MYRIGHTS for %.80s",(char *) t);
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;
      }
      fs_give ((void **) &t);	/* free mailbox name */
    }
  
  				/* this response has a bizarre syntax! */
    else if (!strcmp (reply->key,"QUOTA") && (s = reply->text) &&
  	   (t = imap_parse_astring (stream,&s,reply,NIL))) {
  				/* in case error */
      sprintf (LOCAL->tmp,"Bad quota resource list for %.80s",(char *) t);
      if (s && (*s++ == ' ') && (*s++ == '(') && *s && ((*s != ')') || !s[1])) {
        quota_t qt = (quota_t) mail_parameters (NIL,GET_QUOTA,NIL);
        QUOTALIST *ql = NIL;
        QUOTALIST *qc;
  				/* parse non-empty quota resource list */
        if (*s != ')') for (ql = qc = mail_newquotalist (); T;
  			  qc = qc->next = mail_newquotalist ()) {
  	if ((qc->name = imap_parse_astring (stream,&s,reply,NIL)) && s &&
  	    (*s++ == ' ') && (isdigit (*s) || (LOCAL->loser && (*s == '-')))) {
  	  if (isdigit (*s)) qc->usage = strtoul (s,(char **) &s,10);
  	  else if ((t = strchr (s,' ')) != NULL) t = s;
  	  if ((*s++ == ' ') && (isdigit (*s) || (LOCAL->loser &&(*s == '-')))){
  	    if (isdigit (*s)) qc->limit = strtoul (s,(char **) &s,10);
  	    else if ((t = strpbrk (s," )")) != NULL) t = s;
  				/* another resource follows? */
  	    if (*s == ' ') continue;
  				/* end of resource list? */
  	    if ((*s == ')') && !s[1]) {
  	      if (qt) (*qt) (stream,t,ql);
  	      break;		/* all done */
  	    }
  	  }
  	}
  				/* something bad happened */
  	mm_notify (stream,LOCAL->tmp,WARN);
  	stream->unhealthy = T;
  	break;			/* parse failed */
        }
  				/* all done with quota resource list now */
        if (ql) mail_free_quotalist (&ql);
      }
      else {
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;
      }
      fs_give ((void **) &t);	/* free root name */
    }
    else if (!strcmp (reply->key,"ID") && (s = reply->text)){
  	if(compare_cstring (s,"NIL")) LOCAL->id = imap_parse_idlist(s);
    }
    else if (!strcmp (reply->key,"QUOTAROOT") && (s = reply->text) &&
  	   (t = imap_parse_astring (stream,&s,reply,NIL))) {
      sprintf (LOCAL->tmp,"Bad quota root list for %.80s",(char *) t);
      if (s && (*s++ == ' ')) {
        quotaroot_t qr = (quotaroot_t) mail_parameters (NIL,GET_QUOTAROOT,NIL);
        STRINGLIST *rl = mail_newstringlist ();
        STRINGLIST *rc = rl;
        do rc->text.data = (unsigned char *)
  	imap_parse_astring (stream,&s,reply,&rc->text.size);
        while (rc->text.data && *s && (*s++ == ' ') &&
  	       (rc = rc->next = mail_newstringlist ()));
        if (!rc->text.data || (s && *s)) {
  	mm_notify (stream,LOCAL->tmp,WARN);
  	stream->unhealthy = T;
        }
        else if (qr) (*qr) (stream,t,rl);
  				/* clean up */
        mail_free_stringlist (&rl);
      }
      else {
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;
      }
      fs_give ((void **) &t);
    }
  
    else if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH"))
      imap_parse_response (stream,reply->text,NIL,T);
    else if (!strcmp (reply->key,"NO"))
      imap_parse_response (stream,reply->text,WARN,T);
    else if (!strcmp (reply->key,"BAD"))
      imap_parse_response (stream,reply->text,ERROR,T);
    else if (!strcmp (reply->key,"BYE")) {
      LOCAL->byeseen = T;		/* note that a BYE seen */
      imap_parse_response (stream,reply->text,BYE,T);
    }
    else if (!strcmp (reply->key,"CAPABILITY") && reply->text)
      imap_parse_capabilities (stream,reply->text);
    else if (!strcmp (reply->key,"MAILBOX") && reply->text) {
      if (LOCAL->prefix &&
  	((strlen (LOCAL->prefix) + strlen (reply->text)) < IMAPTMPLEN))
        sprintf (t = LOCAL->tmp,"%s%s",LOCAL->prefix,(char *) reply->text);
      else t = reply->text;
      mm_list (stream,NIL,t,NIL);
    }
    else {
      sprintf (LOCAL->tmp,"Unexpected untagged message: %.80s",
  	     (char *) reply->key);
      mm_notify (stream,LOCAL->tmp,WARN);
      stream->unhealthy = T;
    }
  }
  
  /* Parse human-readable response text
   * Accepts: mail stream
   *	    text
   *	    error level for mm_notify()
   *	    non-NIL if want mm_notify() event even if no response code
   */
  
  void imap_parse_response (MAILSTREAM *stream,char *text,long errflg,long ntfy)
  {
    char *s,*t,*r;
    size_t i;
    unsigned long j;
    MESSAGECACHE *elt;
    copyuid_t cu;
    appenduid_t au;
    SEARCHSET *source = NIL;
    SEARCHSET *dest = NIL;
    if (text && (*text == '[') && (t = strchr (s = text + 1,']')) &&
        ((i = t - s) < IMAPTMPLEN)) {
      LOCAL->tmp[i] = '\0';	/* make mungable copy of text code */
      if ((s = strchr (strncpy (t = LOCAL->tmp,s,i),' ')) != NULL) *s++ = '\0';
      if (s) {			/* have argument? */
        ntfy = NIL;		/* suppress mm_notify if normal SELECT data */
        if (!compare_cstring (t,"CAPABILITY")) imap_parse_capabilities(stream,s);
        else if (!compare_cstring (t,"PERMANENTFLAGS") && (*s == '(') &&
  	       (t[i-1] == ')')) {
  	t[i-1] = '\0';		/* tie off flags */
  	stream->perm_seen = stream->perm_deleted = stream->perm_answered =
  	  stream->perm_draft = stream->kwd_create = NIL;
  	stream->perm_user_flags = NIL;
  	if ((s = strtok_r (s+1," ",&r)) != NULL) do {
  	  if (*s == '\\') {	/* system flags */
  	    if (!compare_cstring (s,"\\Seen")) stream->perm_seen = T;
  	    else if (!compare_cstring (s,"\\Deleted"))
  	      stream->perm_deleted = T;
  	    else if (!compare_cstring (s,"\\Flagged"))
  	      stream->perm_flagged = T;
  	    else if (!compare_cstring (s,"\\Answered"))
  	      stream->perm_answered = T;
  	    else if (!compare_cstring (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_r (NIL," ",&r)) != NULL);
        }
  
        else if (!compare_cstring (t,"UIDVALIDITY") && (j = strtoul (s,NIL,10))){
  				/* do this in separate if because of ntfy */
  	if (j != stream->uid_validity) {
  	  mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
  	  stream->uid_validity = j;
  				/* purge any UIDs in cache */
  	  for (j = 1; j <= stream->nmsgs; j++)
  	    if ((elt = (MESSAGECACHE *) (*mc) (stream,j,CH_ELT)) != NULL)
  	      elt->private.uid = 0;
  	}
        }
        else if (!compare_cstring (t,"UIDNEXT"))
  	stream->uid_last = strtoul (s,NIL,10) - 1;
        else if ((j = LEVELUIDPLUS (stream) && LOCAL->appendmailbox) &&
  	       !compare_cstring (t,"COPYUID") &&
  	       (cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL)) &&
  	       isdigit (*s) && (j = strtoul (s,&s,10)) && (*s++ == ' ') &&
  	       (source = mail_parse_set (s,&s)) && (*s++ == ' ') &&
  	       (dest = mail_parse_set (s,&s)) && !*s)
  	(*cu) (stream,LOCAL->appendmailbox,j,source,dest);
        else if (j && !compare_cstring (t,"APPENDUID") &&
  	       (au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL)) &&
  	       isdigit (*s) && (j = strtoul (s,&s,10)) && (*s++ == ' ') &&
  	       (dest = mail_parse_set (s,&s)) && !*s)
  	(*au) (LOCAL->appendmailbox,j,dest);
        else {			/* all other response code events */
  	ntfy = T;		/* must mm_notify() */
  	if (!compare_cstring (t,"REFERRAL"))
  	  LOCAL->referral = cpystr (t + 9);
        }
        mail_free_searchset (&source);
        mail_free_searchset (&dest);
      }
      else {			/* no arguments */
        if (!compare_cstring (t,"UIDNOTSTICKY")) {
  	ntfy = NIL;
  	stream->uid_nosticky = T;
        }
        else if (!compare_cstring (t,"READ-ONLY")) stream->rdonly = T;
        else if (!compare_cstring (t,"READ-WRITE"))
  	stream->rdonly = NIL;
        else if (!compare_cstring (t,"PARSE") && !errflg)
  	errflg = PARSE;
      }
    }
  				/* give event to main program */
    if (ntfy && !stream->silent) mm_notify (stream,text ? text : "",errflg);
  }
  
  /* Parse a namespace
   * Accepts: mail stream
   *	    current text pointer
   *	    parsed reply
   * Returns: namespace list, text pointer updated
   */
  
  NAMESPACE *imap_parse_namespace (MAILSTREAM *stream,unsigned char **txtptr,
  				 IMAPPARSEDREPLY *reply)
  {
    NAMESPACE *ret = NIL;
    NAMESPACE *nam = NIL;
    NAMESPACE *prev = NIL;
    PARAMETER *par = NIL;
    if (*txtptr) {		/* only if argument given */
  				/* ignore leading space */
      while (**txtptr == ' ') ++*txtptr;
      switch (**txtptr) {
      case 'N':			/* if NIL */
      case 'n':
        ++*txtptr;		/* bump past "N" */
        ++*txtptr;		/* bump past "I" */
        ++*txtptr;		/* bump past "L" */
        break;
      case '(':
        ++*txtptr;		/* skip past open paren */
        while (**txtptr == '(') {
  	++*txtptr;		/* skip past open paren */
  	prev = nam;		/* note previous if any */
  	nam = (NAMESPACE *) memset (fs_get (sizeof (NAMESPACE)),0,
  				  sizeof (NAMESPACE));
  	if (!ret) ret = nam;	/* if first time note first namespace */
  				/* if previous link new block to it */
  	if (prev) prev->next = nam;
  	nam->name = imap_parse_string (stream,txtptr,reply,NIL,NIL,NIL);
  				/* ignore whitespace */
  	while (**txtptr == ' ') ++*txtptr;
  	switch (**txtptr) {	/* parse delimiter */
  	case 'N':
  	case 'n':
  	  *txtptr += 3;		/* bump past "NIL" */
  	  break;
  	case '"':
  	  if (*++*txtptr == '\\') nam->delimiter = *++*txtptr;
  	  else nam->delimiter = **txtptr;
  	  *txtptr += 2;		/* bump past character and closing quote */
  	  break;
  	default:
  	  sprintf (LOCAL->tmp,"Missing delimiter in namespace: %.80s",
  		   (char *) *txtptr);
  	  mm_notify (stream,LOCAL->tmp,WARN);
  	  stream->unhealthy = T;
  	  *txtptr = NIL;	/* stop parse */
  	  return ret;
  	}
  
  	while (**txtptr == ' '){/* append new parameter to tail */
  	  if (nam->param) par = par->next = mail_newbody_parameter ();
  	  else nam->param = par = mail_newbody_parameter ();
  	  if (!(par->attribute = imap_parse_string (stream,txtptr,reply,NIL,
  						    NIL,NIL))) {
  	    mm_notify (stream,"Missing namespace extension attribute",WARN);
  	    stream->unhealthy = T;
  	    par->attribute = cpystr ("UNKNOWN");
  	  }
  				/* skip space */
  	  while (**txtptr == ' ') ++*txtptr;
  	  if (**txtptr == '(') {/* have value list?  */
  	    char *att = par->attribute;
  	    ++*txtptr;		/* yes */
  	    do {		/* parse each value */
  	      if (!(par->value = imap_parse_string (stream,txtptr,reply,NIL,
  						    NIL,LONGT))) {
  		sprintf (LOCAL->tmp,
  			 "Missing value for namespace attribute %.80s",att);
  		mm_notify (stream,LOCAL->tmp,WARN);
  		stream->unhealthy = T;
  		par->value = cpystr ("UNKNOWN");
  	      }
  				/* is there another value? */
  	      if (**txtptr == ' ') par = par->next = mail_newbody_parameter ();
  	    } while (!par->value);
  	  }
  	  else {
  	    sprintf (LOCAL->tmp,"Missing values for namespace attribute %.80s",
  		     par->attribute);
  	    mm_notify (stream,LOCAL->tmp,WARN);
  	    stream->unhealthy = T;
  	    par->value = cpystr ("UNKNOWN");
  	  }
  	}
  	if (**txtptr == ')') ++*txtptr;
  	else {			/* missing trailing paren */
  	  sprintf (LOCAL->tmp,"Junk at end of namespace: %.80s",
  		   (char *) *txtptr);
  	  mm_notify (stream,LOCAL->tmp,WARN);
  	  stream->unhealthy = T;
  	  return ret;
  	}
        }
        if (**txtptr == ')') {	/* expected trailing paren? */
  	++*txtptr;		/* got it! */
  	break;
        }
      default:
        sprintf (LOCAL->tmp,"Not a namespace: %.80s",(char *) *txtptr);
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;
        *txtptr = NIL;		/* stop parse now */
        break;
      }
    }
    return ret;
  }
  
  /* Parse a thread node list
   * Accepts: mail stream
   *	    current text pointer
   * Returns: thread node list, text pointer updated
   */
  
  THREADNODE *imap_parse_thread (MAILSTREAM *stream,unsigned char **txtptr)
  {
    char *s;
    THREADNODE *ret = NIL;	/* returned tree */
    THREADNODE *last = NIL;	/* last branch in this tree */
    THREADNODE *parent = NIL;	/* parent of current node */
    THREADNODE *cur;		/* current node */
    while (**txtptr == '(') {	/* see a thread? */
      ++*txtptr;			/* skip past open paren */
      while (**txtptr != ')') {	/* parse thread */
        if (**txtptr == '(') {	/* thread branch */
  	cur = imap_parse_thread (stream,txtptr);
  				/* add to parent */
  	if (parent) parent = parent->next = cur;
  	else {			/* no parent, create dummy */
  	  if (last) last = last->branch = mail_newthreadnode (NIL);
  				/* new tree */
  	  else ret = last = mail_newthreadnode (NIL);
  				/* add to dummy parent */
  	  last->next = parent = cur;
  	}
        }
  				/* threaded message number */
        else if (isdigit (*(s = *txtptr)) &&
  	       ((cur = mail_newthreadnode (NIL))->num =
  		strtoul (*txtptr,(char **) txtptr,10))) {
  	if (LOCAL->filter && !mail_elt (stream,cur->num)->searched)
  	  cur->num = NIL;	/* make dummy if filtering and not searched */
  				/* add to parent */
  	if (parent) parent = parent->next = cur;
  				/* no parent, start new thread */
  	else if (last) last = last->branch = parent = cur;
  				/* create new tree */
  	else ret = last = parent = cur;
        }
        else {			/* anything else is a bogon */
  	char tmp[MAILTMPLEN];
  	sprintf (tmp,"Bogus thread member: %.80s",s);
  	mm_notify (stream,tmp,WARN);
  	stream->unhealthy = T;
  	return ret;
        }
  				/* skip past any space */
        if (**txtptr == ' ') ++*txtptr;
      }
      ++*txtptr;			/* skip pase end of thread */
      parent = NIL;		/* close this thread */
    }
    return ret;			/* return parsed thread */
  }
  
  /* Parse RFC822 message header
   * Accepts: MAIL stream
   *	    envelope to parse into
   *	    header as sized text
   *	    stringlist if partial header
   */
  
  void imap_parse_header (MAILSTREAM *stream,ENVELOPE **env,SIZEDTEXT *hdr,
  			STRINGLIST *stl)
  {
    ENVELOPE *nenv;
  				/* parse what we can from this header */
    rfc822_parse_msg (&nenv,NIL,(char *) hdr->data,hdr->size,NIL,
  		    net_host (LOCAL->netstream),stream->dtb->flags);
    if (*env) {			/* need to merge this header into envelope? */
      if (!(*env)->newsgroups) {	/* need Newsgroups? */
        (*env)->newsgroups = nenv->newsgroups;
        (*env)->ngpathexists = nenv->ngpathexists;
        nenv->newsgroups = NIL;
      }
      if (!(*env)->followup_to) {	/* need Followup-To? */
        (*env)->followup_to = nenv->followup_to;
        nenv->followup_to = NIL;
      }
      if (!(*env)->references) {	/* need References? */
        (*env)->references = nenv->references;
        nenv->references = NIL;
      }
      if (!(*env)->sparep) {	/* need spare pointer? */
        (*env)->sparep = nenv->sparep;
        nenv->sparep = NIL;
      }
      mail_free_envelope (&nenv);
      (*env)->imapenvonly = NIL;	/* have complete envelope now */
    }
  				/* otherwise set it to this envelope */
    else (*env = nenv)->incomplete = stl ? T : NIL;
  }
  
  /* 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,
  			  unsigned char **txtptr,IMAPPARSEDREPLY *reply)
  {
    ENVELOPE *oenv = *env;
    char c = **txtptr;		/* grab first character */
  				/* ignore leading spaces */
    while (c == ' ') c = *++*txtptr;
    if (c) ++*txtptr;		/* skip past first character */
    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,NIL,NIL,LONGT);
      (*env)->subject = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
      (*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,NIL,NIL,
  					     LONGT);
      (*env)->message_id = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
      if (oenv) {			/* need to merge old envelope? */
        (*env)->newsgroups = oenv->newsgroups;
        oenv->newsgroups = NIL;
        (*env)->ngpathexists = oenv->ngpathexists;
        (*env)->followup_to = oenv->followup_to;
        oenv->followup_to = NIL;
        (*env)->references = oenv->references;
        oenv->references = NIL;
        mail_free_envelope(&oenv);/* free old envelope */
      }
  				/* have IMAP envelope components only */
      else (*env)->imapenvonly = T;
      if (**txtptr != ')') {
        sprintf (LOCAL->tmp,"Junk at end of envelope: %.80s",(char *) *txtptr);
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;
      }
      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",(char *) *txtptr);
      mm_notify (stream,LOCAL->tmp,WARN);
      stream->unhealthy = T;
      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,unsigned char **txtptr,
  			     IMAPPARSEDREPLY *reply)
  {
    ADDRESS *adr = NIL;
    char c = **txtptr;		/* sniff at first character */
  				/* ignore leading spaces */
    while (c == ' ') c = *++*txtptr;
    if (c) ++*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",
  	       (char *) *txtptr);
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;
      }
      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",(char *) *txtptr);
      mm_notify (stream,LOCAL->tmp,WARN);
      stream->unhealthy = T;
      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,unsigned char **txtptr,
  			     IMAPPARSEDREPLY *reply)
  {
    long ingroup = 0;
    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,NIL,NIL,LONGT);
        adr->adl = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
        adr->mailbox = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
        adr->host = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
        if (**txtptr != ')') {	/* handle trailing paren */
  	sprintf (LOCAL->tmp,"Junk at end of address: %.80s",(char *) *txtptr);
  	mm_notify (stream,LOCAL->tmp,WARN);
  	stream->unhealthy = T;
        }
        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 (!adr->mailbox) {	/* end of group? */
  				/* decrement group if all looks well */
  	if (ingroup && !(adr->personal || adr->adl || adr->host)) --ingroup;
  	else {
  	  if (ingroup) {	/* in a group? */
  	    sprintf (LOCAL->tmp,/* yes, must be bad syntax */
  		     "Junk in end of group: pn=%.80s al=%.80s dn=%.80s",
  		     adr->personal ? adr->personal : "",
  		     adr->adl ? adr->adl : "",
  		     adr->host ? adr->host : "");
  	    mm_notify (stream,LOCAL->tmp,WARN);
  	  }
  	  else mm_notify (stream,"End of group encountered when not in group",
  			  WARN);
  	  stream->unhealthy = T;
  	  mail_free_address (&adr);
  	  adr = prev;
  	  prev = NIL;
  	}
        }
        else if (!adr->host) {	/* start of group? */
  	if (adr->personal || adr->adl) {
  	  sprintf (LOCAL->tmp,"Junk in start of group: pn=%.80s al=%.80s",
  		   adr->personal ? adr->personal : "",
  		   adr->adl ? adr->adl : "");
  	  mm_notify (stream,LOCAL->tmp,WARN);
  	  stream->unhealthy = T;
  	  mail_free_address (&adr);
  	  adr = prev;
  	  prev = NIL;
  	}
  	else ++ingroup;		/* in a group now */
        }
        if (adr) {		/* good address */
  	if (!ret) ret = adr;	/* if first time note first adr */
  				/* if previous link new block to it */
  	if (prev) prev->next = adr;
  				/* flush bogus personal name */
  	if (LOCAL->loser && adr->personal && strchr (adr->personal,'@'))
  	  fs_give ((void **) &adr->personal);
        }
      }
      break;
    case 'N':			/* if NIL */
    case 'n':
      *txtptr += 3;		/* bump past NIL */
      break;
    default:
      sprintf (LOCAL->tmp,"Not an address: %.80s",(char *) *txtptr);
      mm_notify (stream,LOCAL->tmp,WARN);
      stream->unhealthy = T;
      break;
    }
    return ret;
  }
  
  /* IMAP parse flags
   * Accepts: current message cache
   *	    current text pointer
   *
   * Updates text pointer
   */
  
  void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt,
  		       unsigned char **txtptr)
  {
    char *flag;
    char c = '\0';
    struct {			/* old flags */
      unsigned int valid : 1;
      unsigned int seen : 1;
      unsigned int deleted : 1;
      unsigned int flagged : 1;
      unsigned int answered : 1;
      unsigned int draft : 1;
      unsigned long user_flags;
    } old;
    old.valid = elt->valid; old.seen = elt->seen; old.deleted = elt->deleted;
    old.flagged = elt->flagged; old.answered = elt->answered;
    old.draft = elt->draft; old.user_flags = elt->user_flags;
    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->draft =
      elt->recent = NIL;
    do {				/* parse list of flags */
  				/* point at a flag */
      while (*(flag = ++*txtptr) == ' ');
  				/* scan for end of flag */
      while (**txtptr && (**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 (*flag == '\\') {
        if (!compare_cstring (flag,"\\Seen")) elt->seen = T;
        else if (!compare_cstring (flag,"\\Deleted")) elt->deleted = T;
        else if (!compare_cstring (flag,"\\Flagged")) elt->flagged = T;
        else if (!compare_cstring (flag,"\\Answered")) elt->answered = T;
        else if (!compare_cstring (flag,"\\Recent")) elt->recent = T;
        else if (!compare_cstring (flag,"\\Draft")) elt->draft = T;
      }
  				/* otherwise user flag */
      else elt->user_flags |= imap_parse_user_flag (stream,flag);
    } while (c && (c != ')'));
    if (c) ++*txtptr;		/* bump past delimiter */
    else {
      mm_notify (stream,"Unterminated flags list",WARN);
      stream->unhealthy = T;
    }
    if (!old.valid || (old.seen != elt->seen) ||
        (old.deleted != elt->deleted) || (old.flagged != elt->flagged) ||
        (old.answered != elt->answered) || (old.draft != elt->draft) ||
        (old.user_flags != elt->user_flags)) mm_flags (stream,elt->msgno);
  }
  
  
  /* IMAP parse user flag
   * Accepts: MAIL stream
   *	    flag name
   * Returns: flag bit position
   */
  
  unsigned long imap_parse_user_flag (MAILSTREAM *stream,char *flag)
  {
    long i;
  				/* sniff through all user flags */
    for (i = 0; i < NUSERFLAGS; ++i) if (stream->user_flags[i])
      if (!compare_cstring (flag,stream->user_flags[i])) return (1 << i);
    return (unsigned long) 0;	/* not found */
  }
  
  /* IMAP parse atom-string
   * Accepts: MAIL stream
   *	    current text pointer
   *	    parsed reply
   *	    returned string length
   * Returns: string
   *
   * Updates text pointer
   */
  
  unsigned char *imap_parse_astring (MAILSTREAM *stream,unsigned char **txtptr,
  				   IMAPPARSEDREPLY *reply,unsigned long *len)
  {
    unsigned long i;
    unsigned char c,*s,*ret;
  				/* ignore leading spaces */
    for (c = **txtptr; c == ' '; c = *++*txtptr);
    switch (c) {
    case '"':			/* quoted string? */
    case '{':			/* literal? */
      ret = imap_parse_string (stream,txtptr,reply,NIL,len,NIL);
      break;
    default:			/* must be atom */
      for (c = *(s = *txtptr);	/* find end of atom */
  	 c && (c > ' ') && (c != '(') && (c != ')') && (c != '{') &&
  	   (c != '%') && (c != '*') && (c != '"') && (c != '\\') && (c < 0x80);
  	 c = *++*txtptr);
      if ((i = *txtptr - s) != 0L) {	/* atom ends at atom_special */
        if (len) *len = i;	/* return length of atom */
        ret = strncpy ((char *) fs_get (i + 1),s,i);
        ret[i] = '\0';		/* tie off string */
      }
      else {			/* no atom found */
        sprintf (LOCAL->tmp,"Not an atom: %.80s",(char *) *txtptr);
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;
        if (len) *len = 0;
        ret = NIL;
      }
      break;
    }
    return ret;
  }
  
  /* IMAP parse string
   * Accepts: MAIL stream
   *	    current text pointer
   *	    parsed reply
   *	    mailgets data
   *	    returned string length
   *	    filter newline flag
   * Returns: string
   *
   * Updates text pointer
   */
  
  unsigned char *imap_parse_string (MAILSTREAM *stream,unsigned char **txtptr,
  				  IMAPPARSEDREPLY *reply,GETS_DATA *md,
  				  unsigned long *len,long flags)
  {
    char *st;
    char *string = NIL;
    unsigned long i,j,k;
    int bogon = NIL;
    unsigned char c = **txtptr;	/* sniff at first character */
    mailgets_t mg = (mailgets_t) mail_parameters (NIL,GET_GETS,NIL);
    readprogress_t rp =
      (readprogress_t) mail_parameters (NIL,GET_READPROGRESS,NIL);
  				/* ignore leading spaces */
    while (c == ' ') c = *++*txtptr;
    if (c) st = ++*txtptr;	/* remember start of string */
    switch (c) {
    case '"':			/* if quoted string */
      i = 0;			/* initial byte count */
  				/* search for end of string */
      for (c = **txtptr; c != '"'; ++i,c = *++*txtptr) {
  				/* backslash quotes next character */
        if (c == '\\') c = *++*txtptr;
  				/* CHAR8 not permitted in quoted string */
        if (!bogon && (bogon = (c & 0x80))) {
  	sprintf (LOCAL->tmp,"Invalid CHAR in quoted string: %x",
  		 (unsigned int) c);
  	mm_notify (stream,LOCAL->tmp,WARN);
  	stream->unhealthy = T;
        }
        else if (!c) {		/* NUL not permitted either */
  	mm_notify (stream,"Unterminated quoted string",WARN);
  	stream->unhealthy = T;
  	if (len) *len = 0;	/* punt, since may be at end of string */
  	return NIL;
        }
      }
      ++*txtptr;			/* bump past delimiter */
      string = (char *) fs_get ((size_t) i + 1);
      for (j = 0; j < i; j++) {	/* copy the string */
        if (*st == '\\') ++st;	/* quoted character */
        string[j] = *st++;
      }
      string[j] = '\0';		/* tie off string */
      if (len) *len = i;		/* set return value too */
      if (md && mg) {		/* have special routine to slurp string? */
        STRING bs;
        if (md->first) {		/* partial fetch? */
  	md->first--;		/* restore origin octet */
  	md->last = i;		/* number of octets that we got */
        }
        INIT (&bs,mail_string,string,i);
        (*mg) (mail_read,&bs,i,md);
      }
      break;
  
    case 'N':			/* if NIL */
    case 'n':
      ++*txtptr;			/* bump past "I" */
      ++*txtptr;			/* bump past "L" */
      if (len) *len = 0;
      break;
    case '{':			/* if literal string */
      if (!isdigit (**txtptr)) {
        sprintf (LOCAL->tmp,"Invalid server literal length %.80s",*txtptr);
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;	/* read and discard */
        i = 0;
      }
  				/* get size of string */ 
      else if ((i = strtoul (*txtptr,(char **) txtptr,10)) > MAXSERVERLIT) {
        sprintf (LOCAL->tmp,"Absurd server literal length %lu",i);
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;	/* read and discard */
        for (j = IMAPTMPLEN - 1; i; i -= j) {
  	if (j > i) j = i;
  	net_getbuffer (LOCAL->netstream,j,LOCAL->tmp);
        }
      }
      if (len) *len = i;		/* set return value */
      if (md && mg) {		/* have special routine to slurp string? */
        if (md->first) {		/* partial fetch? */
  	md->first--;		/* restore origin octet */
  	md->last = i;		/* number of octets that we got */
        }
        else md->flags |= MG_COPY;/* otherwise flag need to copy */
        string = (*mg) (net_getbuffer,LOCAL->netstream,i,md);
      }
      else {			/* must slurp into free storage */
        string = (char *) fs_get ((size_t) i + 1);
        *string = '\0';		/* init in case getbuffer fails */
  				/* get the literal */
        if (rp) for (k = 0; (j = min ((long) MAILTMPLEN,(long) i)) != 0L; i -= j) {
  	net_getbuffer (LOCAL->netstream,j,string + k);
  	(*rp) (md,k += j);
        }
        else net_getbuffer (LOCAL->netstream,i,string);
      }
      fs_give ((void **) &reply->line);
      if (flags && string)	/* need to filter newlines? */
        for (st = string; (st = strpbrk (st,"\015\012\011")) != NULL; *st++ = ' ');
  				/* get new reply text line */
      if (!(reply->line = net_getline (LOCAL->netstream)))
        reply->line = cpystr ("");
      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,(char *) *txtptr);
      mm_notify (stream,LOCAL->tmp,WARN);
      stream->unhealthy = T;
      if (len) *len = 0;
      break;
    }
    return (unsigned char *) string;
  }
  
  /* Register text in IMAP cache
   * Accepts: MAIL stream
   *	    message number
   *	    IMAP segment specifier
   *	    header string list (if a HEADER section specifier)
   *	    sized text to register
   * Returns: non-zero if cache non-empty
   */
  
  long imap_cache (MAILSTREAM *stream,unsigned long msgno,char *seg,
  		 STRINGLIST *stl,SIZEDTEXT *text)
  {
    char *t,tmp[MAILTMPLEN];
    unsigned long i;
    BODY *b;
    SIZEDTEXT *ret;
    STRINGLIST *stc;
    MESSAGECACHE *elt = mail_elt (stream,msgno);
  				/* top-level header never does mailgets */
    if (!strcmp (seg,"HEADER") || !strcmp (seg,"0") ||
        !strcmp (seg,"HEADER.FIELDS") || !strcmp (seg,"HEADER.FIELDS.NOT")) {
      ret = &elt->private.msg.header.text;
      if (text) {			/* don't do this if no text */
        if (ret->data) fs_give ((void **) &ret->data);
        mail_free_stringlist (&elt->private.msg.lines);
        elt->private.msg.lines = stl;
  				/* prevent cache reuse of .NOT */
        if ((seg[0] == 'H') && (seg[6] == '.') && (seg[13] == '.'))
  	for (stc = stl; stc; stc = stc->next) stc->text.size = 0;
        if (stream->scache) {	/* short caching puts it in the stream */
  	if (stream->msgno != msgno) {
  				/* flush old stuff */
  	  mail_free_envelope (&stream->env);
  	  mail_free_body (&stream->body);
  	  stream->msgno = msgno;
  	}
  	imap_parse_header (stream,&stream->env,text,stl);
        }
  				/* regular caching */
        else imap_parse_header (stream,&elt->private.msg.env,text,stl);
      }
    }
  				/* top level text */
    else if (!strcmp (seg,"TEXT")) {
      ret = &elt->private.msg.text.text;
      if (text && ret->data) fs_give ((void **) &ret->data);
    }
    else if (!*seg) {		/* full message */
      ret = &elt->private.msg.full.text;
      if (text && ret->data) fs_give ((void **) &ret->data);
    }
  
    else {			/* nested, find non-contents specifier */
      for (t = seg; *t && !((*t == '.') && (isalpha(t[1]) || !atol (t+1))); t++);
      if (*t) *t++ = '\0';	/* tie off section from data specifier */
      if (!(b = mail_body (stream,msgno,seg))) {
        sprintf (tmp,"Unknown section number: %.80s",seg);
        mm_notify (stream,tmp,WARN);
        stream->unhealthy = T;
        return NIL;
      }
      if (*t) {			/* if a non-numberic subpart */
        if ((i = (b->type == TYPEMESSAGE) && (!strcmp (b->subtype,"RFC822"))) &&
  	  (!strcmp (t,"HEADER") || !strcmp (t,"0") ||
  	   !strcmp (t,"HEADER.FIELDS") || !strcmp (t,"HEADER.FIELDS.NOT"))) {
  	ret = &b->nested.msg->header.text;
  	if (text) {
  	  if (ret->data) fs_give ((void **) &ret->data);
  	  mail_free_stringlist (&b->nested.msg->lines);
  	  b->nested.msg->lines = stl;
  				/* prevent cache reuse of .NOT */
  	  if ((t[0] == 'H') && (t[6] == '.') && (t[13] == '.'))
  	    for (stc = stl; stc; stc = stc->next) stc->text.size = 0;
  	  imap_parse_header (stream,&b->nested.msg->env,text,stl);
  	}
        }
        else if (i && !strcmp (t,"TEXT")) {
  	ret = &b->nested.msg->text.text;
  	if (text && ret->data) fs_give ((void **) &ret->data);
        }
  				/* otherwise it must be MIME */
        else if (!strcmp (t,"MIME")) {
  	ret = &b->mime.text;
  	if (text && ret->data) fs_give ((void **) &ret->data);
        }
        else {
  	sprintf (tmp,"Unknown section specifier: %.80s.%.80s",seg,t);
  	mm_notify (stream,tmp,WARN);
  	stream->unhealthy = T;
  	return NIL;
        }
      }
      else {			/* ordinary contents */
        ret = &b->contents.text;
        if (text && ret->data) fs_give ((void **) &ret->data);
      }
    }
    if (text) {			/* update cache if requested */
      ret->data = text->data;
      ret->size = text->size;
    }
    return ret->data ? LONGT : NIL;
  }
  
  /* 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,
  				unsigned char **txtptr,IMAPPARSEDREPLY *reply)
  {
    int i;
    char *s;
    PART *part = NIL;
    char c = **txtptr;		/* grab first character */
  				/* ignore leading spaces */
    while (c == ' ') c = *++*txtptr;
    if (c) ++*txtptr;		/* skip past first character */
    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->nested.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,NIL,NIL,LONGT)) != NULL)
  	ucase (body->subtype);
        else {
  	mm_notify (stream,"Missing multipart subtype",WARN);
  	stream->unhealthy = T;
  	body->subtype = cpystr (rfc822_default_subtype (body->type));
        }
        if (**txtptr == ' ')	/* multipart parameters */
  	body->parameter = imap_parse_body_parameter (stream,txtptr,reply);
        if (**txtptr == ' ') {	/* disposition */
  	imap_parse_disposition (stream,body,txtptr,reply);
  	if (LOCAL->cap.extlevel < BODYEXTDSP) LOCAL->cap.extlevel = BODYEXTDSP;
        }
        if (**txtptr == ' ') {	/* language */
  	body->language = imap_parse_language (stream,txtptr,reply);
  	if (LOCAL->cap.extlevel < BODYEXTLANG)
  	  LOCAL->cap.extlevel = BODYEXTLANG;
        }
        if (**txtptr == ' ') {	/* location */
  	body->location = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
  	if (LOCAL->cap.extlevel < BODYEXTLOC) LOCAL->cap.extlevel = BODYEXTLOC;
        }
        while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply);
        if (**txtptr != ')') {	/* validate ending */
  	sprintf (LOCAL->tmp,"Junk at end of multipart body: %.80s",
  		 (char *) *txtptr);
  	mm_notify (stream,LOCAL->tmp,WARN);
  	stream->unhealthy = T;
        }
        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,NIL,NIL,LONGT)) != NULL) {
  	ucase (s);		/* application always gets uppercase form */
  	for (i = 0;		/* look in existing table */
  	     (i <= TYPEMAX) && body_types[i] && strcmp (s,body_types[i]); i++);
  	if (i <= TYPEMAX) {	/* only if found a slot */
  	  body->type = i;	/* set body type */
  	  if (!body_types[i]) {	/* assign empty slot */
  	    body_types[i] = s;
  	    s = NIL;		/* don't free this string */
  	  }
  	}
  	if (s) fs_give ((void **) &s);
        }
        if ((body->subtype = imap_parse_string(stream,txtptr,reply,NIL,NIL,LONGT)) != NULL)
  	ucase (body->subtype);	/* parse subtype */
        else {
  	mm_notify (stream,"Missing body subtype",WARN);
  	stream->unhealthy = T;
  	body->subtype = cpystr (rfc822_default_subtype (body->type));
        }
        body->parameter = imap_parse_body_parameter (stream,txtptr,reply);
        body->id = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
        body->description = imap_parse_string (stream,txtptr,reply,NIL,NIL,
  					     LONGT);
        if ((s = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT)) != NULL) {
  	ucase (s);		/* application always gets uppercase form */
  	for (i = 0;		/* search for body encoding */
  	     (i <= ENCMAX) && body_encodings[i] && strcmp(s,body_encodings[i]);
  	     i++);
  	if (i > ENCMAX) body->encoding = ENCOTHER;
  	else {			/* only if found a slot */
  	  body->encoding = i;	/* set body encoding */
  				/* assign empty slot */
  	  if (!body_encodings[i]) {
  	    body_encodings[i] = s;
  	    s = NIL;		/* don't free this string */
  	  }
  	}
  	if (s) fs_give ((void **) &s);
        }
  				/* parse size of contents in bytes */
        body->size.bytes = strtoul (*txtptr,(char **) txtptr,10);
        switch (body->type) {	/* possible extra stuff */
        case TYPEMESSAGE:		/* message envelope and body */
  				/* non MESSAGE/RFC822 is basic type */
  	if (strcmp (body->subtype,"RFC822")) break;
  	{			/* make certain server sends an envelope */
  	  ENVELOPE *env = NIL;
  	  imap_parse_envelope (stream,&env,txtptr,reply);
  	  if (!env) {
  	    mm_notify (stream,"Missing body message envelope",WARN);
  	    stream->unhealthy = T;
  	    fs_give ((void **) &body->subtype);
  	    body->subtype = cpystr ("RFC822_MISSING_ENVELOPE");
  	    break;
  	  }
  	  (body->nested.msg = mail_newmsg ())->env = env;
  	}
  	body->nested.msg->body = mail_newbody ();
  	imap_parse_body_structure (stream,body->nested.msg->body,txtptr,reply);
  				/* drop into text case */
        case TYPETEXT:		/* size in lines */
  	body->size.lines = strtoul (*txtptr,(char **) txtptr,10);
  	break;
        default:			/* otherwise nothing special */
  	break;
        }
  
        if (**txtptr == ' ') {	/* extension data - md5 */
  	body->md5 = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
  	if (LOCAL->cap.extlevel < BODYEXTMD5) LOCAL->cap.extlevel = BODYEXTMD5;
        }
        if (**txtptr == ' ') {	/* disposition */
  	imap_parse_disposition (stream,body,txtptr,reply);
  	if (LOCAL->cap.extlevel < BODYEXTDSP) LOCAL->cap.extlevel = BODYEXTDSP;
        }
        if (**txtptr == ' ') {	/* language */
  	body->language = imap_parse_language (stream,txtptr,reply);
  	if (LOCAL->cap.extlevel < BODYEXTLANG)
  	  LOCAL->cap.extlevel = BODYEXTLANG;
        }
        if (**txtptr == ' ') {	/* location */
  	body->location = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT);
  	if (LOCAL->cap.extlevel < BODYEXTLOC) LOCAL->cap.extlevel = BODYEXTLOC;
        }
        while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply);
        if (**txtptr != ')') {	/* validate ending */
  	sprintf (LOCAL->tmp,"Junk at end of body part: %.80s",
  		 (char *) *txtptr);
  	mm_notify (stream,LOCAL->tmp,WARN);
  	stream->unhealthy = T;
        }
        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",(char *) *txtptr);
      mm_notify (stream,LOCAL->tmp,WARN);
      stream->unhealthy = T;
      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,
  				      unsigned char **txtptr,
  				      IMAPPARSEDREPLY *reply)
  {
    PARAMETER *ret = NIL;
    PARAMETER *par = NIL;
    char c,*s;
  				/* ignore leading spaces */
    while ((c = *(*txtptr)++) == ' ');
    if (c == '(') do {		/* parse parameter list */
  				/* 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,NIL,NIL,
  					   LONGT))) {
        mm_notify (stream,"Missing parameter attribute",WARN);
        stream->unhealthy = T;
        par->attribute = cpystr ("UNKNOWN");
      }
      if (!(par->value = imap_parse_string (stream,txtptr,reply,NIL,NIL,LONGT))){
        sprintf (LOCAL->tmp,"Missing value for parameter %.80s",par->attribute);
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;
        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;
      case '\0':
        mm_notify (stream,"Unterminated parameter list", WARN);
        stream->unhealthy = T;
        break;
      default:
        sprintf (LOCAL->tmp,"Junk at end of parameter: %.80s",(char *) *txtptr);
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;
        break;
      }
    } while (c && (c != ')'));
  				/* 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,
  	     (char *) (*txtptr) - 1);
      mm_notify (stream,LOCAL->tmp,WARN);
      stream->unhealthy = T;
    }
    return ret;
  }
  
  /* IMAP parse body disposition
   * Accepts: MAIL stream
   *	    body structure to write into
   *	    current text pointer
   *	    parsed reply
   */
  
  void imap_parse_disposition (MAILSTREAM *stream,BODY *body,
  			     unsigned char **txtptr,IMAPPARSEDREPLY *reply)
  {
    switch (*++*txtptr) {
    case '(':
      ++*txtptr;			/* skip open paren */
      body->disposition.type = imap_parse_string (stream,txtptr,reply,NIL,NIL,
  						LONGT);
      body->disposition.parameter =
        imap_parse_body_parameter (stream,txtptr,reply);
      if (**txtptr != ')') {	/* validate ending */
        sprintf (LOCAL->tmp,"Junk at end of disposition: %.80s",
  	       (char *) *txtptr);
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;
      }
      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",(char *) *txtptr);
      mm_notify (stream,LOCAL->tmp,WARN);
      stream->unhealthy = T;
  				/* 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,unsigned 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,LONGT)) != NULL) {
      (ret = mail_newstringlist ())->text.data = (unsigned char *) s;
      ret->text.size = i;
    }
    return ret;
  }
  
  /* IMAP parse string list
   * Accepts: MAIL stream
   *	    current text pointer
   *	    parsed reply
   * Returns: string list or NIL if empty or error
   */
  
  STRINGLIST *imap_parse_stringlist (MAILSTREAM *stream,unsigned char **txtptr,
  				   IMAPPARSEDREPLY *reply)
  {
    STRINGLIST *stl = NIL;
    STRINGLIST *stc = NIL;
    unsigned char *t = *txtptr;
  				/* parse the list */
    if (*t++ == '(') while (*t != ')') {
      if (stl) stc = stc->next = mail_newstringlist ();
      else stc = stl = mail_newstringlist ();
  				/* parse astring */
      if (!(stc->text.data =
  	  imap_parse_astring (stream,&t,reply,&stc->text.size))) {
        sprintf (LOCAL->tmp,"Bogus string list member: %.80s",(char *) t);
        mm_notify (stream,LOCAL->tmp,WARN);
        stream->unhealthy = T;
        mail_free_stringlist (&stl);
        break;
      }
      else if (*t == ' ') ++t;	/* another token follows */
    }
    if (stl) *txtptr = ++t;	/* update return string */
    return stl;
  }
  
  /* IMAP parse unknown body extension data
   * Accepts: MAIL stream
   *	    current text pointer
   *	    parsed reply
   *
   * Updates text pointer
   */
  
  void imap_parse_extension (MAILSTREAM *stream,unsigned char **txtptr,
  			   IMAPPARSEDREPLY *reply)
  {
    unsigned long i,j;
    switch (*++*txtptr) {		/* action depends upon first character */
    case '(':
      while (**txtptr && (**txtptr != ')'))
        imap_parse_extension (stream,txtptr,reply);
      if (**txtptr) ++*txtptr;	/* bump past closing parenthesis */
      break;
    case '"':			/* if quoted string */
      while ((*++*txtptr != '"') && **txtptr) if (**txtptr == '\\') ++*txtptr;
      if (**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,(char **) txtptr,10)) != 0L) do
        net_getbuffer (LOCAL->netstream,j = min (i,(long) IMAPTMPLEN - 1),
  		     LOCAL->tmp);
      while (i -= j);
  				/* get new reply text line */
      if (!(reply->line = net_getline (LOCAL->netstream)))
        reply->line = cpystr ("");
      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,(char **) txtptr,10);
      break;
    default:
      sprintf (LOCAL->tmp,"Unknown extension token: %.80s",(char *) *txtptr);
      mm_notify (stream,LOCAL->tmp,WARN);
      stream->unhealthy = T;
  				/* try to skip to next space */
      while (**txtptr && (*++*txtptr != ' ') && (**txtptr != ')'));
      break;
    }
  }
  
  /* IMAP parse capabilities
   * Accepts: MAIL stream
   *	    capability list
   */
  
  void imap_parse_capabilities (MAILSTREAM *stream,char *t)
  {
    char *s,*r;
    unsigned long i;
    THREADER *thr,*th;
    if (!LOCAL->gotcapability) {	/* need to save previous capabilities? */
  				/* no, flush threaders */
      if ((thr = LOCAL->cap.threader) != NULL) while ((th = thr) != NULL) {
        fs_give ((void **) &th->name);
        thr = th->next;
        fs_give ((void **) &th);
      }
  				/* zap capabilities */
      memset (&LOCAL->cap,0,sizeof (LOCAL->cap));
      LOCAL->gotcapability = T;	/* flag that capabilities arrived */
    }
    for (t = strtok_r (t," ",&r); t; t = strtok_r (NIL," ",&r)) {
      if (!compare_cstring (t,"IMAP4"))
        LOCAL->cap.imap4 = LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
      else if (!compare_cstring (t,"IMAP4rev1"))
        LOCAL->cap.imap4rev1 = LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
      else if (!compare_cstring (t,"IMAP2")) LOCAL->cap.rfc1176 = T;
      else if (!compare_cstring (t,"IMAP2bis"))
        LOCAL->cap.imap2bis = LOCAL->cap.rfc1176 = T;
      else if (!compare_cstring (t,"ACL")) LOCAL->cap.acl = T;
      else if (!compare_cstring (t,"QUOTA")) LOCAL->cap.quota = T;
      else if (!compare_cstring (t,"LITERAL+")) LOCAL->cap.litplus = T;
      else if (!compare_cstring (t,"IDLE")) LOCAL->cap.idle = T;
      else if (!compare_cstring (t,"MAILBOX-REFERRALS")) LOCAL->cap.mbx_ref = T;
      else if (!compare_cstring (t,"LOGIN-REFERRALS")) LOCAL->cap.log_ref = T;
      else if (!compare_cstring (t,"NAMESPACE")) LOCAL->cap.namespace = T;
      else if (!compare_cstring (t,"UIDPLUS")) LOCAL->cap.uidplus = T;
      else if (!compare_cstring (t,"STARTTLS")) LOCAL->cap.starttls = T;
      else if (!compare_cstring (t,"LOGINDISABLED"))LOCAL->cap.logindisabled = T;
      else if (!compare_cstring (t,"ID")) LOCAL->cap.id = T;
      else if (!compare_cstring (t,"CHILDREN")) LOCAL->cap.children = T;
      else if (!compare_cstring (t,"MULTIAPPEND")) LOCAL->cap.multiappend = T;
      else if (!compare_cstring (t,"BINARY")) LOCAL->cap.binary = T;
      else if (!compare_cstring (t,"UNSELECT")) LOCAL->cap.unselect = T;
      else if (!compare_cstring (t,"SASL-IR")) LOCAL->cap.sasl_ir = T;
      else if (!compare_cstring (t,"SCAN")) LOCAL->cap.scan = T;
      else if (!compare_cstring (t,"URLAUTH")) LOCAL->cap.urlauth = T;
      else if (!compare_cstring (t,"CATENATE")) LOCAL->cap.catenate = T;
      else if (!compare_cstring (t,"CONDSTORE")) LOCAL->cap.condstore = T;
      else if (!compare_cstring (t,"ESEARCH")) LOCAL->cap.esearch = T;
      else if (!compare_cstring (t,"X-GM-EXT-1")) LOCAL->cap.x_gm_ext1 = T;
      else if (((t[0] == 'S') || (t[0] == 's')) &&
  	     ((t[1] == 'O') || (t[1] == 'o')) &&
  	     ((t[2] == 'R') || (t[2] == 'r')) &&
  	     ((t[3] == 'T') || (t[3] == 't'))) LOCAL->cap.sort = T;
  				/* capability with value? */
      else if ((s = strchr (t,'=')) != NULL) {
        *s++ = '\0';		/* separate token from value */
        if (!compare_cstring (t,"THREAD") && !LOCAL->loser) {
  	THREADER *thread = (THREADER *) fs_get (sizeof (THREADER));
  	thread->name = cpystr (s);
  	thread->dispatch = NIL;
  	thread->next = LOCAL->cap.threader;
  	LOCAL->cap.threader = thread;
        }
        else if (!compare_cstring (t,"AUTH")) {
  	if ((i = mail_lookup_auth_name (s,LOCAL->authflags)) &&
  	    (--i < MAXAUTHENTICATORS)) LOCAL->cap.auth |= (1 << i);
  	else if (!compare_cstring (s,"ANONYMOUS")) LOCAL->cap.authanon = T;
        }
      }
  				/* ignore other capabilities */
    }
  				/* disable LOGIN if PLAIN also advertised */
    if ((i = mail_lookup_auth_name ("PLAIN",NIL)) && (--i < MAXAUTHENTICATORS) &&
        (LOCAL->cap.auth & (1 << i)) &&
        (i = mail_lookup_auth_name ("LOGIN",NIL)) && (--i < MAXAUTHENTICATORS))
      LOCAL->cap.auth &= ~(1 << i);
  }
  
  /* IMAP load cache
   * Accepts: MAIL stream
   *	    sequence
   *	    flags
   * Returns: parsed reply from fetch
   */
  
  IMAPPARSEDREPLY *imap_fetch (MAILSTREAM *stream,char *sequence,long flags)
  {
    int i = 2;
    char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ?
      "UID FETCH" : "FETCH";
    IMAPARG *args[9],aseq,aarg,aenv,ahhr,axtr,ahtr,abdy,atrl;
    if (LOCAL->loser) sequence = imap_reform_sequence (stream,sequence,
  						     flags & FT_UID);
    args[0] = &aseq; aseq.type = SEQUENCE; aseq.text = (void *) sequence;
    args[1] = &aarg; aarg.type = ATOM;
    aenv.type = ATOM; aenv.text = (void *) "ENVELOPE";
    ahhr.type = ATOM; ahhr.text = (void *) hdrheader[LOCAL->cap.extlevel];
    axtr.type = ATOM; axtr.text = (void *) imap_extrahdrs;
    ahtr.type = ATOM; ahtr.text = (void *) hdrtrailer;
    abdy.type = ATOM; abdy.text = (void *) "BODYSTRUCTURE";
    atrl.type = ATOM; atrl.text = (void *) "INTERNALDATE RFC822.SIZE FLAGS)";
    if (LEVELIMAP4 (stream)) {	/* include UID if IMAP4 or IMAP4rev1 */
      aarg.text = (void *) "(UID";
      if (flags & FT_NEEDENV) {	/* if need envelopes */
        args[i++] = &aenv;	/* include envelope */
  				/* extra header poop if IMAP4rev1 */
        if (!(flags & FT_NOHDRS) && LEVELIMAP4rev1 (stream)) {
  	args[i++] = &ahhr;	/* header header */
  	if (axtr.text) args[i++] = &axtr;
  	args[i++] = &ahtr;	/* header trailer */
        }
  				/* fetch body if requested */
        if (flags & FT_NEEDBODY) args[i++] = &abdy;
      }
      args[i++] = &atrl;		/* fetch trailer */
    }
  				/* easy if IMAP2 */
    else aarg.text = (void *) (flags & FT_NEEDENV) ?
      ((flags & FT_NEEDBODY) ?
       "(RFC822.HEADER BODY INTERNALDATE RFC822.SIZE FLAGS)" :
       "(RFC822.HEADER INTERNALDATE RFC822.SIZE FLAGS)") : "FAST";
    args[i] = NIL;		/* tie off command */
    return imap_send (stream,cmd,args);
  }
  
  /* Reform sequence for losing server that doesn't handle ranges right
   * Accepts: MAIL stream
   *	    sequence
   *	    non-zero if UID
   * Returns: sequence
   */
  
  char *imap_reform_sequence (MAILSTREAM *stream,char *sequence,long flags)
  {
    unsigned long i,j,star;
    char *s,*t,*tl,*rs;
  				/* can't win if empty */
    if (!stream->nmsgs) return sequence;
  				/* get highest possible range value */
    star = flags ? mail_uid (stream,stream->nmsgs) : stream->nmsgs;
  				/* flush old reformed sequence */
    if (LOCAL->reform) fs_give ((void **) &LOCAL->reform);
    rs = LOCAL->reform = (char *) fs_get (1+ strlen (sequence));
    for (s = sequence; (t = strpbrk (s,",:")) != NULL; ) switch (*t++) {
    case ',':			/* single message */
      strncpy (rs,s,i = t - s);	/* copy string up to that point */
      rs += i;			/* advance destination pointer */
      s += i;			/* and source */
      break;
    case ':':			/* message range */
      i = (*s == '*') ? star : strtoul (s,NIL,10);
      if (*t == '*') {		/* range ends with star */
        j = star;
        tl = t+1;
      }
      else {			/* numeric range end */
        j = strtoul (t,(char **) &tl,10);
        if (!tl) tl = t + strlen (t);
      }
      if (i <= j) {		/* if first less than second */
        if (*tl) tl++;		/* skip past end of range if present */
        strncpy (rs,s,i = tl - s);/* copy string up to that point */
        rs += i;			/* advance destination and source pointers */
        s += i;
      }
      else {			/* here's the workaround for losing servers */
        strncpy (rs,t,i = tl - t);/* swap the order */
        rs[i] = ':';		/* delimit */
        strncpy (rs+i+1,s,j = (t-1) - s);
        rs += i + 1 + j;		/* advance destination pointer */
        if (*tl) *rs++ = *tl++;	/* write trailing delimiter if present */
        s = tl;			/* advance source pointer */
      }
    }
    if (*s) strcpy (rs,s);	/* write remainder of sequence */
    else *rs = '\0';		/* tie off string */
    return LOCAL->reform;
  }
  
  /* IMAP return host name
   * Accepts: MAIL stream
   * Returns: host name
   */
  
  char *imap_host (MAILSTREAM *stream)
  {
    if (stream->dtb != &imapdriver)
      fatal ("imap_host called on non-IMAP stream!");
  				/* return host name on stream if open */
    return (LOCAL && LOCAL->netstream) ? net_host (LOCAL->netstream) :
      ".NO-IMAP-CONNECTION.";
  }
  
  
  /* IMAP return IMAP capability structure
   * Accepts: MAIL stream
   * Returns: IMAP capability structure
   */
  
  IMAPCAP *imap_cap (MAILSTREAM *stream)
  {
    if (stream->dtb != &imapdriver)
      fatal ("imap_cap called on non-IMAP stream!");
    return &LOCAL->cap;		/* return capability structure */
  }
  