/*
 * Program:	Mailbox Access routines
 *
 * Author:	Mark Crispin
 *		Networks and Distributed Computing
 *		Computing & Communications
 *		University of Washington
 *		Administration Building, AG-44
 *		Seattle, WA  98195
 *		Internet: MRC@CAC.Washington.EDU
 *
 * Date:	22 November 1989
 * Last Edited:	4 January 1996
 *
 * Copyright 1996 by the University of Washington
 *
 *  Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appears in all copies and that both the
 * above copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the University of Washington not be
 * used in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  This software is made
 * available "as is", and
 * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
 * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
 * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
 * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */


#include <ctype.h>
#include <stdio.h>
#include "mail.h"
#include "osdep.h"
#include <time.h>
#include "misc.h"
#include "rfc822.h"


/* c-client global data */

				/* list of mail drivers */
static DRIVER *maildrivers = NIL;
				/* pointer to alternate gets function */
static mailgets_t mailgets = NIL;
				/* mail cache manipulation function */
static mailcache_t mailcache = mm_cache;
				/* list of authenticators (used by imapd) */
AUTHENTICATOR *authenticators = NIL;

/* Default limited get string
 * Accepts: readin function pointer
 *	    stream to use
 *	    number of bytes
 * Returns: string read in, truncated if necessary
 *
 * This is a sample mailgets routine.  It simply truncates any data larger
 * than MAXMESSAGESIZE.  On most systems, you generally don't use a mailgets
 * routine at all, but on some systems (e.g. DOS) it's required to prevent the
 * application from crashing.  This one is filled in by the os driver for any
 * OS that requires a mailgets routine and the main program has not already
 * supplied one, generally in tcp_open().
 */

char *mm_gets (readfn_t f,void *stream,unsigned long size)
{
  char *s;
  char tmp[MAILTMPLEN+1];
  unsigned long i,j = 0;
				/* truncate? */
  if (i = (size > MAXMESSAGESIZE)) {
    sprintf (tmp,"%ld character literal truncated to %ld characters",
	     size,MAXMESSAGESIZE);
    mm_log (tmp,WARN);		/* warn user */
    i = size - MAXMESSAGESIZE;	/* number of bytes of slop */
    size = MAXMESSAGESIZE;	/* maximum length string we can read */
  }
  s = (char *) fs_get ((size_t) size + 1);
  *s = s[size] = '\0';		/* init in case getbuffer fails */
  (*f) (stream,size,s);		/* get the literal */
				/* toss out everything after that */
  while (i -= j) (*f) (stream,j = min ((long) MAILTMPLEN,i),tmp);
  return s;
}

/* Default mail cache handler
 * Accepts: pointer to cache handle
 *	    message number
 *	    caching function
 * Returns: cache data
 */

void *mm_cache (MAILSTREAM *stream,unsigned long msgno,long op)
{
  size_t newsize;
  void *ret = NIL;
  unsigned long i = msgno - 1;
  unsigned long j = stream->cachesize;
  switch ((int) op) {		/* what function? */
  case CH_INIT:			/* initialize cache */
    if (stream->cachesize) {	/* flush old cache contents */
      while (stream->cachesize) mm_cache (stream,stream->cachesize--,CH_FREE);
      fs_give ((void **) &stream->cache.c);
      stream->nmsgs = 0;	/* can't have any messages now */
    }
    break;
  case CH_SIZE:			/* (re-)size the cache */
    if (msgno > j) {		/* do nothing if size adequate */
      newsize = (stream->cachesize = msgno + CACHEINCREMENT) * sizeof (void *);
      if (stream->cache.c) fs_resize ((void **) &stream->cache.c,newsize);
      else stream->cache.c = (void **) fs_get (newsize);
				/* init cache */
      while (j < stream->cachesize) stream->cache.c[j++] = NIL;
    }
    break;
  case CH_MAKELELT:		/* return long elt, make if necessary */
    if (!stream->cache.c[i]) {	/* have one already? */
				/* no, instantiate it */
      stream->cache.l[i] = (LONGCACHE *) fs_get (sizeof (LONGCACHE));
      memset (&stream->cache.l[i]->elt,0,sizeof (MESSAGECACHE));
      stream->cache.l[i]->elt.lockcount = 1;
      stream->cache.l[i]->elt.msgno = msgno;
      stream->cache.l[i]->env = NIL;
      stream->cache.l[i]->body = NIL;
    }
				/* drop in to CH_LELT */
  case CH_LELT:			/* return long elt */
    ret = stream->cache.c[i];	/* return void version */
    break;

  case CH_MAKEELT:		/* return short elt, make if necessary */
    if (!stream->cache.c[i]) {	/* have one already? */
      if (stream->scache) {	/* short cache? */
	stream->cache.s[i] = (MESSAGECACHE *) fs_get (sizeof(MESSAGECACHE));
	memset (stream->cache.s[i],0,sizeof (MESSAGECACHE));
	stream->cache.s[i]->lockcount = 1;
	stream->cache.s[i]->msgno = msgno;
      }
      else mm_cache (stream,msgno,CH_MAKELELT);
    }
				/* drop in to CH_ELT */
  case CH_ELT:			/* return short elt */
    ret = stream->cache.c[i] && !stream->scache ?
      (void *) &stream->cache.l[i]->elt : stream->cache.c[i];
    break;
  case CH_FREE:			/* free (l)elt */
    if (stream->scache) mail_free_elt (&stream->cache.s[i]);
    else mail_free_lelt (&stream->cache.l[i]);
    break;
  case CH_EXPUNGE:		/* expunge cache slot */
				/* slide down remainder of cache */
    for (i = msgno; i < stream->nmsgs; ++i)
      if (stream->cache.c[i-1] = stream->cache.c[i])
	((MESSAGECACHE *) mm_cache (stream,i,CH_ELT))->msgno = i;
    stream->cache.c[stream->nmsgs-1] = NIL;
    break;
  default:
    fatal ("Bad mm_cache op");
    break;
  }
  return ret;
}

/* Dummy string driver for complete in-memory strings */

STRINGDRIVER mail_string = {
  mail_string_init,		/* initialize string structure */
  mail_string_next,		/* get next byte in string structure */
  mail_string_setpos		/* set position in string structure */
};


/* Initialize mail string structure for in-memory string
 * Accepts: string structure
 *	    pointer to string
 *	    size of string
 */

void mail_string_init (STRING *s,void *data,unsigned long size)
{
				/* set initial string pointers */
  s->chunk = s->curpos = (char *) (s->data = data);
				/* and sizes */
  s->size = s->chunksize = s->cursize = size;
  s->data1 = s->offset = 0;	/* never any offset */
}


/* Get next character from string
 * Accepts: string structure
 * Returns: character, string structure chunk refreshed
 */

char mail_string_next (STRING *s)
{
  return *s->curpos++;		/* return the last byte */
}


/* Set string pointer position
 * Accepts: string structure
 *	    new position
 */

void mail_string_setpos (STRING *s,unsigned long i)
{
  s->curpos = s->chunk + i;	/* set new position */
  s->cursize = s->chunksize - i;/* and new size */
}

/* Mail routines
 *
 *  mail_xxx routines are the interface between this module and the outside
 * world.  Only these routines should be referenced by external callers.
 *
 *  Note that there is an important difference between a "sequence" and a
 * "message #" (msgno).  A sequence is a string representing a sequence in
 * {"n", "n:m", or combination separated by commas} format, whereas a msgno
 * is a single integer.
 *
 */

/* Mail link driver
 * Accepts: driver to add to list
 */

void mail_link (DRIVER *driver)
{
  DRIVER **d = &maildrivers;
  while (*d) d = &(*d)->next;	/* find end of list of drivers */
  *d = driver;			/* put driver at the end */
  driver->next = NIL;		/* this driver is the end of the list */
}

/* Mail manipulate driver parameters
 * Accepts: mail stream
 *	    function code
 *	    function-dependent value
 * Returns: function-dependent return value
 */

void *mail_parameters (MAILSTREAM *stream,long function,void *value)
{
  void *r,*ret = NIL;
  DRIVER *d;
  switch ((int) function) {
  case SET_DRIVERS:
    fatal ("SET_DRIVERS not permitted");
  case GET_DRIVERS:
    ret = (void *) maildrivers;
    break;
  case SET_GETS:
    mailgets = (mailgets_t) value;
  case GET_GETS:
    ret = (void *) mailgets;
    break;
  case SET_CACHE:
    mailcache = (mailcache_t) value;
  case GET_CACHE:
    ret = (void *) mailcache;
    break;
  case ENABLE_DRIVER:
    for (d = maildrivers; d && strcmp (d->name,(char *) value); d = d->next);
    if (ret = (void *) d) d->flags &= ~DR_DISABLE;
    break;
  case DISABLE_DRIVER:
    for (d = maildrivers; d && strcmp (d->name,(char *) value); d = d->next);
    if (ret = (void *) d) d->flags |= DR_DISABLE;
    break;
  default:
    if (stream && stream->dtb)	/* if have stream, do for that stream only */
      ret = (*stream->dtb->parameters) (function,value);
				/* else do all drivers */
    else for (d = maildrivers; d; d = d->next)
      if (r = (d->parameters) (function,value)) ret = r;
				/* do environment */
    if (r = env_parameters (function,value)) ret = r;
				/* do TCP/IP */
    if (r = tcp_parameters (function,value)) ret = r;
    break;
  }
  return ret;
}

/* Mail validate mailbox name
 * Accepts: MAIL stream
 *	    mailbox name
 *	    purpose string for error message
 * Return: driver factory on success, NIL on failure
 */

DRIVER *mail_valid (MAILSTREAM *stream,char *mailbox,char *purpose)
{
  char tmp[MAILTMPLEN];
  DRIVER *factory;
  for (factory = maildrivers; factory && 
       ((factory->flags & DR_DISABLE) ||
	((factory->flags & DR_LOCAL) && (*mailbox == '{')) ||
	((!(factory->flags & DR_NAMESPACE)) && (*mailbox == '#')) ||
	!(*factory->valid) (mailbox));
       factory = factory->next);
				/* must match stream if not dummy */
  if (factory && stream && (stream->dtb != factory))
    factory = strcmp (factory->name,"dummy") ? NIL : stream->dtb;
  if (!factory && purpose) {	/* if want an error message */
    sprintf (tmp,"Can't %s %s: %s",purpose,mailbox,(*mailbox == '{') ?
	     "invalid remote specification" : "no such mailbox");
    mm_log (tmp,ERROR);
  }
  return factory;		/* return driver factory */
}

/* Mail validate network mailbox name
 * Accepts: mailbox name
 *	    mailbox driver to validate against
 *	    pointer to where to return host name if non-NIL
 *	    pointer to where to return mailbox name if non-NIL
 * Returns: driver on success, NIL on failure
 */

DRIVER *mail_valid_net (char *name,DRIVER *drv,char *host,char *mailbox)
{
  NETMBX mb;
  if (!mail_valid_net_parse (name,&mb) || 
      (strcmp (mb.service,"imap") ? strcmp (mb.service,drv->name) :
       strncmp (drv->name,"imap",4))) return NIL;
  if (host) strcpy (host,mb.host);
  if (mailbox) strcpy (mailbox,mb.mailbox);
  return drv;
}


