Newer
Older
imapext / src / c-client / imap4r1.c
@yuuji@gentei.org yuuji@gentei.org on 14 Sep 2009 193 KB imap-2007e
/* ========================================================================
 * 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
 *
 * 
 * ========================================================================
 */

/*
 * Program:	Interactive Message Access Protocol 4rev1 (IMAP4R1) routines
 *
 * Author:	Mark Crispin
 *		UW Technology
 *		University of Washington
 *		Seattle, WA  98195
 *		Internet: MRC@CAC.Washington.EDU
 *
 * Date:	15 June 1988
 * Last Edited:	8 May 2008
 *
 * 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 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 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 */
} 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);
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);

/* 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;
  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];
				/* 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 (&sdb)) do if (imap_valid (s) && pmatch (s,mbx))
    mm_lsub (stream,NIL,s,NIL);
  while (s = sm_read (&sdb));	/* until no more subscriptions */
}

/* 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));
    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,
		       (mb.tlssslv23 ? NIL : NET_TLSCLIENT) |
		       (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 (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.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->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 (!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) while (t = thr) {
      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);
				/* 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)) {
	    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)
				/* 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")) {
      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) {/* 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 ((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) {
				/* 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))
	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) while (t = thr) {
      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;
}

/* 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) while (arg = *arglst++) {
    *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))
	return reply;
      break;
    case LITERAL:		/* literal, as a stringstruct */
      if (reply = imap_send_literal (stream,tag,&s,arg->text)) return reply;
      break;

    case LIST:			/* list of strings */
      list = (STRINGLIST *) arg->text;
      c = '(';			/* open paren */
      do {			/* for each list item */
	*s++ = c;		/* write prefix character */
	if (reply = imap_send_astring (stream,tag,&s,&list->text,NIL,
				       CMDBASE+MAXCOMMAND)) return reply;
	c = ' ';		/* prefix character for subsequent strings */
      }
      while (list = list->next);
      *s++ = ')';		/* close list */
      break;
    case SEARCHPROGRAM:		/* search program */
      if (reply = imap_send_spgm (stream,tag,CMDBASE,&s,arg->text,
				  CMDBASE+MAXCOMMAND))
	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))
	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) {	/* 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)) return reply;
	    *s++ = ' ';		/* delimit with space */
	  }
	  if (reply = imap_send_literal (stream,tag,&s,map->message))
	    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);
      *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 */
}

/* 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) do {
    *s = imap_send_spgm_trim (base,*s," HEADER ");
    if (reply = imap_send_astring (stream,tag,s,&hdr->line,NIL,limit))
      return reply;
    *(*s)++ = ' ';
    if (reply = imap_send_astring (stream,tag,s,&hdr->text,NIL,limit))
      return reply;
  } while (hdr = hdr->next);
  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))
      return reply;
    for (t = ") ("; *t; *(*s)++ = *t++);
    if (reply = imap_send_spgm (stream,tag,base,s,pgo->second,limit))
      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))
      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)) return reply;
    *(*s)++ = ')';		/* close glue */
    if (reply = imap_send_sset (stream,tag,NIL,s,set,prefix,limit))
      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))) {
				/* 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));
				/* and locate the text after it */
    t = strtok_r (NIL,"\n",&r);
				/* 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 ((!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,' ')) *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 */
      }
				/* 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));
  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)) {
				/* 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));
  }
  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) {
      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[1]) {
      *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)) 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;
				/* ignore extension flags */
    }
    while (t = strtok_r (NIL," ",&r));
    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)) {
				/* 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) {	/* 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,' ')) t = s;
	  if ((*s++ == ' ') && (isdigit (*s) || (LOCAL->loser &&(*s == '-')))){
	    if (isdigit (*s)) qc->limit = strtoul (s,(char **) &s,10);
	    else if (t = strpbrk (s," )")) 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,"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),' ')) *s++ = '\0';
    if (s) {			/* have argument? */
      ntfy = NIL;		/* suppress mm_notify if normal SELECT data */
      if (!compare_cstring (t,"UIDVALIDITY") &&
	  ((j = strtoul (s,NIL,10)) != 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))
	    elt->private.uid = 0;
      }
      else if (!compare_cstring (t,"UIDNEXT"))
	stream->uid_last = strtoul (s,NIL,10) - 1;
      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)) 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));
      }

      else if (!compare_cstring (t,"CAPABILITY"))
	imap_parse_capabilities (stream,s);
      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;
      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)++);
  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)->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;
  ++*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;
  while (c != ')') {		/* parse list of flags */
				/* point at a flag */
    while (*(flag = ++*txtptr) == ' ');
				/* scan for end of flag */
    while (**txtptr != ' ' && **txtptr != ')') ++*txtptr;
    c = **txtptr;		/* save delimiter */
    **txtptr = '\0';		/* tie off flag */
    if (!*flag) break;		/* null flag */
				/* if starts with \ must be sys flag */
    else if (*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);
  }
  ++*txtptr;			/* bump past delimiter */
  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) {	/* 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;
  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 */
				/* get size of string */ 
    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 */
      do net_getbuffer (LOCAL->netstream,j = min (i,(long) IMAPTMPLEN - 1),
			LOCAL->tmp);
      while (i -= j);
    }
    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); 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"); *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)++);
  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))
	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)) {
	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]) fs_give ((void **) &s);
	  else body_types[i]=s;	/* assign empty slot */
	}
      }
      if (body->subtype = imap_parse_string(stream,txtptr,reply,NIL,NIL,LONGT))
	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)) {
	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 */
	  if (body_encodings[i]) fs_give ((void **) &s);
				/* assign empty slot */
	  else body_encodings[i] = 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;
	    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)++) == ' ');
				/* parse parameter list */
  if (c == '(') while (c != ')') {
				/* append new parameter to tail */
    if (ret) par = par->next = mail_newbody_parameter ();
    else ret = par = mail_newbody_parameter ();
    if(!(par->attribute=imap_parse_string (stream,txtptr,reply,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;
    default:
      sprintf (LOCAL->tmp,"Junk at end of parameter: %.80s",(char *) *txtptr);
      mm_notify (stream,LOCAL->tmp,WARN);
      stream->unhealthy = T;
      break;
    }
  }
				/* empty parameter, must be NIL */
  else if (((c == 'N') || (c == 'n')) &&
	   ((*(s = *txtptr) == 'I') || (*s == 'i')) &&
	   ((s[1] == 'L') || (s[1] == 'l'))) *txtptr += 2;
  else {
    sprintf (LOCAL->tmp,"Bogus body parameter: %c%.80s",c,
	     (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)) {
    (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 != ')') imap_parse_extension (stream,txtptr,reply);
    ++*txtptr;			/* bump past closing parenthesis */
    break;
  case '"':			/* if quoted string */
    while (*++*txtptr != '"') if (**txtptr == '\\') ++*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)) 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) while (th = thr) {
      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 (((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,'=')) {
      *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,",:"); ) 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 */
}