/* Mail validate network mailbox name
 * Accepts: mailbox name
 *	    NETMBX structure to return values
 * Returns: T on success, NIL on failure
 */

long mail_valid_net_parse (char *name,NETMBX *mb)
{
  int i;
  char c,*s,*t,*v;
  mb->port = 0;			/* initialize structure */
  *mb->host = *mb->user = *mb->mailbox = *mb->service = '\0';
				/* init flags */
  mb->anoflag = mb->dbgflag = NIL;
				/* have host specification? */
  if (!(*name == '{' && (t = strchr (s = name+1,'}')) && (i = t - s) &&
	(i < NETMAXHOST) && (strlen (t+1) < (size_t) NETMAXMBX))) return NIL;
  strncpy (mb->host,s,i);	/* set host name */
  mb->host[i] = '\0';		/* tie it off */
  strcpy (mb->mailbox,t+1);	/* set mailbox name */
				/* any switches or port specification? */
  if (t = strpbrk (mb->host,"/:")) {
    c = *t;			/* yes, remember delimiter */
    *t++ = '\0';		/* tie off host name */
    lcase (t);			/* coerce remaining stuff to lowercase */
    do switch (c) {		/* act based upon the character */
    case ':':			/* port specification */
      if (mb->port || !(mb->port = strtoul (t,&t,10))) return NIL;
      c = t ? *t++ : '\0';	/* get delimiter, advance pointer */
      break;

    case '/':			/* switch */
				/* find delimiter */
      if (t = strpbrk (s = t,"/:=")) {
	c = *t;			/* remember delimiter for later */
	*t++ = '\0';		/* tie off switch name */
      }
      else c = '\0';		/* no delimiter */
      if (c == '=') {		/* parse switches which take arguments */
	if (t = strpbrk (v = t,"/:")) {
	  c = *t;		/* remember delimiter for later */
	  *t++ = '\0';		/* tie off switch name */
	}
	else c = '\0';		/* no delimiter */
	i = strlen (v);		/* length of argument */
	if (!strcmp (s,"service") && (i < NETMAXSRV)) {
	  if (*mb->service) return NIL;
	  else strcpy (mb->service,v);
	}
	if (!strcmp (s,"user") && (i < NETMAXUSER)) {
	  if (*mb->user) return NIL;
	  else strcpy (mb->user,v);
	}
	else return NIL;	/* invalid argument switch */
      }
      else {			/* non-argument switch */
	if (!strcmp (s,"anonymous")) mb->anoflag = T;
	else if (!strcmp (s,"debug")) mb->dbgflag = T;
				/* service switches below here */
	else if (*mb->service) return NIL;
	else if (!strcmp (s,"imap") || !strcmp (s,"imap2") ||
		 !strcmp (s,"imap4")) strcpy (mb->service,"imap");
	else if (!strcmp (s,"pop3") || !strcmp (s,"nntp"))
	  strcpy (mb->service,s);
	else return NIL;	/* invalid non-argument switch */
      }
      break;
    default:			/* anything else is bogus */
      return NIL;
    } while (c);		/* see if anything more to parse */
  }
				/* default mailbox name */
  if (!*mb->mailbox) strcpy (mb->mailbox,"INBOX");
				/* default service name */
  if (!*mb->service) strcpy (mb->service,"imap");
  return T;
}

/* Mail scan mailboxes for string
 * Accepts: mail stream
 *	    reference
 *	    pattern to search
 *	    contents to search
 */

void mail_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
{
  int remote = ((*pat == '{') || (ref && *ref == '{'));
  int ns = ((*pat == '#') || (ref && *ref == '#'));
  DRIVER *d;
  if (*pat == '{') ref = NIL;	/* ignore reference if pattern is remote */
  if (stream) {			/* if have a stream, do it for that stream */
    if ((d = stream->dtb) && d->scan &&
	!(((d->flags & DR_LOCAL) && remote) ||
	  ((!(d->flags & DR_NAMESPACE)) && ns)))
      (*d->scan) (stream,ref,pat,contents);
  }
				/* otherwise do for all DTB's */
  else for (d = maildrivers; d; d = d->next)
    if (d->scan && !((d->flags & DR_DISABLE) ||
		     ((d->flags & DR_LOCAL) && remote) ||
		     ((!(d->flags & DR_NAMESPACE)) && ns)))
      (d->scan) (NIL,ref,pat,contents);
}

/* Mail list mailboxes
 * Accepts: mail stream
 *	    reference
 *	    pattern to search
 */

void mail_list (MAILSTREAM *stream,char *ref,char *pat)
{
  int remote = ((*pat == '{') || (ref && *ref == '{'));
  int ns = ((*pat == '#') || (ref && *ref == '#'));
  DRIVER *d = maildrivers;
  if (*pat == '{') ref = NIL;	/* ignore reference if pattern is remote */
  if (stream && stream->dtb) {	/* if have a stream, do it for that stream */
    if (!((((d = stream->dtb)->flags & DR_LOCAL) && remote) ||
	  ((!(d->flags & DR_NAMESPACE)) && ns)))
      (*d->list) (stream,ref,pat);
  }
				/* otherwise do for all DTB's */
  else do if (!((d->flags & DR_DISABLE) ||
		((d->flags & DR_LOCAL) && remote) ||
		((!(d->flags & DR_NAMESPACE)) && ns)))
    (d->list) (NIL,ref,pat);
  while (d = d->next);		/* until at the end */
}


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

void mail_lsub (MAILSTREAM *stream,char *ref,char *pat)
{
  int remote = ((*pat == '{') || (ref && *ref == '{'));
  int ns = ((*pat == '#') || (ref && *ref == '#'));
  DRIVER *d = maildrivers;
  if (*pat == '{') ref = NIL;	/* ignore reference if pattern is remote */
  if (stream && stream->dtb) {	/* if have a stream, do it for that stream */
    if (!((((d = stream->dtb)->flags & DR_LOCAL) && remote) ||
	  ((!(d->flags & DR_NAMESPACE)) && ns)))
      (*d->lsub) (stream,ref,pat);
  }
				/* otherwise do for all DTB's */
  else do if (!((d->flags & DR_DISABLE) ||
		((d->flags & DR_LOCAL) && remote) ||
		((!(d->flags & DR_NAMESPACE)) && ns)))
    (d->lsub) (NIL,ref,pat);
  while (d = d->next);		/* until at the end */
}

/* Mail subscribe to mailbox
 * Accepts: mail stream
 *	    mailbox to add to subscription list
 * Returns: T on success, NIL on failure
 */

long mail_subscribe (MAILSTREAM *stream,char *mailbox)
{
  DRIVER *factory = mail_valid (stream,mailbox,"subscribe to mailbox");
  return factory ?
    (factory->subscribe ?
     (*factory->subscribe) (stream,mailbox) : sm_subscribe (mailbox)) : NIL;
}


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

long mail_unsubscribe (MAILSTREAM *stream,char *mailbox)
{
  DRIVER *factory = mail_valid (stream,mailbox,NIL);
  return (factory && factory->unsubscribe) ?
    (*factory->unsubscribe) (stream,mailbox) : sm_unsubscribe (mailbox);
}

/* Mail create mailbox
 * Accepts: mail stream
 *	    mailbox name to create
 * Returns: T on success, NIL on failure
 */

long mail_create (MAILSTREAM *stream,char *mailbox)
{
  /* A local mailbox is one that is not qualified as being a remote or a
   * namespace mailbox.  Any remote or namespace mailbox driver must check
   * for itself whether or not the mailbox already exists. */
  int remote = (*mailbox == '{');
  int ns = (*mailbox == '#');
  char tmp[MAILTMPLEN];
				/* guess at driver if stream not specified */
  if (!(stream || (stream = (remote || ns) ?
		   mail_open (NIL,mailbox,OP_PROTOTYPE | OP_SILENT) :
		   default_proto ()))) {
    sprintf (tmp,"Can't create mailbox %s: indeterminate format",mailbox);
    mm_log (tmp,ERROR);
    return NIL;
  }
				/* must not already exist if local */
  if (!remote && !ns && mail_valid (stream,mailbox,NIL)) {
    sprintf (tmp,"Can't create mailbox %s: mailbox already exists",mailbox);
    mm_log (tmp,ERROR);
    return NIL;
  }
  return stream->dtb ? (*stream->dtb->create) (stream,mailbox) : NIL;
}

/* Mail delete mailbox
 * Accepts: mail stream
 *	    mailbox name to delete
 * Returns: T on success, NIL on failure
 */

long mail_delete (MAILSTREAM *stream,char *mailbox)
{
  DRIVER *factory = mail_valid (stream,mailbox,"delete mailbox");
  return factory ? (*factory->mbxdel) (stream,mailbox) : NIL;
}


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

long mail_rename (MAILSTREAM *stream,char *old,char *newname)
{
  char tmp[MAILTMPLEN];
  DRIVER *factory = mail_valid (stream,old,"rename mailbox");
  if ((*old != '{') && (*old != '#') && mail_valid (NIL,newname,NIL)) {
    sprintf (tmp,"Can't rename to mailbox %s: mailbox already exists",newname);
    mm_log (tmp,ERROR);
    return NIL;
  }
  return factory ? (*factory->rename) (stream,old,newname) : NIL;
}

/* Mail status of mailbox
 * Accepts: mail stream
 *	    mailbox name
 *	    status flags
 * Returns: T on success, NIL on failure
 */

long mail_status (MAILSTREAM *stream,char *mbx,long flags)
{
  MAILSTATUS status;
  unsigned long i;
  MAILSTREAM *tstream;
  DRIVER *factory = mail_valid (stream,mbx,"get status of mailbox");
  if (!factory) return NIL;	/* bad name */
				/* use driver's routine if have one */
  if (factory->status) return (*factory->status) (stream,mbx,flags);
				/* make temporary stream (unless this mbx) */
  if (!(tstream = (stream && !strcmp (mbx,stream->mailbox)) ?
	stream : mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL;
  status.flags = flags;		/* return status values */
  status.messages = tstream->nmsgs;
  status.recent = tstream->recent;
  if (flags & SA_UNSEEN)	/* must search to get unseen messages */
    for (i = 1,status.unseen = 0; i <= tstream->nmsgs; i++)
      if (!mail_elt (tstream,i)->seen) status.unseen++;
  status.uidnext = tstream->uid_last + 1;
  status.uidvalidity = tstream->uid_validity;
				/* pass status to main program */
  mm_status (tstream,mbx,&status);
  if (stream != tstream) mail_close (tstream);
  return T;			/* success */
}

/* Mail open
 * Accepts: candidate stream for recycling
 *	    mailbox name
 *	    open options
 * Returns: stream to use on success, NIL on failure
 */

MAILSTREAM *mail_open (MAILSTREAM *stream,char *name,long options)
{
  int i;
  DRIVER *factory = mail_valid (NIL,name,(options & OP_SILENT) ?
				(char *) NIL : "open mailbox");
  if (factory) {		/* must have a factory */
    if (!stream) {		/* instantiate stream if none to recycle */
      if (options & OP_PROTOTYPE) return (*factory->open) (NIL);
      stream = (MAILSTREAM *) fs_get (sizeof (MAILSTREAM));
				/* initialize stream */
      memset ((void *) stream,0,sizeof (MAILSTREAM));
      stream->dtb = factory;	/* set dispatch */
				/* set mailbox name */
      stream->mailbox = cpystr (name);
				/* initialize cache */
      (*mailcache) (stream,(long) 0,CH_INIT);
				/* default UID validity */
      stream->uid_validity = time (0);
      stream->uid_last = 0;
    }
    else {			/* close driver if different from factory */
      if (stream->dtb != factory) {
	if (stream->dtb) (*stream->dtb->close) (stream,NIL);
	stream->dtb = factory;	/* establish factory as our driver */
	stream->local = NIL;	/* flush old driver's local data */
	mail_free_cache (stream);
				/* reset UID validity */
	stream->uid_validity = time (0);
	stream->uid_last = 0;
      }
				/* flush user flags */
      for (i = 0; i < NUSERFLAGS; i++)
	if (stream->user_flags[i])
	  fs_give ((void **) &stream->user_flags[i]);
				/* clean up old mailbox name for recycling */
      if (stream->mailbox) fs_give ((void **) &stream->mailbox);
      stream->mailbox = cpystr (name);
    }
    stream->lock = NIL;		/* initialize lock and options */
    stream->debug = (options & OP_DEBUG) ? T : NIL;
    stream->rdonly = (options & OP_READONLY) ? T : NIL;
    stream->anonymous = (options & OP_ANONYMOUS) ? T : NIL;
    stream->scache = (options & OP_SHORTCACHE) ? T : NIL;
    stream->silent = (options & OP_SILENT) ? T : NIL;
    stream->halfopen = (options & OP_HALFOPEN) ? T : NIL;
    stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
      stream->perm_answered = stream->perm_draft = stream->kwd_create = NIL;
				/* have driver open, flush if failed */
    if (!(*factory->open) (stream)) stream = mail_close (stream);
  }
  return stream;		/* return the stream */
}

/* Mail close
 * Accepts: mail stream
 *	    close options
 * Returns: NIL
 */

MAILSTREAM *mail_close_full (MAILSTREAM *stream,long options)
{
  int i;
  if (stream) {			/* make sure argument given */
				/* do the driver's close action */
    if (stream->dtb) (*stream->dtb->close) (stream,options);
    if (stream->mailbox) fs_give ((void **) &stream->mailbox);
    stream->sequence++;		/* invalidate sequence */
				/* flush user flags */
    for (i = 0; i < NUSERFLAGS; i++)
      if (stream->user_flags[i]) fs_give ((void **) &stream->user_flags[i]);
    mail_free_cache (stream);	/* finally free the stream's storage */
    if (!stream->use) fs_give ((void **) &stream);
  }
  return NIL;
}

/* Mail make handle
 * Accepts: mail stream
 * Returns: handle
 *
 *  Handles provide a way to have multiple pointers to a stream yet allow the
 * stream's owner to nuke it or recycle it.
 */

MAILHANDLE *mail_makehandle (MAILSTREAM *stream)
{
  MAILHANDLE *handle = (MAILHANDLE *) fs_get (sizeof (MAILHANDLE));
  handle->stream = stream;	/* copy stream */
				/* and its sequence */
  handle->sequence = stream->sequence;
  stream->use++;		/* let stream know another handle exists */
  return handle;
}


/* Mail release handle
 * Accepts: Mail handle
 */

void mail_free_handle (MAILHANDLE **handle)
{
  MAILSTREAM *s;
  if (*handle) {		/* only free if exists */
				/* resign stream, flush unreferenced zombies */
    if ((!--(s = (*handle)->stream)->use) && !s->dtb) fs_give ((void **) &s);
    fs_give ((void **) handle);	/* now flush the handle */
  }
}


/* Mail get stream handle
 * Accepts: Mail handle
 * Returns: mail stream or NIL if stream gone
 */

MAILSTREAM *mail_stream (MAILHANDLE *handle)
{
  MAILSTREAM *s = handle->stream;
  return (s->dtb && (handle->sequence == s->sequence)) ? s : NIL;
}

/* Mail fetch long cache element
 * Accepts: mail stream
 *	    message # to fetch
 * Returns: long cache element of this message
 * Can also be used to create cache elements for new messages.
 */

LONGCACHE *mail_lelt (MAILSTREAM *stream,unsigned long msgno)
{
  if (stream->scache) fatal ("Short cache in mail_lelt");
				/* be sure it the cache is large enough */
  (*mailcache) (stream,msgno,CH_SIZE);
  return (LONGCACHE *) (*mailcache) (stream,msgno,CH_MAKELELT);
}


/* Mail fetch cache element
 * Accepts: mail stream
 *	    message # to fetch
 * Returns: cache element of this message
 * Can also be used to create cache elements for new messages.
 */

MESSAGECACHE *mail_elt (MAILSTREAM *stream,unsigned long msgno)
{
  if (msgno < 1) fatal ("Bad msgno in mail_elt");
				/* be sure the cache is large enough */
  (*mailcache) (stream,msgno,CH_SIZE);
  return (MESSAGECACHE *) (*mailcache) (stream,msgno,CH_MAKEELT);
}

/* Mail fetch fast information
 * Accepts: mail stream
 *	    sequence
 *	    option flags
 *
 * Generally, mail_fetchstructure_full is preferred
 */

void mail_fetchfast_full (MAILSTREAM *stream,char *sequence,long flags)
{
  				/* do the driver's action */
  if (stream->dtb) (*stream->dtb->fetchfast) (stream,sequence,flags);
}


/* Mail fetch flags
 * Accepts: mail stream
 *	    sequence
 *	    option flags
 */

void mail_fetchflags_full (MAILSTREAM *stream,char *sequence,long flags)
{
  				/* do the driver's action */
  if (stream->dtb) (*stream->dtb->fetchflags) (stream,sequence,flags);
}


/* Mail fetch message 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 *mail_fetchstructure_full (MAILSTREAM *stream,unsigned long msgno,
				    BODY **body,long flags)
{
  if ((msgno < 1 || msgno > stream->nmsgs) && !(flags & FT_UID))
    fatal ("Bad msgno in mail_fetchstructure_full");
  return stream->dtb ?		/* do the driver's action */
    (*stream->dtb->fetchstructure) (stream,msgno,body,flags) :NIL;
}

/* Mail fetch message header
 * Accepts: mail stream
 *	    message # to fetch
 *	    list of lines to fetch
 *	    pointer to returned length
 *	    flags
 * Returns: message header in RFC822 format
 */

char *mail_fetchheader_full (MAILSTREAM *stream,unsigned long msgno,
			     STRINGLIST *lines,unsigned long *len,long flags)
{
  if ((msgno < 1 || msgno > stream->nmsgs) && !(flags & FT_UID))
    fatal ("Bad msgno in mail_fetchheader");
  return stream->dtb ?		/* do the driver's action */
    (*stream->dtb->fetchheader) (stream,msgno,lines,len,flags) : "";
}


/* Mail fetch message text (body only)
 * Accepts: mail stream
 *	    message # to fetch
 *	    pointer to returned length
 *	    flags
 * Returns: message text in RFC822 format
 */

char *mail_fetchtext_full (MAILSTREAM *stream,unsigned long msgno,
			   unsigned long *len,long flags)
{
  if ((msgno < 1 || msgno > stream->nmsgs) && !(flags & FT_UID))
    fatal ("Bad msgno in mail_fetchtext");
  				/* do the driver's action */
  return stream->dtb ? (*stream->dtb->fetchtext) (stream,msgno,len,flags) : "";
}


/* Mail fetch message body part text
 * Accepts: mail stream
 *	    message # to fetch
 *	    section specifier (#.#.#...#)
 *	    pointer to returned length
 *	    flags
 * Returns: pointer to section of message body
 */

char *mail_fetchbody_full (MAILSTREAM *stream,unsigned long msgno,char *sec,
			   unsigned long *len,long flags)
{
  if ((msgno < 1 || msgno > stream->nmsgs) && !(flags & FT_UID))
    fatal ("Bad msgno in mail_fetchbody");
  				/* do the driver's action */
  return stream->dtb ?
    (*stream->dtb->fetchbody) (stream,msgno,sec,len,flags) : "";
}

/* Mail fetch UID
 * Accepts: mail stream
 *	    message number
 * Returns: UID
 */

unsigned long mail_uid (MAILSTREAM *stream,unsigned long msgno)
{
  if (!stream->dtb) return 0;	/* error if dead stream */
  if (msgno < 1 || msgno > stream->nmsgs) fatal ("Bad msgno in mail_uid");
  				/* do the driver's action */
  return stream->dtb->uid ? (*stream->dtb->uid) (stream,msgno) :
    mail_elt (stream,msgno)->uid;
}


/* Mail fetch From string for menu
 * Accepts: destination string
 *	    mail stream
 *	    message # to fetch
 *	    desired string length
 * Returns: string of requested length
 */

void mail_fetchfrom (char *s,MAILSTREAM *stream,unsigned long msgno,
		     long length)
{
  char *t;
  char tmp[MAILTMPLEN];
  ENVELOPE *env = mail_fetchenvelope (stream,msgno);
  ADDRESS *adr = env ? env->from : NIL;
  memset (s,' ',(size_t)length);/* fill it with spaces */
  s[length] = '\0';		/* tie off with null */
				/* get first from address from envelope */
  while (adr && !adr->host) adr = adr->next;
  if (adr) {			/* if a personal name exists use it */
    if (!(t = adr->personal)) sprintf (t = tmp,"%s@%s",adr->mailbox,adr->host);
    memcpy (s,t,(size_t) min (length,(long) strlen (t)));
  }
}


/* Mail fetch Subject string for menu
 * Accepts: destination string
 *	    mail stream
 *	    message # to fetch
 *	    desired string length
 * Returns: string of no more than requested length
 */

void mail_fetchsubject (char *s,MAILSTREAM *stream,unsigned long msgno,
			long length)
{
  ENVELOPE *env = mail_fetchenvelope (stream,msgno);
  memset (s,'\0',(size_t) length+1);
				/* copy subject from envelope */
  if (env && env->subject) strncpy (s,env->subject,(size_t) length);
  else *s = ' ';		/* if no subject then just a space */
}

/* Mail set flag
 * Accepts: mail stream
 *	    sequence
 *	    flag(s)
 *	    option flags
 */

void mail_setflag_full (MAILSTREAM *stream,char *sequence,char *flag,
			long flags)
{
  				/* do the driver's action */
  if (stream->dtb) (*stream->dtb->setflag) (stream,sequence,flag,flags);
}


/* Mail clear flag
 * Accepts: mail stream
 *	    sequence
 *	    flag(s)
 *	    option flags
 */

void mail_clearflag_full (MAILSTREAM *stream,char *sequence,char *flag,
			  long flags)
{
  				/* do the driver's action */
  if (stream->dtb) (*stream->dtb->clearflag) (stream,sequence,flag,flags);
}


/* Mail search for messages
 * Accepts: mail stream
 *	    character set
 *	    search program
 *	    option flags
 */

void mail_search_full (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,
		       long flags)
{
  unsigned long i;
				/* clear search vector */
  for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = NIL;
  if (pgm && stream->dtb) {	/* must have a search program and driver */
				/* do the driver's action if requested */
    if (stream->dtb->search) (*stream->dtb->search) (stream,charset,pgm,flags);
    else for (i = 1; i <= stream->nmsgs; ++i)
      if (mail_search_msg (stream,i,charset,pgm)) {
	if (flags & SE_UID) mm_searched (stream,mail_uid (stream,i));
	else {			/* mark as searched, notify mail program */
	  mail_elt (stream,i)->searched = T;
	  mm_searched (stream,i);
	}
      }
  }
				/* flush search program if requested */
  if (flags & SE_FREE) mail_free_searchpgm (&pgm);
}

/* Mail ping mailbox
 * Accepts: mail stream
 * Returns: stream if still open else NIL
 */

long mail_ping (MAILSTREAM *stream)
{
  				/* do the driver's action */
  return stream->dtb ? (*stream->dtb->ping) (stream) : NIL;
}


/* Mail check mailbox
 * Accepts: mail stream
 */

void mail_check (MAILSTREAM *stream)
{
  				/* do the driver's action */
  if (stream->dtb) (*stream->dtb->check) (stream);
}


/* Mail expunge mailbox
 * Accepts: mail stream
 */

void mail_expunge (MAILSTREAM *stream)
{
  				/* do the driver's action */
  if (stream->dtb) (*stream->dtb->expunge) (stream);
}

/* Mail copy message(s)
 * Accepts: mail stream
 *	    sequence
 *	    destination mailbox
 *	    flags
 */

long mail_copy_full (MAILSTREAM *stream,char *sequence,char *mailbox,
		     long options)
{
  return stream->dtb ?		/* do the driver's action */
    (*stream->dtb->copy) (stream,sequence,mailbox,options) : NIL;
}


/* Mail append message string
 * Accepts: mail stream
 *	    destination mailbox
 *	    initial flags
 *	    message internal date
 *	    stringstruct of message to append
 * Returns: T on success, NIL on failure
 */

long mail_append_full (MAILSTREAM *stream,char *mailbox,char *flags,char *date,
		       STRING *message)
{
  DRIVER *factory = mail_valid (stream,mailbox,NIL);
  if (!factory) {		/* got a driver to use? */
				/* ask default for TRYCREATE if no stream */
    if (!stream && default_proto () &&
	(*default_proto ()->dtb->append) (stream,mailbox,flags,date,message)) {
				/* timing race? */
      mm_notify (stream,"Append validity confusion",WARN);
      return T;
    }
				/* now generate error message */
    mail_valid (stream,mailbox,"append to mailbox");
    return NIL;			/* return failure */
  }
				/* do the driver's action */
  return (factory->append) (stream,mailbox,flags,date,message);
}

/* Mail garbage collect stream
 * Accepts: mail stream
 *	    garbage collection flags
 */

void mail_gc (MAILSTREAM *stream,long gcflags)
{
  unsigned long i = 1;
  LONGCACHE *lelt;
  				/* do the driver's action first */
  if (stream->dtb) (*stream->dtb->gc) (stream,gcflags);
  if (gcflags & GC_ENV) {	/* garbage collect envelopes? */
				/* yes, free long cache if in use */
    if (!stream->scache) while (i <= stream->nmsgs)
      if (lelt = (LONGCACHE *) (*mailcache) (stream,i++,CH_LELT)) {
	mail_free_envelope (&lelt->env);
	mail_free_body (&lelt->body);
      }
    stream->msgno = 0;		/* free this cruft too */
    mail_free_envelope (&stream->env);
    mail_free_body (&stream->body);
  }
				/* free text if any */
  if ((gcflags & GC_TEXTS) && (stream->text)) fs_give ((void **)&stream->text);
}

/* Mail output date from elt fields
 * Accepts: character string to write into
 *	    elt to get data data from
 * Returns: the character string
 */

const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};

const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
			"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

char *mail_date (char *string,MESSAGECACHE *elt)
{
  const char *s = (elt->month && elt->month < 13) ?
    months[elt->month - 1] : (const char *) "???";
  sprintf (string,"%2d-%s-%d %02d:%02d:%02d %c%02d%02d",
	   elt->day,s,elt->year + BASEYEAR,
	   elt->hours,elt->minutes,elt->seconds,
	   elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes);
  return string;
}


/* Mail output cdate format date from elt fields
 * Accepts: character string to write into
 *	    elt to get data data from
 * Returns: the character string
 */

char *mail_cdate (char *string,MESSAGECACHE *elt)
{
  const char *s = (elt->month && elt->month < 13) ?
    months[elt->month - 1] : (const char *) "???";
  int m = elt->month;
  int y = elt->year + BASEYEAR;
  if (elt->month <= 2) {	/* if before March, */
    m = elt->month + 9;		/* January = month 10 of previous year */
    y--;
  }
  else m = elt->month - 3;	/* March is month 0 */
  sprintf (string,"%s %s %2d %02d:%02d:%02d %4d\n",
	   days[(int)(elt->day+2+((7+31*m)/12)+y+(y/4)+(y/400)-(y/100)) % 7],s,
	   elt->day,elt->hours,elt->minutes,elt->seconds,elt->year + BASEYEAR);
  return string;
}

/* Mail parse date into elt fields
 * Accepts: elt to write into
 *	    date string to parse
 * Returns: T if parse successful, else NIL 
 * This routine parses dates as follows:
 * . leading three alphas followed by comma and space are ignored
 * . date accepted in format: mm/dd/yy, mm/dd/yyyy, dd-mmm-yy, dd-mmm-yyyy,
 *    dd mmm yy, dd mmm yyyy
 * . space or end of string required
 * . time accepted in format hh:mm:ss or hh:mm
 * . end of string accepted
 * . timezone accepted: hyphen followed by symbolic timezone, or space
 *    followed by signed numeric timezone or symbolic timezone
 * Examples of normal input:
 * . IMAP date-only (SEARCH): dd-mmm-yy, dd-mmm-yyyy, mm/dd/yy, mm/dd/yyyy
 * . IMAP date-time (INTERNALDATE):
 *    dd-mmm-yy hh:mm:ss-zzz
 *    dd-mmm-yyyy hh:mm:ss +zzzz
 * . RFC-822:
 *    www, dd mmm yy hh:mm:ss zzz
 *    www, dd mmm yyyy hh:mm:ss +zzzz
 */

long mail_parse_date (MESSAGECACHE *elt,char *s)
{
  unsigned long d,m,y;
  int mi,ms;
  struct tm *t;
  time_t tn;
  char tmp[MAILTMPLEN];
				/* make a writeable uppercase copy */
  if (s && *s && (strlen (s) < (size_t)MAILTMPLEN)) s = ucase (strcpy (tmp,s));
  else return NIL;
				/* skip over possible day of week */
  if (isalpha (*s) && isalpha (s[1]) && isalpha (s[2]) && (s[3] == ',') &&
      (s[4] == ' ')) s += 5;
  if (*s == ' ') s++;		/* parse first number (probable month) */
  if (!(m = strtoul ((const char *) s,&s,10))) return NIL;

  switch (*s) {			/* different parse based on delimiter */
  case '/':			/* mm/dd/yy format */
    if (!((d = strtoul ((const char *) ++s,&s,10)) && *s == '/' &&
	  (y = strtoul ((const char *) ++s,&s,10)) && *s == '\0')) return NIL;
    break;
  case ' ':			/* dd mmm yy format */
  case '-':			/* dd-mmm-yy format */
    d = m;			/* so the number we got is a day */
				/* make sure string long enough! */
    if (strlen (s) < (size_t) 5) return NIL;
    /* Some compilers don't allow `<<' and/or longs in case statements. */
				/* slurp up the month string */
    ms = ((s[1] - 'A') * 1024) + ((s[2] - 'A') * 32) + (s[3] - 'A');
    switch (ms) {		/* determine the month */
    case (('J'-'A') * 1024) + (('A'-'A') * 32) + ('N'-'A'): m = 1; break;
    case (('F'-'A') * 1024) + (('E'-'A') * 32) + ('B'-'A'): m = 2; break;
    case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('R'-'A'): m = 3; break;
    case (('A'-'A') * 1024) + (('P'-'A') * 32) + ('R'-'A'): m = 4; break;
    case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('Y'-'A'): m = 5; break;
    case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('N'-'A'): m = 6; break;
    case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('L'-'A'): m = 7; break;
    case (('A'-'A') * 1024) + (('U'-'A') * 32) + ('G'-'A'): m = 8; break;
    case (('S'-'A') * 1024) + (('E'-'A') * 32) + ('P'-'A'): m = 9; break;
    case (('O'-'A') * 1024) + (('C'-'A') * 32) + ('T'-'A'): m = 10; break;
    case (('N'-'A') * 1024) + (('O'-'A') * 32) + ('V'-'A'): m = 11; break;
    case (('D'-'A') * 1024) + (('E'-'A') * 32) + ('C'-'A'): m = 12; break;
    default: return NIL;
    }
    if ((s[4] == *s) &&	(y = strtoul ((const char *) s+5,&s,10)) &&
	(*s == '\0' || *s == ' ')) break;
  default: return NIL;		/* unknown date format */
  }
				/* minimal validity check of date */
  if ((d > 31) || (m > 12)) return NIL; 
				/* Tenex/ARPAnet began in 1969 */
  if (y < 100) y += (y >= (BASEYEAR - 1900)) ? 1900 : 2000;
				/* set values in elt */
  elt->day = d; elt->month = m; elt->year = y - BASEYEAR;

  if (*s) {			/* time specification present? */
				/* parse time */
    d = strtoul ((const char *) s+1,&s,10);
    if (*s != ':') return NIL;
    m = strtoul ((const char *) ++s,&s,10);
    y = (*s == ':') ? strtoul ((const char *) ++s,&s,10) : 0;
				/* validity check time */
    if ((d > 23) || (m > 59) || (y > 59)) return NIL; 
				/* set values in elt */
    elt->hours = d; elt->minutes = m; elt->seconds = y;
    switch (*s) {		/* time zone specifier? */
    case ' ':			/* numeric time zone */
      if (!isalpha (s[1])) {	/* treat as '-' case if alphabetic */
				/* test for sign character */
	if ((elt->zoccident = (*++s == '-')) || (*s == '+')) s++;
				/* validate proper timezone */
	if (!(isdigit (*s) && isdigit (s[1]) && isdigit (s[2]) &&
	      isdigit (s[3])) || (s[4] && (s[4] != ' '))) return NIL;
	elt->zhours = (*s - '0') * 10 + (s[1] - '0');
	elt->zminutes = (s[2] - '0') * 10 + (s[3] - '0');
	break;
      }
				/* falls through */

    case '-':			/* symbolic time zone */
      if (!(ms = *++s)) return NIL;
      if (*++s) {		/* multi-character? */
	ms -= 'A'; ms *= 1024;	/* yes, make compressed three-byte form */
	ms += ((*s++ - 'A') * 32);
	if (*s) ms += *s++ - 'A';
	if (*s) return NIL;	/* more than three characters */
      }
      /* This is not intended to be a comprehensive list of all possible
       * timezone strings.  Such a list would be impractical.  Rather, this
       * listing is intended to incorporate all military, north American, and
       * a few special cases such as Japan and the major European zone names,
       * such as what might be expected to be found in a Tenex format mailbox
       * and spewed from an IMAP server.  The trend is to migrate to numeric
       * timezones which lack the flavor but also the ambiguity of the names.
       */
      switch (ms) {		/* determine the timezone */
	/* oriental (from Greenwich) timezones */
				/* Middle Europe */
      case (('M'-'A')*1024)+(('E'-'A')*32)+'T'-'A':
      case 'A': elt->zhours = 1; break;
				/* Eastern Europe */
      case (('E'-'A')*1024)+(('E'-'A')*32)+'T'-'A':
      case 'B': elt->zhours = 2; break;
      case 'C': elt->zhours = 3; break;
      case 'D': elt->zhours = 4; break;
      case 'E': elt->zhours = 5; break;
      case 'F': elt->zhours = 6; break;
      case 'G': elt->zhours = 7; break;
      case 'H': elt->zhours = 8; break;
				/* Japan */
      case (('J'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
      case 'I': elt->zhours = 9; break;
      case 'K': elt->zhours = 10; break;
      case 'L': elt->zhours = 11; break;
      case 'M': elt->zhours = 12; break;

	/* occidental (from Greenwich) timezones */
      case 'N': elt->zoccident = 1; elt->zhours = 1; break;
      case 'O': elt->zoccident = 1; elt->zhours = 2; break;
      case (('A'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
      case 'P': elt->zoccident = 1; elt->zhours = 3; break;
				/* Atlantic */
      case (('A'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
      case (('E'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
      case 'Q': elt->zoccident = 1; elt->zhours = 4; break;
				/* Eastern */
      case (('E'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
      case (('C'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
      case 'R': elt->zoccident = 1; elt->zhours = 5; break;
				/* Central */
      case (('C'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
      case (('M'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
      case 'S': elt->zoccident = 1; elt->zhours = 6; break;
				/* Mountain */
      case (('M'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
      case (('P'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
      case 'T': elt->zoccident = 1; elt->zhours = 7; break;
				/* Pacific */
      case (('P'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
      case (('Y'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
      case 'U': elt->zoccident = 1; elt->zhours = 8; break;
				/* Yukon */
      case (('Y'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
      case (('H'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
      case 'V': elt->zoccident = 1; elt->zhours = 9; break;
				/* Hawaii */
      case (('H'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
      case (('B'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
      case 'W': elt->zoccident = 1; elt->zhours = 10; break;
				/* Bering */
      case (('B'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
      case 'X': elt->zoccident = 1; elt->zhours = 11; break;
      case 'Y': elt->zoccident = 1; elt->zhours = 12; break;
				/* Universal */
      case (('U'-'A')*1024)+(('T'-'A')*32):
      case (('G'-'A')*1024)+(('M'-'A')*32)+'T'-'A':
      case 'Z': elt->zhours = 0; break;

      default:			/* assume local otherwise */
	tn = time (0);		/* time now... */
	t = localtime (&tn);	/* get local minutes since midnight */
	mi = t->tm_hour * 60 + t->tm_min;
	ms = t->tm_yday;	/* note Julian day */
	t = gmtime (&tn);	/* minus UTC minutes since midnight */
	mi -= t->tm_hour * 60 + t->tm_min;
	/* ms can be one of:
	 *  36x  local time is December 31, UTC is January 1, offset -24 hours
	 *    1  local time is 1 day ahead of UTC, offset +24 hours
	 *    0  local time is same day as UTC, no offset
	 *   -1  local time is 1 day behind UTC, offset -24 hours
	 * -36x  local time is January 1, UTC is December 31, offset +24 hours
	 */
	if (ms -= t->tm_yday)	/* correct offset if different Julian day */
	  mi += ((ms < 0) == (abs (ms) == 1)) ? -24*60 : 24*60;
	if (mi < 0) {		/* occidental? */
	  mi = abs (mi);	/* yup, make positive number */
	  elt->zoccident = 1;	/* and note west of UTC */
	}
	elt->zhours = mi / 60;	/* now break into hours and minutes */
	elt->zminutes = mi % 60;
	break;
      }
      elt->zminutes = 0;	/* never a fractional hour */
      break;
    case '\0':			/* no time zone */
      break;
    default:
      return NIL;
    }
  }
  else {			/* make sure all time fields zero */
    elt->hours = elt->minutes = elt->seconds = elt->zhours = elt->zminutes =
      elt->zoccident = 0;
  }
  return T;
}

/* Mail n messages exist
 * Accepts: mail stream
 *	    number of messages
 *
 * Calls external "mm_exists" function that notifies main program prior
 * to updating the stream
 */

void mail_exists (MAILSTREAM *stream,unsigned long nmsgs)
{
				/* make sure cache is large enough */
  (*mailcache) (stream,nmsgs,CH_SIZE);
				/* notify main program of change */
  if (!stream->silent) mm_exists (stream,nmsgs);
  stream->nmsgs = nmsgs;	/* update stream status */
}

/* Mail n messages are recent
 * Accepts: mail stream
 *	    number of recent messages
 */

void mail_recent (MAILSTREAM *stream,unsigned long recent)
{
  stream->recent = recent;	/* update stream status */
}


/* Mail message n is expunged
 * Accepts: mail stream
 *	    message #
 *
 * Calls external "mm_expunged" function that notifies main program prior
 * to updating the stream
 */

void mail_expunged (MAILSTREAM *stream,unsigned long msgno)
{
  MESSAGECACHE *elt = (MESSAGECACHE *) (*mailcache) (stream,msgno,CH_ELT);
  if (elt) {			/* if an element is there */
    elt->msgno = 0;		/* invalidate its message number and free */
    (*mailcache) (stream,msgno,CH_FREE);
  }
				/* expunge the slot */
  (*mailcache) (stream,msgno,CH_EXPUNGE);
  --stream->nmsgs;		/* update stream status */
  stream->msgno = 0;		/* nuke the short cache too */
  mail_free_envelope (&stream->env);
  mail_free_body (&stream->body);
				/* notify main program of change */
  if (!stream->silent) mm_expunged (stream,msgno);
}

/* Mail stream status routines */


/* Mail lock stream
 * Accepts: mail stream
 */

void mail_lock (MAILSTREAM *stream)
{
  if (stream->lock) fatal ("Lock when already locked");
  else stream->lock = T;	/* lock stream */
}


/* Mail unlock stream
 * Accepts: mail stream
 */
 
void mail_unlock (MAILSTREAM *stream)
{
  if (!stream->lock) fatal ("Unlock when not locked");
  else stream->lock = NIL;	/* unlock stream */
}


/* Mail turn on debugging telemetry
 * Accepts: mail stream
 */

void mail_debug (MAILSTREAM *stream)
{
  stream->debug = T;		/* turn on debugging telemetry */
}


/* Mail turn off debugging telemetry
 * Accepts: mail stream
 */

void mail_nodebug (MAILSTREAM *stream)
{
  stream->debug = NIL;		/* turn off debugging telemetry */
}

/* Mail parse UID sequence
 * Accepts: mail stream
 *	    sequence to parse
 * Returns: T if parse successful, else NIL
 */

long mail_uid_sequence (MAILSTREAM *stream,char *sequence)
{
  unsigned long i,j,k,x;
  for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->sequence = NIL;
  while (*sequence) {		/* while there is something to parse */
    if (*sequence == '*') {	/* maximum message */
      i = stream->nmsgs ? mail_uid (stream,stream->nmsgs) : stream->uid_last;
      sequence++;		/* skip past * */
    }
				/* parse and validate message number */
    else if (!(i = strtoul ((const char *) sequence,&sequence,10))) {
      mm_log ("UID sequence invalid",ERROR);
      return NIL;
    }
    switch (*sequence) {	/* see what the delimiter is */
    case ':':			/* sequence range */
      if (*++sequence == '*') {	/* maximum message */
	j = stream->nmsgs ? mail_uid (stream,stream->nmsgs) : stream->uid_last;
	sequence++;		/* skip past * */
      }
				/* parse end of range */
      else if (!(j = strtoul ((const char *) sequence,&sequence,10))) {
	mm_log ("UID sequence range invalid",ERROR);
	return NIL;
      }
      if (*sequence && *sequence++ != ',') {
	mm_log ("UID sequence range syntax error",ERROR);
	return NIL;
      }
      if (i > j) {		/* swap the range if backwards */
	x = i; i = j; j = x;
      }
				/* mark each item in the sequence */
      for (x = 1; x <= stream->nmsgs; x++) {
	if (((k = mail_uid (stream,x)) >= i) && (k <= j))
	  mail_elt (stream,x)->sequence = T;
      }
      break;
    case ',':			/* single message */
      ++sequence;		/* skip the delimiter, fall into end case */
    case '\0':			/* end of sequence, mark this message */
      for (x = 1; x <= stream->nmsgs; x++)
	if (i == mail_uid (stream,x)) mail_elt (stream,x)->sequence = T;
      break;
    default:			/* anything else is a syntax error! */
      mm_log ("UID sequence syntax error",ERROR);
      return NIL;
    }
  }
  return T;			/* successfully parsed sequence */
}

/* Mail filter text by header lines
 * Accepts: text to filter
 *	    length of text
 *	    list of lines
 *	    fetch flags
 * Returns: new text size
 */

unsigned long mail_filter (char *text,unsigned long len,STRINGLIST *lines,
			   long flags)
{
  STRINGLIST *hdrs;
  int notfound;
  unsigned long i;
  char c,*s,*t,tmp[MAILTMPLEN],tst[MAILTMPLEN];
  char *src = text;
  char *dst = src;
  char *end = text + len;
  while (src < end) {		/* process header */
				/* slurp header line name */
    for (s = src,t = tmp; (s < end) && (*s != ' ')  && (*s != '\t') &&
	 (*s != ':') && (*s != '\015') && (*s != '\012'); *t++ = *s++);
    *t = '\0';			/* tie off */
    notfound = T;		/* not found yet */
    if (i = t - ucase (tmp))	/* see if found in header */
      for (hdrs = lines; hdrs && notfound; hdrs = hdrs->next)
	if ((hdrs->size == i) &&/* header matches? */
	    !strncmp(tmp,ucase(strncpy(tst,hdrs->text,(size_t) i)),(size_t) i))
	  notfound = NIL;
				/* skip header line if not wanted */
    if (i && ((flags & FT_NOT) ? !notfound : notfound))
      while ((src < end) && ((*src++ != '\012') ||
			     ((*src == ' ') || (*src == '\t'))));
    else do c = *dst++ = *src++;
    while ((src < end) && ((c != '\012') ||
			   ((*src == ' ') || (*src == '\t'))));
  }
  *dst = '\0';			/* tie off destination */
  return dst - text;
}

/* Local mail search message
 * Accepts: MAIL stream
 *	    message number
 *	    search charset
 *	    search program
 * Returns: T if found, NIL otherwise
 */

static STRINGLIST *sstring =NIL;/* stringlist for searching */
static char *scharset = NIL;	/* charset for searching */

long mail_search_msg (MAILSTREAM *stream,unsigned long msgno,char *charset,
		      SEARCHPGM *pgm)
{
  unsigned short d;
  unsigned long i,uid;
  char *s;
  MESSAGECACHE *elt = mail_elt (stream,msgno);
  MESSAGECACHE delt;
  SEARCHHEADER *hdr;
  SEARCHSET *set;
  SEARCHOR *or;
  SEARCHPGMLIST *not;
				/* message sequences */
  if (set = pgm->msgno) {	/* must be inside this sequence */
    while (set) {		/* run down until find matching range */
      if (set->last ? ((msgno < set->first) || (msgno > set->last)) :
	  msgno != set->first) set = set->next;
      else break;
    }
    if (!set) return NIL;	/* not found within sequence */
  }
  if (set = pgm->uid) {		/* must be inside this sequence */
    uid = mail_uid (stream,msgno);
    while (set) {		/* run down until find matching range */
      if (set->last ? ((uid < set->first) || (uid > set->last)) :
	  uid != set->first) set = set->next;
      else break;
    }
    if (!set) return NIL;	/* not found within sequence */
  }
				/* require fast data for size ranges */
  if ((pgm->larger || pgm->smaller) && !elt->rfc822_size) {
    char tmp[MAILTMPLEN];
    sprintf (tmp,"%ld",elt->msgno);
    mail_fetchfast (stream,tmp);
  }
				/* size ranges */
  if ((pgm->larger && (elt->rfc822_size <= pgm->larger)) ||
      (pgm->smaller && (elt->rfc822_size >= pgm->smaller))) return NIL;
				/* message flags */
  if ((pgm->answered && !elt->answered) ||
      (pgm->unanswered && elt->answered) ||
      (pgm->deleted && !elt->deleted) ||
      (pgm->undeleted && elt->deleted) ||
      (pgm->draft && !elt->draft) ||
      (pgm->undraft && elt->draft) ||
      (pgm->flagged && !elt->flagged) ||
      (pgm->unflagged && elt->flagged) ||
      (pgm->recent && !elt->recent) ||
      (pgm->old && elt->recent) ||
      (pgm->seen && !elt->seen) ||
      (pgm->unseen && elt->seen)) return NIL;

				/* keywords */
  if (pgm->keyword && !mail_search_keyword (stream,elt,pgm->keyword))
    return NIL;
  if (pgm->unkeyword && mail_search_keyword (stream,elt,pgm->unkeyword))
    return NIL;
				/* sent date ranges */
  if (pgm->sentbefore || pgm->senton || pgm->sentsince) {
    ENVELOPE *env = mail_fetchenvelope (stream,msgno);
    if (!(env->date && mail_parse_date (&delt,env->date) &&
	  (d = (delt.year << 9) + (delt.month << 5) + delt.day))) return NIL;
    if (pgm->sentbefore && (d >= pgm->sentbefore)) return NIL;
    if (pgm->senton && (d != pgm->senton)) return NIL;
    if (pgm->sentsince && (d < pgm->sentsince)) return NIL;
  }
				/* internal date ranges */
  if (pgm->before || pgm->on || pgm->since) {
    if (!elt->year) {		/* make sure have fast data for dates */
      char tmp[MAILTMPLEN];
      sprintf (tmp,"%ld",elt->msgno);
      mail_fetchfast (stream,tmp);
    }
    d = (elt->year << 9) + (elt->month << 5) + elt->day;
    if (pgm->before && (d >= pgm->before)) return NIL;
    if (pgm->on && (d != pgm->on)) return NIL;
    if (pgm->since && (d < pgm->since)) return NIL;
  }
				/* search headers */
  if (pgm->bcc && !mail_search_addr (mail_fetchenvelope (stream,msgno)->bcc,
				     charset,pgm->bcc)) return NIL;
  if (pgm->cc && !mail_search_addr (mail_fetchenvelope (stream,msgno)->cc,
				    charset,pgm->cc)) return NIL;
  if (pgm->from && !mail_search_addr (mail_fetchenvelope (stream,msgno)->from,
				      charset,pgm->from)) return NIL;
  if (pgm->to && !mail_search_addr (mail_fetchenvelope (stream,msgno)->to,
				    charset,pgm->to)) return NIL;
  if (pgm->subject &&
      !mail_search_string (mail_fetchenvelope (stream,msgno)->subject,
			   charset,pgm->subject)) return NIL;
  if (hdr = pgm->header) {
    STRINGLIST sth,stc;
    sth.next = stc.next = NIL;	/* only one at a time */
    do {			/* check headers one at a time */
      sth.size = strlen (sth.text = hdr->line);
      stc.size = strlen (stc.text = hdr->text);
      s = mail_fetchheader_full (stream,msgno,&sth,&i,FT_INTERNAL);
      if (!mail_search_text (s,i,charset,&stc)) return NIL;
    }
    while (hdr = hdr->next);
  }

				/* search strings */
  if (stream->dtb->flags & DR_LOWMEM) {
    mailgets_t omg = (mailgets_t) mail_parameters (NIL,GET_GETS,NIL);
    mail_parameters (NIL,SET_GETS,(void *) mail_search_gets);
    if (scharset || sstring) fatal ("Re-entrant low-mem text search");
    scharset = charset;		/* pass down charset */
    if ((sstring = pgm->body) &&
	!mail_fetchtext_full (stream,msgno,NIL,FT_PEEK | FT_INTERNAL))
      return NIL;
    if ((sstring = pgm->text) &&
	!(mail_fetchheader_full (stream,msgno,NIL,NIL,NIL) ||
	  mail_fetchtext_full (stream,msgno,&i,FT_INTERNAL | FT_PEEK)))
      return NIL;
    sstring = NIL;		/* end search */
    scharset = NIL;
				/* restore former gets routine */
    mail_parameters (NIL,SET_GETS,(void *) omg);
  }
  else {
    if (pgm->body) {
      s = mail_fetchtext_full (stream,msgno,&i,FT_INTERNAL | FT_PEEK);
      if (!mail_search_text (s,i,charset,pgm->body)) return NIL;
    }
    if (pgm->text) {
      s = mail_fetchheader_full (stream,msgno,NIL,&i,FT_INTERNAL | FT_PEEK);
      if (!mail_search_text (s,i,charset,pgm->text) &&
	  (s = mail_fetchtext_full (stream,msgno,&i,FT_INTERNAL | FT_PEEK)) &&
	  !mail_search_text (s,i,charset,pgm->text)) return NIL;
    }
  }
  if (or = pgm->or) do
    if (!(mail_search_msg (stream,msgno,charset,pgm->or->first) ||
	  mail_search_msg (stream,msgno,charset,pgm->or->second))) return NIL;
  while (or = or->next);
  if (not = pgm->not) do if (mail_search_msg (stream,msgno,charset,not->pgm))
    return NIL;
  while (not = not->next);
  return T;
}

/* Mail search keyword
 * Accepts: MAIL stream
 *	    elt to get flags from
 *	    keyword list
 * Returns: T if search found a match
 */

long mail_search_keyword (MAILSTREAM *stream,MESSAGECACHE *elt,STRINGLIST *st)
{
  int i;
  char tmp[MAILTMPLEN],tmp2[MAILTMPLEN];
  do {				/* get uppercase form of flag */
    ucase (strcpy (tmp,st->text));
    for (i = 0;; ++i) {		/* check each possible keyword */
      if (i < NUSERFLAGS && stream->user_flags[i]) {
	if ((elt->user_flags & (1 << i)) &&
	    !strcmp (tmp,ucase (strcpy (tmp2,stream->user_flags[i])))) break;
      }
      else return NIL;
    }
  }
  while (st = st->next);	/* try next keyword */
  return T;
}


/* Mail search an address list
 * Accepts: address list
 *	    character set
 *	    string list
 * Returns: T if search found a match
 */

long mail_search_addr (ADDRESS *adr,char *charset,STRINGLIST *st)
{
  char t[8*MAILTMPLEN];
  t[0] = '\0';			/* initially empty string */
  rfc822_write_address (t,adr);	/* get text for address */
  return mail_search_string (t,charset,st);
}


/* Mail search string
 * Accepts: text string
 *	    character set
 *	    string list
 * Returns: T if search found a match
 */

long mail_search_string (char *txt,char *charset,STRINGLIST *st)
{
  return txt ? mail_search_text (txt,(long) strlen (txt),charset,st) : NIL;
}

/* Get string for searching
 * Accepts: readin function pointer
 *	    stream to use
 *	    number of bytes
 * Returns: non-NIL if error
 */

#define SEARCHSLOP 128

char *mail_search_gets (readfn_t f,void *stream,unsigned long size)
{
  char tmp[MAILTMPLEN+SEARCHSLOP+1];
  char *ret = NIL;
  unsigned long i;
				/* make sure buffer clear */
  memset (tmp,'\0',(size_t) MAILTMPLEN+SEARCHSLOP+1);
				/* read first buffer */
  (*f) (stream,i = min (size,(long) MAILTMPLEN),tmp);
				/* search for text */
  if (mail_search_text (tmp,i,scharset,sstring)) ret = "ok";
  if (size -= i) {		/* more to do, blat slop down */
    if (!ret) memmove (tmp,tmp+MAILTMPLEN-SEARCHSLOP,(size_t) SEARCHSLOP);
    do {			/* read subsequent buffers one at a time */
      (*f) (stream,i = min (size,(long) MAILTMPLEN),tmp+SEARCHSLOP);
      if (!ret && mail_search_text (tmp,i+SEARCHSLOP,scharset,sstring)) {
	ret = "ok";
	memmove (tmp,tmp+MAILTMPLEN,(size_t) SEARCHSLOP);
      }
    }
    while (size -= i);
  }
  return ret;
}


/* Mail search text
 * Accepts: text string
 *	    text length
 *	    character set
 *	    string list
 * Returns: T if search found a match
 */

long mail_search_text (char *txt,long len,char *charset,STRINGLIST *st)
{
  char tmp[MAILTMPLEN];
  if (!(charset && *charset &&	/* if US-ASCII search */
	strcmp (ucase (strcpy (tmp,charset)),"US-ASCII"))) {
    do if (!search (txt,len,st->text,st->size)) return NIL;
    while (st = st->next);
    return T;
  }
  sprintf (tmp,"Unknown character set %s",charset);
  mm_log (tmp,ERROR);
  return NIL;			/* not found */
}

/* Mail parse search criteria
 * Accepts: criteria
 * Returns: search program if parse successful, else NIL
 */

SEARCHPGM *mail_criteria (char *criteria)
{
  SEARCHPGM *pgm;
  char tmp[MAILTMPLEN];
  int f = NIL;
  if (!criteria) return NIL;	/* return if no criteria */
  pgm = mail_newsearchpgm ();	/* make a basic search program */
				/* for each criterion */
  for (criteria = strtok (criteria," "); criteria;
       (criteria = strtok (NIL," "))) {
    f = NIL;			/* init then scan the criterion */
    switch (*ucase (criteria)) {
    case 'A':			/* possible ALL, ANSWERED */
      if (!strcmp (criteria+1,"LL")) f = T;
      else if (!strcmp (criteria+1,"NSWERED")) f = pgm->answered = T;
      break;
    case 'B':			/* possible BCC, BEFORE, BODY */
      if (!strcmp (criteria+1,"CC")) f = mail_criteria_string (&pgm->bcc);
      else if (!strcmp (criteria+1,"EFORE"))
	f = mail_criteria_date (&pgm->before);
      else if (!strcmp (criteria+1,"ODY")) f=mail_criteria_string (&pgm->body);
      break;
    case 'C':			/* possible CC */
      if (!strcmp (criteria+1,"C")) f = mail_criteria_string (&pgm->cc);
      break;
    case 'D':			/* possible DELETED */
      if (!strcmp (criteria+1,"ELETED")) f = pgm->deleted = T;
      break;
    case 'F':			/* possible FLAGGED, FROM */
      if (!strcmp (criteria+1,"LAGGED")) f = pgm->flagged = T;
      else if (!strcmp (criteria+1,"ROM")) f=mail_criteria_string (&pgm->from);
      break;
    case 'K':			/* possible KEYWORD */
      if (!strcmp (criteria+1,"EYWORD"))
	f = mail_criteria_string (&pgm->keyword);
      break;

    case 'N':			/* possible NEW */
      if (!strcmp (criteria+1,"EW")) f = pgm->recent = pgm->unseen = T;
      break;
    case 'O':			/* possible OLD, ON */
      if (!strcmp (criteria+1,"LD")) f = pgm->old = T;
      else if (!strcmp (criteria+1,"N")) f = mail_criteria_date (&pgm->on);
      break;
    case 'R':			/* possible RECENT */
      if (!strcmp (criteria+1,"ECENT")) f = pgm->recent = T;
      break;
    case 'S':			/* possible SEEN, SINCE, SUBJECT */
      if (!strcmp (criteria+1,"EEN")) f = pgm->seen = T;
      else if (!strcmp (criteria+1,"INCE")) f=mail_criteria_date (&pgm->since);
      else if (!strcmp (criteria+1,"UBJECT"))
	f = mail_criteria_string (&pgm->subject);
      break;
    case 'T':			/* possible TEXT, TO */
      if (!strcmp (criteria+1,"EXT")) f = mail_criteria_string (&pgm->text);
      else if (!strcmp (criteria+1,"O")) f = mail_criteria_string (&pgm->to);
      break;
    case 'U':			/* possible UN* */
      if (criteria[1] == 'N') {
	if (!strcmp (criteria+2,"ANSWERED")) f = pgm->unanswered = T;
	else if (!strcmp (criteria+2,"DELETED")) f = pgm->undeleted = T;
	else if (!strcmp (criteria+2,"FLAGGED")) f = pgm->unflagged = T;
	else if (!strcmp (criteria+2,"KEYWORD"))
	  f = mail_criteria_string (&pgm->unkeyword);
	else if (!strcmp (criteria+2,"SEEN")) f = pgm->unseen = T;
      }
      break;
    default:			/* we will barf below */
      break;
    }
    if (!f) {			/* if can't determine any criteria */
      sprintf (tmp,"Unknown search criterion: %.30s",criteria);
      mm_log (tmp,ERROR);
      mail_free_searchpgm (&pgm);
      break;
    }
  }
  return pgm;
}

/* Parse a date
 * Accepts: pointer to date integer to return
 * Returns: T if successful, else NIL
 */

int mail_criteria_date (unsigned short *date)
{
  STRINGLIST *s = NIL;
  MESSAGECACHE elt;
				/* parse the date and return fn if OK */
  int ret = (mail_criteria_string (&s) && mail_parse_date (&elt,s->text) &&
	     (*date = (elt.year << 9) + (elt.month << 5) + elt.day)) ? T : NIL;
  if (s) mail_free_stringlist (&s);
  return ret;
}


/* Parse a string
 * Accepts: pointer to stringlist
 * Returns: T if successful, else NIL
 */

int mail_criteria_string (STRINGLIST **s)
{
  unsigned long n;
  char e,*d,*end = " ",*c = strtok (NIL,"");
  if (!c) return NIL;		/* missing argument */
  switch (*c) {			/* see what the argument is */
  case '{':			/* literal string */
    n = strtoul (c+1,&d,10);	/* get its length */
    if ((*d++ == '}') && (*d++ == '\015') && (*d++ == '\012') &&
	(!(*(c = d + n)) || (*c == ' '))) {
      e = *--c;			/* store old delimiter */
      *c = DELIM;		/* make sure not a space */
      strtok (c," ");		/* reset the strtok mechanism */
      *c = e;			/* put character back */
      break;
    }
  case '\0':			/* catch bogons */
  case ' ':
    return NIL;
  case '"':			/* quoted string */
    if (strchr (c+1,'"')) end = "\"";
    else return NIL;		/* falls through */
  default:			/* atomic string */
    if (d = strtok (c,end)) n = strlen (d);
    else return NIL;
    break;
  }
  while (*s) s = &(*s)->next;	/* find tail of list */
  *s = mail_newstringlist ();	/* make new entry */
  (*s)->text = cpystr (d);	/* return the data */
  (*s)->size = n;
  return T;
}

/* Mail parse sequence
 * Accepts: mail stream
 *	    sequence to parse
 * Returns: T if parse successful, else NIL
 */

long mail_sequence (MAILSTREAM *stream,char *sequence)
{
  unsigned long i,j,x;
  for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->sequence = NIL;
  while (*sequence) {		/* while there is something to parse */
    if (*sequence == '*') {	/* maximum message */
      if (stream->nmsgs) i = stream->nmsgs;
      else {
	mm_log ("No messages, so no maximum message number",ERROR);
	return NIL;
      }
      sequence++;		/* skip past * */
    }
				/* parse and validate message number */
    else if (!(i = strtoul ((const char *) sequence,&sequence,10)) ||
	     (i > stream->nmsgs)) {
      mm_log ("Sequence invalid",ERROR);
      return NIL;
    }
    switch (*sequence) {	/* see what the delimiter is */
    case ':':			/* sequence range */
      if (*++sequence == '*') {	/* maximum message */
	if (stream->nmsgs) j = stream->nmsgs;
	else {
	  mm_log ("No messages, so no maximum message number",ERROR);
	  return NIL;
	}
	sequence++;		/* skip past * */
      }
				/* parse end of range */
      else if (!(j = strtoul ((const char *) sequence,&sequence,10)) ||
	       (j > stream->nmsgs)) {
	mm_log ("Sequence range invalid",ERROR);
	return NIL;
      }
      if (*sequence && *sequence++ != ',') {
	mm_log ("Sequence range syntax error",ERROR);
	return NIL;
      }
      if (i > j) {		/* swap the range if backwards */
	x = i; i = j; j = x;
      }
				/* mark each item in the sequence */
      while (i <= j) mail_elt (stream,j--)->sequence = T;
      break;
    case ',':			/* single message */
      ++sequence;		/* skip the delimiter, fall into end case */
    case '\0':			/* end of sequence, mark this message */
      mail_elt (stream,i)->sequence = T;
      break;
    default:			/* anything else is a syntax error! */
      mm_log ("Sequence syntax error",ERROR);
      return NIL;
    }
  }
  return T;			/* successfully parsed sequence */
}

/* Parse flag list
 * Accepts: MAIL stream
 *	    flag list as a character string
 *	    pointer to user flags to return
 * Returns: system flags
 */

long mail_parse_flags (MAILSTREAM *stream,char *flag,unsigned long *uf)
{
  char *t,*n,*s,tmp[MAILTMPLEN],flg[MAILTMPLEN],key[MAILTMPLEN];
  short f = 0;
  long i;
  short j;
  *uf = 0;			/* initially no user flags */
  if (flag && *flag) {		/* no-op if no flag string */
				/* check if a list and make sure valid */
    if ((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) {
      mm_log ("Bad flag list",ERROR);
      return NIL;
    }
				/* copy the flag string w/o list construct */
    strncpy (n = tmp,flag+i,(j = strlen (flag) - (2*i)));
    tmp[j] = '\0';
    while ((t = n) && *t) {	/* parse the flags */
      i = 0;			/* flag not known yet */
				/* find end of flag */
      if (n = strchr (t,' ')) *n++ = '\0';
      ucase (strcpy (flg,t));
      if (flg[0] == '\\') {	/* system flag? */
	switch (flg[1]) {	/* dispatch based on first character */
	case 'S':		/* possible \Seen flag */
	  if (flg[2] == 'E' && flg[3] == 'E' && flg[4] == 'N' && !flg[5])
	    i = fSEEN;
	  break;
	case 'D':		/* possible \Deleted or \Draft flag */
	  if (flg[2] == 'E' && flg[3] == 'L' && flg[4] == 'E' &&
	      flg[5] == 'T' && flg[6] == 'E' && flg[7] == 'D' && !flg[8])
	    i = fDELETED;
	  else if (flg[2] == 'R' && flg[3] == 'A' && flg[4] == 'F' &&
		   flg[5] == 'T' && !flg[6]) i = fDRAFT;
	  break;
	case 'F':		/* possible \Flagged flag */
	  if (flg[2] == 'L' && flg[3] == 'A' && flg[4] == 'G' &&
	      flg[5] == 'G' && flg[6] == 'E' && flg[7] == 'D' && !flg[8])
	    i = fFLAGGED;
	  break;
	case 'A':		/* possible \Answered flag */
	  if (flg[2] == 'N' && flg[3] == 'S' && flg[4] == 'W' &&
	      flg[5] == 'E' && flg[6] == 'R' && flg[7] == 'E' &&
	      flg[8] == 'D' && !flg[9]) i = fANSWERED;
	  break;
	default:		/* unknown */
	  break;
	}
	if (i) f |= i;		/* add flag to flags list */
      }
				/* user flag, search through table */
      else for (j = 0; !i && j < NUSERFLAGS && (s =stream->user_flags[j]); ++j)
	if (!strcmp (flg,ucase (strcpy (key,s)))) *uf |= i = 1 << j;
      if (!i) {			/* didn't find a matching flag? */
				/* can we create it? */
	if (stream->kwd_create && (j < NUSERFLAGS)) {
	  *uf |= 1 << j;	/* set the bit */
	  stream->user_flags[j] = cpystr (t);
				/* if out of user flags */
	  if (j == NUSERFLAGS - 1) stream->kwd_create = NIL;
	}
	else {
	  sprintf (key,"Unknown flag: %.80s",t);
	  mm_log (key,ERROR);
	}
      }
    }
  }
  return f;
}

/* Mail data structure instantiation routines */


/* Mail instantiate envelope
 * Returns: new envelope
 */

ENVELOPE *mail_newenvelope (void)
{
  return (ENVELOPE *) memset (fs_get (sizeof (ENVELOPE)),0,sizeof (ENVELOPE));
}


/* Mail instantiate address
 * Returns: new address
 */

ADDRESS *mail_newaddr (void)
{
  return (ADDRESS *) memset (fs_get (sizeof (ADDRESS)),0,sizeof (ADDRESS));
}

/* Mail instantiate body
 * Returns: new body
 */

BODY *mail_newbody (void)
{
  return mail_initbody ((BODY *) fs_get (sizeof (BODY)));
}


/* Mail initialize body
 * Accepts: body
 * Returns: body
 */

BODY *mail_initbody (BODY *body)
{
  memset ((void *) body,0,sizeof (BODY));
  body->type = TYPETEXT;	/* content type */
  body->encoding = ENC7BIT;	/* content encoding */
  return body;
}


/* Mail instantiate body parameter
 * Returns: new body part
 */

PARAMETER *mail_newbody_parameter (void)
{
  return (PARAMETER *) memset (fs_get (sizeof(PARAMETER)),0,sizeof(PARAMETER));
}


/* Mail instantiate body part
 * Returns: new body part
 */

PART *mail_newbody_part (void)
{
  PART *part = (PART *) memset (fs_get (sizeof (PART)),0,sizeof (PART));
  mail_initbody (&part->body);	/* initialize the body */
  return part;
}


/* Mail instantiate string list
 * Returns: new string list
 */

STRINGLIST *mail_newstringlist (void)
{
  return (STRINGLIST *) memset (fs_get (sizeof (STRINGLIST)),0,
				sizeof (STRINGLIST));
}

/* Mail instantiate new search program
 * Returns: new search program
 */

SEARCHPGM *mail_newsearchpgm (void)
{
  return (SEARCHPGM *) memset (fs_get (sizeof(SEARCHPGM)),0,sizeof(SEARCHPGM));
}


/* Mail instantiate new search program
 * Accepts: header line name   
 * Returns: new search program
 */

SEARCHHEADER *mail_newsearchheader (char *line)
{
  SEARCHHEADER *hdr = (SEARCHHEADER *) memset (fs_get (sizeof (SEARCHHEADER)),
					       0,sizeof (SEARCHHEADER));
  hdr->line = cpystr (line);	/* not defined */
  return hdr;
}


/* Mail instantiate new search set
 * Returns: new search set
 */

SEARCHSET *mail_newsearchset (void)
{
  return (SEARCHSET *) memset (fs_get (sizeof(SEARCHSET)),0,sizeof(SEARCHSET));
}


/* Mail instantiate new search or
 * Returns: new search or
 */

SEARCHOR *mail_newsearchor (void)
{
  SEARCHOR *or = (SEARCHOR *) memset (fs_get (sizeof (SEARCHOR)),0,
				      sizeof (SEARCHOR));
  or->first = mail_newsearchpgm ();
  or->second = mail_newsearchpgm ();
  return or;
}


/* Mail instantiate new searchpgmlist
 * Returns: new searchpgmlist
 */

SEARCHPGMLIST *mail_newsearchpgmlist (void)
{
  SEARCHPGMLIST *pgl = (SEARCHPGMLIST *)
    memset (fs_get (sizeof (SEARCHPGMLIST)),0,sizeof (SEARCHPGMLIST));
  pgl->pgm = mail_newsearchpgm ();
  return pgl;
}

/* Mail garbage collection routines */


/* Mail garbage collect body
 * Accepts: pointer to body pointer
 */

void mail_free_body (BODY **body)
{
  if (*body) {			/* only free if exists */
    mail_free_body_data (*body);/* free its data */
    fs_give ((void **) body);	/* return body to free storage */
  }
}


/* Mail garbage collect body data
 * Accepts: body pointer
 */

void mail_free_body_data (BODY *body)
{
  if (body->subtype) fs_give ((void **) &body->subtype);
  mail_free_body_parameter (&body->parameter);
  if (body->id) fs_give ((void **) &body->id);
  if (body->description) fs_give ((void **) &body->description);
  switch (body->type) {		/* free contents */
  case TYPETEXT:		/* unformatted text */
    if (body->contents.text) fs_give ((void **) &body->contents.text);
    break;
  case TYPEMULTIPART:		/* multiple part */
    mail_free_body_part (&body->contents.part);
    break;
  case TYPEMESSAGE:		/* encapsulated message */
    mail_free_envelope (&body->contents.msg.env);
    mail_free_body (&body->contents.msg.body);
    if (body->contents.msg.text)
      fs_give ((void **) &body->contents.msg.text);
    break;
  case TYPEAPPLICATION:		/* application data */
  case TYPEAUDIO:		/* audio */
  case TYPEIMAGE:		/* static image */
  case TYPEVIDEO:		/* video */
    if (body->contents.binary) fs_give (&body->contents.binary);
    break;
  default:
    break;
  }
}

/* Mail garbage collect body parameter
 * Accepts: pointer to body parameter pointer
 */

void mail_free_body_parameter (PARAMETER **parameter)
{
  if (*parameter) {		/* only free if exists */
    if ((*parameter)->attribute) fs_give ((void **) &(*parameter)->attribute);
    if ((*parameter)->value) fs_give ((void **) &(*parameter)->value);
				/* run down the list as necessary */
    mail_free_body_parameter (&(*parameter)->next);
				/* return body part to free storage */
    fs_give ((void **) parameter);
  }
}


/* Mail garbage collect body part
 * Accepts: pointer to body part pointer
 */

void mail_free_body_part (PART **part)
{
  if (*part) {			/* only free if exists */
    mail_free_body_data (&(*part)->body);
				/* run down the list as necessary */
    mail_free_body_part (&(*part)->next);
    fs_give ((void **) part);	/* return body part to free storage */
  }
}

/* Mail garbage collect message cache
 * Accepts: mail stream
 *
 * The message cache is set to NIL when this function finishes.
 */

void mail_free_cache (MAILSTREAM *stream)
{
				/* flush the cache */
  (*mailcache) (stream,(long) 0,CH_INIT);
  stream->msgno = 0;		/* free this cruft too */
  mail_free_envelope (&stream->env);
  mail_free_body (&stream->body);
  if (stream->text) fs_give ((void **) &stream->text);
}


/* Mail garbage collect cache element
 * Accepts: pointer to cache element pointer
 */

void mail_free_elt (MESSAGECACHE **elt)
{
				/* only free if exists and no sharers */
  if (*elt && !--(*elt)->lockcount) fs_give ((void **) elt);
  else *elt = NIL;		/* else simply drop pointer */
}


/* Mail garbage collect long cache element
 * Accepts: pointer to long cache element pointer
 */

void mail_free_lelt (LONGCACHE **lelt)
{
				/* only free if exists and no sharers */
  if (*lelt && !--(*lelt)->elt.lockcount) {
    mail_free_envelope (&(*lelt)->env);
    mail_free_body (&(*lelt)->body);
    fs_give ((void **) lelt);	/* return cache element to free storage */
  }
  else *lelt = NIL;		/* else simply drop pointer */
}

/* Mail garbage collect envelope
 * Accepts: pointer to envelope pointer
 */

void mail_free_envelope (ENVELOPE **env)
{
  if (*env) {			/* only free if exists */
    if ((*env)->remail) fs_give ((void **) &(*env)->remail);
    mail_free_address (&(*env)->return_path);
    if ((*env)->date) fs_give ((void **) &(*env)->date);
    mail_free_address (&(*env)->from);
    mail_free_address (&(*env)->sender);
    mail_free_address (&(*env)->reply_to);
    if ((*env)->subject) fs_give ((void **) &(*env)->subject);
    mail_free_address (&(*env)->to);
    mail_free_address (&(*env)->cc);
    mail_free_address (&(*env)->bcc);
    if ((*env)->in_reply_to) fs_give ((void **) &(*env)->in_reply_to);
    if ((*env)->message_id) fs_give ((void **) &(*env)->message_id);
    if ((*env)->newsgroups) fs_give ((void **) &(*env)->newsgroups);
    if ((*env)->followup_to) fs_give ((void **) &(*env)->followup_to);
    if ((*env)->references) fs_give ((void **) &(*env)->references);
    fs_give ((void **) env);	/* return envelope to free storage */
  }
}


/* Mail garbage collect address
 * Accepts: pointer to address pointer
 */

void mail_free_address (ADDRESS **address)
{
  if (*address) {		/* only free if exists */
    if ((*address)->personal) fs_give ((void **) &(*address)->personal);
    if ((*address)->adl) fs_give ((void **) &(*address)->adl);
    if ((*address)->mailbox) fs_give ((void **) &(*address)->mailbox);
    if ((*address)->host) fs_give ((void **) &(*address)->host);
    if ((*address)->error) fs_give ((void **) &(*address)->error);
    mail_free_address (&(*address)->next);
    fs_give ((void **) address);/* return address to free storage */
  }
}


/* Mail garbage collect stringlist
 * Accepts: pointer to stringlist pointer
 */

void mail_free_stringlist (STRINGLIST **string)
{
  if (*string) {		/* only free if exists */
    if ((*string)->text) fs_give ((void **) &(*string)->text);
    mail_free_stringlist (&(*string)->next);
    fs_give ((void **) string);	/* return string to free storage */
  }
}

/* Mail garbage collect searchpgm
 * Accepts: pointer to searchpgm pointer
 */

void mail_free_searchpgm (SEARCHPGM **pgm)
{
  if (*pgm) {			/* only free if exists */
    mail_free_searchset (&(*pgm)->msgno);
    mail_free_searchset (&(*pgm)->uid);
    mail_free_searchor (&(*pgm)->or);
    mail_free_searchpgmlist (&(*pgm)->not);
    mail_free_searchheader (&(*pgm)->header);
    mail_free_stringlist (&(*pgm)->bcc);
    mail_free_stringlist (&(*pgm)->body);
    mail_free_stringlist (&(*pgm)->cc);
    mail_free_stringlist (&(*pgm)->from);
    mail_free_stringlist (&(*pgm)->keyword);
    mail_free_stringlist (&(*pgm)->subject);
    mail_free_stringlist (&(*pgm)->text);
    mail_free_stringlist (&(*pgm)->to);
    fs_give ((void **) pgm);	/* return program to free storage */
  }
}


/* Mail garbage collect searchheader
 * Accepts: pointer to searchheader pointer
 */

void mail_free_searchheader (SEARCHHEADER **hdr)
{
  if (*hdr) {			/* only free if exists */
    fs_give ((void **) &(*hdr)->line);
    if ((*hdr)->text) fs_give ((void **) &(*hdr)->text);
    mail_free_searchheader (&(*hdr)->next);
    fs_give ((void **) hdr);	/* return header to free storage */
  }
}


/* Mail garbage collect searchset
 * Accepts: pointer to searchset pointer
 */

void mail_free_searchset (SEARCHSET **set)
{
  if (*set) {			/* only free if exists */
    mail_free_searchset (&(*set)->next);
    fs_give ((void **) set);	/* return set to free storage */
  }
}


/* Mail garbage collect searchor
 * Accepts: pointer to searchor pointer
 */

void mail_free_searchor (SEARCHOR **orl)
{
  if (*orl) {			/* only free if exists */
    mail_free_searchpgm (&(*orl)->first);
    mail_free_searchpgm (&(*orl)->second);
    mail_free_searchor (&(*orl)->next);
    fs_give ((void **) orl);	/* return searchor to free storage */
  }
}


/* Mail garbage collect search program list
 * Accepts: pointer to searchor pointer
 */

void mail_free_searchpgmlist (SEARCHPGMLIST **pgl)
{
  if (*pgl) {			/* only free if exists */
    mail_free_searchpgm (&(*pgl)->pgm);
    mail_free_searchpgmlist (&(*pgl)->next);
    fs_give ((void **) pgl);	/* return searchpgmlist to free storage */
  }
}

/* Link authenicator
 * Accepts: authenticator to add to list
 */

void auth_link (AUTHENTICATOR *auth)
{
  AUTHENTICATOR **a = &authenticators;
  while (*a) a = &(*a)->next;	/* find end of list of authenticators */
  *a = auth;			/* put authenticator at the end */
  auth->next = NIL;		/* this authenticator is the end of the list */
}


/* Authenticate access
 * Accepts: mechanism name
 *	    responder function
 *	    argument count
 *	    argument vector
 * Returns: authenticated user name or NIL
 */

char *mail_auth (char *mechanism,authresponse_t resp,int argc,char *argv[])
{
  char tmp[MAILTMPLEN];
  AUTHENTICATOR *auth;
				/* make upper case copy of mechanism name */
  ucase (strcpy (tmp,mechanism));
  for (auth = authenticators; auth; auth = auth->next)
    if (!strcmp (auth->name,tmp)) return (*auth->server) (resp,argc,argv);
  return NIL;			/* no authenticator found */
}


syntax highlighted by Code2HTML, v. 0.9.1