imapext-2007

diff src/c-client/mail.c @ 0:ada5e610ab86

imap-2007e
author yuuji@gentei.org
date Mon, 14 Sep 2009 15:17:45 +0900
parents
children 28a55bc1110c
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/c-client/mail.c	Mon Sep 14 15:17:45 2009 +0900
     1.3 @@ -0,0 +1,6331 @@
     1.4 +/* ========================================================================
     1.5 + * Copyright 1988-2008 University of Washington
     1.6 + *
     1.7 + * Licensed under the Apache License, Version 2.0 (the "License");
     1.8 + * you may not use this file except in compliance with the License.
     1.9 + * You may obtain a copy of the License at
    1.10 + *
    1.11 + *     http://www.apache.org/licenses/LICENSE-2.0
    1.12 + *
    1.13 + * 
    1.14 + * ========================================================================
    1.15 + */
    1.16 +
    1.17 +/*
    1.18 + * Program:	Mailbox Access routines
    1.19 + *
    1.20 + * Author:	Mark Crispin
    1.21 + *		UW Technology
    1.22 + *		University of Washington
    1.23 + *		Seattle, WA  98195
    1.24 + *		Internet: MRC@Washington.EDU
    1.25 + *
    1.26 + * Date:	22 November 1989
    1.27 + * Last Edited:	15 April 2008
    1.28 + */
    1.29 +
    1.30 +
    1.31 +#include <ctype.h>
    1.32 +#include <stdio.h>
    1.33 +#include <time.h>
    1.34 +#include "c-client.h"
    1.35 +
    1.36 +char *UW_copyright = "Copyright 1988-2007 University of Washington\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n     http://www.apache.org/licenses/LICENSE-2.0\n";
    1.37 +
    1.38 +/* c-client global data */
    1.39 +
    1.40 +				/* version of this library */
    1.41 +static char *mailcclientversion = CCLIENTVERSION;
    1.42 +				/* list of mail drivers */
    1.43 +static DRIVER *maildrivers = NIL;
    1.44 +				/* list of authenticators */
    1.45 +static AUTHENTICATOR *mailauthenticators = NIL;
    1.46 +				/* SSL driver pointer */
    1.47 +static NETDRIVER *mailssldriver = NIL;
    1.48 +				/* pointer to alternate gets function */
    1.49 +static mailgets_t mailgets = NIL;
    1.50 +				/* pointer to read progress function */
    1.51 +static readprogress_t mailreadprogress = NIL;
    1.52 +				/* mail cache manipulation function */
    1.53 +static mailcache_t mailcache = mm_cache;
    1.54 +				/* RFC-822 output generator */
    1.55 +static rfc822out_t mail822out = NIL;
    1.56 +				/* RFC-822 output generator (new style) */
    1.57 +static rfc822outfull_t mail822outfull = NIL;
    1.58 +				/* SMTP verbose callback */
    1.59 +static smtpverbose_t mailsmtpverbose = mm_dlog;
    1.60 +				/* proxy copy routine */
    1.61 +static mailproxycopy_t mailproxycopy = NIL;
    1.62 +				/* RFC-822 external line parse */
    1.63 +static parseline_t mailparseline = NIL;
    1.64 +				/* RFC-822 external phrase parser */
    1.65 +static parsephrase_t mailparsephrase = NIL;
    1.66 +static kinit_t mailkinit = NIL;	/* application kinit callback */
    1.67 +				/* note network sent command */
    1.68 +static sendcommand_t mailsendcommand = NIL;
    1.69 +				/* newsrc file name decision function */
    1.70 +static newsrcquery_t mailnewsrcquery = NIL;
    1.71 +				/* ACL results callback */
    1.72 +static getacl_t mailaclresults = NIL;
    1.73 +				/* list rights results callback */
    1.74 +static listrights_t maillistrightsresults = NIL;
    1.75 +				/* my rights results callback */
    1.76 +static myrights_t mailmyrightsresults = NIL;
    1.77 +				/* quota results callback */
    1.78 +static quota_t mailquotaresults = NIL;
    1.79 +				/* quota root results callback */
    1.80 +static quotaroot_t mailquotarootresults = NIL;
    1.81 +				/* sorted results callback */
    1.82 +static sortresults_t mailsortresults = NIL;
    1.83 +				/* threaded results callback */
    1.84 +static threadresults_t mailthreadresults = NIL;
    1.85 +				/* COPY UID results */
    1.86 +static copyuid_t mailcopyuid = NIL;
    1.87 +				/* APPEND UID results */
    1.88 +static appenduid_t mailappenduid = NIL;
    1.89 +				/* free elt extra stuff callback */
    1.90 +static freeeltsparep_t mailfreeeltsparep = NIL;
    1.91 +				/* free envelope extra stuff callback */
    1.92 +static freeenvelopesparep_t mailfreeenvelopesparep = NIL;
    1.93 +				/* free body extra stuff callback */
    1.94 +static freebodysparep_t mailfreebodysparep = NIL;
    1.95 +				/* free stream extra stuff callback */
    1.96 +static freestreamsparep_t mailfreestreamsparep = NIL;
    1.97 +				/* SSL start routine */
    1.98 +static sslstart_t mailsslstart = NIL;
    1.99 +				/* SSL certificate query */
   1.100 +static sslcertificatequery_t mailsslcertificatequery = NIL;
   1.101 +				/* SSL client certificate */
   1.102 +static sslclientcert_t mailsslclientcert = NIL;
   1.103 +				/* SSL client private key */
   1.104 +static sslclientkey_t mailsslclientkey = NIL;
   1.105 +				/* SSL failure notify */
   1.106 +static sslfailure_t mailsslfailure = NIL;
   1.107 +				/* snarf interval */
   1.108 +static long mailsnarfinterval = 60;
   1.109 +				/* snarf preservation */
   1.110 +static long mailsnarfpreserve = NIL;
   1.111 +				/* newsrc name uses canonical host */
   1.112 +static long mailnewsrccanon = LONGT;
   1.113 +
   1.114 +				/* supported threaders */
   1.115 +static THREADER mailthreadordsub = {
   1.116 +  "ORDEREDSUBJECT",mail_thread_orderedsubject,NIL
   1.117 +};
   1.118 +static THREADER mailthreadlist = {
   1.119 +  "REFERENCES",mail_thread_references,&mailthreadordsub
   1.120 +};
   1.121 +
   1.122 +				/* server name */
   1.123 +static char *servicename = "unknown";
   1.124 +				/* server externally-set authentication ID */
   1.125 +static char *externalauthid = NIL;
   1.126 +static int expungeatping = T;	/* mail_ping() may call mm_expunged() */
   1.127 +static int trysslfirst = NIL;	/* always try SSL first */
   1.128 +static int notimezones = NIL;	/* write timezones in "From " header */
   1.129 +static int trustdns = T;	/* do DNS canonicalization */
   1.130 +static int saslusesptrname = T;	/* SASL uses name from DNS PTR lookup */
   1.131 +				/* trustdns also must be set */
   1.132 +static int debugsensitive = NIL;/* debug telemetry includes sensitive data */
   1.133 +
   1.134 +/* Default mail cache handler
   1.135 + * Accepts: pointer to cache handle
   1.136 + *	    message number
   1.137 + *	    caching function
   1.138 + * Returns: cache data
   1.139 + */
   1.140 +
   1.141 +void *mm_cache (MAILSTREAM *stream,unsigned long msgno,long op)
   1.142 +{
   1.143 +  size_t n;
   1.144 +  void *ret = NIL;
   1.145 +  unsigned long i;
   1.146 +  switch ((int) op) {		/* what function? */
   1.147 +  case CH_INIT:			/* initialize cache */
   1.148 +    if (stream->cache) {	/* flush old cache contents */
   1.149 +      while (stream->cachesize) {
   1.150 +	mm_cache (stream,stream->cachesize,CH_FREE);
   1.151 +	mm_cache (stream,stream->cachesize--,CH_FREESORTCACHE);
   1.152 +      }
   1.153 +      fs_give ((void **) &stream->cache);
   1.154 +      fs_give ((void **) &stream->sc);
   1.155 +      stream->nmsgs = 0;	/* can't have any messages now */
   1.156 +    }
   1.157 +    break;
   1.158 +  case CH_SIZE:			/* (re-)size the cache */
   1.159 +    if (!stream->cache)	{	/* have a cache already? */
   1.160 +				/* no, create new cache */
   1.161 +      n = (stream->cachesize = msgno + CACHEINCREMENT) * sizeof (void *);
   1.162 +      stream->cache = (MESSAGECACHE **) memset (fs_get (n),0,n);
   1.163 +      stream->sc = (SORTCACHE **) memset (fs_get (n),0,n);
   1.164 +    }
   1.165 +				/* is existing cache size large neough */
   1.166 +    else if (msgno > stream->cachesize) {
   1.167 +      i = stream->cachesize;	/* remember old size */
   1.168 +      n = (stream->cachesize = msgno + CACHEINCREMENT) * sizeof (void *);
   1.169 +      fs_resize ((void **) &stream->cache,n);
   1.170 +      fs_resize ((void **) &stream->sc,n);
   1.171 +      while (i < stream->cachesize) {
   1.172 +	stream->cache[i] = NIL;
   1.173 +	stream->sc[i++] = NIL;
   1.174 +      }
   1.175 +    }
   1.176 +    break;
   1.177 +
   1.178 +  case CH_MAKEELT:		/* return elt, make if necessary */
   1.179 +    if (!stream->cache[msgno - 1])
   1.180 +      stream->cache[msgno - 1] = mail_new_cache_elt (msgno);
   1.181 +				/* falls through */
   1.182 +  case CH_ELT:			/* return elt */
   1.183 +    ret = (void *) stream->cache[msgno - 1];
   1.184 +    break;
   1.185 +  case CH_SORTCACHE:		/* return sortcache entry, make if needed */
   1.186 +    if (!stream->sc[msgno - 1]) stream->sc[msgno - 1] =
   1.187 +      (SORTCACHE *) memset (fs_get (sizeof (SORTCACHE)),0,sizeof (SORTCACHE));
   1.188 +    ret = (void *) stream->sc[msgno - 1];
   1.189 +    break;
   1.190 +  case CH_FREE:			/* free elt */
   1.191 +    mail_free_elt (&stream->cache[msgno - 1]);
   1.192 +    break;
   1.193 +  case CH_FREESORTCACHE:
   1.194 +    if (stream->sc[msgno - 1]) {
   1.195 +      if (stream->sc[msgno - 1]->from)
   1.196 +	fs_give ((void **) &stream->sc[msgno - 1]->from);
   1.197 +      if (stream->sc[msgno - 1]->to)
   1.198 +	fs_give ((void **) &stream->sc[msgno - 1]->to);
   1.199 +      if (stream->sc[msgno - 1]->cc)
   1.200 +	fs_give ((void **) &stream->sc[msgno - 1]->cc);
   1.201 +      if (stream->sc[msgno - 1]->subject)
   1.202 +	fs_give ((void **) &stream->sc[msgno - 1]->subject);
   1.203 +      if (stream->sc[msgno - 1]->unique &&
   1.204 +	  (stream->sc[msgno - 1]->unique != stream->sc[msgno - 1]->message_id))
   1.205 +	fs_give ((void **) &stream->sc[msgno - 1]->unique);
   1.206 +      if (stream->sc[msgno - 1]->message_id)
   1.207 +	fs_give ((void **) &stream->sc[msgno - 1]->message_id);
   1.208 +      if (stream->sc[msgno - 1]->references)
   1.209 +	mail_free_stringlist (&stream->sc[msgno - 1]->references);
   1.210 +      fs_give ((void **) &stream->sc[msgno - 1]);
   1.211 +    }
   1.212 +    break;
   1.213 +  case CH_EXPUNGE:		/* expunge cache slot */
   1.214 +    for (i = msgno - 1; msgno < stream->nmsgs; i++,msgno++) {
   1.215 +      if (stream->cache[i] = stream->cache[msgno])
   1.216 +	stream->cache[i]->msgno = msgno;
   1.217 +      stream->sc[i] = stream->sc[msgno];
   1.218 +    }
   1.219 +    stream->cache[i] = NIL;	/* top of cache goes away */
   1.220 +    stream->sc[i] = NIL;
   1.221 +    break;
   1.222 +  default:
   1.223 +    fatal ("Bad mm_cache op");
   1.224 +    break;
   1.225 +  }
   1.226 +  return ret;
   1.227 +}
   1.228 +
   1.229 +/* Dummy string driver for complete in-memory strings */
   1.230 +
   1.231 +static void mail_string_init (STRING *s,void *data,unsigned long size);
   1.232 +static char mail_string_next (STRING *s);
   1.233 +static void mail_string_setpos (STRING *s,unsigned long i);
   1.234 +
   1.235 +STRINGDRIVER mail_string = {
   1.236 +  mail_string_init,		/* initialize string structure */
   1.237 +  mail_string_next,		/* get next byte in string structure */
   1.238 +  mail_string_setpos		/* set position in string structure */
   1.239 +};
   1.240 +
   1.241 +
   1.242 +/* Initialize mail string structure for in-memory string
   1.243 + * Accepts: string structure
   1.244 + *	    pointer to string
   1.245 + *	    size of string
   1.246 + */
   1.247 +
   1.248 +static void mail_string_init (STRING *s,void *data,unsigned long size)
   1.249 +{
   1.250 +				/* set initial string pointers */
   1.251 +  s->chunk = s->curpos = (char *) (s->data = data);
   1.252 +				/* and sizes */
   1.253 +  s->size = s->chunksize = s->cursize = size;
   1.254 +  s->data1 = s->offset = 0;	/* never any offset */
   1.255 +}
   1.256 +
   1.257 +
   1.258 +/* Get next character from string
   1.259 + * Accepts: string structure
   1.260 + * Returns: character, string structure chunk refreshed
   1.261 + */
   1.262 +
   1.263 +static char mail_string_next (STRING *s)
   1.264 +{
   1.265 +  return *s->curpos++;		/* return the last byte */
   1.266 +}
   1.267 +
   1.268 +
   1.269 +/* Set string pointer position
   1.270 + * Accepts: string structure
   1.271 + *	    new position
   1.272 + */
   1.273 +
   1.274 +static void mail_string_setpos (STRING *s,unsigned long i)
   1.275 +{
   1.276 +  s->curpos = s->chunk + i;	/* set new position */
   1.277 +  s->cursize = s->chunksize - i;/* and new size */
   1.278 +}
   1.279 +
   1.280 +/* Mail routines
   1.281 + *
   1.282 + *  mail_xxx routines are the interface between this module and the outside
   1.283 + * world.  Only these routines should be referenced by external callers.
   1.284 + *
   1.285 + *  Note that there is an important difference between a "sequence" and a
   1.286 + * "message #" (msgno).  A sequence is a string representing a sequence in
   1.287 + * {"n", "n:m", or combination separated by commas} format, whereas a msgno
   1.288 + * is a single integer.
   1.289 + *
   1.290 + */
   1.291 +
   1.292 +/* Mail version check
   1.293 + * Accepts: version
   1.294 + */
   1.295 +
   1.296 +void mail_versioncheck (char *version)
   1.297 +{
   1.298 +				/* attempt to protect again wrong .h */
   1.299 +  if (strcmp (version,mailcclientversion)) {
   1.300 +    char tmp[MAILTMPLEN];
   1.301 +    sprintf (tmp,"c-client library version skew, app=%.100s library=%.100s",
   1.302 +	     version,mailcclientversion);
   1.303 +    fatal (tmp);
   1.304 +  }
   1.305 +}
   1.306 +
   1.307 +
   1.308 +/* Mail link driver
   1.309 + * Accepts: driver to add to list
   1.310 + */
   1.311 +
   1.312 +void mail_link (DRIVER *driver)
   1.313 +{
   1.314 +  DRIVER **d = &maildrivers;
   1.315 +  while (*d) d = &(*d)->next;	/* find end of list of drivers */
   1.316 +  *d = driver;			/* put driver at the end */
   1.317 +  driver->next = NIL;		/* this driver is the end of the list */
   1.318 +}
   1.319 +
   1.320 +/* Mail manipulate driver parameters
   1.321 + * Accepts: mail stream
   1.322 + *	    function code
   1.323 + *	    function-dependent value
   1.324 + * Returns: function-dependent return value
   1.325 + */
   1.326 +
   1.327 +void *mail_parameters (MAILSTREAM *stream,long function,void *value)
   1.328 +{
   1.329 +  void *r,*ret = NIL;
   1.330 +  DRIVER *d;
   1.331 +  AUTHENTICATOR *a;
   1.332 +  switch ((int) function) {
   1.333 +  case SET_INBOXPATH:
   1.334 +    fatal ("SET_INBOXPATH not permitted");
   1.335 +  case GET_INBOXPATH:
   1.336 +    if ((stream || (stream = mail_open (NIL,"INBOX",OP_PROTOTYPE))) &&
   1.337 +	stream->dtb) ret = (*stream->dtb->parameters) (function,value);
   1.338 +    break;
   1.339 +  case SET_THREADERS:
   1.340 +    fatal ("SET_THREADERS not permitted");
   1.341 +  case GET_THREADERS:		/* use stream dtb instead of global */
   1.342 +    ret = (stream && stream->dtb) ?
   1.343 +				/* KLUDGE ALERT: note stream passed as value */
   1.344 +      (*stream->dtb->parameters) (function,stream) : (void *) &mailthreadlist;
   1.345 +    break;
   1.346 +  case SET_NAMESPACE:
   1.347 +    fatal ("SET_NAMESPACE not permitted");
   1.348 +    break;
   1.349 +  case SET_NEWSRC:		/* too late on open stream */
   1.350 +    if (stream && stream->dtb && (stream != ((*stream->dtb->open) (NIL))))
   1.351 +      fatal ("SET_NEWSRC not permitted");
   1.352 +    else ret = env_parameters (function,value);
   1.353 +    break;
   1.354 +  case GET_NAMESPACE:
   1.355 +  case GET_NEWSRC:		/* use stream dtb instead of environment */
   1.356 +    ret = (stream && stream->dtb) ?
   1.357 +				/* KLUDGE ALERT: note stream passed as value */
   1.358 +      (*stream->dtb->parameters) (function,stream) :
   1.359 +	env_parameters (function,value);
   1.360 +    break;
   1.361 +  case ENABLE_DEBUG:
   1.362 +    fatal ("ENABLE_DEBUG not permitted");
   1.363 +  case DISABLE_DEBUG:
   1.364 +    fatal ("DISABLE_DEBUG not permitted");
   1.365 +  case SET_DIRFMTTEST:
   1.366 +    fatal ("SET_DIRFMTTEST not permitted");
   1.367 +  case GET_DIRFMTTEST:
   1.368 +    if (!(stream && stream->dtb &&
   1.369 +	  (ret = (*stream->dtb->parameters) (function,NIL))))
   1.370 +      fatal ("GET_DIRFMTTEST not permitted");
   1.371 +    break;
   1.372 +
   1.373 +  case SET_DRIVERS:
   1.374 +    fatal ("SET_DRIVERS not permitted");
   1.375 +  case GET_DRIVERS:		/* always return global */
   1.376 +    ret = (void *) maildrivers;
   1.377 +    break;
   1.378 +  case SET_DRIVER:
   1.379 +    fatal ("SET_DRIVER not permitted");
   1.380 +  case GET_DRIVER:
   1.381 +    for (d = maildrivers; d && compare_cstring (d->name,(char *) value);
   1.382 +	 d = d->next);
   1.383 +    ret = (void *) d;
   1.384 +    break;
   1.385 +  case ENABLE_DRIVER:
   1.386 +    for (d = maildrivers; d && compare_cstring (d->name,(char *) value);
   1.387 +	 d = d->next);
   1.388 +    if (ret = (void *) d) d->flags &= ~DR_DISABLE;
   1.389 +    break;
   1.390 +  case DISABLE_DRIVER:
   1.391 +    for (d = maildrivers; d && compare_cstring (d->name,(char *) value);
   1.392 +	 d = d->next);
   1.393 +    if (ret = (void *) d) d->flags |= DR_DISABLE;
   1.394 +    break;
   1.395 +  case ENABLE_AUTHENTICATOR:
   1.396 +    for (a = mailauthenticators;/* scan authenticators */
   1.397 +	 a && compare_cstring (a->name,(char *) value); a = a->next);
   1.398 +    if (ret = (void *) a) a->flags &= ~AU_DISABLE;
   1.399 +    break;
   1.400 +  case DISABLE_AUTHENTICATOR:
   1.401 +    for (a = mailauthenticators;/* scan authenticators */
   1.402 +	 a && compare_cstring (a->name,(char *) value); a = a->next);
   1.403 +    if (ret = (void *) a) a->flags |= AU_DISABLE;
   1.404 +    break;
   1.405 +  case UNHIDE_AUTHENTICATOR:
   1.406 +    for (a = mailauthenticators;/* scan authenticators */
   1.407 +	 a && compare_cstring (a->name,(char *) value); a = a->next);
   1.408 +    if (ret = (void *) a) a->flags &= ~AU_HIDE;
   1.409 +    break;
   1.410 +  case HIDE_AUTHENTICATOR:
   1.411 +    for (a = mailauthenticators;/* scan authenticators */
   1.412 +	 a && compare_cstring (a->name,(char *) value); a = a->next);
   1.413 +    if (ret = (void *) a) a->flags |= AU_HIDE;
   1.414 +    break;
   1.415 +  case SET_EXTERNALAUTHID:
   1.416 +    if (value) {		/* setting external authentication ID */
   1.417 +      externalauthid = cpystr ((char *) value);
   1.418 +      mail_parameters (NIL,UNHIDE_AUTHENTICATOR,"EXTERNAL");
   1.419 +    }
   1.420 +    else {			/* clearing external authentication ID */
   1.421 +      if (externalauthid) fs_give ((void **) &externalauthid);
   1.422 +      mail_parameters (NIL,HIDE_AUTHENTICATOR,"EXTERNAL");
   1.423 +    }
   1.424 +  case GET_EXTERNALAUTHID:
   1.425 +    ret = (void *) externalauthid;
   1.426 +    break;
   1.427 +
   1.428 +  case SET_GETS:
   1.429 +    mailgets = (mailgets_t) value;
   1.430 +  case GET_GETS:
   1.431 +    ret = (void *) mailgets;
   1.432 +    break;
   1.433 +  case SET_READPROGRESS:
   1.434 +    mailreadprogress = (readprogress_t) value;
   1.435 +  case GET_READPROGRESS:
   1.436 +    ret = (void *) mailreadprogress;
   1.437 +    break;
   1.438 +  case SET_CACHE:
   1.439 +    mailcache = (mailcache_t) value;
   1.440 +  case GET_CACHE:
   1.441 +    ret = (void *) mailcache;
   1.442 +    break;
   1.443 +  case SET_RFC822OUTPUT:
   1.444 +    mail822out = (rfc822out_t) value;
   1.445 +  case GET_RFC822OUTPUT:
   1.446 +    ret = (void *) mail822out;
   1.447 +    break;
   1.448 +  case SET_RFC822OUTPUTFULL:
   1.449 +    mail822outfull = (rfc822outfull_t) value;
   1.450 +  case GET_RFC822OUTPUTFULL:
   1.451 +    ret = (void *) mail822outfull;
   1.452 +    break;
   1.453 +  case SET_SMTPVERBOSE:
   1.454 +    mailsmtpverbose = (smtpverbose_t) value;
   1.455 +  case GET_SMTPVERBOSE:
   1.456 +    ret = (void *) mailsmtpverbose;
   1.457 +    break;
   1.458 +  case SET_MAILPROXYCOPY:
   1.459 +    mailproxycopy = (mailproxycopy_t) value;
   1.460 +  case GET_MAILPROXYCOPY:
   1.461 +    ret = (void *) mailproxycopy;
   1.462 +    break;
   1.463 +  case SET_PARSELINE:
   1.464 +    mailparseline = (parseline_t) value;
   1.465 +  case GET_PARSELINE:
   1.466 +    ret = (void *) mailparseline;
   1.467 +    break;
   1.468 +  case SET_PARSEPHRASE:
   1.469 +    mailparsephrase = (parsephrase_t) value;
   1.470 +  case GET_PARSEPHRASE:
   1.471 +    ret = (void *) mailparsephrase;
   1.472 +    break;
   1.473 +  case SET_NEWSRCQUERY:
   1.474 +    mailnewsrcquery = (newsrcquery_t) value;
   1.475 +  case GET_NEWSRCQUERY:
   1.476 +    ret = (void *) mailnewsrcquery;
   1.477 +    break;
   1.478 +  case SET_NEWSRCCANONHOST:
   1.479 +    mailnewsrccanon = (long) value;
   1.480 +  case GET_NEWSRCCANONHOST:
   1.481 +    ret = (void *) mailnewsrccanon;
   1.482 +    break;
   1.483 +
   1.484 +  case SET_COPYUID:
   1.485 +    mailcopyuid = (copyuid_t) value;
   1.486 +  case GET_COPYUID:
   1.487 +    ret = (void *) mailcopyuid;
   1.488 +    break;
   1.489 +  case SET_APPENDUID:
   1.490 +    mailappenduid = (appenduid_t) value;
   1.491 +  case GET_APPENDUID:
   1.492 +    ret = (void *) mailappenduid;
   1.493 +    break;
   1.494 +  case SET_FREEENVELOPESPAREP:
   1.495 +    mailfreeenvelopesparep = (freeenvelopesparep_t) value;
   1.496 +  case GET_FREEENVELOPESPAREP:
   1.497 +    ret = (void *) mailfreeenvelopesparep;
   1.498 +    break;
   1.499 +  case SET_FREEELTSPAREP:
   1.500 +    mailfreeeltsparep = (freeeltsparep_t) value;
   1.501 +  case GET_FREEELTSPAREP:
   1.502 +    ret = (void *) mailfreeeltsparep;
   1.503 +    break;
   1.504 +  case SET_FREESTREAMSPAREP:
   1.505 +    mailfreestreamsparep = (freestreamsparep_t) value;
   1.506 +  case GET_FREESTREAMSPAREP:
   1.507 +    ret = (void *) mailfreestreamsparep;
   1.508 +    break;
   1.509 +  case SET_FREEBODYSPAREP:
   1.510 +    mailfreebodysparep = (freebodysparep_t) value;
   1.511 +  case GET_FREEBODYSPAREP:
   1.512 +    ret = (void *) mailfreebodysparep;
   1.513 +    break;
   1.514 +
   1.515 +  case SET_SSLSTART:
   1.516 +    mailsslstart = (sslstart_t) value;
   1.517 +  case GET_SSLSTART:
   1.518 +    ret = (void *) mailsslstart;
   1.519 +    break;
   1.520 +  case SET_SSLCERTIFICATEQUERY:
   1.521 +    mailsslcertificatequery = (sslcertificatequery_t) value;
   1.522 +  case GET_SSLCERTIFICATEQUERY:
   1.523 +    ret = (void *) mailsslcertificatequery;
   1.524 +    break;
   1.525 +  case SET_SSLCLIENTCERT:
   1.526 +    mailsslclientcert = (sslclientcert_t) value;
   1.527 +  case GET_SSLCLIENTCERT:
   1.528 +    ret = (void *) mailsslclientcert;
   1.529 +    break;
   1.530 +  case SET_SSLCLIENTKEY:
   1.531 +    mailsslclientkey = (sslclientkey_t) value;
   1.532 +  case GET_SSLCLIENTKEY:
   1.533 +    ret = (void *) mailsslclientkey;
   1.534 +    break;
   1.535 +  case SET_SSLFAILURE:
   1.536 +    mailsslfailure = (sslfailure_t) value;
   1.537 +  case GET_SSLFAILURE:
   1.538 +    ret = (void *) mailsslfailure;
   1.539 +    break;
   1.540 +  case SET_KINIT:
   1.541 +    mailkinit = (kinit_t) value;
   1.542 +  case GET_KINIT:
   1.543 +    ret = (void *) mailkinit;
   1.544 +    break;
   1.545 +  case SET_SENDCOMMAND:
   1.546 +    mailsendcommand = (sendcommand_t) value;
   1.547 +  case GET_SENDCOMMAND:
   1.548 +    ret = (void *) mailsendcommand;
   1.549 +    break;
   1.550 +
   1.551 +  case SET_SERVICENAME:
   1.552 +    servicename = (char *) value;
   1.553 +  case GET_SERVICENAME:
   1.554 +    ret = (void *) servicename;
   1.555 +    break;
   1.556 +  case SET_EXPUNGEATPING:
   1.557 +    expungeatping = (value ? T : NIL);
   1.558 +  case GET_EXPUNGEATPING:
   1.559 +    ret = (void *) (expungeatping ? VOIDT : NIL);
   1.560 +    break;
   1.561 +  case SET_SORTRESULTS:
   1.562 +    mailsortresults = (sortresults_t) value;
   1.563 +  case GET_SORTRESULTS:
   1.564 +    ret = (void *) mailsortresults;
   1.565 +    break;
   1.566 +  case SET_THREADRESULTS:
   1.567 +    mailthreadresults = (threadresults_t) value;
   1.568 +  case GET_THREADRESULTS:
   1.569 +    ret = (void *) mailthreadresults;
   1.570 +    break;
   1.571 +  case SET_SSLDRIVER:
   1.572 +    mailssldriver = (NETDRIVER *) value;
   1.573 +  case GET_SSLDRIVER:
   1.574 +    ret = (void *) mailssldriver;
   1.575 +    break;
   1.576 +  case SET_TRYSSLFIRST:
   1.577 +    trysslfirst = (value ? T : NIL);
   1.578 +  case GET_TRYSSLFIRST:
   1.579 +    ret = (void *) (trysslfirst ? VOIDT : NIL);
   1.580 +    break;
   1.581 +  case SET_NOTIMEZONES:
   1.582 +    notimezones = (value ? T : NIL);
   1.583 +  case GET_NOTIMEZONES:
   1.584 +    ret = (void *) (notimezones ? VOIDT : NIL);
   1.585 +    break;
   1.586 +  case SET_TRUSTDNS:
   1.587 +    trustdns = (value ? T : NIL);
   1.588 +  case GET_TRUSTDNS:
   1.589 +    ret = (void *) (trustdns ? VOIDT : NIL);
   1.590 +    break;
   1.591 +  case SET_SASLUSESPTRNAME:
   1.592 +    saslusesptrname = (value ? T : NIL);
   1.593 +  case GET_SASLUSESPTRNAME:
   1.594 +    ret = (void *) (saslusesptrname ? VOIDT : NIL);
   1.595 +    break;
   1.596 +  case SET_DEBUGSENSITIVE:
   1.597 +    debugsensitive = (value ? T : NIL);
   1.598 +  case GET_DEBUGSENSITIVE:
   1.599 +    ret = (void *) (debugsensitive ? VOIDT : NIL);
   1.600 +    break;
   1.601 +
   1.602 +  case SET_ACL:
   1.603 +    mailaclresults = (getacl_t) value;
   1.604 +  case GET_ACL:
   1.605 +    ret = (void *) mailaclresults;
   1.606 +    break;
   1.607 +  case SET_LISTRIGHTS:
   1.608 +    maillistrightsresults = (listrights_t) value;
   1.609 +  case GET_LISTRIGHTS:
   1.610 +    ret = (void *) maillistrightsresults;
   1.611 +    break;
   1.612 +  case SET_MYRIGHTS:
   1.613 +    mailmyrightsresults = (myrights_t) value;
   1.614 +  case GET_MYRIGHTS:
   1.615 +    ret = (void *) mailmyrightsresults;
   1.616 +    break;
   1.617 +  case SET_QUOTA:
   1.618 +    mailquotaresults = (quota_t) value;
   1.619 +  case GET_QUOTA:
   1.620 +    ret = (void *) mailquotaresults;
   1.621 +    break;
   1.622 +  case SET_QUOTAROOT:
   1.623 +    mailquotarootresults = (quotaroot_t) value;
   1.624 +  case GET_QUOTAROOT:
   1.625 +    ret = (void *) mailquotarootresults;
   1.626 +    break;
   1.627 +  case SET_SNARFINTERVAL:
   1.628 +    mailsnarfinterval = (long) value;
   1.629 +  case GET_SNARFINTERVAL:
   1.630 +    ret = (void *) mailsnarfinterval;
   1.631 +    break;
   1.632 +  case SET_SNARFPRESERVE:
   1.633 +    mailsnarfpreserve = (long) value;
   1.634 +  case GET_SNARFPRESERVE:
   1.635 +    ret = (void *) mailsnarfpreserve;
   1.636 +    break;
   1.637 +  case SET_SNARFMAILBOXNAME:
   1.638 +    if (stream) {		/* have a stream? */
   1.639 +      if (stream->snarf.name) fs_give ((void **) &stream->snarf.name);
   1.640 +      stream->snarf.name = cpystr ((char *) value);
   1.641 +    }
   1.642 +    else fatal ("SET_SNARFMAILBOXNAME with no stream");
   1.643 +  case GET_SNARFMAILBOXNAME:
   1.644 +    if (stream) ret = (void *) stream->snarf.name;
   1.645 +    break;
   1.646 +  default:
   1.647 +    if (r = smtp_parameters (function,value)) ret = r;
   1.648 +    if (r = env_parameters (function,value)) ret = r;
   1.649 +    if (r = tcp_parameters (function,value)) ret = r;
   1.650 +    if (stream && stream->dtb) {/* if have stream, do for its driver only */
   1.651 +      if (r = (*stream->dtb->parameters) (function,value)) ret = r;
   1.652 +    }
   1.653 +				/* else do all drivers */
   1.654 +    else for (d = maildrivers; d; d = d->next)
   1.655 +      if (r = (d->parameters) (function,value)) ret = r;
   1.656 +    break;
   1.657 +  }
   1.658 +  return ret;
   1.659 +}
   1.660 +
   1.661 +/* Mail validate mailbox name
   1.662 + * Accepts: MAIL stream
   1.663 + *	    mailbox name
   1.664 + *	    purpose string for error message
   1.665 + * Return: driver factory on success, NIL on failure
   1.666 + */
   1.667 +
   1.668 +DRIVER *mail_valid (MAILSTREAM *stream,char *mailbox,char *purpose)
   1.669 +{
   1.670 +  char tmp[MAILTMPLEN];
   1.671 +  DRIVER *factory = NIL;
   1.672 +				/* never allow names with newlines */
   1.673 +  if (strpbrk (mailbox,"\015\012")) {
   1.674 +    if (purpose) {		/* if want an error message */
   1.675 +      sprintf (tmp,"Can't %s with such a name",purpose);
   1.676 +      MM_LOG (tmp,ERROR);
   1.677 +    }
   1.678 +    return NIL;
   1.679 +  }
   1.680 +				/* validate name, find driver factory */
   1.681 +  if (strlen (mailbox) < (NETMAXHOST+(NETMAXUSER*2)+NETMAXMBX+NETMAXSRV+50))
   1.682 +    for (factory = maildrivers; factory && 
   1.683 +	 ((factory->flags & DR_DISABLE) ||
   1.684 +	  ((factory->flags & DR_LOCAL) && (*mailbox == '{')) ||
   1.685 +	  !(*factory->valid) (mailbox));
   1.686 +	 factory = factory->next);
   1.687 +				/* validate factory against non-dummy stream */
   1.688 +  if (factory && stream && stream->dtb && (stream->dtb != factory) &&
   1.689 +      strcmp (stream->dtb->name,"dummy"))
   1.690 +				/* factory invalid; if dummy, use stream */
   1.691 +    factory = strcmp (factory->name,"dummy") ? NIL : stream->dtb;
   1.692 +  if (!factory && purpose) {	/* if want an error message */
   1.693 +    sprintf (tmp,"Can't %s %.80s: %s",purpose,mailbox,(*mailbox == '{') ?
   1.694 +	     "invalid remote specification" : "no such mailbox");
   1.695 +    MM_LOG (tmp,ERROR);
   1.696 +  }
   1.697 +  return factory;		/* return driver factory */
   1.698 +}
   1.699 +
   1.700 +/* Mail validate network mailbox name
   1.701 + * Accepts: mailbox name
   1.702 + *	    mailbox driver to validate against
   1.703 + *	    pointer to where to return host name if non-NIL
   1.704 + *	    pointer to where to return mailbox name if non-NIL
   1.705 + * Returns: driver on success, NIL on failure
   1.706 + */
   1.707 +
   1.708 +DRIVER *mail_valid_net (char *name,DRIVER *drv,char *host,char *mailbox)
   1.709 +{
   1.710 +  NETMBX mb;
   1.711 +  if (!mail_valid_net_parse (name,&mb) || strcmp (mb.service,drv->name))
   1.712 +    return NIL;
   1.713 +  if (host) strcpy (host,mb.host);
   1.714 +  if (mailbox) strcpy (mailbox,mb.mailbox);
   1.715 +  return drv;
   1.716 +}
   1.717 +
   1.718 +
   1.719 +/* Mail validate network mailbox name
   1.720 + * Accepts: mailbox name
   1.721 + *	    NETMBX structure to return values
   1.722 + * Returns: T on success, NIL on failure
   1.723 + */
   1.724 +
   1.725 +long mail_valid_net_parse (char *name,NETMBX *mb)
   1.726 +{
   1.727 +  return mail_valid_net_parse_work (name,mb,"imap");
   1.728 +}
   1.729 +
   1.730 +/* Mail validate network mailbox name worker routine
   1.731 + * Accepts: mailbox name
   1.732 + *	    NETMBX structure to return values
   1.733 + *	    default service
   1.734 + * Returns: T on success, NIL on failure
   1.735 + */
   1.736 +
   1.737 +long mail_valid_net_parse_work (char *name,NETMBX *mb,char *service)
   1.738 +{
   1.739 +  int i,j;
   1.740 +  char c,*s,*t,*v,tmp[MAILTMPLEN],arg[MAILTMPLEN];
   1.741 +				/* initialize structure */
   1.742 +  memset (mb,'\0',sizeof (NETMBX));
   1.743 +				/* must have host specification */
   1.744 +  if (*name++ != '{') return NIL;
   1.745 +  if (*name == '[') {		/* if domain literal, find its ending */
   1.746 +    if (!((v = strpbrk (name,"]}")) && (*v++ == ']'))) return NIL;
   1.747 +  }
   1.748 +				/* find end of host name */
   1.749 +  else if (!(v = strpbrk (name,"/:}"))) return NIL;
   1.750 +				/* validate length, find mailbox part */
   1.751 +  if (!((i = v - name) && (i < NETMAXHOST) && (t = strchr (v,'}')) &&
   1.752 +	((j = t - v) < MAILTMPLEN) && (strlen (t+1) < (size_t) NETMAXMBX)))
   1.753 +    return NIL;			/* invalid mailbox */
   1.754 +  strncpy (mb->host,name,i);	/* set host name */
   1.755 +  strncpy (mb->orighost,name,i);
   1.756 +  mb->host[i] = mb->orighost[i] = '\0';
   1.757 +  strcpy (mb->mailbox,t+1);	/* set mailbox name */
   1.758 +  if (t - v) {			/* any switches or port specification? */
   1.759 +    strncpy (t = tmp,v,j);	/* copy it */
   1.760 +    tmp[j] = '\0';		/* tie it off */
   1.761 +    c = *t++;			/* get first delimiter */
   1.762 +    do switch (c) {		/* act based upon the character */
   1.763 +    case ':':			/* port specification */
   1.764 +      if (mb->port || !(mb->port = strtoul (t,&t,10))) return NIL;
   1.765 +      c = t ? *t++ : '\0';	/* get delimiter, advance pointer */
   1.766 +      break;
   1.767 +    case '/':			/* switch */
   1.768 +				/* find delimiter */
   1.769 +      if (t = strpbrk (s = t,"/:=")) {
   1.770 +	c = *t;			/* remember delimiter for later */
   1.771 +	*t++ = '\0';		/* tie off switch name */
   1.772 +      }
   1.773 +      else c = '\0';		/* no delimiter */
   1.774 +      if (c == '=') {		/* parse switches which take arguments */
   1.775 +	if (*t == '"') {	/* quoted string? */
   1.776 +	  for (v = arg,i = 0,++t; (c = *t++) != '"';) {
   1.777 +	    if (!c) return NIL;	/* unterminated string */
   1.778 +				/* quote next character */
   1.779 +	    if (c == '\\') c = *t++;
   1.780 +	    if (!c) return NIL;	/* can't quote NUL either */
   1.781 +	    arg[i++] = c;
   1.782 +	  }
   1.783 +	  c = *t++;		/* remember delimiter for later */
   1.784 +	  arg[i] = '\0';	/* tie off argument */
   1.785 +	}
   1.786 +	else {			/* non-quoted argument */
   1.787 +	  if (t = strpbrk (v = t,"/:")) {
   1.788 +	    c = *t;		/* remember delimiter for later */
   1.789 +	    *t++ = '\0';	/* tie off switch name */
   1.790 +	  }
   1.791 +	  else c = '\0';	/* no delimiter */
   1.792 +	  i = strlen (v);	/* length of argument */
   1.793 +	}
   1.794 +	if (!compare_cstring (s,"service") && (i < NETMAXSRV) && !*mb->service)
   1.795 +	  lcase (strcpy (mb->service,v));
   1.796 +	else if (!compare_cstring (s,"user") && (i < NETMAXUSER) && !*mb->user)
   1.797 +	  strcpy (mb->user,v);
   1.798 +	else if (!compare_cstring (s,"authuser") && (i < NETMAXUSER) &&
   1.799 +		 !*mb->authuser) strcpy (mb->authuser,v);
   1.800 +	else return NIL;
   1.801 +      }
   1.802 +
   1.803 +      else {			/* non-argument switch */
   1.804 +	if (!compare_cstring (s,"anonymous")) mb->anoflag = T;
   1.805 +	else if (!compare_cstring (s,"debug")) mb->dbgflag = T;
   1.806 +	else if (!compare_cstring (s,"readonly")) mb->readonlyflag = T;
   1.807 +	else if (!compare_cstring (s,"secure")) mb->secflag = T;
   1.808 +	else if (!compare_cstring (s,"norsh")) mb->norsh = T;
   1.809 +	else if (!compare_cstring (s,"loser")) mb->loser = T;
   1.810 +	else if (!compare_cstring (s,"tls") && !mb->notlsflag)
   1.811 +	  mb->tlsflag = T;
   1.812 +	else if (!compare_cstring (s,"tls-sslv23") && !mb->notlsflag)
   1.813 +	  mb->tlssslv23 = mb->tlsflag = T;
   1.814 +	else if (!compare_cstring (s,"notls") && !mb->tlsflag)
   1.815 +	  mb->notlsflag = T;
   1.816 +	else if (!compare_cstring (s,"tryssl"))
   1.817 +	  mb->trysslflag = mailssldriver? T : NIL;
   1.818 +	else if (mailssldriver && !compare_cstring (s,"ssl") && !mb->tlsflag)
   1.819 +	  mb->sslflag = mb->notlsflag = T;
   1.820 +	else if (mailssldriver && !compare_cstring (s,"novalidate-cert"))
   1.821 +	  mb->novalidate = T;
   1.822 +				/* hack for compatibility with the past */
   1.823 +	else if (mailssldriver && !compare_cstring (s,"validate-cert"));
   1.824 +				/* service switches below here */
   1.825 +	else if (*mb->service) return NIL;
   1.826 +	else if (!compare_cstring (s,"imap") ||
   1.827 +		 !compare_cstring (s,"nntp") ||
   1.828 +		 !compare_cstring (s,"pop3") ||
   1.829 +		 !compare_cstring (s,"smtp") ||
   1.830 +		 !compare_cstring (s,"submit"))
   1.831 +	  lcase (strcpy (mb->service,s));
   1.832 +	else if (!compare_cstring (s,"imap2") ||
   1.833 +		 !compare_cstring (s,"imap2bis") ||
   1.834 +		 !compare_cstring (s,"imap4") ||
   1.835 +		 !compare_cstring (s,"imap4rev1"))
   1.836 +	  strcpy (mb->service,"imap");
   1.837 +	else if (!compare_cstring (s,"pop"))
   1.838 +	  strcpy (mb->service,"pop3");
   1.839 +	else return NIL;	/* invalid non-argument switch */
   1.840 +      }
   1.841 +      break;
   1.842 +    default:			/* anything else is bogus */
   1.843 +      return NIL;
   1.844 +    } while (c);		/* see if anything more to parse */
   1.845 +  }
   1.846 +				/* default mailbox name */
   1.847 +  if (!*mb->mailbox) strcpy (mb->mailbox,"INBOX");
   1.848 +				/* default service name */
   1.849 +  if (!*mb->service) strcpy (mb->service,service);
   1.850 +				/* /norsh only valid if imap */
   1.851 +  if (mb->norsh && strcmp (mb->service,"imap")) return NIL;
   1.852 +  return T;
   1.853 +}
   1.854 +
   1.855 +/* Mail scan mailboxes for string
   1.856 + * Accepts: mail stream
   1.857 + *	    reference
   1.858 + *	    pattern to search
   1.859 + *	    contents to search
   1.860 + */
   1.861 +
   1.862 +void mail_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
   1.863 +{
   1.864 +  int remote = ((*pat == '{') || (ref && *ref == '{'));
   1.865 +  DRIVER *d;
   1.866 +  if (ref && (strlen (ref) > NETMAXMBX)) {
   1.867 +    char tmp[MAILTMPLEN];
   1.868 +    sprintf (tmp,"Invalid LIST reference specification: %.80s",ref);
   1.869 +    MM_LOG (tmp,ERROR);
   1.870 +    return;
   1.871 +  }
   1.872 +  if (strlen (pat) > NETMAXMBX) {
   1.873 +    char tmp[MAILTMPLEN];
   1.874 +    sprintf (tmp,"Invalid LIST pattern specification: %.80s",pat);
   1.875 +    MM_LOG (tmp,ERROR);
   1.876 +    return;
   1.877 +  }
   1.878 +  if (*pat == '{') ref = NIL;	/* ignore reference if pattern is remote */
   1.879 +  if (stream) {			/* if have a stream, do it for that stream */
   1.880 +    if ((d = stream->dtb) && d->scan &&
   1.881 +	!(((d->flags & DR_LOCAL) && remote)))
   1.882 +      (*d->scan) (stream,ref,pat,contents);
   1.883 +  }
   1.884 +				/* otherwise do for all DTB's */
   1.885 +  else for (d = maildrivers; d; d = d->next)
   1.886 +    if (d->scan && !((d->flags & DR_DISABLE) ||
   1.887 +		     ((d->flags & DR_LOCAL) && remote)))
   1.888 +      (d->scan) (NIL,ref,pat,contents);
   1.889 +}
   1.890 +
   1.891 +/* Mail list mailboxes
   1.892 + * Accepts: mail stream
   1.893 + *	    reference
   1.894 + *	    pattern to search
   1.895 + */
   1.896 +
   1.897 +void mail_list (MAILSTREAM *stream,char *ref,char *pat)
   1.898 +{
   1.899 +  int remote = ((*pat == '{') || (ref && *ref == '{'));
   1.900 +  DRIVER *d = maildrivers;
   1.901 +  if (ref && (strlen (ref) > NETMAXMBX)) {
   1.902 +    char tmp[MAILTMPLEN];
   1.903 +    sprintf (tmp,"Invalid LIST reference specification: %.80s",ref);
   1.904 +    MM_LOG (tmp,ERROR);
   1.905 +    return;
   1.906 +  }
   1.907 +  if (strlen (pat) > NETMAXMBX) {
   1.908 +    char tmp[MAILTMPLEN];
   1.909 +    sprintf (tmp,"Invalid LIST pattern specification: %.80s",pat);
   1.910 +    MM_LOG (tmp,ERROR);
   1.911 +    return;
   1.912 +  }
   1.913 +  if (*pat == '{') ref = NIL;	/* ignore reference if pattern is remote */
   1.914 +  if (stream && stream->dtb) {	/* if have a stream, do it for that stream */
   1.915 +    if (!(((d = stream->dtb)->flags & DR_LOCAL) && remote))
   1.916 +      (*d->list) (stream,ref,pat);
   1.917 +  }
   1.918 +				/* otherwise do for all DTB's */
   1.919 +  else do if (!((d->flags & DR_DISABLE) ||
   1.920 +		((d->flags & DR_LOCAL) && remote)))
   1.921 +    (d->list) (NIL,ref,pat);
   1.922 +  while (d = d->next);		/* until at the end */
   1.923 +}
   1.924 +
   1.925 +/* Mail list subscribed mailboxes
   1.926 + * Accepts: mail stream
   1.927 + *	    pattern to search
   1.928 + */
   1.929 +
   1.930 +void mail_lsub (MAILSTREAM *stream,char *ref,char *pat)
   1.931 +{
   1.932 +  int remote = ((*pat == '{') || (ref && *ref == '{'));
   1.933 +  DRIVER *d = maildrivers;
   1.934 +  if (ref && (strlen (ref) > NETMAXMBX)) {
   1.935 +    char tmp[MAILTMPLEN];
   1.936 +    sprintf (tmp,"Invalid LSUB reference specification: %.80s",ref);
   1.937 +    MM_LOG (tmp,ERROR);
   1.938 +    return;
   1.939 +  }
   1.940 +  if (strlen (pat) > NETMAXMBX) {
   1.941 +    char tmp[MAILTMPLEN];
   1.942 +    sprintf (tmp,"Invalid LSUB pattern specification: %.80s",pat);
   1.943 +    MM_LOG (tmp,ERROR);
   1.944 +    return;
   1.945 +  }
   1.946 +  if (*pat == '{') ref = NIL;	/* ignore reference if pattern is remote */
   1.947 +  if (stream && stream->dtb) {	/* if have a stream, do it for that stream */
   1.948 +    if (!(((d = stream->dtb)->flags & DR_LOCAL) && remote))
   1.949 +      (*d->lsub) (stream,ref,pat);
   1.950 +  }
   1.951 +				/* otherwise do for all DTB's */
   1.952 +  else do if (!((d->flags & DR_DISABLE) ||
   1.953 +		((d->flags & DR_LOCAL) && remote)))
   1.954 +    (d->lsub) (NIL,ref,pat);
   1.955 +  while (d = d->next);		/* until at the end */
   1.956 +}
   1.957 +
   1.958 +/* Mail subscribe to mailbox
   1.959 + * Accepts: mail stream
   1.960 + *	    mailbox to add to subscription list
   1.961 + * Returns: T on success, NIL on failure
   1.962 + */
   1.963 +
   1.964 +long mail_subscribe (MAILSTREAM *stream,char *mailbox)
   1.965 +{
   1.966 +  DRIVER *factory = mail_valid (stream,mailbox,"subscribe to mailbox");
   1.967 +  return factory ?
   1.968 +    (factory->subscribe ?
   1.969 +     (*factory->subscribe) (stream,mailbox) : sm_subscribe (mailbox)) : NIL;
   1.970 +}
   1.971 +
   1.972 +
   1.973 +/* Mail unsubscribe to mailbox
   1.974 + * Accepts: mail stream
   1.975 + *	    mailbox to delete from subscription list
   1.976 + * Returns: T on success, NIL on failure
   1.977 + */
   1.978 +
   1.979 +long mail_unsubscribe (MAILSTREAM *stream,char *mailbox)
   1.980 +{
   1.981 +  DRIVER *factory = mail_valid (stream,mailbox,NIL);
   1.982 +  return (factory && factory->unsubscribe) ?
   1.983 +    (*factory->unsubscribe) (stream,mailbox) : sm_unsubscribe (mailbox);
   1.984 +}
   1.985 +
   1.986 +/* Mail create mailbox
   1.987 + * Accepts: mail stream
   1.988 + *	    mailbox name to create
   1.989 + * Returns: T on success, NIL on failure
   1.990 + */
   1.991 +
   1.992 +long mail_create (MAILSTREAM *stream,char *mailbox)
   1.993 +{
   1.994 +  MAILSTREAM *ts;
   1.995 +  char *s,*t,tmp[MAILTMPLEN];
   1.996 +  size_t i;
   1.997 +  DRIVER *d;
   1.998 +				/* never allow names with newlines */
   1.999 +  if (s = strpbrk (mailbox,"\015\012")) {
  1.1000 +    MM_LOG ("Can't create mailbox with such a name",ERROR);
  1.1001 +    return NIL;
  1.1002 +  }
  1.1003 +  if (strlen (mailbox) >= (NETMAXHOST+(NETMAXUSER*2)+NETMAXMBX+NETMAXSRV+50)) {
  1.1004 +    sprintf (tmp,"Can't create %.80s: %s",mailbox,(*mailbox == '{') ?
  1.1005 +	     "invalid remote specification" : "no such mailbox");
  1.1006 +    MM_LOG (tmp,ERROR);
  1.1007 +    return NIL;
  1.1008 +  }
  1.1009 +				/* create of INBOX invalid */
  1.1010 +  if (!compare_cstring (mailbox,"INBOX")) {
  1.1011 +    MM_LOG ("Can't create INBOX",ERROR);
  1.1012 +    return NIL;
  1.1013 +  }
  1.1014 +				/* validate name */
  1.1015 +  if (s = mail_utf7_valid (mailbox)) {
  1.1016 +    sprintf (tmp,"Can't create %s: %.80s",s,mailbox);
  1.1017 +    MM_LOG (tmp,ERROR);
  1.1018 +    return NIL;
  1.1019 +  }
  1.1020 +
  1.1021 +				/* see if special driver hack */
  1.1022 +  if ((mailbox[0] == '#') && ((mailbox[1] == 'd') || (mailbox[1] == 'D')) &&
  1.1023 +      ((mailbox[2] == 'r') || (mailbox[2] == 'R')) &&
  1.1024 +      ((mailbox[3] == 'i') || (mailbox[3] == 'I')) &&
  1.1025 +      ((mailbox[4] == 'v') || (mailbox[4] == 'V')) &&
  1.1026 +      ((mailbox[5] == 'e') || (mailbox[5] == 'E')) &&
  1.1027 +      ((mailbox[6] == 'r') || (mailbox[6] == 'R')) && (mailbox[7] == '.')) {
  1.1028 +				/* copy driver until likely delimiter */
  1.1029 +    if ((s = strpbrk (t = mailbox+8,"/\\:")) && (i = s - t)) {
  1.1030 +      strncpy (tmp,t,i);
  1.1031 +      tmp[i] = '\0';
  1.1032 +    } 
  1.1033 +    else {
  1.1034 +      sprintf (tmp,"Can't create mailbox %.80s: bad driver syntax",mailbox);
  1.1035 +      MM_LOG (tmp,ERROR);
  1.1036 +      return NIL;
  1.1037 +    }
  1.1038 +    for (d = maildrivers; d && strcmp (d->name,tmp); d = d->next);
  1.1039 +    if (d) mailbox = ++s;	/* skip past driver specification */
  1.1040 +    else {
  1.1041 +      sprintf (tmp,"Can't create mailbox %.80s: unknown driver",mailbox);
  1.1042 +      MM_LOG (tmp,ERROR);
  1.1043 +      return NIL;
  1.1044 +    }
  1.1045 +  }
  1.1046 +				/* use stream if one given or deterministic */
  1.1047 +  else if ((stream && stream->dtb) ||
  1.1048 +	   (((*mailbox == '{') || (*mailbox == '#')) &&
  1.1049 +	    (stream = mail_open (NIL,mailbox,OP_PROTOTYPE | OP_SILENT))))
  1.1050 +    d = stream->dtb;
  1.1051 +  else if ((*mailbox != '{') && (ts = default_proto (NIL))) d = ts->dtb;
  1.1052 +  else {			/* failed utterly */
  1.1053 +    sprintf (tmp,"Can't create mailbox %.80s: indeterminate format",mailbox);
  1.1054 +    MM_LOG (tmp,ERROR);
  1.1055 +    return NIL;
  1.1056 +  }
  1.1057 +  return (*d->create) (stream,mailbox);
  1.1058 +}
  1.1059 +
  1.1060 +/* Mail delete mailbox
  1.1061 + * Accepts: mail stream
  1.1062 + *	    mailbox name to delete
  1.1063 + * Returns: T on success, NIL on failure
  1.1064 + */
  1.1065 +
  1.1066 +long mail_delete (MAILSTREAM *stream,char *mailbox)
  1.1067 +{
  1.1068 +  DRIVER *dtb = mail_valid (stream,mailbox,"delete mailbox");
  1.1069 +  if (!dtb) return NIL;
  1.1070 +  if (((mailbox[0] == 'I') || (mailbox[0] == 'i')) &&
  1.1071 +      ((mailbox[1] == 'N') || (mailbox[1] == 'n')) &&
  1.1072 +      ((mailbox[2] == 'B') || (mailbox[2] == 'b')) &&
  1.1073 +      ((mailbox[3] == 'O') || (mailbox[3] == 'o')) &&
  1.1074 +      ((mailbox[4] == 'X') || (mailbox[4] == 'x')) && !mailbox[5]) {
  1.1075 +    MM_LOG ("Can't delete INBOX",ERROR);
  1.1076 +    return NIL;
  1.1077 +  }
  1.1078 +  return SAFE_DELETE (dtb,stream,mailbox);
  1.1079 +}
  1.1080 +
  1.1081 +
  1.1082 +/* Mail rename mailbox
  1.1083 + * Accepts: mail stream
  1.1084 + *	    old mailbox name
  1.1085 + *	    new mailbox name
  1.1086 + * Returns: T on success, NIL on failure
  1.1087 + */
  1.1088 +
  1.1089 +long mail_rename (MAILSTREAM *stream,char *old,char *newname)
  1.1090 +{
  1.1091 +  char *s,tmp[MAILTMPLEN];
  1.1092 +  DRIVER *dtb = mail_valid (stream,old,"rename mailbox");
  1.1093 +  if (!dtb) return NIL;
  1.1094 +				/* validate name */
  1.1095 +  if (s = mail_utf7_valid (newname)) {
  1.1096 +    sprintf (tmp,"Can't rename to %s: %.80s",s,newname);
  1.1097 +    MM_LOG (tmp,ERROR);
  1.1098 +    return NIL;
  1.1099 +  }
  1.1100 +  if ((*old != '{') && (*old != '#') && mail_valid (NIL,newname,NIL)) {
  1.1101 +    sprintf (tmp,"Can't rename %.80s: mailbox %.80s already exists",
  1.1102 +	     old,newname);
  1.1103 +    MM_LOG (tmp,ERROR);
  1.1104 +    return NIL;
  1.1105 +  }
  1.1106 +  return SAFE_RENAME (dtb,stream,old,newname);
  1.1107 +}
  1.1108 +
  1.1109 +/* Validate mailbox as Modified UTF-7
  1.1110 + * Accepts: candidate mailbox name
  1.1111 + * Returns: error string if error, NIL if valid
  1.1112 + */
  1.1113 +
  1.1114 +char *mail_utf7_valid (char *mailbox)
  1.1115 +{
  1.1116 +  char *s;
  1.1117 +  for (s = mailbox; *s; s++) {	/* make sure valid name */
  1.1118 +				/* reserved for future use with UTF-8 */
  1.1119 +    if (*s & 0x80) return "mailbox name with 8-bit octet";
  1.1120 +				/* validate modified UTF-7 */
  1.1121 +    else if (*s == '&') while (*++s != '-') switch (*s) {
  1.1122 +    case '\0':
  1.1123 +      return "unterminated modified UTF-7 name";
  1.1124 +    case '+':			/* valid modified BASE64 */
  1.1125 +    case ',':
  1.1126 +      break;			/* all OK so far */
  1.1127 +    default:			/* must be alphanumeric */
  1.1128 +      if (!isalnum (*s)) return "invalid modified UTF-7 name";
  1.1129 +      break;
  1.1130 +    }
  1.1131 +  }
  1.1132 +  return NIL;			/* all OK */
  1.1133 +}
  1.1134 +
  1.1135 +/* Mail status of mailbox
  1.1136 + * Accepts: mail stream if open on this mailbox
  1.1137 + *	    mailbox name
  1.1138 + *	    status flags
  1.1139 + * Returns: T on success, NIL on failure
  1.1140 + */
  1.1141 +
  1.1142 +long mail_status (MAILSTREAM *stream,char *mbx,long flags)
  1.1143 +{
  1.1144 +  DRIVER *dtb = mail_valid (stream,mbx,"get status of mailbox");
  1.1145 +  if (!dtb) return NIL;		/* only if valid */
  1.1146 +  if (stream && ((dtb != stream->dtb) ||
  1.1147 +		 ((dtb->flags & DR_LOCAL) && strcmp (mbx,stream->mailbox) &&
  1.1148 +		  strcmp (mbx,stream->original_mailbox))))
  1.1149 +    stream = NIL;		/* stream not suitable */
  1.1150 +  return SAFE_STATUS (dtb,stream,mbx,flags);
  1.1151 +}
  1.1152 +
  1.1153 +
  1.1154 +/* Mail status of mailbox default handler
  1.1155 + * Accepts: mail stream
  1.1156 + *	    mailbox name
  1.1157 + *	    status flags
  1.1158 + * Returns: T on success, NIL on failure
  1.1159 + */
  1.1160 +
  1.1161 +long mail_status_default (MAILSTREAM *stream,char *mbx,long flags)
  1.1162 +{
  1.1163 +  MAILSTATUS status;
  1.1164 +  unsigned long i;
  1.1165 +  MAILSTREAM *tstream = NIL;
  1.1166 +				/* make temporary stream (unless this mbx) */
  1.1167 +  if (!stream && !(stream = tstream =
  1.1168 +		   mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL;
  1.1169 +  status.flags = flags;		/* return status values */
  1.1170 +  status.messages = stream->nmsgs;
  1.1171 +  status.recent = stream->recent;
  1.1172 +  if (flags & SA_UNSEEN)	/* must search to get unseen messages */
  1.1173 +    for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
  1.1174 +      if (!mail_elt (stream,i)->seen) status.unseen++;
  1.1175 +  status.uidnext = stream->uid_last + 1;
  1.1176 +  status.uidvalidity = stream->uid_validity;
  1.1177 +  MM_STATUS(stream,mbx,&status);/* pass status to main program */
  1.1178 +  if (tstream) mail_close (tstream);
  1.1179 +  return T;			/* success */
  1.1180 +}
  1.1181 +
  1.1182 +/* Mail open
  1.1183 + * Accepts: candidate stream for recycling
  1.1184 + *	    mailbox name
  1.1185 + *	    open options
  1.1186 + * Returns: stream to use on success, NIL on failure
  1.1187 + */
  1.1188 +
  1.1189 +MAILSTREAM *mail_open (MAILSTREAM *stream,char *name,long options)
  1.1190 +{
  1.1191 +  int i;
  1.1192 +  char c,*s,tmp[MAILTMPLEN];
  1.1193 +  NETMBX mb;
  1.1194 +  DRIVER *d;
  1.1195 +  switch (name[0]) {		/* see if special handling */
  1.1196 +  case '#':			/* possible special hacks */
  1.1197 +    if (((name[1] == 'M') || (name[1] == 'm')) &&
  1.1198 +	((name[2] == 'O') || (name[2] == 'o')) &&
  1.1199 +	((name[3] == 'V') || (name[3] == 'v')) &&
  1.1200 +	((name[4] == 'E') || (name[4] == 'e')) && (c = name[5]) &&
  1.1201 +	(s = strchr (name+6,c)) && (i = s - (name + 6)) && (i < MAILTMPLEN)) {
  1.1202 +      if (stream = mail_open (stream,s+1,options)) {
  1.1203 +	strncpy (tmp,name+6,i);	/* copy snarf mailbox name */
  1.1204 +	tmp[i] = '\0';		/* tie off name */
  1.1205 +	mail_parameters (stream,SET_SNARFMAILBOXNAME,(void *) tmp);
  1.1206 +	stream->snarf.options = options;
  1.1207 +	mail_ping (stream);	/* do initial snarf */
  1.1208 +				/* punt if can't do initial snarf */
  1.1209 +	if (!stream->snarf.time) stream = mail_close (stream);
  1.1210 +      }
  1.1211 +      return stream;
  1.1212 +    }
  1.1213 +				/* special POP hack */
  1.1214 +    else if (((name[1] == 'P') || (name[1] == 'p')) &&
  1.1215 +	     ((name[2] == 'O') || (name[2] == 'o')) &&
  1.1216 +	     ((name[3] == 'P') || (name[3] == 'p')) &&
  1.1217 +	     mail_valid_net_parse_work (name+4,&mb,"pop3") &&
  1.1218 +	!strcmp (mb.service,"pop3") && !mb.anoflag && !mb.readonlyflag) {
  1.1219 +      if (stream = mail_open (stream,mb.mailbox,options)) {
  1.1220 +	sprintf (tmp,"{%.255s",mb.host);
  1.1221 +	if (mb.port) sprintf (tmp + strlen (tmp),":%lu",mb.port);
  1.1222 +	if (mb.user[0]) sprintf (tmp + strlen (tmp),"/user=%.64s",mb.user);
  1.1223 +	if (mb.dbgflag) strcat (tmp,"/debug");
  1.1224 +	if (mb.secflag) strcat (tmp,"/secure");
  1.1225 +	if (mb.tlsflag) strcat (tmp,"/tls");
  1.1226 +	if (mb.notlsflag) strcat (tmp,"/notls");
  1.1227 +	if (mb.sslflag) strcat (tmp,"/ssl");
  1.1228 +	if (mb.trysslflag) strcat (tmp,"/tryssl");
  1.1229 +	if (mb.novalidate) strcat (tmp,"/novalidate-cert");
  1.1230 +	strcat (tmp,"/pop3/loser}");
  1.1231 +	mail_parameters (stream,SET_SNARFMAILBOXNAME,(void *) tmp);
  1.1232 +	mail_ping (stream);	/* do initial snarf */
  1.1233 +      }
  1.1234 +      return stream;		/* return local mailbox stream */
  1.1235 +    }
  1.1236 +
  1.1237 +    else if ((options & OP_PROTOTYPE) &&
  1.1238 +	     ((name[1] == 'D') || (name[1] == 'd')) &&
  1.1239 +	     ((name[2] == 'R') || (name[2] == 'r')) &&
  1.1240 +	     ((name[3] == 'I') || (name[3] == 'i')) &&
  1.1241 +	     ((name[4] == 'V') || (name[4] == 'v')) &&
  1.1242 +	     ((name[5] == 'E') || (name[5] == 'e')) &&
  1.1243 +	     ((name[6] == 'R') || (name[6] == 'r')) && (name[7] == '.')) {
  1.1244 +      sprintf (tmp,"%.80s",name+8);
  1.1245 +				/* tie off name at likely delimiter */
  1.1246 +      if (s = strpbrk (tmp,"/\\:")) *s++ = '\0';
  1.1247 +      else {
  1.1248 +	sprintf (tmp,"Can't resolve mailbox %.80s: bad driver syntax",name);
  1.1249 +	MM_LOG (tmp,ERROR);
  1.1250 +	return mail_close (stream);
  1.1251 +      }
  1.1252 +      for (d = maildrivers; d && compare_cstring (d->name,tmp); d = d->next);
  1.1253 +      if (d) return (*d->open) (NIL);
  1.1254 +      sprintf (tmp,"Can't resolve mailbox %.80s: unknown driver",name);
  1.1255 +      MM_LOG (tmp,ERROR);
  1.1256 +      return mail_close (stream);
  1.1257 +    }
  1.1258 +				/* fall through to default case */
  1.1259 +  default:			/* not special hack (but could be # name */
  1.1260 +    d = mail_valid (NIL,name,(options & OP_SILENT) ?
  1.1261 +		    (char *) NIL : "open mailbox");
  1.1262 +  }
  1.1263 +  return d ? mail_open_work (d,stream,name,options) : stream;
  1.1264 +}
  1.1265 +
  1.1266 +/* Mail open worker routine
  1.1267 + * Accepts: factory
  1.1268 + *	    candidate stream for recycling
  1.1269 + *	    mailbox name
  1.1270 + *	    open options
  1.1271 + * Returns: stream to use on success, NIL on failure
  1.1272 + */
  1.1273 +
  1.1274 +MAILSTREAM *mail_open_work (DRIVER *d,MAILSTREAM *stream,char *name,
  1.1275 +			    long options)
  1.1276 +{
  1.1277 +  int i;
  1.1278 +  char tmp[MAILTMPLEN];
  1.1279 +  NETMBX mb;
  1.1280 +  if (options & OP_PROTOTYPE) return (*d->open) (NIL);
  1.1281 +  /* name is copied here in case the caller does a re-open using
  1.1282 +   * stream->mailbox or stream->original_mailbox as the argument.
  1.1283 +   */
  1.1284 +  name = cpystr (name);		/* make copy of name */
  1.1285 +  if (stream) {			/* recycling requested? */
  1.1286 +    if ((stream->dtb == d) && (d->flags & DR_RECYCLE) &&
  1.1287 +	((d->flags & DR_HALFOPEN) || !(options & OP_HALFOPEN)) &&
  1.1288 +	mail_usable_network_stream (stream,name)) {
  1.1289 +				/* yes, checkpoint if needed */
  1.1290 +      if (d->flags & DR_XPOINT) mail_check (stream);
  1.1291 +      mail_free_cache (stream);	/* clean up stream */
  1.1292 +      if (stream->mailbox) fs_give ((void **) &stream->mailbox);
  1.1293 +      if (stream->original_mailbox)
  1.1294 +	fs_give ((void **) &stream->original_mailbox);
  1.1295 +				/* flush user flags */
  1.1296 +      for (i = 0; i < NUSERFLAGS; i++)
  1.1297 +	if (stream->user_flags[i]) fs_give ((void **) &stream->user_flags[i]);
  1.1298 +    }
  1.1299 +    else {			/* stream not recycleable, babble if net */
  1.1300 +      if (!stream->silent && stream->dtb && !(stream->dtb->flags&DR_LOCAL) &&
  1.1301 +	  mail_valid_net_parse (stream->mailbox,&mb)) {
  1.1302 +	sprintf (tmp,"Closing connection to %.80s",mb.host);
  1.1303 +	MM_LOG (tmp,(long) NIL);
  1.1304 +      }
  1.1305 +				/* flush the old stream */
  1.1306 +      stream = mail_close (stream);
  1.1307 +    }
  1.1308 +  }
  1.1309 +				/* check if driver does not support halfopen */
  1.1310 +  else if ((options & OP_HALFOPEN) && !(d->flags & DR_HALFOPEN)) {
  1.1311 +    fs_give ((void **) &name);
  1.1312 +    return NIL;
  1.1313 +  }
  1.1314 +
  1.1315 +				/* instantiate new stream if not recycling */
  1.1316 +  if (!stream) (*mailcache) (stream = (MAILSTREAM *)
  1.1317 +			     memset (fs_get (sizeof (MAILSTREAM)),0,
  1.1318 +				     sizeof (MAILSTREAM)),(long) 0,CH_INIT);
  1.1319 +  stream->dtb = d;		/* set dispatch */
  1.1320 +				/* set mailbox name */
  1.1321 +  stream->mailbox = cpystr (stream->original_mailbox = name);
  1.1322 +				/* initialize stream flags */
  1.1323 +  stream->inbox = stream->lock = NIL;
  1.1324 +  stream->debug = (options & OP_DEBUG) ? T : NIL;
  1.1325 +  stream->rdonly = (options & OP_READONLY) ? T : NIL;
  1.1326 +  stream->anonymous = (options & OP_ANONYMOUS) ? T : NIL;
  1.1327 +  stream->scache = (options & OP_SHORTCACHE) ? T : NIL;
  1.1328 +  stream->silent = (options & OP_SILENT) ? T : NIL;
  1.1329 +  stream->halfopen = (options & OP_HALFOPEN) ? T : NIL;
  1.1330 +  stream->secure = (options & OP_SECURE) ? T : NIL;
  1.1331 +  stream->tryssl = (options & OP_TRYSSL) ? T : NIL;
  1.1332 +  stream->mulnewsrc = (options & OP_MULNEWSRC) ? T : NIL;
  1.1333 +  stream->nokod = (options & OP_NOKOD) ? T : NIL;
  1.1334 +  stream->sniff = (options & OP_SNIFF) ? T : NIL;
  1.1335 +  stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
  1.1336 +    stream->perm_answered = stream->perm_draft = stream->kwd_create = NIL;
  1.1337 +  stream->uid_nosticky = (d->flags & DR_NOSTICKY) ? T : NIL;
  1.1338 +  stream->uid_last = 0;		/* default UID validity */
  1.1339 +  stream->uid_validity = (unsigned long) time (0);
  1.1340 +				/* have driver open, flush if failed */
  1.1341 +  return ((*d->open) (stream)) ? stream : mail_close (stream);
  1.1342 +}
  1.1343 +
  1.1344 +/* Mail close
  1.1345 + * Accepts: mail stream
  1.1346 + *	    close options
  1.1347 + * Returns: NIL, always
  1.1348 + */
  1.1349 +
  1.1350 +MAILSTREAM *mail_close_full (MAILSTREAM *stream,long options)
  1.1351 +{
  1.1352 +  int i;
  1.1353 +  if (stream) {			/* make sure argument given */
  1.1354 +				/* do the driver's close action */
  1.1355 +    if (stream->dtb) (*stream->dtb->close) (stream,options);
  1.1356 +    stream->dtb = NIL;		/* resign driver */
  1.1357 +    if (stream->mailbox) fs_give ((void **) &stream->mailbox);
  1.1358 +    if (stream->original_mailbox)
  1.1359 +      fs_give ((void **) &stream->original_mailbox);
  1.1360 +    if (stream->snarf.name) fs_give ((void **) &stream->snarf.name);
  1.1361 +    stream->sequence++;		/* invalidate sequence */
  1.1362 +				/* flush user flags */
  1.1363 +    for (i = 0; i < NUSERFLAGS; i++)
  1.1364 +      if (stream->user_flags[i]) fs_give ((void **) &stream->user_flags[i]);
  1.1365 +    mail_free_cache (stream);	/* finally free the stream's storage */
  1.1366 +    if (mailfreestreamsparep && stream->sparep)
  1.1367 +      (*mailfreestreamsparep) (&stream->sparep);
  1.1368 +    if (!stream->use) fs_give ((void **) &stream);
  1.1369 +  }
  1.1370 +  return NIL;
  1.1371 +}
  1.1372 +
  1.1373 +/* Mail make handle
  1.1374 + * Accepts: mail stream
  1.1375 + * Returns: handle
  1.1376 + *
  1.1377 + *  Handles provide a way to have multiple pointers to a stream yet allow the
  1.1378 + * stream's owner to nuke it or recycle it.
  1.1379 + */
  1.1380 +
  1.1381 +MAILHANDLE *mail_makehandle (MAILSTREAM *stream)
  1.1382 +{
  1.1383 +  MAILHANDLE *handle = (MAILHANDLE *) fs_get (sizeof (MAILHANDLE));
  1.1384 +  handle->stream = stream;	/* copy stream */
  1.1385 +				/* and its sequence */
  1.1386 +  handle->sequence = stream->sequence;
  1.1387 +  stream->use++;		/* let stream know another handle exists */
  1.1388 +  return handle;
  1.1389 +}
  1.1390 +
  1.1391 +
  1.1392 +/* Mail release handle
  1.1393 + * Accepts: Mail handle
  1.1394 + */
  1.1395 +
  1.1396 +void mail_free_handle (MAILHANDLE **handle)
  1.1397 +{
  1.1398 +  MAILSTREAM *s;
  1.1399 +  if (*handle) {		/* only free if exists */
  1.1400 +				/* resign stream, flush unreferenced zombies */
  1.1401 +    if ((!--(s = (*handle)->stream)->use) && !s->dtb) fs_give ((void **) &s);
  1.1402 +    fs_give ((void **) handle);	/* now flush the handle */
  1.1403 +  }
  1.1404 +}
  1.1405 +
  1.1406 +
  1.1407 +/* Mail get stream handle
  1.1408 + * Accepts: Mail handle
  1.1409 + * Returns: mail stream or NIL if stream gone
  1.1410 + */
  1.1411 +
  1.1412 +MAILSTREAM *mail_stream (MAILHANDLE *handle)
  1.1413 +{
  1.1414 +  MAILSTREAM *s = handle->stream;
  1.1415 +  return (s->dtb && (handle->sequence == s->sequence)) ? s : NIL;
  1.1416 +}
  1.1417 +
  1.1418 +/* Mail fetch cache element
  1.1419 + * Accepts: mail stream
  1.1420 + *	    message # to fetch
  1.1421 + * Returns: cache element of this message
  1.1422 + * Can also be used to create cache elements for new messages.
  1.1423 + */
  1.1424 +
  1.1425 +MESSAGECACHE *mail_elt (MAILSTREAM *stream,unsigned long msgno)
  1.1426 +{
  1.1427 +  if (msgno < 1 || msgno > stream->nmsgs) {
  1.1428 +    char tmp[MAILTMPLEN];
  1.1429 +    sprintf (tmp,"Bad msgno %lu in mail_elt, nmsgs = %lu, mbx=%.80s",
  1.1430 +	     msgno,stream->nmsgs,stream->mailbox ? stream->mailbox : "???");
  1.1431 +    fatal (tmp);
  1.1432 +  }
  1.1433 +  return (MESSAGECACHE *) (*mailcache) (stream,msgno,CH_MAKEELT);
  1.1434 +}
  1.1435 +
  1.1436 +
  1.1437 +/* Mail fetch fast information
  1.1438 + * Accepts: mail stream
  1.1439 + *	    sequence
  1.1440 + *	    option flags
  1.1441 + *
  1.1442 + * Generally, mail_fetch_structure is preferred
  1.1443 + */
  1.1444 +
  1.1445 +void mail_fetch_fast (MAILSTREAM *stream,char *sequence,long flags)
  1.1446 +{
  1.1447 +  				/* do the driver's action */
  1.1448 +  if (stream->dtb && stream->dtb->fast)
  1.1449 +    (*stream->dtb->fast) (stream,sequence,flags);
  1.1450 +}
  1.1451 +
  1.1452 +
  1.1453 +/* Mail fetch flags
  1.1454 + * Accepts: mail stream
  1.1455 + *	    sequence
  1.1456 + *	    option flags
  1.1457 + */
  1.1458 +
  1.1459 +void mail_fetch_flags (MAILSTREAM *stream,char *sequence,long flags)
  1.1460 +{
  1.1461 +  				/* do the driver's action */
  1.1462 +  if (stream->dtb && stream->dtb->msgflags)
  1.1463 +    (*stream->dtb->msgflags) (stream,sequence,flags);
  1.1464 +}
  1.1465 +
  1.1466 +/* Mail fetch message overview
  1.1467 + * Accepts: mail stream
  1.1468 + *	    UID sequence to fetch
  1.1469 + *	    pointer to overview return function
  1.1470 + */
  1.1471 +
  1.1472 +void mail_fetch_overview (MAILSTREAM *stream,char *sequence,overview_t ofn)
  1.1473 +{
  1.1474 +  if (stream->dtb && mail_uid_sequence (stream,sequence) &&
  1.1475 +      !(stream->dtb->overview && (*stream->dtb->overview) (stream,ofn)) &&
  1.1476 +      mail_ping (stream))
  1.1477 +    mail_fetch_overview_default (stream,ofn);
  1.1478 +}
  1.1479 +
  1.1480 +
  1.1481 +/* Mail fetch message overview using sequence numbers instead of UIDs
  1.1482 + * Accepts: mail stream
  1.1483 + *	    sequence to fetch
  1.1484 + *	    pointer to overview return function
  1.1485 + */
  1.1486 +
  1.1487 +void mail_fetch_overview_sequence (MAILSTREAM *stream,char *sequence,
  1.1488 +				   overview_t ofn)
  1.1489 +{
  1.1490 +  if (stream->dtb && mail_sequence (stream,sequence) &&
  1.1491 +      !(stream->dtb->overview && (*stream->dtb->overview) (stream,ofn)) &&
  1.1492 +      mail_ping (stream))
  1.1493 +    mail_fetch_overview_default (stream,ofn);
  1.1494 +}
  1.1495 +
  1.1496 +
  1.1497 +/* Mail fetch message overview default handler
  1.1498 + * Accepts: mail stream with sequence bits lit
  1.1499 + *	    pointer to overview return function
  1.1500 + */
  1.1501 +
  1.1502 +void mail_fetch_overview_default (MAILSTREAM *stream,overview_t ofn)
  1.1503 +{
  1.1504 +  MESSAGECACHE *elt;
  1.1505 +  ENVELOPE *env;
  1.1506 +  OVERVIEW ov;
  1.1507 +  unsigned long i;
  1.1508 +  ov.optional.lines = 0;
  1.1509 +  ov.optional.xref = NIL;
  1.1510 +  for (i = 1; i <= stream->nmsgs; i++)
  1.1511 +    if (((elt = mail_elt (stream,i))->sequence) &&
  1.1512 +	(env = mail_fetch_structure (stream,i,NIL,NIL)) && ofn) {
  1.1513 +      ov.subject = env->subject;
  1.1514 +      ov.from = env->from;
  1.1515 +      ov.date = env->date;
  1.1516 +      ov.message_id = env->message_id;
  1.1517 +      ov.references = env->references;
  1.1518 +      ov.optional.octets = elt->rfc822_size;
  1.1519 +      (*ofn) (stream,mail_uid (stream,i),&ov,i);
  1.1520 +    }
  1.1521 +}
  1.1522 +
  1.1523 +/* Mail fetch message structure
  1.1524 + * Accepts: mail stream
  1.1525 + *	    message # to fetch
  1.1526 + *	    pointer to return body
  1.1527 + *	    option flags
  1.1528 + * Returns: envelope of this message, body returned in body value
  1.1529 + *
  1.1530 + * Fetches the "fast" information as well
  1.1531 + */
  1.1532 +
  1.1533 +ENVELOPE *mail_fetch_structure (MAILSTREAM *stream,unsigned long msgno,
  1.1534 +				BODY **body,long flags)
  1.1535 +{
  1.1536 +  ENVELOPE **env;
  1.1537 +  BODY **b;
  1.1538 +  MESSAGECACHE *elt;
  1.1539 +  char c,*s,*hdr;
  1.1540 +  unsigned long hdrsize;
  1.1541 +  STRING bs;
  1.1542 +				/* do the driver's action if specified */
  1.1543 +  if (stream->dtb && stream->dtb->structure)
  1.1544 +    return (*stream->dtb->structure) (stream,msgno,body,flags);
  1.1545 +  if (flags & FT_UID) {		/* UID form of call */
  1.1546 +    if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
  1.1547 +    else return NIL;		/* must get UID/msgno map first */
  1.1548 +  }
  1.1549 +  elt = mail_elt (stream,msgno);/* get elt for real message number */
  1.1550 +  if (stream->scache) {		/* short caching */
  1.1551 +    if (msgno != stream->msgno){/* garbage collect if not same message */
  1.1552 +      mail_gc (stream,GC_ENV | GC_TEXTS);
  1.1553 +      stream->msgno = msgno;	/* this is the current message now */
  1.1554 +    }
  1.1555 +    env = &stream->env;		/* get pointers to envelope and body */
  1.1556 +    b = &stream->body;
  1.1557 +  }
  1.1558 +  else {			/* get pointers to elt envelope and body */
  1.1559 +    env = &elt->private.msg.env;
  1.1560 +    b = &elt->private.msg.body;
  1.1561 +  }
  1.1562 +
  1.1563 +  if (stream->dtb && ((body && !*b) || !*env || (*env)->incomplete)) {
  1.1564 +    mail_free_envelope (env);	/* flush old envelope and body */
  1.1565 +    mail_free_body (b);
  1.1566 +				/* see if need to fetch the whole thing */
  1.1567 +    if (body || !elt->rfc822_size) {
  1.1568 +      s = (*stream->dtb->header) (stream,msgno,&hdrsize,flags & ~FT_INTERNAL);
  1.1569 +				/* make copy in case body fetch smashes it */
  1.1570 +      hdr = (char *) memcpy (fs_get ((size_t) hdrsize+1),s,(size_t) hdrsize);
  1.1571 +      hdr[hdrsize] = '\0';	/* tie off header */
  1.1572 +      (*stream->dtb->text) (stream,msgno,&bs,(flags & ~FT_INTERNAL) | FT_PEEK);
  1.1573 +      if (!elt->rfc822_size) elt->rfc822_size = hdrsize + SIZE (&bs);
  1.1574 +      if (body)			/* only parse body if requested */
  1.1575 +	rfc822_parse_msg (env,b,hdr,hdrsize,&bs,BADHOST,stream->dtb->flags);
  1.1576 +      else
  1.1577 +	rfc822_parse_msg (env,NIL,hdr,hdrsize,NIL,BADHOST,stream->dtb->flags);
  1.1578 +      fs_give ((void **) &hdr);	/* flush header */
  1.1579 +    }
  1.1580 +    else {			/* can save memory doing it this way */
  1.1581 +      hdr = (*stream->dtb->header) (stream,msgno,&hdrsize,flags | FT_INTERNAL);
  1.1582 +      if (hdrsize) {		/* in case null header */
  1.1583 +	c = hdr[hdrsize];	/* preserve what's there */
  1.1584 +	hdr[hdrsize] = '\0';	/* tie off header */
  1.1585 +	rfc822_parse_msg (env,NIL,hdr,hdrsize,NIL,BADHOST,stream->dtb->flags);
  1.1586 +	hdr[hdrsize] = c;	/* restore in case cached data */
  1.1587 +      }
  1.1588 +      else *env = mail_newenvelope ();
  1.1589 +    }
  1.1590 +  }
  1.1591 +				/* if need date, have date in envelope? */
  1.1592 +  if (!elt->day && *env && (*env)->date) mail_parse_date (elt,(*env)->date);
  1.1593 +				/* sigh, fill in bogus default */
  1.1594 +  if (!elt->day) elt->day = elt->month = 1;
  1.1595 +  if (body) *body = *b;		/* return the body */
  1.1596 +  return *env;			/* return the envelope */
  1.1597 +}
  1.1598 +
  1.1599 +/* Mail mark single message (internal use only)
  1.1600 + * Accepts: mail stream
  1.1601 + *	    elt to mark
  1.1602 + *	    fetch flags
  1.1603 + */
  1.1604 +
  1.1605 +static void markseen (MAILSTREAM *stream,MESSAGECACHE *elt,long flags)
  1.1606 +{
  1.1607 +  unsigned long i;
  1.1608 +  char sequence[20];
  1.1609 +  MESSAGECACHE *e;
  1.1610 +				/* non-peeking and needs to set \Seen? */
  1.1611 +  if (!(flags & FT_PEEK) && !elt->seen) {
  1.1612 +    if (stream->dtb->flagmsg){	/* driver wants per-message call? */
  1.1613 +      elt->valid = NIL;		/* do pre-alteration driver call */
  1.1614 +      (*stream->dtb->flagmsg) (stream,elt);
  1.1615 +				/* set seen, do post-alteration driver call */
  1.1616 +      elt->seen = elt->valid = T;
  1.1617 +      (*stream->dtb->flagmsg) (stream,elt);
  1.1618 +    }
  1.1619 +    if (stream->dtb->flag) {	/* driver wants one-time call?  */
  1.1620 +				/* better safe than sorry, save seq bits */
  1.1621 +      for (i = 1; i <= stream->nmsgs; i++) {
  1.1622 +	e = mail_elt (stream,i);
  1.1623 +	e->private.sequence = e->sequence;
  1.1624 +      }
  1.1625 +				/* call driver to set the message */
  1.1626 +      sprintf (sequence,"%lu",elt->msgno);
  1.1627 +      (*stream->dtb->flag) (stream,sequence,"\\Seen",ST_SET);
  1.1628 +				/* restore sequence bits */
  1.1629 +      for (i = 1; i <= stream->nmsgs; i++) {
  1.1630 +	e = mail_elt (stream,i);
  1.1631 +	e->sequence = e->private.sequence;
  1.1632 +      }
  1.1633 +    }
  1.1634 +				/* notify mail program of flag change */
  1.1635 +    MM_FLAGS (stream,elt->msgno);
  1.1636 +  }
  1.1637 +}
  1.1638 +
  1.1639 +/* Mail fetch message
  1.1640 + * Accepts: mail stream
  1.1641 + *	    message # to fetch
  1.1642 + *	    pointer to returned length
  1.1643 + *	    flags
  1.1644 + * Returns: message text
  1.1645 + */
  1.1646 +
  1.1647 +char *mail_fetch_message (MAILSTREAM *stream,unsigned long msgno,
  1.1648 +			  unsigned long *len,long flags)
  1.1649 +{
  1.1650 +  GETS_DATA md;
  1.1651 +  SIZEDTEXT *t;
  1.1652 +  STRING bs;
  1.1653 +  MESSAGECACHE *elt;
  1.1654 +  char *s,*u;
  1.1655 +  unsigned long i,j;
  1.1656 +  if (len) *len = 0;		/* default return size */
  1.1657 +  if (flags & FT_UID) {		/* UID form of call */
  1.1658 +    if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
  1.1659 +    else return "";		/* must get UID/msgno map first */
  1.1660 +  }
  1.1661 +				/* initialize message data identifier */
  1.1662 +  INIT_GETS (md,stream,msgno,"",0,0);
  1.1663 +				/* is data already cached? */
  1.1664 +  if ((t = &(elt = mail_elt (stream,msgno))->private.msg.full.text)->data) {
  1.1665 +    markseen (stream,elt,flags);/* mark message seen */
  1.1666 +    return mail_fetch_text_return (&md,t,len);
  1.1667 +  }
  1.1668 +  if (!stream->dtb) return "";	/* not in cache, must have live driver */
  1.1669 +  if (stream->dtb->msgdata) return
  1.1670 +    ((*stream->dtb->msgdata) (stream,msgno,"",0,0,NIL,flags) && t->data) ?
  1.1671 +      mail_fetch_text_return (&md,t,len) : "";
  1.1672 +				/* ugh, have to do this the crufty way */
  1.1673 +  u = mail_fetch_header (stream,msgno,NIL,NIL,&i,flags);
  1.1674 +				/* copy in case text method stomps on it */
  1.1675 +  s = (char *) memcpy (fs_get ((size_t) i),u,(size_t) i);
  1.1676 +  if ((*stream->dtb->text) (stream,msgno,&bs,flags)) {
  1.1677 +    t = &stream->text;		/* build combined copy */
  1.1678 +    if (t->data) fs_give ((void **) &t->data);
  1.1679 +    t->data = (unsigned char *) fs_get ((t->size = i + SIZE (&bs)) + 1);
  1.1680 +    if (!elt->rfc822_size) elt->rfc822_size = t->size;
  1.1681 +    else if (elt->rfc822_size != t->size) {
  1.1682 +      char tmp[MAILTMPLEN];
  1.1683 +      sprintf (tmp,"Calculated RFC822.SIZE (%lu) != reported size (%lu)",
  1.1684 +	       t->size,elt->rfc822_size);
  1.1685 +      mm_log (tmp,WARN);	/* bug trap */
  1.1686 +    }
  1.1687 +    memcpy (t->data,s,(size_t) i);
  1.1688 +    for (u = (char *) t->data + i, j = SIZE (&bs); j;) {
  1.1689 +      memcpy (u,bs.curpos,bs.cursize);
  1.1690 +      u += bs.cursize;		/* update text */
  1.1691 +      j -= bs.cursize;
  1.1692 +      bs.curpos += (bs.cursize -1);
  1.1693 +      bs.cursize = 0;
  1.1694 +      (*bs.dtb->next) (&bs);	/* advance to next buffer's worth */
  1.1695 +    } 
  1.1696 +    *u = '\0';			/* tie off data */
  1.1697 +    u = mail_fetch_text_return (&md,t,len);
  1.1698 +  }
  1.1699 +  else u = "";
  1.1700 +  fs_give ((void **) &s);	/* finished with copy of header */
  1.1701 +  return u;
  1.1702 +}
  1.1703 +
  1.1704 +/* Mail fetch message header
  1.1705 + * Accepts: mail stream
  1.1706 + *	    message # to fetch
  1.1707 + *	    MIME section specifier (#.#.#...#)
  1.1708 + *	    list of lines to fetch
  1.1709 + *	    pointer to returned length
  1.1710 + *	    flags
  1.1711 + * Returns: message header in RFC822 format
  1.1712 + *
  1.1713 + * Note: never calls a mailgets routine
  1.1714 + */
  1.1715 +
  1.1716 +char *mail_fetch_header (MAILSTREAM *stream,unsigned long msgno,char *section,
  1.1717 +			 STRINGLIST *lines,unsigned long *len,long flags)
  1.1718 +{
  1.1719 +  STRING bs;
  1.1720 +  BODY *b = NIL;
  1.1721 +  SIZEDTEXT *t = NIL,rt;
  1.1722 +  MESSAGE *m = NIL;
  1.1723 +  MESSAGECACHE *elt;
  1.1724 +  char tmp[MAILTMPLEN];
  1.1725 +  if (len) *len = 0;		/* default return size */
  1.1726 +  if (section && (strlen (section) > (MAILTMPLEN - 20))) return "";
  1.1727 +  if (flags & FT_UID) {		/* UID form of call */
  1.1728 +    if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
  1.1729 +    else return "";		/* must get UID/msgno map first */
  1.1730 +  }
  1.1731 +  elt = mail_elt (stream,msgno);/* get cache data */
  1.1732 +  if (section && *section) {	/* nested body header wanted? */
  1.1733 +    if (!((b = mail_body (stream,msgno,section)) &&
  1.1734 +	  (b->type == TYPEMESSAGE) && !strcmp (b->subtype,"RFC822")))
  1.1735 +      return "";		/* lose if no body or not MESSAGE/RFC822 */
  1.1736 +    m = b->nested.msg;		/* point to nested message */
  1.1737 +  }
  1.1738 +				/* else top-level message header wanted */
  1.1739 +  else m = &elt->private.msg;
  1.1740 +  if (m->header.text.data && mail_match_lines (lines,m->lines,flags)) {
  1.1741 +    if (lines) textcpy (t = &stream->text,&m->header.text);
  1.1742 +    else t = &m->header.text;	/* in cache, and cache is valid */
  1.1743 +    markseen (stream,elt,flags);/* mark message seen */
  1.1744 +  }
  1.1745 +
  1.1746 +  else if (stream->dtb) {	/* not in cache, has live driver? */
  1.1747 +    if (stream->dtb->msgdata) {	/* has driver section fetch? */
  1.1748 +				/* build driver section specifier */
  1.1749 +      if (section && *section) sprintf (tmp,"%s.HEADER",section);
  1.1750 +      else strcpy (tmp,"HEADER");
  1.1751 +      if ((*stream->dtb->msgdata) (stream,msgno,tmp,0,0,lines,flags)) {
  1.1752 +	t = &m->header.text;	/* fetch data */
  1.1753 +				/* don't need to postprocess lines */
  1.1754 +	if (m->lines) lines = NIL;
  1.1755 +	else if (lines) textcpy (t = &stream->text,&m->header.text);
  1.1756 +      }
  1.1757 +    }
  1.1758 +    else if (b) {		/* nested body wanted? */
  1.1759 +      if (stream->private.search.text) {
  1.1760 +	rt.data = (unsigned char *) stream->private.search.text +
  1.1761 +	  b->nested.msg->header.offset;
  1.1762 +	rt.size = b->nested.msg->header.text.size;
  1.1763 +	t = &rt;
  1.1764 +      }
  1.1765 +      else if ((*stream->dtb->text) (stream,msgno,&bs,flags & ~FT_INTERNAL)) {
  1.1766 +	if ((bs.dtb->next == mail_string_next) && !lines) {
  1.1767 +	  rt.data = (unsigned char *) bs.curpos + b->nested.msg->header.offset;
  1.1768 +	  rt.size = b->nested.msg->header.text.size;
  1.1769 +	  if (stream->private.search.string)
  1.1770 +	    stream->private.search.text = bs.curpos;
  1.1771 +	  t = &rt;		/* special hack to avoid extra copy */
  1.1772 +	}
  1.1773 +	else textcpyoffstring (t = &stream->text,&bs,
  1.1774 +			       b->nested.msg->header.offset,
  1.1775 +			       b->nested.msg->header.text.size);
  1.1776 +      }
  1.1777 +    }
  1.1778 +    else {			/* top-level header fetch */
  1.1779 +				/* mark message seen */
  1.1780 +      markseen (stream,elt,flags);
  1.1781 +      if (rt.data = (unsigned char *)
  1.1782 +	  (*stream->dtb->header) (stream,msgno,&rt.size,flags)) {
  1.1783 +				/* make a safe copy if need to filter */
  1.1784 +	if (lines) textcpy (t = &stream->text,&rt);
  1.1785 +	else t = &rt;		/* top level header */
  1.1786 +      }
  1.1787 +    }
  1.1788 +  }
  1.1789 +  if (!t || !t->data) return "";/* error if no string */
  1.1790 +				/* filter headers if requested */
  1.1791 +  if (lines) t->size = mail_filter ((char *) t->data,t->size,lines,flags);
  1.1792 +  if (len) *len = t->size;	/* return size if requested */
  1.1793 +  return (char *) t->data;	/* and text */
  1.1794 +}
  1.1795 +
  1.1796 +/* Mail fetch message text
  1.1797 + * Accepts: mail stream
  1.1798 + *	    message # to fetch
  1.1799 + *	    MIME section specifier (#.#.#...#)
  1.1800 + *	    pointer to returned length
  1.1801 + *	    flags
  1.1802 + * Returns: message text
  1.1803 + */
  1.1804 +
  1.1805 +char *mail_fetch_text (MAILSTREAM *stream,unsigned long msgno,char *section,
  1.1806 +		       unsigned long *len,long flags)
  1.1807 +{
  1.1808 +  GETS_DATA md;
  1.1809 +  PARTTEXT *p;
  1.1810 +  STRING bs;
  1.1811 +  MESSAGECACHE *elt;
  1.1812 +  BODY *b = NIL;
  1.1813 +  char tmp[MAILTMPLEN];
  1.1814 +  unsigned long i;
  1.1815 +  if (len) *len = 0;		/* default return size */
  1.1816 +  memset (&stream->private.string,NIL,sizeof (STRING));
  1.1817 +  if (section && (strlen (section) > (MAILTMPLEN - 20))) return "";
  1.1818 +  if (flags & FT_UID) {		/* UID form of call */
  1.1819 +    if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
  1.1820 +    else return "";		/* must get UID/msgno map first */
  1.1821 +  }
  1.1822 +  elt = mail_elt (stream,msgno);/* get cache data */
  1.1823 +  if (section && *section) {	/* nested body text wanted? */
  1.1824 +    if (!((b = mail_body (stream,msgno,section)) &&
  1.1825 +	  (b->type == TYPEMESSAGE) && !strcmp (b->subtype,"RFC822")))
  1.1826 +      return "";		/* lose if no body or not MESSAGE/RFC822 */
  1.1827 +    p = &b->nested.msg->text;	/* point at nested message */
  1.1828 +				/* build IMAP-format section specifier */
  1.1829 +    sprintf (tmp,"%s.TEXT",section);
  1.1830 +    flags &= ~FT_INTERNAL;	/* can't win with this set */
  1.1831 +  }
  1.1832 +  else {			/* top-level message text wanted */
  1.1833 +    p = &elt->private.msg.text;
  1.1834 +    strcpy (tmp,"TEXT");
  1.1835 +  }
  1.1836 +				/* initialize message data identifier */
  1.1837 +  INIT_GETS (md,stream,msgno,section,0,0);
  1.1838 +  if (p->text.data) {		/* is data already cached? */
  1.1839 +    markseen (stream,elt,flags);/* mark message seen */
  1.1840 +    return mail_fetch_text_return (&md,&p->text,len);
  1.1841 +  }
  1.1842 +  if (!stream->dtb) return "";	/* not in cache, must have live driver */
  1.1843 +  if (stream->dtb->msgdata) return
  1.1844 +    ((*stream->dtb->msgdata) (stream,msgno,tmp,0,0,NIL,flags) && p->text.data)?
  1.1845 +      mail_fetch_text_return (&md,&p->text,len) : "";
  1.1846 +  if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) return "";
  1.1847 +  if (section && *section) {	/* nested is more complex */
  1.1848 +    SETPOS (&bs,p->offset);
  1.1849 +    i = p->text.size;		/* just want this much */
  1.1850 +  }
  1.1851 +  else i = SIZE (&bs);		/* want entire text */
  1.1852 +  return mail_fetch_string_return (&md,&bs,i,len,flags);
  1.1853 +}
  1.1854 +
  1.1855 +/* Mail fetch message body part MIME headers
  1.1856 + * Accepts: mail stream
  1.1857 + *	    message # to fetch
  1.1858 + *	    MIME section specifier (#.#.#...#)
  1.1859 + *	    pointer to returned length
  1.1860 + *	    flags
  1.1861 + * Returns: message text
  1.1862 + */
  1.1863 +
  1.1864 +char *mail_fetch_mime (MAILSTREAM *stream,unsigned long msgno,char *section,
  1.1865 +		       unsigned long *len,long flags)
  1.1866 +{
  1.1867 +  PARTTEXT *p;
  1.1868 +  STRING bs;
  1.1869 +  BODY *b;
  1.1870 +  char tmp[MAILTMPLEN];
  1.1871 +  if (len) *len = 0;		/* default return size */
  1.1872 +  if (section && (strlen (section) > (MAILTMPLEN - 20))) return "";
  1.1873 +  if (flags & FT_UID) {		/* UID form of call */
  1.1874 +    if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
  1.1875 +    else return "";		/* must get UID/msgno map first */
  1.1876 +  }
  1.1877 +  flags &= ~FT_INTERNAL;	/* can't win with this set */
  1.1878 +  if (!(section && *section && (b = mail_body (stream,msgno,section))))
  1.1879 +    return "";			/* not valid section */
  1.1880 +				/* in cache? */
  1.1881 +  if ((p = &b->mime)->text.data) {
  1.1882 +				/* mark message seen */
  1.1883 +    markseen (stream,mail_elt (stream,msgno),flags);
  1.1884 +    if (len) *len = p->text.size;
  1.1885 +    return (char *) p->text.data;
  1.1886 +  }
  1.1887 +  if (!stream->dtb) return "";	/* not in cache, must have live driver */
  1.1888 +  if (stream->dtb->msgdata) {	/* has driver fetch? */
  1.1889 +				/* build driver section specifier */
  1.1890 +    sprintf (tmp,"%s.MIME",section);
  1.1891 +    if ((*stream->dtb->msgdata) (stream,msgno,tmp,0,0,NIL,flags) &&
  1.1892 +	p->text.data) {
  1.1893 +      if (len) *len = p->text.size;
  1.1894 +      return (char *) p->text.data;
  1.1895 +    }
  1.1896 +    else return "";
  1.1897 +  }
  1.1898 +  if (len) *len = b->mime.text.size;
  1.1899 +  if (!b->mime.text.size) {	/* empty MIME header -- mark seen anyway */
  1.1900 +    markseen (stream,mail_elt (stream,msgno),flags);
  1.1901 +    return "";
  1.1902 +  }
  1.1903 +				/* have to get it from offset */
  1.1904 +  if (stream->private.search.text)
  1.1905 +    return stream->private.search.text + b->mime.offset;
  1.1906 +  if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) {
  1.1907 +    if (len) *len = 0;
  1.1908 +    return "";
  1.1909 +  }
  1.1910 +  if (bs.dtb->next == mail_string_next) {
  1.1911 +    if (stream->private.search.string) stream->private.search.text = bs.curpos;
  1.1912 +    return bs.curpos + b->mime.offset;
  1.1913 +  }
  1.1914 +  return textcpyoffstring (&stream->text,&bs,b->mime.offset,b->mime.text.size);
  1.1915 +}
  1.1916 +
  1.1917 +/* Mail fetch message body part
  1.1918 + * Accepts: mail stream
  1.1919 + *	    message # to fetch
  1.1920 + *	    MIME section specifier (#.#.#...#)
  1.1921 + *	    pointer to returned length
  1.1922 + *	    flags
  1.1923 + * Returns: message body
  1.1924 + */
  1.1925 +
  1.1926 +char *mail_fetch_body (MAILSTREAM *stream,unsigned long msgno,char *section,
  1.1927 +		       unsigned long *len,long flags)
  1.1928 +{
  1.1929 +  GETS_DATA md;
  1.1930 +  PARTTEXT *p;
  1.1931 +  STRING bs;
  1.1932 +  BODY *b;
  1.1933 +  SIZEDTEXT *t;
  1.1934 +  char *s,tmp[MAILTMPLEN];
  1.1935 +  memset (&stream->private.string,NIL,sizeof (STRING));
  1.1936 +  if (!(section && *section))	/* top-level text wanted? */
  1.1937 +    return mail_fetch_message (stream,msgno,len,flags);
  1.1938 +  else if (strlen (section) > (MAILTMPLEN - 20)) return "";
  1.1939 +  flags &= ~FT_INTERNAL;	/* can't win with this set */
  1.1940 +				/* initialize message data identifier */
  1.1941 +  INIT_GETS (md,stream,msgno,section,0,0);
  1.1942 +				/* kludge for old section 0 header */
  1.1943 +  if (!strcmp (s = strcpy (tmp,section),"0") ||
  1.1944 +      ((s = strstr (tmp,".0")) && !s[2])) {
  1.1945 +    SIZEDTEXT ht;
  1.1946 +    *s = '\0';			/* tie off section */
  1.1947 +				/* this silly way so it does mailgets */
  1.1948 +    ht.data = (unsigned char *) mail_fetch_header (stream,msgno,
  1.1949 +						   tmp[0] ? tmp : NIL,NIL,
  1.1950 +						   &ht.size,flags);
  1.1951 +				/* may have UIDs here */
  1.1952 +    md.flags = (flags & FT_UID) ? MG_UID : NIL;
  1.1953 +    return mail_fetch_text_return (&md,&ht,len);
  1.1954 +  }
  1.1955 +  if (len) *len = 0;		/* default return size */
  1.1956 +  if (flags & FT_UID) {		/* UID form of call */
  1.1957 +    if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
  1.1958 +    else return "";		/* must get UID/msgno map first */
  1.1959 +  }
  1.1960 +				/* must have body */
  1.1961 +  if (!(b = mail_body (stream,msgno,section))) return "";
  1.1962 +				/* have cached text? */
  1.1963 +  if ((t = &(p = &b->contents)->text)->data) {
  1.1964 +				/* mark message seen */
  1.1965 +    markseen (stream,mail_elt (stream,msgno),flags);
  1.1966 +    return mail_fetch_text_return (&md,t,len);
  1.1967 +  }
  1.1968 +  if (!stream->dtb) return "";	/* not in cache, must have live driver */
  1.1969 +  if (stream->dtb->msgdata) return
  1.1970 +    ((*stream->dtb->msgdata)(stream,msgno,section,0,0,NIL,flags) && t->data) ?
  1.1971 +      mail_fetch_text_return (&md,t,len) : "";
  1.1972 +  if (len) *len = t->size;
  1.1973 +  if (!t->size) {		/* empty body part -- mark seen anyway */
  1.1974 +    markseen (stream,mail_elt (stream,msgno),flags);
  1.1975 +    return "";
  1.1976 +  }
  1.1977 +				/* copy body from stringstruct offset */
  1.1978 +  if (stream->private.search.text)
  1.1979 +    return stream->private.search.text + p->offset;
  1.1980 +  if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) {
  1.1981 +    if (len) *len = 0;
  1.1982 +    return "";
  1.1983 +  }
  1.1984 +  if (bs.dtb->next == mail_string_next) {
  1.1985 +    if (stream->private.search.string) stream->private.search.text = bs.curpos;
  1.1986 +    return bs.curpos + p->offset;
  1.1987 +  }
  1.1988 +  SETPOS (&bs,p->offset);
  1.1989 +  return mail_fetch_string_return (&md,&bs,t->size,len,flags);
  1.1990 +}
  1.1991 +
  1.1992 +/* Mail fetch partial message text
  1.1993 + * Accepts: mail stream
  1.1994 + *	    message # to fetch
  1.1995 + *	    MIME section specifier (#.#.#...#)
  1.1996 + *	    offset of first designed byte or 0 to start at beginning
  1.1997 + *	    maximum number of bytes or 0 for all bytes
  1.1998 + *	    flags
  1.1999 + * Returns: T if successful, else NIL
  1.2000 + */
  1.2001 +
  1.2002 +long mail_partial_text (MAILSTREAM *stream,unsigned long msgno,char *section,
  1.2003 +			unsigned long first,unsigned long last,long flags)
  1.2004 +{
  1.2005 +  GETS_DATA md;
  1.2006 +  PARTTEXT *p = NIL;
  1.2007 +  MESSAGECACHE *elt;
  1.2008 +  STRING bs;
  1.2009 +  BODY *b;
  1.2010 +  char tmp[MAILTMPLEN];
  1.2011 +  unsigned long i;
  1.2012 +  if (!mailgets) fatal ("mail_partial_text() called without a mailgets!");
  1.2013 +  if (section && (strlen (section) > (MAILTMPLEN - 20))) return NIL;
  1.2014 +  if (flags & FT_UID) {		/* UID form of call */
  1.2015 +    if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
  1.2016 +    else return NIL;		/* must get UID/msgno map first */
  1.2017 +  }
  1.2018 +  elt = mail_elt (stream,msgno);/* get cache data */
  1.2019 +  flags &= ~FT_INTERNAL;	/* bogus if this is set */
  1.2020 +  if (section && *section) {	/* nested body text wanted? */
  1.2021 +    if (!((b = mail_body (stream,msgno,section)) &&
  1.2022 +	  (b->type == TYPEMESSAGE) && !strcmp (b->subtype,"RFC822")))
  1.2023 +      return NIL;		/* lose if no body or not MESSAGE/RFC822 */
  1.2024 +    p = &b->nested.msg->text;	/* point at nested message */
  1.2025 +				/* build IMAP-format section specifier */
  1.2026 +    sprintf (tmp,"%s.TEXT",section);
  1.2027 +  }
  1.2028 +  else {			/* else top-level message text wanted */
  1.2029 +    p = &elt->private.msg.text;
  1.2030 +    strcpy (tmp,"TEXT");
  1.2031 +  }
  1.2032 +
  1.2033 +				/* initialize message data identifier */
  1.2034 +  INIT_GETS (md,stream,msgno,tmp,first,last);
  1.2035 +  if (p->text.data) {		/* is data already cached? */
  1.2036 +    INIT (&bs,mail_string,p->text.data,i = p->text.size);
  1.2037 +    markseen (stream,elt,flags);/* mark message seen */
  1.2038 +  }
  1.2039 +  else {			/* else get data from driver */
  1.2040 +    if (!stream->dtb) return NIL;
  1.2041 +    if (stream->dtb->msgdata)	/* driver will handle this */
  1.2042 +      return (*stream->dtb->msgdata) (stream,msgno,tmp,first,last,NIL,flags);
  1.2043 +    if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) return NIL;
  1.2044 +    if (section && *section) {	/* nexted if more complex */
  1.2045 +      SETPOS (&bs,p->offset);	/* offset stringstruct to data */
  1.2046 +      i = p->text.size;		/* maximum size of data */
  1.2047 +    }
  1.2048 +    else i = SIZE (&bs);	/* just want this much */
  1.2049 +  }
  1.2050 +  if (i <= first) i = first = 0;/* first byte is beyond end of text */
  1.2051 +				/* truncate as needed */
  1.2052 +  else {			/* offset and truncate */
  1.2053 +    SETPOS (&bs,first + GETPOS (&bs));
  1.2054 +    i -= first;			/* reduced size */
  1.2055 +    if (last && (i > last)) i = last;
  1.2056 +  }
  1.2057 +				/* do the mailgets thing */
  1.2058 +  (*mailgets) (mail_read,&bs,i,&md);
  1.2059 +  return T;			/* success */
  1.2060 +}
  1.2061 +
  1.2062 +/* Mail fetch partial message body part
  1.2063 + * Accepts: mail stream
  1.2064 + *	    message # to fetch
  1.2065 + *	    MIME section specifier (#.#.#...#)
  1.2066 + *	    offset of first designed byte or 0 to start at beginning
  1.2067 + *	    maximum number of bytes or 0 for all bytes
  1.2068 + *	    flags
  1.2069 + * Returns: T if successful, else NIL
  1.2070 + */
  1.2071 +
  1.2072 +long mail_partial_body (MAILSTREAM *stream,unsigned long msgno,char *section,
  1.2073 +			unsigned long first,unsigned long last,long flags)
  1.2074 +{
  1.2075 +  GETS_DATA md;
  1.2076 +  PARTTEXT *p;
  1.2077 +  STRING bs;
  1.2078 +  BODY *b;
  1.2079 +  SIZEDTEXT *t;
  1.2080 +  unsigned long i;
  1.2081 +  if (!(section && *section))	/* top-level text wanted? */
  1.2082 +    return mail_partial_text (stream,msgno,NIL,first,last,flags);
  1.2083 +  if (!mailgets) fatal ("mail_partial_body() called without a mailgets!");
  1.2084 +  if (flags & FT_UID) {		/* UID form of call */
  1.2085 +    if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
  1.2086 +    else return NIL;		/* must get UID/msgno map first */
  1.2087 +  }
  1.2088 +				/* must have body */
  1.2089 +  if (!(b = mail_body (stream,msgno,section))) return NIL;
  1.2090 +  flags &= ~FT_INTERNAL;	/* bogus if this is set */
  1.2091 +
  1.2092 +				/* initialize message data identifier */
  1.2093 +  INIT_GETS (md,stream,msgno,section,first,last);
  1.2094 +				/* have cached text? */
  1.2095 +  if ((t = &(p = &b->contents)->text)->data) {
  1.2096 +				/* mark message seen */
  1.2097 +    markseen (stream,mail_elt (stream,msgno),flags);
  1.2098 +    INIT (&bs,mail_string,t->data,i = t->size);
  1.2099 +  }
  1.2100 +  else {			/* else get data from driver */
  1.2101 +    if (!stream->dtb) return NIL;
  1.2102 +    if (stream->dtb->msgdata)	/* driver will handle this */
  1.2103 +      return (*stream->dtb->msgdata) (stream,msgno,section,first,last,NIL,
  1.2104 +				      flags);
  1.2105 +    if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) return NIL;
  1.2106 +    if (section && *section) {	/* nexted if more complex */
  1.2107 +      SETPOS (&bs,p->offset);	/* offset stringstruct to data */
  1.2108 +      i = t->size;		/* maximum size of data */
  1.2109 +    }
  1.2110 +    else i = SIZE (&bs);	/* just want this much */
  1.2111 +  }
  1.2112 +  if (i <= first) i = first = 0;/* first byte is beyond end of text */
  1.2113 +  else {			/* offset and truncate */
  1.2114 +    SETPOS (&bs,first + GETPOS (&bs));
  1.2115 +    i -= first;			/* reduced size */
  1.2116 +    if (last && (i > last)) i = last;
  1.2117 +  }
  1.2118 +				/* do the mailgets thing */
  1.2119 +  (*mailgets) (mail_read,&bs,i,&md);
  1.2120 +  return T;			/* success */
  1.2121 +}
  1.2122 +
  1.2123 +/* Mail return message text
  1.2124 + * Accepts: identifier data
  1.2125 + *	    sized text
  1.2126 + *	    pointer to returned length
  1.2127 + * Returns: text
  1.2128 + */
  1.2129 +
  1.2130 +char *mail_fetch_text_return (GETS_DATA *md,SIZEDTEXT *t,unsigned long *len)
  1.2131 +{
  1.2132 +  STRING bs;
  1.2133 +  if (len) *len = t->size;	/* return size */
  1.2134 +  if (t->size && mailgets) {	/* have to do the mailgets thing? */
  1.2135 +				/* silly but do it anyway for consistency */
  1.2136 +    INIT (&bs,mail_string,t->data,t->size);
  1.2137 +    return (*mailgets) (mail_read,&bs,t->size,md);
  1.2138 +  }
  1.2139 +  return t->size ? (char *) t->data : "";
  1.2140 +}
  1.2141 +
  1.2142 +
  1.2143 +/* Mail return message string
  1.2144 + * Accepts: identifier data
  1.2145 + *	    stringstruct
  1.2146 + *	    text length
  1.2147 + *	    pointer to returned length
  1.2148 + *	    flags
  1.2149 + * Returns: text, or NIL if stringstruct returned
  1.2150 + */
  1.2151 +
  1.2152 +char *mail_fetch_string_return (GETS_DATA *md,STRING *bs,unsigned long i,
  1.2153 +				unsigned long *len,long flags)
  1.2154 +{
  1.2155 +  char *ret = NIL;
  1.2156 +  if (len) *len = i;		/* return size */
  1.2157 +				/* return stringstruct hack */
  1.2158 +  if (flags & FT_RETURNSTRINGSTRUCT) {
  1.2159 +    memcpy (&md->stream->private.string,bs,sizeof (STRING));
  1.2160 +    SETPOS (&md->stream->private.string,GETPOS (&md->stream->private.string));
  1.2161 +  }
  1.2162 +				/* have to do the mailgets thing? */
  1.2163 +  else if (mailgets) ret = (*mailgets) (mail_read,bs,i,md);
  1.2164 +				/* special hack to avoid extra copy */
  1.2165 +  else if (bs->dtb->next == mail_string_next) ret = bs->curpos;
  1.2166 +				/* make string copy in memory */
  1.2167 +  else ret = textcpyoffstring (&md->stream->text,bs,GETPOS (bs),i);
  1.2168 +  return ret;
  1.2169 +}
  1.2170 +
  1.2171 +/* Read data from stringstruct
  1.2172 + * Accepts: stringstruct
  1.2173 + *	    size of data to read
  1.2174 + *	    buffer to read into
  1.2175 + * Returns: T, always, stringstruct updated
  1.2176 + */
  1.2177 +
  1.2178 +long mail_read (void *stream,unsigned long size,char *buffer)
  1.2179 +{
  1.2180 +  unsigned long i;
  1.2181 +  STRING *s = (STRING *) stream;
  1.2182 +  while (size) {		/* until satisfied */
  1.2183 +    memcpy (buffer,s->curpos,i = min (s->cursize,size));
  1.2184 +    buffer += i;		/* update buffer */
  1.2185 +    size -= i;			/* note that we read this much */
  1.2186 +    s->curpos += --i;		/* advance that many spaces minus 1 */
  1.2187 +    s->cursize -= i;
  1.2188 +    SNX (s);			/* now use SNX to advance the last byte */
  1.2189 +  }
  1.2190 +  return T;
  1.2191 +}
  1.2192 +
  1.2193 +/* Mail fetch UID
  1.2194 + * Accepts: mail stream
  1.2195 + *	    message number
  1.2196 + * Returns: UID or zero if dead stream
  1.2197 + */
  1.2198 +
  1.2199 +unsigned long mail_uid (MAILSTREAM *stream,unsigned long msgno)
  1.2200 +{
  1.2201 +  unsigned long uid = mail_elt (stream,msgno)->private.uid;
  1.2202 +  return uid ? uid :
  1.2203 +    (stream->dtb && stream->dtb->uid) ? (*stream->dtb->uid) (stream,msgno) : 0;
  1.2204 +}
  1.2205 +
  1.2206 +
  1.2207 +/* Mail fetch msgno from UID
  1.2208 + * Accepts: mail stream
  1.2209 + *	    UID
  1.2210 + * Returns: msgno or zero if failed
  1.2211 + */
  1.2212 +
  1.2213 +unsigned long mail_msgno (MAILSTREAM *stream,unsigned long uid)
  1.2214 +{
  1.2215 +  unsigned long msgno,delta,first,firstuid,last,lastuid,middle,miduid;
  1.2216 +  if (stream->dtb) {		/* active stream? */
  1.2217 +    if (stream->dtb->msgno)	/* direct way */
  1.2218 +      return (*stream->dtb->msgno) (stream,uid);
  1.2219 +    else if (stream->dtb->uid) {/* indirect way */
  1.2220 +      /* Placeholder for now, since currently there are no drivers which
  1.2221 +       * have a uid method but not a msgno method
  1.2222 +       */
  1.2223 +      for (msgno = 1; msgno <= stream->nmsgs; msgno++)
  1.2224 +	if ((*stream->dtb->uid) (stream,msgno) == uid) return msgno;
  1.2225 +    }
  1.2226 +				/* binary search since have full map */
  1.2227 +    else for (first = 1,last = stream->nmsgs, delta = (first <= last) ? 1 : 0;
  1.2228 +	      delta &&
  1.2229 +	      (uid >= (firstuid = mail_elt (stream,first)->private.uid)) &&
  1.2230 +		(uid <= (lastuid = mail_elt (stream,last)->private.uid));) {
  1.2231 +				/* done if match at an endpoint */
  1.2232 +	if (uid == firstuid) return first;
  1.2233 +	if (uid == lastuid) return last;
  1.2234 +				/* have anything between endpoints? */
  1.2235 +	if (delta = ((last - first) / 2)) {
  1.2236 +	  if ((miduid = mail_elt (stream,middle = first + delta)->private.uid)
  1.2237 +	      == uid)
  1.2238 +	    return middle;	/* found match in middle */
  1.2239 +	  else if (uid < miduid) last = middle - 1;
  1.2240 +	  else first = middle + 1;
  1.2241 +	}
  1.2242 +    }
  1.2243 +  }
  1.2244 +  else {			/* dead stream, do linear search for UID */
  1.2245 +    for (msgno = 1; msgno <= stream->nmsgs; msgno++)
  1.2246 +      if (mail_elt (stream,msgno)->private.uid == uid) return msgno;
  1.2247 +  }
  1.2248 +  return 0;			/* didn't find the UID anywhere */
  1.2249 +}
  1.2250 +
  1.2251 +/* Mail fetch From string for menu
  1.2252 + * Accepts: destination string
  1.2253 + *	    mail stream
  1.2254 + *	    message # to fetch
  1.2255 + *	    desired string length
  1.2256 + * Returns: string of requested length
  1.2257 + */
  1.2258 +
  1.2259 +void mail_fetchfrom (char *s,MAILSTREAM *stream,unsigned long msgno,
  1.2260 +		     long length)
  1.2261 +{
  1.2262 +  char *t;
  1.2263 +  char tmp[MAILTMPLEN];
  1.2264 +  ENVELOPE *env = mail_fetchenvelope (stream,msgno);
  1.2265 +  ADDRESS *adr = env ? env->from : NIL;
  1.2266 +  memset (s,' ',(size_t)length);/* fill it with spaces */
  1.2267 +  s[length] = '\0';		/* tie off with null */
  1.2268 +				/* get first from address from envelope */
  1.2269 +  while (adr && !adr->host) adr = adr->next;
  1.2270 +  if (adr) {			/* if a personal name exists use it */
  1.2271 +    if (!(t = adr->personal))
  1.2272 +      sprintf (t = tmp,"%.256s@%.256s",adr->mailbox,adr->host);
  1.2273 +    memcpy (s,t,(size_t) min (length,(long) strlen (t)));
  1.2274 +  }
  1.2275 +}
  1.2276 +
  1.2277 +
  1.2278 +/* Mail fetch Subject string for menu
  1.2279 + * Accepts: destination string
  1.2280 + *	    mail stream
  1.2281 + *	    message # to fetch
  1.2282 + *	    desired string length
  1.2283 + * Returns: string of no more than requested length
  1.2284 + */
  1.2285 +
  1.2286 +void mail_fetchsubject (char *s,MAILSTREAM *stream,unsigned long msgno,
  1.2287 +			long length)
  1.2288 +{
  1.2289 +  ENVELOPE *env = mail_fetchenvelope (stream,msgno);
  1.2290 +  memset (s,'\0',(size_t) length+1);
  1.2291 +				/* copy subject from envelope */
  1.2292 +  if (env && env->subject) strncpy (s,env->subject,(size_t) length);
  1.2293 +  else *s = ' ';		/* if no subject then just a space */
  1.2294 +}
  1.2295 +
  1.2296 +/* Mail modify flags
  1.2297 + * Accepts: mail stream
  1.2298 + *	    sequence
  1.2299 + *	    flag(s)
  1.2300 + *	    option flags
  1.2301 + */
  1.2302 +
  1.2303 +void mail_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
  1.2304 +{
  1.2305 +  MESSAGECACHE *elt;
  1.2306 +  unsigned long i,uf;
  1.2307 +  long f;
  1.2308 +  short nf;
  1.2309 +  if (!stream->dtb) return;	/* no-op if no stream */
  1.2310 +  if ((stream->dtb->flagmsg || !stream->dtb->flag) &&
  1.2311 +      ((flags & ST_UID) ? mail_uid_sequence (stream,sequence) :
  1.2312 +       mail_sequence (stream,sequence)) &&
  1.2313 +      ((f = mail_parse_flags (stream,flag,&uf)) || uf))
  1.2314 +    for (i = 1,nf = (flags & ST_SET) ? T : NIL; i <= stream->nmsgs; i++)
  1.2315 +      if ((elt = mail_elt (stream,i))->sequence) {
  1.2316 +	struct {		/* old flags */
  1.2317 +	  unsigned int valid : 1;
  1.2318 +	  unsigned int seen : 1;
  1.2319 +	  unsigned int deleted : 1;
  1.2320 +	  unsigned int flagged : 1;
  1.2321 +	  unsigned int answered : 1;
  1.2322 +	  unsigned int draft : 1;
  1.2323 +	  unsigned long user_flags;
  1.2324 +	} old;
  1.2325 +	old.valid = elt->valid; old.seen = elt->seen;
  1.2326 +	old.deleted = elt->deleted; old.flagged = elt->flagged;
  1.2327 +	old.answered = elt->answered; old.draft = elt->draft;
  1.2328 +	old.user_flags = elt->user_flags;
  1.2329 +	elt->valid = NIL;	/* prepare for flag alteration */
  1.2330 +	if (stream->dtb->flagmsg) (*stream->dtb->flagmsg) (stream,elt);
  1.2331 +	if (f&fSEEN) elt->seen = nf;
  1.2332 +	if (f&fDELETED) elt->deleted = nf;
  1.2333 +	if (f&fFLAGGED) elt->flagged = nf;
  1.2334 +	if (f&fANSWERED) elt->answered = nf;
  1.2335 +	if (f&fDRAFT) elt->draft = nf;
  1.2336 +				/* user flags */
  1.2337 +	if (flags & ST_SET) elt->user_flags |= uf;
  1.2338 +	else elt->user_flags &= ~uf;
  1.2339 +	elt->valid = T;		/* flags now altered */
  1.2340 +	if ((old.valid != elt->valid) || (old.seen != elt->seen) ||
  1.2341 +	    (old.deleted != elt->deleted) || (old.flagged != elt->flagged) ||
  1.2342 +	    (old.answered != elt->answered) || (old.draft != elt->draft) ||
  1.2343 +	    (old.user_flags != elt->user_flags))
  1.2344 +	  MM_FLAGS (stream,elt->msgno);
  1.2345 +	if (stream->dtb->flagmsg) (*stream->dtb->flagmsg) (stream,elt);
  1.2346 +      }
  1.2347 +				/* call driver once */
  1.2348 +  if (stream->dtb->flag) (*stream->dtb->flag) (stream,sequence,flag,flags);
  1.2349 +}
  1.2350 +
  1.2351 +/* Mail search for messages
  1.2352 + * Accepts: mail stream
  1.2353 + *	    character set
  1.2354 + *	    search program
  1.2355 + *	    option flags
  1.2356 + * Returns: T if successful, NIL if dead stream, NIL searchpgm or bad charset
  1.2357 + */
  1.2358 +
  1.2359 +long mail_search_full (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,
  1.2360 +		       long flags)
  1.2361 +{
  1.2362 +  unsigned long i;
  1.2363 +  long ret = NIL;
  1.2364 +  if (!(flags & SE_RETAIN))	/* clear search vector unless retaining */
  1.2365 +    for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = NIL;
  1.2366 +  if (pgm && stream->dtb)	/* must have a search program and driver */
  1.2367 +    ret = (*(stream->dtb->search ? stream->dtb->search : mail_search_default))
  1.2368 +      (stream,charset,pgm,flags);
  1.2369 +				/* flush search program if requested */
  1.2370 +  if (flags & SE_FREE) mail_free_searchpgm (&pgm);
  1.2371 +  return ret;
  1.2372 +}
  1.2373 +
  1.2374 +
  1.2375 +/* Mail search for messages default handler
  1.2376 + * Accepts: mail stream
  1.2377 + *	    character set
  1.2378 + *	    search program
  1.2379 + *	    option flags
  1.2380 + * Returns: T if successful, NIL if bad charset
  1.2381 + */
  1.2382 +
  1.2383 +long mail_search_default (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,
  1.2384 +			  long flags)
  1.2385 +{
  1.2386 +  unsigned long i;
  1.2387 +  char *msg;
  1.2388 +				/* make sure that charset is good */
  1.2389 +  if (msg = utf8_badcharset (charset)) {
  1.2390 +    MM_LOG (msg,ERROR);		/* output error */
  1.2391 +    fs_give ((void **) &msg);
  1.2392 +    return NIL;
  1.2393 +  }
  1.2394 +  utf8_searchpgm (pgm,charset);
  1.2395 +  for (i = 1; i <= stream->nmsgs; ++i)
  1.2396 +    if (mail_search_msg (stream,i,NIL,pgm)) {
  1.2397 +      if (flags & SE_UID) mm_searched (stream,mail_uid (stream,i));
  1.2398 +      else {			/* mark as searched, notify mail program */
  1.2399 +	mail_elt (stream,i)->searched = T;
  1.2400 +	if (!stream->silent) mm_searched (stream,i);
  1.2401 +      }
  1.2402 +    }
  1.2403 +  return LONGT;		/* search completed */
  1.2404 +}
  1.2405 +
  1.2406 +/* Mail ping mailbox
  1.2407 + * Accepts: mail stream
  1.2408 + * Returns: stream if still open else NIL
  1.2409 + */
  1.2410 +
  1.2411 +long mail_ping (MAILSTREAM *stream)
  1.2412 +{
  1.2413 +  unsigned long i,n,uf,len;
  1.2414 +  char *s,*f,tmp[MAILTMPLEN],flags[MAILTMPLEN];
  1.2415 +  MAILSTREAM *snarf;
  1.2416 +  MESSAGECACHE *elt;
  1.2417 +  STRING bs;
  1.2418 +  long ret;
  1.2419 +				/* do driver action */
  1.2420 +  if ((ret = ((stream && stream->dtb) ? (stream->dtb->ping) (stream) : NIL)) &&
  1.2421 +      stream->snarf.name &&	/* time to snarf? */
  1.2422 +				/* prohibit faster than once/min */
  1.2423 +      (time (0) > (time_t) (stream->snarf.time + min(60,mailsnarfinterval))) &&
  1.2424 +      (snarf = mail_open (NIL,stream->snarf.name,
  1.2425 +			  stream->snarf.options | OP_SILENT))) {
  1.2426 +    if ((n = snarf->nmsgs) &&	/* yes, have messages to snarf? */
  1.2427 +	mail_search_full (snarf,NIL,mail_criteria ("UNDELETED"),SE_FREE)) {
  1.2428 +      for (i = 1; ret && (i <= n); i++)	/* for each message */
  1.2429 +	if ((elt = mail_elt (snarf,i))->searched &&
  1.2430 +	    (s = mail_fetch_message (snarf,i,&len,FT_PEEK)) && len) {
  1.2431 +	  INIT (&bs,mail_string,s,len);
  1.2432 +	  if (mailsnarfpreserve) {
  1.2433 +				/* yes, make sure have fast data */
  1.2434 +	    if (!elt->valid || !elt->day) {
  1.2435 +	      sprintf (tmp,"%lu",n);
  1.2436 +	      mail_fetch_fast (snarf,tmp,NIL);
  1.2437 +	    }
  1.2438 +				/* initialize flag string */
  1.2439 +	    memset (flags,0,MAILTMPLEN);
  1.2440 +				/* output system flags except \Deleted */
  1.2441 +	    if (elt->seen) strcat (flags," \\Seen");
  1.2442 +	    if (elt->flagged) strcat (flags," \\Flagged");
  1.2443 +	    if (elt->answered) strcat (flags," \\Answered");
  1.2444 +	    if (elt->draft) strcat (flags," \\Draft");
  1.2445 +				/* any user flags? */
  1.2446 +	    for (uf = elt->user_flags,s = flags + strlen (flags);
  1.2447 +		 uf && (f = stream->user_flags[find_rightmost_bit (&uf)]) &&
  1.2448 +		   ((MAILTMPLEN - (s - tmp)) > (long) (2 + strlen (f)));
  1.2449 +		 s += strlen (s)) sprintf (s," %s",f);
  1.2450 +	    ret = mail_append_full (stream,stream->mailbox,flags + 1,
  1.2451 +				    mail_date (tmp,elt),&bs);
  1.2452 +	  }
  1.2453 +	  else ret = mail_append (stream,stream->mailbox,&bs);
  1.2454 +
  1.2455 +	  if (ret) {		/* did snarf succeed? */
  1.2456 +				/* driver has per-message (or no) flag call */
  1.2457 +	    if (snarf->dtb->flagmsg || !snarf->dtb->flag) {
  1.2458 +	      elt->valid = NIL;	/* prepare for flag alteration */
  1.2459 +	      if (snarf->dtb->flagmsg) (*snarf->dtb->flagmsg) (snarf,elt);
  1.2460 +				/* flags now altered */
  1.2461 +	      elt->deleted = elt->seen = elt->valid = T;
  1.2462 +	      if (snarf->dtb->flagmsg) (*snarf->dtb->flagmsg) (snarf,elt);
  1.2463 +	    }
  1.2464 +				/* driver has one-time flag call */
  1.2465 +	    if (snarf->dtb->flag) {
  1.2466 +	      sprintf (tmp,"%lu",i);
  1.2467 +	      (*snarf->dtb->flag) (snarf,tmp,"\\Deleted \\Seen",ST_SET);
  1.2468 +	    }
  1.2469 +	  }
  1.2470 +	  else {		/* copy failed */
  1.2471 +	    sprintf (tmp,"Unable to move message %lu from %s mailbox",
  1.2472 +		     i,snarf->dtb->name);
  1.2473 +	    mm_log (tmp,WARN);
  1.2474 +	  }
  1.2475 +	}
  1.2476 +    }
  1.2477 +				/* expunge the messages */
  1.2478 +    mail_close_full (snarf,n ? CL_EXPUNGE : NIL);
  1.2479 +    stream->snarf.time = (unsigned long) time (0);
  1.2480 +    /* Even if the snarf failed, we don't want to return NIL if the stream
  1.2481 +     * is still alive.  Or at least that's what we currently think.
  1.2482 +     */
  1.2483 +  				/* redo the driver's action */
  1.2484 +    ret = stream->dtb ? (*stream->dtb->ping) (stream) : NIL;
  1.2485 +  }
  1.2486 +  return ret;
  1.2487 +}
  1.2488 +
  1.2489 +/* Mail check mailbox
  1.2490 + * Accepts: mail stream
  1.2491 + */
  1.2492 +
  1.2493 +void mail_check (MAILSTREAM *stream)
  1.2494 +{
  1.2495 +  				/* do the driver's action */
  1.2496 +  if (stream->dtb) (*stream->dtb->check) (stream);
  1.2497 +}
  1.2498 +
  1.2499 +
  1.2500 +/* Mail expunge mailbox
  1.2501 + * Accepts: mail stream
  1.2502 + *	    sequence to expunge if non-NIL
  1.2503 + *	    expunge options
  1.2504 + * Returns: T on success, NIL on failure
  1.2505 + */
  1.2506 +
  1.2507 +long mail_expunge_full (MAILSTREAM *stream,char *sequence,long options)
  1.2508 +{
  1.2509 +  				/* do the driver's action */
  1.2510 +  return stream->dtb ? (*stream->dtb->expunge) (stream,sequence,options) : NIL;
  1.2511 +}
  1.2512 +
  1.2513 +
  1.2514 +/* Mail copy message(s)
  1.2515 + * Accepts: mail stream
  1.2516 + *	    sequence
  1.2517 + *	    destination mailbox
  1.2518 + *	    flags
  1.2519 + */
  1.2520 +
  1.2521 +long mail_copy_full (MAILSTREAM *stream,char *sequence,char *mailbox,
  1.2522 +		     long options)
  1.2523 +{
  1.2524 +  return stream->dtb ?
  1.2525 +    SAFE_COPY (stream->dtb,stream,sequence,mailbox,options) : NIL;
  1.2526 +}
  1.2527 +
  1.2528 +/* Append data package to use for old single-message mail_append() interface */
  1.2529 +
  1.2530 +typedef struct mail_append_package {
  1.2531 +  char *flags;			/* initial flags */
  1.2532 +  char *date;			/* message internal date */
  1.2533 +  STRING *message;		/* stringstruct of message */
  1.2534 +} APPENDPACKAGE;
  1.2535 +
  1.2536 +
  1.2537 +/* Single append message string
  1.2538 + * Accepts: mail stream
  1.2539 + *	    package pointer (cast as a void *)
  1.2540 + *	    pointer to return initial flags
  1.2541 + *	    pointer to return message internal date
  1.2542 + *	    pointer to return stringstruct of message to append
  1.2543 + * Returns: T, always
  1.2544 + */
  1.2545 +
  1.2546 +static long mail_append_single (MAILSTREAM *stream,void *data,char **flags,
  1.2547 +				char **date,STRING **message)
  1.2548 +{
  1.2549 +  APPENDPACKAGE *ap = (APPENDPACKAGE *) data;
  1.2550 +  *flags = ap->flags;		/* get desired data from the package */
  1.2551 +  *date = ap->date;
  1.2552 +  *message = ap->message;
  1.2553 +  ap->message = NIL;		/* so next callback puts a stop to it */
  1.2554 +  return LONGT;			/* always return success */
  1.2555 +}
  1.2556 +
  1.2557 +
  1.2558 +/* Mail append message string
  1.2559 + * Accepts: mail stream
  1.2560 + *	    destination mailbox
  1.2561 + *	    initial flags
  1.2562 + *	    message internal date
  1.2563 + *	    stringstruct of message to append
  1.2564 + * Returns: T on success, NIL on failure
  1.2565 + */
  1.2566 +
  1.2567 +long mail_append_full (MAILSTREAM *stream,char *mailbox,char *flags,char *date,
  1.2568 +		       STRING *message)
  1.2569 +{
  1.2570 +  APPENDPACKAGE ap;
  1.2571 +  ap.flags = flags;		/* load append package */
  1.2572 +  ap.date = date;
  1.2573 +  ap.message = message;
  1.2574 +  return mail_append_multiple (stream,mailbox,mail_append_single,(void *) &ap);
  1.2575 +}
  1.2576 +
  1.2577 +/* Mail append message(s)
  1.2578 + * Accepts: mail stream
  1.2579 + *	    destination mailbox
  1.2580 + *	    append data callback
  1.2581 + *	    arbitrary data for callback use
  1.2582 + * Returns: T on success, NIL on failure
  1.2583 + */
  1.2584 +
  1.2585 +long mail_append_multiple (MAILSTREAM *stream,char *mailbox,append_t af,
  1.2586 +			   void *data)
  1.2587 +{
  1.2588 +  char *s,tmp[MAILTMPLEN];
  1.2589 +  DRIVER *d = NIL;
  1.2590 +  long ret = NIL;
  1.2591 +				/* never allow names with newlines */
  1.2592 +  if (strpbrk (mailbox,"\015\012"))
  1.2593 +    MM_LOG ("Can't append to mailbox with such a name",ERROR);
  1.2594 +  else if (strlen (mailbox) >=
  1.2595 +	   (NETMAXHOST+(NETMAXUSER*2)+NETMAXMBX+NETMAXSRV+50)) {
  1.2596 +    sprintf (tmp,"Can't append %.80s: %s",mailbox,(*mailbox == '{') ?
  1.2597 +	     "invalid remote specification" : "no such mailbox");
  1.2598 +    MM_LOG (tmp,ERROR);
  1.2599 +  }
  1.2600 +				/* special driver hack? */
  1.2601 +  else if (!strncmp (lcase (strcpy (tmp,mailbox)),"#driver.",8)) {
  1.2602 +				/* yes, tie off name at likely delimiter */
  1.2603 +    if (!(s = strpbrk (tmp+8,"/\\:"))) {
  1.2604 +      sprintf (tmp,"Can't append to mailbox %.80s: bad driver syntax",mailbox);
  1.2605 +      MM_LOG (tmp,ERROR);
  1.2606 +      return NIL;
  1.2607 +    }
  1.2608 +    *s++ = '\0';		/* tie off at delimiter */
  1.2609 +    if (!(d = (DRIVER *) mail_parameters (NIL,GET_DRIVER,tmp+8))) {
  1.2610 +      sprintf (tmp,"Can't append to mailbox %.80s: unknown driver",mailbox);
  1.2611 +      MM_LOG (tmp,ERROR);
  1.2612 +    }
  1.2613 +    else ret = SAFE_APPEND (d,stream,mailbox + (s - tmp),af,data);
  1.2614 +  }
  1.2615 +  else if (d = mail_valid (stream,mailbox,NIL))
  1.2616 +    ret = SAFE_APPEND (d,stream,mailbox,af,data);
  1.2617 +  /* No driver, try for TRYCREATE if no stream.  Note that we use the
  1.2618 +   * createProto here, not the appendProto, since the dummy driver already
  1.2619 +   * took care of the appendProto case.  Otherwise, if appendProto is set to
  1.2620 +   * NIL, we won't get a TRYCREATE.
  1.2621 +   */
  1.2622 +  else if (!stream && (stream = default_proto (NIL)) && stream->dtb &&
  1.2623 +	   SAFE_APPEND (stream->dtb,stream,mailbox,af,data))
  1.2624 +				/* timing race? */
  1.2625 +    MM_NOTIFY (stream,"Append validity confusion",WARN);
  1.2626 +				/* generate error message */
  1.2627 +  else mail_valid (stream,mailbox,"append to mailbox");
  1.2628 +  return ret;
  1.2629 +}
  1.2630 +
  1.2631 +/* Mail garbage collect stream
  1.2632 + * Accepts: mail stream
  1.2633 + *	    garbage collection flags
  1.2634 + */
  1.2635 +
  1.2636 +void mail_gc (MAILSTREAM *stream,long gcflags)
  1.2637 +{
  1.2638 +  MESSAGECACHE *elt;
  1.2639 +  unsigned long i;
  1.2640 +  				/* do the driver's action first */
  1.2641 +  if (stream->dtb && stream->dtb->gc) (*stream->dtb->gc) (stream,gcflags);
  1.2642 +  stream->msgno = 0;		/* nothing cached now */
  1.2643 +  if (gcflags & GC_ENV) {	/* garbage collect envelopes? */
  1.2644 +    if (stream->env) mail_free_envelope (&stream->env);
  1.2645 +    if (stream->body) mail_free_body (&stream->body);
  1.2646 +  }
  1.2647 +  if (gcflags & GC_TEXTS) {	/* free texts */
  1.2648 +    if (stream->text.data) fs_give ((void **) &stream->text.data);
  1.2649 +    stream->text.size = 0;
  1.2650 +  }
  1.2651 +				/* garbage collect per-message stuff */
  1.2652 +  for (i = 1; i <= stream->nmsgs; i++) 
  1.2653 +    if (elt = (MESSAGECACHE *) (*mailcache) (stream,i,CH_ELT))
  1.2654 +      mail_gc_msg (&elt->private.msg,gcflags);
  1.2655 +}
  1.2656 +
  1.2657 +
  1.2658 +/* Mail garbage collect message
  1.2659 + * Accepts: message structure
  1.2660 + *	    garbage collection flags
  1.2661 + */
  1.2662 +
  1.2663 +void mail_gc_msg (MESSAGE *msg,long gcflags)
  1.2664 +{
  1.2665 +  if (gcflags & GC_ENV) {	/* garbage collect envelopes? */
  1.2666 +    mail_free_envelope (&msg->env);
  1.2667 +    mail_free_body (&msg->body);
  1.2668 +  }
  1.2669 +  if (gcflags & GC_TEXTS) {	/* garbage collect texts */
  1.2670 +    if (msg->full.text.data) fs_give ((void **) &msg->full.text.data);
  1.2671 +    if (msg->header.text.data) {
  1.2672 +      mail_free_stringlist (&msg->lines);
  1.2673 +      fs_give ((void **) &msg->header.text.data);
  1.2674 +    }
  1.2675 +    if (msg->text.text.data) fs_give ((void **) &msg->text.text.data);
  1.2676 +				/* now GC all body components */
  1.2677 +    if (msg->body) mail_gc_body (msg->body);
  1.2678 +  }
  1.2679 +}
  1.2680 +
  1.2681 +/* Mail garbage collect texts in BODY structure
  1.2682 + * Accepts: BODY structure
  1.2683 + */
  1.2684 +
  1.2685 +void mail_gc_body (BODY *body)
  1.2686 +{
  1.2687 +  PART *part;
  1.2688 +  switch (body->type) {		/* free contents */
  1.2689 +  case TYPEMULTIPART:		/* multiple part */
  1.2690 +    for (part = body->nested.part; part; part = part->next)
  1.2691 +      mail_gc_body (&part->body);
  1.2692 +    break;
  1.2693 +  case TYPEMESSAGE:		/* encapsulated message */
  1.2694 +    if (body->subtype && !strcmp (body->subtype,"RFC822")) {
  1.2695 +      mail_free_stringlist (&body->nested.msg->lines);
  1.2696 +      mail_gc_msg (body->nested.msg,GC_TEXTS);
  1.2697 +    }
  1.2698 +    break;
  1.2699 +  default:
  1.2700 +    break;
  1.2701 +  }
  1.2702 +  if (body->mime.text.data) fs_give ((void **) &body->mime.text.data);
  1.2703 +  if (body->contents.text.data) fs_give ((void **) &body->contents.text.data);
  1.2704 +}
  1.2705 +
  1.2706 +/* Mail get body part
  1.2707 + * Accepts: mail stream
  1.2708 + *	    message number
  1.2709 + *	    section specifier
  1.2710 + * Returns: pointer to body
  1.2711 + */
  1.2712 +
  1.2713 +BODY *mail_body (MAILSTREAM *stream,unsigned long msgno,unsigned char *section)
  1.2714 +{
  1.2715 +  BODY *b = NIL;
  1.2716 +  PART *pt;
  1.2717 +  unsigned long i;
  1.2718 +				/* make sure have a body */
  1.2719 +  if (section && *section && mail_fetchstructure (stream,msgno,&b) && b)
  1.2720 +    while (*section) {		/* find desired section */
  1.2721 +      if (isdigit (*section)) {	/* get section specifier */
  1.2722 +				/* make sure what follows is valid */
  1.2723 +	if (!(i = strtoul (section,(char **) &section,10)) ||
  1.2724 +	    (*section && ((*section++ != '.') || !*section))) return NIL;
  1.2725 +				/* multipart content? */
  1.2726 +	if (b->type == TYPEMULTIPART) {
  1.2727 +				/* yes, find desired part */
  1.2728 +	  if (pt = b->nested.part) while (--i && (pt = pt->next));
  1.2729 +	  if (!pt) return NIL;	/* bad specifier */
  1.2730 +	  b = &pt->body;	/* note new body */
  1.2731 +	}
  1.2732 +				/* otherwise must be section 1 */
  1.2733 +	else if (i != 1) return NIL;
  1.2734 +				/* need to go down further? */
  1.2735 +	if (*section) switch (b->type) {
  1.2736 +	case TYPEMULTIPART:	/* multipart */
  1.2737 +	  break;
  1.2738 +	case TYPEMESSAGE:	/* embedded message */
  1.2739 +	  if (!strcmp (b->subtype,"RFC822")) {
  1.2740 +	    b = b->nested.msg->body;
  1.2741 +	    break;
  1.2742 +	  }
  1.2743 +	default:		/* bogus subpart specification */
  1.2744 +	  return NIL;
  1.2745 +	}
  1.2746 +      }
  1.2747 +      else return NIL;		/* unknown section specifier */
  1.2748 +    }
  1.2749 +  return b;
  1.2750 +}  
  1.2751 +
  1.2752 +/* Mail output date from elt fields
  1.2753 + * Accepts: character string to write into
  1.2754 + *	    elt to get data data from
  1.2755 + * Returns: the character string
  1.2756 + */
  1.2757 +
  1.2758 +const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
  1.2759 +
  1.2760 +const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
  1.2761 +			"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
  1.2762 +
  1.2763 +char *mail_date (char *string,MESSAGECACHE *elt)
  1.2764 +{
  1.2765 +  sprintf (string,"%2d-%s-%d %02d:%02d:%02d %c%02d%02d",
  1.2766 +	   elt->day ? elt->day : 1,
  1.2767 +	   months[elt->month ? (elt->month - 1) : 0],
  1.2768 +	   elt->year + BASEYEAR,elt->hours,elt->minutes,elt->seconds,
  1.2769 +	   elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes);
  1.2770 +  return string;
  1.2771 +}
  1.2772 +
  1.2773 +
  1.2774 +/* Mail output extended-ctime format date from elt fields
  1.2775 + * Accepts: character string to write into
  1.2776 + *	    elt to get data data from
  1.2777 + * Returns: the character string
  1.2778 + */
  1.2779 +
  1.2780 +char *mail_cdate (char *string,MESSAGECACHE *elt)
  1.2781 +{
  1.2782 +  char *fmt = "%s %s %2d %02d:%02d:%02d %4d %s%02d%02d\n";
  1.2783 +  int d = elt->day ? elt->day : 1;
  1.2784 +  int m = elt->month ? (elt->month - 1) : 0;
  1.2785 +  int y = elt->year + BASEYEAR;
  1.2786 +  const char *s = months[m];
  1.2787 +  if (m < 2) {			/* if before March, */
  1.2788 +    m += 10;			/* January = month 10 of previous year */
  1.2789 +    y--;
  1.2790 +  }
  1.2791 +  else m -= 2;			/* March is month 0 */
  1.2792 +  sprintf (string,fmt,days[(int) (d + 2 + ((7 + 31 * m) / 12)
  1.2793 +#ifndef USEJULIANCALENDAR
  1.2794 +#ifndef USEORTHODOXCALENDAR	/* Gregorian calendar */
  1.2795 +				  + (y / 400)
  1.2796 +#ifdef Y4KBUGFIX
  1.2797 +				  - (y / 4000)
  1.2798 +#endif
  1.2799 +#else				/* Orthodox calendar */
  1.2800 +				  + (2 * (y / 900)) + ((y % 900) >= 200)
  1.2801 +				  + ((y % 900) >= 600)
  1.2802 +#endif
  1.2803 +				  - (y / 100)
  1.2804 +#endif
  1.2805 +				  + y + (y / 4)) % 7],
  1.2806 +	   s,d,elt->hours,elt->minutes,elt->seconds,elt->year + BASEYEAR,
  1.2807 +	   elt->zoccident ? "-" : "+",elt->zhours,elt->zminutes);
  1.2808 +  return string;
  1.2809 +}
  1.2810 +
  1.2811 +/* Mail parse date into elt fields
  1.2812 + * Accepts: elt to write into
  1.2813 + *	    date string to parse
  1.2814 + * Returns: T if parse successful, else NIL 
  1.2815 + * This routine parses dates as follows:
  1.2816 + * . leading three alphas followed by comma and space are ignored
  1.2817 + * . date accepted in format: mm/dd/yy, mm/dd/yyyy, dd-mmm-yy, dd-mmm-yyyy,
  1.2818 + *    dd mmm yy, dd mmm yyyy, yyyy-mm-dd, yyyymmdd
  1.2819 + * . two and three digit years interpreted according to RFC 2822 rules
  1.2820 + * . mandatory end of string if yyyy-mm-dd or yyyymmdd; otherwise optional
  1.2821 + *    space followed by time:
  1.2822 + * . time accepted in format hh:mm:ss or hh:mm
  1.2823 + * . end of string accepted
  1.2824 + * . timezone accepted: hyphen followed by symbolic timezone, or space
  1.2825 + *    followed by signed numeric timezone or symbolic timezone
  1.2826 + * Examples of normal input:
  1.2827 + * . IMAP date-only (SEARCH):
  1.2828 + *    dd-mmm-yyyy
  1.2829 + * . IMAP date-time (INTERNALDATE):
  1.2830 + *    dd-mmm-yyyy hh:mm:ss +zzzz
  1.2831 + * . RFC-822:
  1.2832 + *    www, dd mmm yy hh:mm:ss zzz
  1.2833 + * . RFC-2822:
  1.2834 + *    www, dd mmm yyyy hh:mm:ss +zzzz
  1.2835 + */
  1.2836 +
  1.2837 +long mail_parse_date (MESSAGECACHE *elt,unsigned char *s)
  1.2838 +{
  1.2839 +  unsigned long d,m,y;
  1.2840 +  int mi,ms;
  1.2841 +  struct tm *t;
  1.2842 +  time_t tn;
  1.2843 +  char tmp[MAILTMPLEN];
  1.2844 +  static unsigned long maxyear = 0;
  1.2845 +  if (!maxyear) {		/* know the end of time yet? */
  1.2846 +    MESSAGECACHE tmpelt;
  1.2847 +    memset (&tmpelt,0xff,sizeof (MESSAGECACHE));
  1.2848 +    maxyear = BASEYEAR + tmpelt.year;
  1.2849 +  }
  1.2850 +				/* clear elt */
  1.2851 +  elt->zoccident = elt->zhours = elt->zminutes =
  1.2852 +    elt->hours = elt->minutes = elt->seconds =
  1.2853 +      elt->day = elt->month = elt->year = 0;
  1.2854 +				/* make a writeable uppercase copy */
  1.2855 +  if (s && *s && (strlen (s) < (size_t)MAILTMPLEN)) s = ucase (strcpy (tmp,s));
  1.2856 +  else return NIL;
  1.2857 +				/* skip over possible day of week */
  1.2858 +  if (isalpha (*s) && isalpha (s[1]) && isalpha (s[2]) && (s[3] == ',') &&
  1.2859 +      (s[4] == ' ')) s += 5;
  1.2860 +  while (*s == ' ') s++;	/* parse first number (probable month) */
  1.2861 +  if (!(m = strtoul (s,(char **) &s,10))) return NIL;
  1.2862 +
  1.2863 +  switch (*s) {			/* different parse based on delimiter */
  1.2864 +  case '/':			/* mm/dd/yy format */
  1.2865 +    if (isdigit (*++s) && (d = strtoul (s,(char **) &s,10)) &&
  1.2866 +	(*s == '/') && isdigit (*++s)) {
  1.2867 +      y = strtoul (s,(char **) &s,10);
  1.2868 +      if (*s == '\0') break;	/* must end here */
  1.2869 +    }
  1.2870 +    return NIL;			/* bogon */
  1.2871 +  case ' ':			/* dd mmm yy format */
  1.2872 +    while (s[1] == ' ') s++;	/* slurp extra whitespace */
  1.2873 +  case '-':
  1.2874 +    if (isdigit (s[1])) {	/* possible ISO 8601 date format? */
  1.2875 +      y = m;			/* yes, first number is year */
  1.2876 +				/* get month and day */
  1.2877 +      if ((m = strtoul (s+1,(char **) &s,10)) && (*s++ == '-') && 
  1.2878 +	  (d = strtoul (s,(char **) &s,10)) && !*s) break;
  1.2879 +      return NIL;		/* syntax error or time present */
  1.2880 +    }
  1.2881 +    d = m;			/* dd-mmm-yy[yy], so first number is a day */
  1.2882 +				/* make sure string long enough! */
  1.2883 +    if (strlen (s) < (size_t) 5) return NIL;
  1.2884 +    /* Some compilers don't allow `<<' and/or longs in case statements. */
  1.2885 +				/* slurp up the month string */
  1.2886 +    ms = ((s[1] - 'A') * 1024) + ((s[2] - 'A') * 32) + (s[3] - 'A');
  1.2887 +    switch (ms) {		/* determine the month */
  1.2888 +    case (('J'-'A') * 1024) + (('A'-'A') * 32) + ('N'-'A'): m = 1; break;
  1.2889 +    case (('F'-'A') * 1024) + (('E'-'A') * 32) + ('B'-'A'): m = 2; break;
  1.2890 +    case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('R'-'A'): m = 3; break;
  1.2891 +    case (('A'-'A') * 1024) + (('P'-'A') * 32) + ('R'-'A'): m = 4; break;
  1.2892 +    case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('Y'-'A'): m = 5; break;
  1.2893 +    case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('N'-'A'): m = 6; break;
  1.2894 +    case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('L'-'A'): m = 7; break;
  1.2895 +    case (('A'-'A') * 1024) + (('U'-'A') * 32) + ('G'-'A'): m = 8; break;
  1.2896 +    case (('S'-'A') * 1024) + (('E'-'A') * 32) + ('P'-'A'): m = 9; break;
  1.2897 +    case (('O'-'A') * 1024) + (('C'-'A') * 32) + ('T'-'A'): m = 10; break;
  1.2898 +    case (('N'-'A') * 1024) + (('O'-'A') * 32) + ('V'-'A'): m = 11; break;
  1.2899 +    case (('D'-'A') * 1024) + (('E'-'A') * 32) + ('C'-'A'): m = 12; break;
  1.2900 +    default: return NIL;	/* unknown month */
  1.2901 +    }
  1.2902 +    if (s[4] == *s) s += 5;	/* advance to year */
  1.2903 +    else {			/* first three were OK, possibly full name */
  1.2904 +      mi = *s;			/* note delimiter, skip alphas */
  1.2905 +      for (s += 4; isalpha (*s); s++);
  1.2906 +				/* error if delimiter not here */
  1.2907 +      if (mi != *s++) return NIL;
  1.2908 +    }
  1.2909 +    while (*s == ' ') s++;	/* parse year */
  1.2910 +    if (isdigit (*s)) {		/* must be a digit here */
  1.2911 +      y = strtoul (s,(char **) &s,10);
  1.2912 +      if (*s == '\0' || *s == ' ') break;
  1.2913 +    }
  1.2914 +  case '\0':			/* ISO 8601 compact date */
  1.2915 +    if (m < (BASEYEAR * 10000)) return NIL;
  1.2916 +    y = m / 10000;		/* get year */
  1.2917 +    d = (m %= 10000) % 100;	/* get day */
  1.2918 +    m /= 100;			/* and month */
  1.2919 +    break;
  1.2920 +  default:
  1.2921 +    return NIL;			/* unknown date format */
  1.2922 +  }
  1.2923 +
  1.2924 +				/* minimal validity check of date */
  1.2925 +  if ((d > 31) || (m > 12)) return NIL; 
  1.2926 +  if (y < 49) y += 2000;	/* RFC 2282 rules for two digit years 00-49 */
  1.2927 +  else if (y < 999) y += 1900;	/* 2-digit years 50-99 and 3-digit years */
  1.2928 +				/* reject prehistoric and far future years */
  1.2929 +  if ((y < BASEYEAR) || (y > maxyear)) return NIL;
  1.2930 +				/* set values in elt */
  1.2931 +  elt->day = d; elt->month = m; elt->year = y - BASEYEAR;
  1.2932 +  ms = '\0';			/* initially no time zone string */
  1.2933 +  if (*s) {			/* time specification present? */
  1.2934 +				/* parse time */
  1.2935 +    d = strtoul (s+1,(char **) &s,10);
  1.2936 +    if (*s != ':') return NIL;
  1.2937 +    m = strtoul (++s,(char **) &s,10);
  1.2938 +    y = (*s == ':') ? strtoul (++s,(char **) &s,10) : 0;
  1.2939 +				/* validity check time */
  1.2940 +    if ((d > 23) || (m > 59) || (y > 60)) return NIL; 
  1.2941 +				/* set values in elt */
  1.2942 +    elt->hours = d; elt->minutes = m; elt->seconds = y;
  1.2943 +    switch (*s) {		/* time zone specifier? */
  1.2944 +    case ' ':			/* numeric time zone */
  1.2945 +      while (s[1] == ' ') s++;	/* slurp extra whitespace */
  1.2946 +      if (!isalpha (s[1])) {	/* treat as '-' case if alphabetic */
  1.2947 +				/* test for sign character */
  1.2948 +	if ((elt->zoccident = (*++s == '-')) || (*s == '+')) s++;
  1.2949 +				/* validate proper timezone */
  1.2950 +	if (isdigit(*s) && isdigit(s[1]) && isdigit(s[2]) && (s[2] < '6') &&
  1.2951 +	    isdigit(s[3])) {
  1.2952 +	  elt->zhours = (*s - '0') * 10 + (s[1] - '0');
  1.2953 +	  elt->zminutes = (s[2] - '0') * 10 + (s[3] - '0');
  1.2954 +	}
  1.2955 +	return T;		/* all done! */
  1.2956 +      }
  1.2957 +				/* falls through */
  1.2958 +    case '-':			/* symbolic time zone */
  1.2959 +      if (!(ms = *++s)) ms = 'Z';
  1.2960 +      else if (*++s) {		/* multi-character? */
  1.2961 +	ms -= 'A'; ms *= 1024;	/* yes, make compressed three-byte form */
  1.2962 +	ms += ((*s++ - 'A') * 32);
  1.2963 +	if (*s) ms += *s++ - 'A';
  1.2964 +	if (*s) ms = '\0';	/* more than three characters */
  1.2965 +      }
  1.2966 +    default:			/* ignore anything else */
  1.2967 +      break;
  1.2968 +    }
  1.2969 +  }
  1.2970 +
  1.2971 +  /*  This is not intended to be a comprehensive list of all possible
  1.2972 +   * timezone strings.  Such a list would be impractical.  Rather, this
  1.2973 +   * listing is intended to incorporate all military, North American, and
  1.2974 +   * a few special cases such as Japan and the major European zone names,
  1.2975 +   * such as what might be expected to be found in a Tenex format mailbox
  1.2976 +   * and spewed from an IMAP server.  The trend is to migrate to numeric
  1.2977 +   * timezones which lack the flavor but also the ambiguity of the names.
  1.2978 +   *
  1.2979 +   *  RFC-822 only recognizes UT, GMT, 1-letter military timezones, and the
  1.2980 +   * 4 CONUS timezones and their summer time variants.  [Sorry, Canadian
  1.2981 +   * Atlantic Provinces, Alaska, and Hawaii.]
  1.2982 +   */
  1.2983 +  switch (ms) {			/* determine the timezone */
  1.2984 +				/* Universal */
  1.2985 +  case (('U'-'A')*1024)+(('T'-'A')*32):
  1.2986 +#ifndef STRICT_RFC822_TIMEZONES
  1.2987 +  case (('U'-'A')*1024)+(('T'-'A')*32)+'C'-'A':
  1.2988 +#endif
  1.2989 +				/* Greenwich */
  1.2990 +  case (('G'-'A')*1024)+(('M'-'A')*32)+'T'-'A':
  1.2991 +  case 'Z': elt->zhours = 0; break;
  1.2992 +
  1.2993 +    /* oriental (from Greenwich) timezones */
  1.2994 +#ifndef STRICT_RFC822_TIMEZONES
  1.2995 +				/* Middle Europe */
  1.2996 +  case (('M'-'A')*1024)+(('E'-'A')*32)+'T'-'A':
  1.2997 +#endif
  1.2998 +#ifdef BRITISH_SUMMER_TIME
  1.2999 +				/* British Summer */
  1.3000 +  case (('B'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
  1.3001 +#endif
  1.3002 +  case 'A': elt->zhours = 1; break;
  1.3003 +#ifndef STRICT_RFC822_TIMEZONES
  1.3004 +				/* Eastern Europe */
  1.3005 +  case (('E'-'A')*1024)+(('E'-'A')*32)+'T'-'A':
  1.3006 +#endif
  1.3007 +  case 'B': elt->zhours = 2; break;
  1.3008 +  case 'C': elt->zhours = 3; break;
  1.3009 +  case 'D': elt->zhours = 4; break;
  1.3010 +  case 'E': elt->zhours = 5; break;
  1.3011 +  case 'F': elt->zhours = 6; break;
  1.3012 +  case 'G': elt->zhours = 7; break;
  1.3013 +  case 'H': elt->zhours = 8; break;
  1.3014 +#ifndef STRICT_RFC822_TIMEZONES
  1.3015 +				/* Japan */
  1.3016 +  case (('J'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
  1.3017 +#endif
  1.3018 +  case 'I': elt->zhours = 9; break;
  1.3019 +  case 'K': elt->zhours = 10; break;
  1.3020 +  case 'L': elt->zhours = 11; break;
  1.3021 +  case 'M': elt->zhours = 12; break;
  1.3022 +
  1.3023 +	/* occidental (from Greenwich) timezones */
  1.3024 +  case 'N': elt->zoccident = 1; elt->zhours = 1; break;
  1.3025 +  case 'O': elt->zoccident = 1; elt->zhours = 2; break;
  1.3026 +#ifndef STRICT_RFC822_TIMEZONES
  1.3027 +  case (('A'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
  1.3028 +#endif
  1.3029 +  case 'P': elt->zoccident = 1; elt->zhours = 3; break;
  1.3030 +#ifdef NEWFOUNDLAND_STANDARD_TIME
  1.3031 +				/* Newfoundland */
  1.3032 +  case (('N'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
  1.3033 +    elt->zoccident = 1; elt->zhours = 3; elt->zminutes = 30; break;
  1.3034 +#endif
  1.3035 +#ifndef STRICT_RFC822_TIMEZONES
  1.3036 +				/* Atlantic */
  1.3037 +  case (('A'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
  1.3038 +#endif
  1.3039 +	/* CONUS */
  1.3040 +  case (('E'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
  1.3041 +  case 'Q': elt->zoccident = 1; elt->zhours = 4; break;
  1.3042 +				/* Eastern */
  1.3043 +  case (('E'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
  1.3044 +  case (('C'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
  1.3045 +  case 'R': elt->zoccident = 1; elt->zhours = 5; break;
  1.3046 +				/* Central */
  1.3047 +  case (('C'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
  1.3048 +  case (('M'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
  1.3049 +  case 'S': elt->zoccident = 1; elt->zhours = 6; break;
  1.3050 +				/* Mountain */
  1.3051 +  case (('M'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
  1.3052 +  case (('P'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
  1.3053 +  case 'T': elt->zoccident = 1; elt->zhours = 7; break;
  1.3054 +				/* Pacific */
  1.3055 +  case (('P'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
  1.3056 +#ifndef STRICT_RFC822_TIMEZONES
  1.3057 +  case (('Y'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
  1.3058 +#endif
  1.3059 +  case 'U': elt->zoccident = 1; elt->zhours = 8; break;
  1.3060 +#ifndef STRICT_RFC822_TIMEZONES
  1.3061 +				/* Yukon */
  1.3062 +  case (('Y'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
  1.3063 +#endif
  1.3064 +  case 'V': elt->zoccident = 1; elt->zhours = 9; break;
  1.3065 +#ifndef STRICT_RFC822_TIMEZONES
  1.3066 +				/* Hawaii */
  1.3067 +  case (('H'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
  1.3068 +#endif
  1.3069 +  case 'W': elt->zoccident = 1; elt->zhours = 10; break;
  1.3070 +				/* Nome/Bering/Samoa */
  1.3071 +#ifdef NOME_STANDARD_TIME
  1.3072 +  case (('N'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
  1.3073 +#endif
  1.3074 +#ifdef BERING_STANDARD_TIME
  1.3075 +  case (('B'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
  1.3076 +#endif
  1.3077 +#ifdef SAMOA_STANDARD_TIME
  1.3078 +  case (('S'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
  1.3079 +#endif
  1.3080 +  case 'X': elt->zoccident = 1; elt->zhours = 11; break;
  1.3081 +  case 'Y': elt->zoccident = 1; elt->zhours = 12; break;
  1.3082 +
  1.3083 +  default:			/* unknown time zones treated as local */
  1.3084 +    tn = time (0);		/* time now... */
  1.3085 +    t = localtime (&tn);	/* get local minutes since midnight */
  1.3086 +    mi = t->tm_hour * 60 + t->tm_min;
  1.3087 +    ms = t->tm_yday;		/* note Julian day */
  1.3088 +    if (t = gmtime (&tn)) {	/* minus UTC minutes since midnight */
  1.3089 +      mi -= t->tm_hour * 60 + t->tm_min;
  1.3090 +	/* ms can be one of:
  1.3091 +	 *  36x  local time is December 31, UTC is January 1, offset -24 hours
  1.3092 +	 *    1  local time is 1 day ahead of UTC, offset +24 hours
  1.3093 +	 *    0  local time is same day as UTC, no offset
  1.3094 +	 *   -1  local time is 1 day behind UTC, offset -24 hours
  1.3095 +	 * -36x  local time is January 1, UTC is December 31, offset +24 hours
  1.3096 +	 */
  1.3097 +      if (ms -= t->tm_yday)	/* correct offset if different Julian day */
  1.3098 +	mi += ((ms < 0) == (abs (ms) == 1)) ? -24*60 : 24*60;
  1.3099 +      if (mi < 0) {		/* occidental? */
  1.3100 +	mi = abs (mi);		/* yup, make positive number */
  1.3101 +	elt->zoccident = 1;	/* and note west of UTC */
  1.3102 +      }
  1.3103 +      elt->zhours = mi / 60;	/* now break into hours and minutes */
  1.3104 +      elt->zminutes = mi % 60;
  1.3105 +    }
  1.3106 +    break;
  1.3107 +  }
  1.3108 +  return T;
  1.3109 +}
  1.3110 +
  1.3111 +/* Mail n messages exist
  1.3112 + * Accepts: mail stream
  1.3113 + *	    number of messages
  1.3114 + */
  1.3115 +
  1.3116 +void mail_exists (MAILSTREAM *stream,unsigned long nmsgs)
  1.3117 +{
  1.3118 +  char tmp[MAILTMPLEN];
  1.3119 +  if (nmsgs > MAXMESSAGES) {
  1.3120 +    sprintf (tmp,"Mailbox has more messages (%lu) exist than maximum (%lu)",
  1.3121 +	     nmsgs,MAXMESSAGES);
  1.3122 +    mm_log (tmp,ERROR);
  1.3123 +    nmsgs = MAXMESSAGES;	/* cap to maximum */
  1.3124 +    /* probably will crash in mail_elt() soon enough... */
  1.3125 +  }
  1.3126 +				/* make sure cache is large enough */
  1.3127 +  (*mailcache) (stream,nmsgs,CH_SIZE);
  1.3128 +  stream->nmsgs = nmsgs;	/* update stream status */
  1.3129 +				/* notify main program of change */
  1.3130 +  if (!stream->silent) MM_EXISTS (stream,nmsgs);
  1.3131 +}
  1.3132 +
  1.3133 +
  1.3134 +/* Mail n messages are recent
  1.3135 + * Accepts: mail stream
  1.3136 + *	    number of recent messages
  1.3137 + */
  1.3138 +
  1.3139 +void mail_recent (MAILSTREAM *stream,unsigned long recent)
  1.3140 +{
  1.3141 +  char tmp[MAILTMPLEN];
  1.3142 +  if (recent <= stream->nmsgs) stream->recent = recent;
  1.3143 +  else {
  1.3144 +    sprintf (tmp,"Non-existent recent message(s) %lu, nmsgs=%lu",
  1.3145 +	     recent,stream->nmsgs);
  1.3146 +    mm_log (tmp,ERROR);
  1.3147 +  }
  1.3148 +}
  1.3149 +
  1.3150 +
  1.3151 +/* Mail message n is expunged
  1.3152 + * Accepts: mail stream
  1.3153 + *	    message #
  1.3154 + */
  1.3155 +
  1.3156 +void mail_expunged (MAILSTREAM *stream,unsigned long msgno)
  1.3157 +{
  1.3158 +  char tmp[MAILTMPLEN];
  1.3159 +  MESSAGECACHE *elt;
  1.3160 +  if (msgno > stream->nmsgs) {
  1.3161 +    sprintf (tmp,"Expunge of non-existent message %lu, nmsgs=%lu",
  1.3162 +	     msgno,stream->nmsgs);
  1.3163 +    mm_log (tmp,ERROR);
  1.3164 +  }
  1.3165 +  else {
  1.3166 +    elt = (MESSAGECACHE *) (*mailcache) (stream,msgno,CH_ELT);
  1.3167 +				/* notify main program of change */
  1.3168 +    if (!stream->silent) MM_EXPUNGED (stream,msgno);
  1.3169 +    if (elt) {			/* if an element is there */
  1.3170 +      elt->msgno = 0;		/* invalidate its message number and free */
  1.3171 +      (*mailcache) (stream,msgno,CH_FREE);
  1.3172 +      (*mailcache) (stream,msgno,CH_FREESORTCACHE);
  1.3173 +    }
  1.3174 +				/* expunge the slot */
  1.3175 +    (*mailcache) (stream,msgno,CH_EXPUNGE);
  1.3176 +    --stream->nmsgs;		/* update stream status */
  1.3177 +    if (stream->msgno) {	/* have stream pointers? */
  1.3178 +				/* make sure the short cache is nuked */
  1.3179 +      if (stream->scache) mail_gc (stream,GC_ENV | GC_TEXTS);
  1.3180 +      else stream->msgno = 0;	/* make sure invalidated in any case */
  1.3181 +    }
  1.3182 +  }
  1.3183 +}
  1.3184 +
  1.3185 +/* Mail stream status routines */
  1.3186 +
  1.3187 +
  1.3188 +/* Mail lock stream
  1.3189 + * Accepts: mail stream
  1.3190 + */
  1.3191 +
  1.3192 +void mail_lock (MAILSTREAM *stream)
  1.3193 +{
  1.3194 +  if (stream->lock) {
  1.3195 +    char tmp[MAILTMPLEN];
  1.3196 +    sprintf (tmp,"Lock when already locked, mbx=%.80s",
  1.3197 +	     stream->mailbox ? stream->mailbox : "???");
  1.3198 +    fatal (tmp);
  1.3199 +  }
  1.3200 +  else stream->lock = T;	/* lock stream */
  1.3201 +}
  1.3202 +
  1.3203 +
  1.3204 +/* Mail unlock stream
  1.3205 + * Accepts: mail stream
  1.3206 + */
  1.3207 + 
  1.3208 +void mail_unlock (MAILSTREAM *stream)
  1.3209 +{
  1.3210 +  if (!stream->lock) fatal ("Unlock when not locked");
  1.3211 +  else stream->lock = NIL;	/* unlock stream */
  1.3212 +}
  1.3213 +
  1.3214 +
  1.3215 +/* Mail turn on debugging telemetry
  1.3216 + * Accepts: mail stream
  1.3217 + */
  1.3218 +
  1.3219 +void mail_debug (MAILSTREAM *stream)
  1.3220 +{
  1.3221 +  stream->debug = T;		/* turn on debugging telemetry */
  1.3222 +  if (stream->dtb) (*stream->dtb->parameters) (ENABLE_DEBUG,stream);
  1.3223 +}
  1.3224 +
  1.3225 +
  1.3226 +/* Mail turn off debugging telemetry
  1.3227 + * Accepts: mail stream
  1.3228 + */
  1.3229 +
  1.3230 +void mail_nodebug (MAILSTREAM *stream)
  1.3231 +{
  1.3232 +  stream->debug = NIL;		/* turn off debugging telemetry */
  1.3233 +  if (stream->dtb) (*stream->dtb->parameters) (DISABLE_DEBUG,stream);
  1.3234 +}
  1.3235 +
  1.3236 +
  1.3237 +/* Mail log to debugging telemetry
  1.3238 + * Accepts: message
  1.3239 + *	    flag that data is "sensitive"
  1.3240 + */
  1.3241 +
  1.3242 +void mail_dlog (char *string,long flag)
  1.3243 +{
  1.3244 +  mm_dlog ((debugsensitive || !flag) ? string : "<suppressed>");
  1.3245 +}
  1.3246 +
  1.3247 +/* Mail parse UID sequence
  1.3248 + * Accepts: mail stream
  1.3249 + *	    sequence to parse
  1.3250 + * Returns: T if parse successful, else NIL
  1.3251 + */
  1.3252 +
  1.3253 +long mail_uid_sequence (MAILSTREAM *stream,unsigned char *sequence)
  1.3254 +{
  1.3255 +  unsigned long i,j,k,x,y;
  1.3256 +  for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->sequence = NIL;
  1.3257 +  while (sequence && *sequence){/* while there is something to parse */
  1.3258 +    if (*sequence == '*') {	/* maximum message */
  1.3259 +      i = stream->nmsgs ? mail_uid (stream,stream->nmsgs) : stream->uid_last;
  1.3260 +      sequence++;		/* skip past * */
  1.3261 +    }
  1.3262 +				/* parse and validate message number */
  1.3263 +				/* parse and validate message number */
  1.3264 +    else if (!isdigit (*sequence)) {
  1.3265 +      MM_LOG ("Syntax error in sequence",ERROR);
  1.3266 +      return NIL;
  1.3267 +    }
  1.3268 +    else if (!(i = strtoul (sequence,(char **) &sequence,10))) {
  1.3269 +      MM_LOG ("UID may not be zero",ERROR);
  1.3270 +      return NIL;
  1.3271 +    }
  1.3272 +    switch (*sequence) {	/* see what the delimiter is */
  1.3273 +    case ':':			/* sequence range */
  1.3274 +      if (*++sequence == '*') {	/* maximum message */
  1.3275 +	j = stream->nmsgs ? mail_uid (stream,stream->nmsgs) : stream->uid_last;
  1.3276 +	sequence++;		/* skip past * */
  1.3277 +      }
  1.3278 +				/* parse end of range */
  1.3279 +      else if (!(j = strtoul (sequence,(char **) &sequence,10))) {
  1.3280 +	MM_LOG ("UID sequence range invalid",ERROR);
  1.3281 +	return NIL;
  1.3282 +      }
  1.3283 +      if (*sequence && *sequence++ != ',') {
  1.3284 +	MM_LOG ("UID sequence range syntax error",ERROR);
  1.3285 +	return NIL;
  1.3286 +      }
  1.3287 +      if (i > j) {		/* swap the range if backwards */
  1.3288 +	x = i; i = j; j = x;
  1.3289 +      }
  1.3290 +      x = mail_msgno (stream,i);/* get msgnos */
  1.3291 +      y = mail_msgno (stream,j);/* for both UIDS (don't && it) */
  1.3292 +				/* easy if both UIDs valid */
  1.3293 +      if (x && y) while (x <= y) mail_elt (stream,x++)->sequence = T;
  1.3294 +				/* start UID valid, end is not */
  1.3295 +      else if (x) while ((x <= stream->nmsgs) && (mail_uid (stream,x) <= j))
  1.3296 +	mail_elt (stream,x++)->sequence = T;
  1.3297 +				/* end UID valid, start is not */
  1.3298 +      else if (y) for (x = 1; x <= y; x++) {
  1.3299 +	if (mail_uid (stream,x) >= i) mail_elt (stream,x)->sequence = T;
  1.3300 +      }
  1.3301 +				/* neither is valid, ugh */
  1.3302 +      else for (x = 1; x <= stream->nmsgs; x++)
  1.3303 +	if (((k = mail_uid (stream,x)) >= i) && (k <= j))
  1.3304 +	  mail_elt (stream,x)->sequence = T;
  1.3305 +      break;
  1.3306 +    case ',':			/* single message */
  1.3307 +      ++sequence;		/* skip the delimiter, fall into end case */
  1.3308 +    case '\0':			/* end of sequence, mark this message */
  1.3309 +      if (x = mail_msgno (stream,i)) mail_elt (stream,x)->sequence = T;
  1.3310 +      break;
  1.3311 +    default:			/* anything else is a syntax error! */
  1.3312 +      MM_LOG ("UID sequence syntax error",ERROR);
  1.3313 +      return NIL;
  1.3314 +    }
  1.3315 +  }
  1.3316 +  return T;			/* successfully parsed sequence */
  1.3317 +}
  1.3318 +
  1.3319 +/* Mail see if line list matches that in cache
  1.3320 + * Accepts: candidate line list
  1.3321 + *	    cached line list
  1.3322 + *	    matching flags
  1.3323 + * Returns: T if match, NIL if no match
  1.3324 + */
  1.3325 +
  1.3326 +long mail_match_lines (STRINGLIST *lines,STRINGLIST *msglines,long flags)
  1.3327 +{
  1.3328 +  unsigned long i;
  1.3329 +  unsigned char *s,*t;
  1.3330 +  STRINGLIST *m;
  1.3331 +  if (!msglines) return T;	/* full header is in cache */
  1.3332 +				/* need full header but filtered in cache */
  1.3333 +  if ((flags & FT_NOT) || !lines) return NIL;
  1.3334 +  do {				/* make sure all present & accounted for */
  1.3335 +    for (m = msglines; m; m = m->next) if (lines->text.size == m->text.size) {
  1.3336 +      for (s = lines->text.data,t = m->text.data,i = lines->text.size;
  1.3337 +	   i && !compare_uchar (*s,*t); s++,t++,i--);
  1.3338 +      if (!i) break;		/* this line matches */
  1.3339 +    }
  1.3340 +    if (!m) return NIL;		/* didn't find in the list */
  1.3341 +  }
  1.3342 +  while (lines = lines->next);
  1.3343 +  return T;			/* all lines found */
  1.3344 +}
  1.3345 +
  1.3346 +/* Mail filter text by header lines
  1.3347 + * Accepts: text to filter, with trailing null
  1.3348 + *	    length of text
  1.3349 + *	    list of lines
  1.3350 + *	    fetch flags
  1.3351 + * Returns: new text size, text overwritten
  1.3352 + */
  1.3353 +
  1.3354 +unsigned long mail_filter (char *text,unsigned long len,STRINGLIST *lines,
  1.3355 +			   long flags)
  1.3356 +{
  1.3357 +  STRINGLIST *hdrs;
  1.3358 +  int notfound;
  1.3359 +  unsigned long i;
  1.3360 +  char c,*s,*e,*t,tmp[MAILTMPLEN];
  1.3361 +  char *src = text;
  1.3362 +  char *dst = src;
  1.3363 +  char *end = text + len;
  1.3364 +  text[len] = '\012';		/* guard against running off buffer */
  1.3365 +  while (src < end) {		/* process header */
  1.3366 +				/* slurp header line name */
  1.3367 +    for (s = src,e = s + MAILTMPLEN - 1,e = (e < end ? e : end),t = tmp;
  1.3368 +	 (s < e) && ((c = (*s ? *s : (*s = ' '))) != ':') &&
  1.3369 +	 ((c > ' ') ||
  1.3370 +	  ((c != ' ') && (c != '\t') && (c != '\015') && (c != '\012')));
  1.3371 +	 *t++ = *s++);
  1.3372 +    *t = '\0';			/* tie off */
  1.3373 +    notfound = T;		/* not found yet */
  1.3374 +    if (i = t - tmp)		/* see if found in header */
  1.3375 +      for (hdrs = lines; hdrs && notfound; hdrs = hdrs->next)
  1.3376 +	if ((hdrs->text.size == i) && !compare_csizedtext (tmp,&hdrs->text))
  1.3377 +	  notfound = NIL;
  1.3378 +				/* skip header line if not wanted */
  1.3379 +    if (i && ((flags & FT_NOT) ? !notfound : notfound))
  1.3380 +      while (((*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
  1.3381 +	      (*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
  1.3382 +	      (*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
  1.3383 +	      (*src++ != '\012')) || ((*src == ' ') || (*src == '\t')));
  1.3384 +    else if (src == dst) {	/* copy to self */
  1.3385 +      while (((*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
  1.3386 +	      (*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
  1.3387 +	      (*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
  1.3388 +	      (*src++ != '\012')) || ((*src == ' ') || (*src == '\t')));
  1.3389 +      dst = src;		/* update destination */
  1.3390 +    }
  1.3391 +    else {			/* copy line and any continuation line */
  1.3392 +      while ((((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') &&
  1.3393 +	      ((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') &&
  1.3394 +	      ((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') &&
  1.3395 +	      ((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') &&
  1.3396 +	      ((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012'))||
  1.3397 +	     ((*src == ' ') || (*src == '\t')));
  1.3398 +				/* in case hit the guard LF */
  1.3399 +      if (src > end) dst -= (src - end);
  1.3400 +    }
  1.3401 +  }
  1.3402 +  *dst = '\0';			/* tie off destination */
  1.3403 +  return dst - text;
  1.3404 +}
  1.3405 +
  1.3406 +/* Local mail search message
  1.3407 + * Accepts: MAIL stream
  1.3408 + *	    message number
  1.3409 + *	    optional section specification
  1.3410 + *	    search program
  1.3411 + * Returns: T if found, NIL otherwise
  1.3412 + */
  1.3413 +
  1.3414 +long mail_search_msg (MAILSTREAM *stream,unsigned long msgno,char *section,
  1.3415 +		      SEARCHPGM *pgm)
  1.3416 +{
  1.3417 +  unsigned short d;
  1.3418 +  char tmp[MAILTMPLEN];
  1.3419 +  MESSAGECACHE *elt = mail_elt (stream,msgno);
  1.3420 +  SEARCHHEADER *hdr;
  1.3421 +  SEARCHOR *or;
  1.3422 +  SEARCHPGMLIST *not;
  1.3423 +  unsigned long now = (unsigned long) time (0);
  1.3424 +  if (pgm->msgno || pgm->uid) {	/* message set searches */
  1.3425 +    SEARCHSET *set;
  1.3426 +				/* message sequences */
  1.3427 +    if (pgm->msgno) {		/* inside this message sequence set */
  1.3428 +      for (set = pgm->msgno; set; set = set->next)
  1.3429 +	if (set->last ? ((set->first <= set->last) ?
  1.3430 +			 ((msgno >= set->first) && (msgno <= set->last)) :
  1.3431 +			 ((msgno >= set->last) && (msgno <= set->first))) :
  1.3432 +	    msgno == set->first) break;
  1.3433 +      if (!set) return NIL;	/* not found within sequence */
  1.3434 +    }
  1.3435 +    if (pgm->uid) {		/* inside this unique identifier set */
  1.3436 +      unsigned long uid = mail_uid (stream,msgno);
  1.3437 +      for (set = pgm->uid; set; set = set->next)
  1.3438 +	if (set->last ? ((set->first <= set->last) ?
  1.3439 +			 ((uid >= set->first) && (uid <= set->last)) :
  1.3440 +			 ((uid >= set->last) && (uid <= set->first))) :
  1.3441 +	    uid == set->first) break;
  1.3442 +      if (!set) return NIL;	/* not found within sequence */
  1.3443 +    }
  1.3444 +  }
  1.3445 +
  1.3446 +  /* Fast data searches */
  1.3447 +				/* need to fetch fast data? */
  1.3448 +  if ((!elt->rfc822_size && (pgm->larger || pgm->smaller)) ||
  1.3449 +      (!elt->year && (pgm->before || pgm->on || pgm->since ||
  1.3450 +		      pgm->older || pgm->younger)) ||
  1.3451 +      (!elt->valid && (pgm->answered || pgm->unanswered ||
  1.3452 +		       pgm->deleted || pgm->undeleted ||
  1.3453 +		       pgm->draft || pgm->undraft ||
  1.3454 +		       pgm->flagged || pgm->unflagged ||
  1.3455 +		       pgm->recent || pgm->old ||
  1.3456 +		       pgm->seen || pgm->unseen ||
  1.3457 +		       pgm->keyword || pgm->unkeyword))) {
  1.3458 +    unsigned long i;
  1.3459 +    MESSAGECACHE *ielt;
  1.3460 +    for (i = elt->msgno;	/* find last unloaded message in range */
  1.3461 +	 (i < stream->nmsgs) && (ielt = mail_elt (stream,i+1)) &&
  1.3462 +	   ((!ielt->rfc822_size && (pgm->larger || pgm->smaller)) ||
  1.3463 +	    (!ielt->year && (pgm->before || pgm->on || pgm->since ||
  1.3464 +			     pgm->older || pgm->younger)) ||
  1.3465 +	    (!ielt->valid && (pgm->answered || pgm->unanswered ||
  1.3466 +			      pgm->deleted || pgm->undeleted ||
  1.3467 +			      pgm->draft || pgm->undraft ||
  1.3468 +			      pgm->flagged || pgm->unflagged ||
  1.3469 +			      pgm->recent || pgm->old ||
  1.3470 +			      pgm->seen || pgm->unseen ||
  1.3471 +			      pgm->keyword || pgm->unkeyword))); ++i);
  1.3472 +    if (i == elt->msgno) sprintf (tmp,"%lu",elt->msgno);
  1.3473 +    else sprintf (tmp,"%lu:%lu",elt->msgno,i);
  1.3474 +    mail_fetch_fast (stream,tmp,NIL);
  1.3475 +  }
  1.3476 +				/* size ranges */
  1.3477 +  if ((pgm->larger && (elt->rfc822_size <= pgm->larger)) ||
  1.3478 +      (pgm->smaller && (elt->rfc822_size >= pgm->smaller))) return NIL;
  1.3479 +				/* message flags */
  1.3480 +  if ((pgm->answered && !elt->answered) ||
  1.3481 +      (pgm->unanswered && elt->answered) ||
  1.3482 +      (pgm->deleted && !elt->deleted) ||
  1.3483 +      (pgm->undeleted && elt->deleted) ||
  1.3484 +      (pgm->draft && !elt->draft) ||
  1.3485 +      (pgm->undraft && elt->draft) ||
  1.3486 +      (pgm->flagged && !elt->flagged) ||
  1.3487 +      (pgm->unflagged && elt->flagged) ||
  1.3488 +      (pgm->recent && !elt->recent) ||
  1.3489 +      (pgm->old && elt->recent) ||
  1.3490 +      (pgm->seen && !elt->seen) ||
  1.3491 +      (pgm->unseen && elt->seen)) return NIL;
  1.3492 +				/* keywords */
  1.3493 +  if ((pgm->keyword && !mail_search_keyword (stream,elt,pgm->keyword,LONGT)) ||
  1.3494 +      (pgm->unkeyword && !mail_search_keyword (stream,elt,pgm->unkeyword,NIL)))
  1.3495 +    return NIL;
  1.3496 +				/* internal date ranges */
  1.3497 +  if (pgm->before || pgm->on || pgm->since) {
  1.3498 +    d = mail_shortdate (elt->year,elt->month,elt->day);
  1.3499 +    if (pgm->before && (d >= pgm->before)) return NIL;
  1.3500 +    if (pgm->on && (d != pgm->on)) return NIL;
  1.3501 +    if (pgm->since && (d < pgm->since)) return NIL;
  1.3502 +  }
  1.3503 +  if (pgm->older || pgm->younger) {
  1.3504 +    unsigned long msgd = mail_longdate (elt);
  1.3505 +    if (pgm->older && msgd > (now - pgm->older)) return NIL;
  1.3506 +    if (pgm->younger && msgd < (now - pgm->younger)) return NIL;
  1.3507 +  }
  1.3508 +
  1.3509 +				/* envelope searches */
  1.3510 +  if (pgm->sentbefore || pgm->senton || pgm->sentsince ||
  1.3511 +      pgm->bcc || pgm->cc || pgm->from || pgm->to || pgm->subject ||
  1.3512 +      pgm->return_path || pgm->sender || pgm->reply_to || pgm->in_reply_to ||
  1.3513 +      pgm->message_id || pgm->newsgroups || pgm->followup_to ||
  1.3514 +      pgm->references) {
  1.3515 +    ENVELOPE *env;
  1.3516 +    MESSAGECACHE delt;
  1.3517 +    if (section) {		/* use body part envelope */
  1.3518 +      BODY *body = mail_body (stream,msgno,section);
  1.3519 +      env = (body && (body->type == TYPEMESSAGE) && body->subtype &&
  1.3520 +	     !strcmp (body->subtype,"RFC822")) ? body->nested.msg->env : NIL;
  1.3521 +    }
  1.3522 +    else {			/* use top level envelope if no section */
  1.3523 +      if (pgm->header && !stream->scache && !(stream->dtb->flags & DR_LOCAL))
  1.3524 +	mail_fetch_header(stream,msgno,NIL,NIL,NIL,FT_PEEK|FT_SEARCHLOOKAHEAD);
  1.3525 +      env = mail_fetchenvelope (stream,msgno);
  1.3526 +    }
  1.3527 +    if (!env) return NIL;	/* no envelope obtained */
  1.3528 +				/* sent date ranges */
  1.3529 +    if ((pgm->sentbefore || pgm->senton || pgm->sentsince) &&
  1.3530 +	(!mail_parse_date (&delt,env->date) ||
  1.3531 +	 !(d = mail_shortdate (delt.year,delt.month,delt.day)) ||
  1.3532 +	 (pgm->sentbefore && (d >= pgm->sentbefore)) ||
  1.3533 +	 (pgm->senton && (d != pgm->senton)) ||
  1.3534 +	 (pgm->sentsince && (d < pgm->sentsince)))) return NIL;
  1.3535 +				/* search headers */
  1.3536 +    if ((pgm->bcc && !mail_search_addr (env->bcc,pgm->bcc)) ||
  1.3537 +	(pgm->cc && !mail_search_addr (env->cc,pgm->cc)) ||
  1.3538 +	(pgm->from && !mail_search_addr (env->from,pgm->from)) ||
  1.3539 +	(pgm->to && !mail_search_addr (env->to,pgm->to)) ||
  1.3540 +	(pgm->subject && !mail_search_header_text (env->subject,pgm->subject)))
  1.3541 +      return NIL;
  1.3542 +    /* These criteria are not supported by IMAP and have to be emulated */
  1.3543 +    if ((pgm->return_path &&
  1.3544 +	 !mail_search_addr (env->return_path,pgm->return_path)) ||
  1.3545 +	(pgm->sender && !mail_search_addr (env->sender,pgm->sender)) ||
  1.3546 +	(pgm->reply_to && !mail_search_addr (env->reply_to,pgm->reply_to)) ||
  1.3547 +	(pgm->in_reply_to &&
  1.3548 +	 !mail_search_header_text (env->in_reply_to,pgm->in_reply_to)) ||
  1.3549 +	(pgm->message_id &&
  1.3550 +	!mail_search_header_text (env->message_id,pgm->message_id)) ||
  1.3551 +	(pgm->newsgroups &&
  1.3552 +	 !mail_search_header_text (env->newsgroups,pgm->newsgroups)) ||
  1.3553 +	(pgm->followup_to &&
  1.3554 +	 !mail_search_header_text (env->followup_to,pgm->followup_to)) ||
  1.3555 +	(pgm->references &&
  1.3556 +	 !mail_search_header_text (env->references,pgm->references)))
  1.3557 +      return NIL;
  1.3558 +  }
  1.3559 +
  1.3560 +				/* search header lines */
  1.3561 +  for (hdr = pgm->header; hdr; hdr = hdr->next) {
  1.3562 +    char *t,*e,*v;
  1.3563 +    SIZEDTEXT s;
  1.3564 +    STRINGLIST sth,stc;
  1.3565 +    sth.next = stc.next = NIL;	/* only one at a time */
  1.3566 +    sth.text.data = hdr->line.data;
  1.3567 +    sth.text.size = hdr->line.size;
  1.3568 +				/* get the header text */
  1.3569 +    if ((t = mail_fetch_header (stream,msgno,NIL,&sth,&s.size,
  1.3570 +				FT_INTERNAL | FT_PEEK |
  1.3571 +				(section ? NIL : FT_SEARCHLOOKAHEAD))) &&
  1.3572 +	strchr (t,':')) {
  1.3573 +      if (hdr->text.size) {	/* anything matches empty search string */
  1.3574 +				/* non-empty, copy field data */
  1.3575 +	s.data = (unsigned char *) fs_get (s.size + 1);
  1.3576 +				/* for each line */
  1.3577 +	for (v = (char *) s.data, e = t + s.size; t < e;) switch (*t) {
  1.3578 +	default:		/* non-continuation, skip leading field name */
  1.3579 +	  while ((t < e) && (*t++ != ':'));
  1.3580 +	  if ((t < e) && (*t == ':')) t++;
  1.3581 +	case '\t': case ' ':	/* copy field data  */
  1.3582 +	  while ((t < e) && (*t != '\015') && (*t != '\012')) *v++ = *t++;
  1.3583 +	  *v++ = '\n';		/* tie off line */
  1.3584 +	  while (((*t == '\015') || (*t == '\012')) && (t < e)) t++;
  1.3585 +	}
  1.3586 +				/* calculate true size */
  1.3587 +	s.size = v - (char *) s.data;
  1.3588 +	*v = '\0';		/* tie off results */
  1.3589 +	stc.text.data = hdr->text.data;
  1.3590 +	stc.text.size = hdr->text.size;
  1.3591 +				/* search header */
  1.3592 +	if (mail_search_header (&s,&stc)) fs_give ((void **) &s.data);
  1.3593 +	else {			/* search failed */
  1.3594 +	  fs_give ((void **) &s.data);
  1.3595 +	  return NIL;
  1.3596 +	}
  1.3597 +      }
  1.3598 +    }
  1.3599 +    else return NIL;		/* no matching header text */
  1.3600 +  }
  1.3601 +				/* search strings */
  1.3602 +  if ((pgm->text && !mail_search_text (stream,msgno,section,pgm->text,LONGT))||
  1.3603 +      (pgm->body && !mail_search_text (stream,msgno,section,pgm->body,NIL)))
  1.3604 +    return NIL;
  1.3605 +				/* logical conditions */
  1.3606 +  for (or = pgm->or; or; or = or->next)
  1.3607 +    if (!(mail_search_msg (stream,msgno,section,or->first) ||
  1.3608 +	  mail_search_msg (stream,msgno,section,or->second))) return NIL;
  1.3609 +  for (not = pgm->not; not; not = not->next)
  1.3610 +    if (mail_search_msg (stream,msgno,section,not->pgm)) return NIL;
  1.3611 +  return T;
  1.3612 +}
  1.3613 +
  1.3614 +/* Mail search message header null-terminated text
  1.3615 + * Accepts: header text
  1.3616 + *	    strings to search
  1.3617 + * Returns: T if search found a match
  1.3618 + */
  1.3619 +
  1.3620 +long mail_search_header_text (char *s,STRINGLIST *st)
  1.3621 +{
  1.3622 +  SIZEDTEXT h;
  1.3623 +				/* have any text? */
  1.3624 +  if (h.data = (unsigned char *) s) {
  1.3625 +    h.size = strlen (s);	/* yes, get its size */
  1.3626 +    return mail_search_header (&h,st);
  1.3627 +  }
  1.3628 +  return NIL;
  1.3629 +}
  1.3630 +
  1.3631 +
  1.3632 +/* Mail search message header
  1.3633 + * Accepts: header as sized text
  1.3634 + *	    strings to search
  1.3635 + * Returns: T if search found a match
  1.3636 + */
  1.3637 +
  1.3638 +long mail_search_header (SIZEDTEXT *hdr,STRINGLIST *st)
  1.3639 +{
  1.3640 +  SIZEDTEXT h;
  1.3641 +  long ret = LONGT;
  1.3642 +				/* make UTF-8 version of header */
  1.3643 +  utf8_mime2text (hdr,&h,U8T_CANONICAL);
  1.3644 +  while (h.size && ((h.data[h.size-1]=='\015') || (h.data[h.size-1]=='\012')))
  1.3645 +    --h.size;			/* slice off trailing newlines */
  1.3646 +  do if (h.size ?		/* search non-empty string */
  1.3647 +	 !ssearch (h.data,h.size,st->text.data,st->text.size) : st->text.size)
  1.3648 +    ret = NIL;
  1.3649 +  while (ret && (st = st->next));
  1.3650 +  if (h.data != hdr->data) fs_give ((void **) &h.data);
  1.3651 +  return ret;
  1.3652 +}
  1.3653 +
  1.3654 +/* Mail search message body
  1.3655 + * Accepts: MAIL stream
  1.3656 + *	    message number
  1.3657 + *	    optional section specification
  1.3658 + *	    string list
  1.3659 + *	    flags
  1.3660 + * Returns: T if search found a match
  1.3661 + */
  1.3662 +
  1.3663 +long mail_search_text (MAILSTREAM *stream,unsigned long msgno,char *section,
  1.3664 +		       STRINGLIST *st,long flags)
  1.3665 +{
  1.3666 +  BODY *body;
  1.3667 +  long ret = NIL;
  1.3668 +  STRINGLIST *s = mail_newstringlist ();
  1.3669 +  mailgets_t omg = mailgets;
  1.3670 +  if (stream->dtb->flags & DR_LOWMEM) mailgets = mail_search_gets;
  1.3671 +				/* strings to search */
  1.3672 +  for (stream->private.search.string = s; st;) {
  1.3673 +    s->text.data = st->text.data;
  1.3674 +    s->text.size = st->text.size;
  1.3675 +    if (st = st->next) s = s->next = mail_newstringlist ();
  1.3676 +  }
  1.3677 +  stream->private.search.text = NIL;
  1.3678 +  if (flags) {			/* want header? */
  1.3679 +    SIZEDTEXT s,t;
  1.3680 +    s.data = (unsigned char *)
  1.3681 +      mail_fetch_header (stream,msgno,section,NIL,&s.size,FT_INTERNAL|FT_PEEK);
  1.3682 +    utf8_mime2text (&s,&t,U8T_CANONICAL);
  1.3683 +    ret = mail_search_string_work (&t,&stream->private.search.string);
  1.3684 +    if (t.data != s.data) fs_give ((void **) &t.data);
  1.3685 +  }
  1.3686 +  if (!ret) {			/* still looking for match? */
  1.3687 +				/* no section, get top-level body */
  1.3688 +    if (!section) mail_fetchstructure (stream,msgno,&body);
  1.3689 +				/* get body of nested message */
  1.3690 +    else if ((body = mail_body (stream,msgno,section)) &&
  1.3691 +	     (body->type == TYPEMULTIPART) && body->subtype &&
  1.3692 +	     !strcmp (body->subtype,"RFC822")) body = body->nested.msg->body;
  1.3693 +    if (body) ret = mail_search_body (stream,msgno,body,NIL,1,flags);
  1.3694 +  }
  1.3695 +  mailgets = omg;		/* restore former gets routine */
  1.3696 +				/* clear searching */
  1.3697 +  for (s = stream->private.search.string; s; s = s->next) s->text.data = NIL;
  1.3698 +  mail_free_stringlist (&stream->private.search.string);
  1.3699 +  stream->private.search.text = NIL;
  1.3700 +  return ret;
  1.3701 +}
  1.3702 +
  1.3703 +/* Mail search message body text parts
  1.3704 + * Accepts: MAIL stream
  1.3705 + *	    message number
  1.3706 + *	    current body pointer
  1.3707 + *	    hierarchical level prefix
  1.3708 + *	    position at current hierarchical level
  1.3709 + *	    string list
  1.3710 + *	    flags
  1.3711 + * Returns: T if search found a match
  1.3712 + */
  1.3713 +
  1.3714 +long mail_search_body (MAILSTREAM *stream,unsigned long msgno,BODY *body,
  1.3715 +		       char *prefix,unsigned long section,long flags)
  1.3716 +{
  1.3717 +  long ret = NIL;
  1.3718 +  unsigned long i;
  1.3719 +  char *s,*t,sect[MAILTMPLEN];
  1.3720 +  SIZEDTEXT st,h;
  1.3721 +  PART *part;
  1.3722 +  PARAMETER *param;
  1.3723 +  if (prefix && (strlen (prefix) > (MAILTMPLEN - 20))) return NIL;
  1.3724 +  sprintf (sect,"%s%lu",prefix ? prefix : "",section++);
  1.3725 +  if (flags && prefix) {	/* want to search MIME header too? */
  1.3726 +    st.data = (unsigned char *) mail_fetch_mime (stream,msgno,sect,&st.size,
  1.3727 +						 FT_INTERNAL | FT_PEEK);
  1.3728 +    if (stream->dtb->flags & DR_LOWMEM) ret = stream->private.search.result;
  1.3729 +    else {
  1.3730 +				/* make UTF-8 version of header */
  1.3731 +      utf8_mime2text (&st,&h,U8T_CANONICAL);
  1.3732 +      ret = mail_search_string_work (&h,&stream->private.search.string);
  1.3733 +      if (h.data != st.data) fs_give ((void **) &h.data);
  1.3734 +    }
  1.3735 +  }
  1.3736 +  if (!ret) switch (body->type) {
  1.3737 +  case TYPEMULTIPART:
  1.3738 +				/* extend prefix if not first time */
  1.3739 +    s = prefix ? strcat (sect,".") : "";
  1.3740 +    for (i = 1,part = body->nested.part; part && !ret; i++,part = part->next)
  1.3741 +      ret = mail_search_body (stream,msgno,&part->body,s,i,flags);
  1.3742 +    break;
  1.3743 +  case TYPEMESSAGE:
  1.3744 +    if (!strcmp (body->subtype,"RFC822")) {
  1.3745 +      if (flags) {		/* want to search nested message header? */
  1.3746 +	st.data = (unsigned char *)
  1.3747 +	  mail_fetch_header (stream,msgno,sect,NIL,&st.size,
  1.3748 +			     FT_INTERNAL | FT_PEEK);
  1.3749 +	if (stream->dtb->flags & DR_LOWMEM) ret =stream->private.search.result;
  1.3750 +	else {
  1.3751 +				/* make UTF-8 version of header */
  1.3752 +	  utf8_mime2text (&st,&h,U8T_CANONICAL);
  1.3753 +	  ret = mail_search_string_work (&h,&stream->private.search.string);
  1.3754 +	  if (h.data != st.data) fs_give ((void **) &h.data);
  1.3755 +	}
  1.3756 +      }
  1.3757 +      if (body = body->nested.msg->body)
  1.3758 +	ret = (body->type == TYPEMULTIPART) ?
  1.3759 +	  mail_search_body (stream,msgno,body,(prefix ? prefix : ""),
  1.3760 +			    section - 1,flags) :
  1.3761 +	mail_search_body (stream,msgno,body,strcat (sect,"."),1,flags);
  1.3762 +      break;
  1.3763 +    }
  1.3764 +				/* non-MESSAGE/RFC822 falls into text case */
  1.3765 +
  1.3766 +  case TYPETEXT:
  1.3767 +    s = mail_fetch_body (stream,msgno,sect,&i,FT_INTERNAL | FT_PEEK);
  1.3768 +    if (stream->dtb->flags & DR_LOWMEM) ret = stream->private.search.result;
  1.3769 +    else {
  1.3770 +      for (t = NIL,param = body->parameter; param && !t; param = param->next)
  1.3771 +	if (!strcmp (param->attribute,"CHARSET")) t = param->value;
  1.3772 +      switch (body->encoding) {	/* what encoding? */
  1.3773 +      case ENCBASE64:
  1.3774 +	if (st.data = (unsigned char *)
  1.3775 +	    rfc822_base64 ((unsigned char *) s,i,&st.size)) {
  1.3776 +	  ret = mail_search_string (&st,t,&stream->private.search.string);
  1.3777 +	  fs_give ((void **) &st.data);
  1.3778 +	}
  1.3779 +	break;
  1.3780 +      case ENCQUOTEDPRINTABLE:
  1.3781 +	if (st.data = rfc822_qprint ((unsigned char *) s,i,&st.size)) {
  1.3782 +	  ret = mail_search_string (&st,t,&stream->private.search.string);
  1.3783 +	  fs_give ((void **) &st.data);
  1.3784 +	}
  1.3785 +	break;
  1.3786 +      default:
  1.3787 +	st.data = (unsigned char *) s;
  1.3788 +	st.size = i;
  1.3789 +	ret = mail_search_string (&st,t,&stream->private.search.string);
  1.3790 +	break;
  1.3791 +      }
  1.3792 +    }
  1.3793 +    break;
  1.3794 +  }
  1.3795 +  return ret;
  1.3796 +}
  1.3797 +
  1.3798 +/* Mail search text
  1.3799 + * Accepts: sized text to search
  1.3800 + *	    character set of sized text
  1.3801 + *	    string list of search keys
  1.3802 + * Returns: T if search found a match
  1.3803 + */
  1.3804 +
  1.3805 +long mail_search_string (SIZEDTEXT *s,char *charset,STRINGLIST **st)
  1.3806 +{
  1.3807 +  SIZEDTEXT u;
  1.3808 +  long ret;
  1.3809 +  STRINGLIST **sc = st;
  1.3810 +				/* convert to UTF-8 as best we can */
  1.3811 +  if (!utf8_text (s,charset,&u,U8T_CANONICAL))
  1.3812 +    utf8_text (s,NIL,&u,U8T_CANONICAL);
  1.3813 +  ret = mail_search_string_work (&u,st);
  1.3814 +  if (u.data != s->data) fs_give ((void **) &u.data);
  1.3815 +  return ret;
  1.3816 +}
  1.3817 +
  1.3818 +
  1.3819 +/* Mail search text worker routine
  1.3820 + * Accepts: sized text to search
  1.3821 + *	    string list of search keys
  1.3822 + * Returns: T if search found a match
  1.3823 + */
  1.3824 +
  1.3825 +long mail_search_string_work (SIZEDTEXT *s,STRINGLIST **st)
  1.3826 +{
  1.3827 +  void *t;
  1.3828 +  STRINGLIST **sc = st;
  1.3829 +  while (*sc) {			/* run down criteria list */
  1.3830 +    if (ssearch (s->data,s->size,(*sc)->text.data,(*sc)->text.size)) {
  1.3831 +      t = (void *) (*sc);	/* found one, need to flush this */
  1.3832 +      *sc = (*sc)->next;	/* remove it from the list */
  1.3833 +      fs_give (&t);		/* flush the buffer */
  1.3834 +    }
  1.3835 +    else sc = &(*sc)->next;	/* move to next in list */
  1.3836 +  }
  1.3837 +  return *st ? NIL : LONGT;
  1.3838 +}
  1.3839 +
  1.3840 +
  1.3841 +/* Mail search keyword
  1.3842 + * Accepts: MAIL stream
  1.3843 + *	    elt to get flags from
  1.3844 + *	    keyword list
  1.3845 + *	    T for keyword search, NIL for unkeyword search
  1.3846 + * Returns: T if search found a match
  1.3847 + */
  1.3848 +
  1.3849 +long mail_search_keyword (MAILSTREAM *stream,MESSAGECACHE *elt,STRINGLIST *st,
  1.3850 +			  long flag)
  1.3851 +{
  1.3852 +  int i,j;
  1.3853 +  unsigned long f = 0;
  1.3854 +  unsigned long tf;
  1.3855 +  do {
  1.3856 +    for (i = 0; (j = (i < NUSERFLAGS) && stream->user_flags[i]); ++i)
  1.3857 +      if (!compare_csizedtext (stream->user_flags[i],&st->text)) {
  1.3858 +	f |= (1 << i);
  1.3859 +	break;
  1.3860 +      }
  1.3861 +    if (flag && !j) return NIL;
  1.3862 +  } while (st = st->next);
  1.3863 +  tf = elt->user_flags & f;	/* get set flags which match */
  1.3864 +  return flag ? (f == tf) : !tf;
  1.3865 +}
  1.3866 +
  1.3867 +/* Mail search an address list
  1.3868 + * Accepts: address list
  1.3869 + *	    string list
  1.3870 + * Returns: T if search found a match
  1.3871 + */
  1.3872 +
  1.3873 +#define SEARCHBUFLEN (size_t) 2000
  1.3874 +#define SEARCHBUFSLOP (size_t) 5
  1.3875 +
  1.3876 +long mail_search_addr (ADDRESS *adr,STRINGLIST *st)
  1.3877 +{
  1.3878 +  ADDRESS *a,tadr;
  1.3879 +  SIZEDTEXT txt;
  1.3880 +  char tmp[SENDBUFLEN + 1];
  1.3881 +  size_t i = SEARCHBUFLEN;
  1.3882 +  size_t k;
  1.3883 +  long ret = NIL;
  1.3884 +  if (adr) {
  1.3885 +    txt.data = (unsigned char *) fs_get (i + SEARCHBUFSLOP);
  1.3886 +				/* never an error or next */
  1.3887 +    tadr.error = NIL,tadr.next = NIL;
  1.3888 +				/* write address list */
  1.3889 +    for (txt.size = 0,a = adr; a; a = a->next) {
  1.3890 +      k = (tadr.mailbox = a->mailbox) ? 4 + 2*strlen (a->mailbox) : 3;
  1.3891 +      if (tadr.personal = a->personal) k += 3 + 2*strlen (a->personal);
  1.3892 +      if (tadr.adl = a->adl) k += 3 + 2*strlen (a->adl);
  1.3893 +      if (tadr.host = a->host) k += 3 + 2*strlen (a->host);
  1.3894 +      if (tadr.personal || tadr.adl) k += 2;
  1.3895 +      if (k < (SENDBUFLEN-10)) {/* ignore ridiculous addresses */
  1.3896 +	tmp[0] = '\0';
  1.3897 +	rfc822_write_address (tmp,&tadr);
  1.3898 +				/* resize buffer if necessary */
  1.3899 +	if (((k = strlen (tmp)) + txt.size) > i)
  1.3900 +	  fs_resize ((void **) &txt.data,SEARCHBUFSLOP + (i += SEARCHBUFLEN));
  1.3901 +				/* add new address */
  1.3902 +	memcpy (txt.data + txt.size,tmp,k);
  1.3903 +	txt.size += k;
  1.3904 +				/* another address follows */
  1.3905 +	if (a->next) txt.data[txt.size++] = ',';
  1.3906 +      }
  1.3907 +    }
  1.3908 +    txt.data[txt.size] = '\0';	/* tie off string */
  1.3909 +    ret = mail_search_header (&txt,st);
  1.3910 +    fs_give ((void **) &txt.data);
  1.3911 +  }
  1.3912 +  return ret;
  1.3913 +}
  1.3914 +
  1.3915 +/* Get string for low-memory searching
  1.3916 + * Accepts: readin function pointer
  1.3917 + *	    stream to use
  1.3918 + *	    number of bytes
  1.3919 + *	    gets data packet
  1.3920 +
  1.3921 + *	    mail stream
  1.3922 + *	    message number
  1.3923 + *	    descriptor string
  1.3924 + *	    option flags
  1.3925 + * Returns: NIL, always
  1.3926 + */
  1.3927 +
  1.3928 +#define SEARCHSLOP 128
  1.3929 +
  1.3930 +char *mail_search_gets (readfn_t f,void *stream,unsigned long size,
  1.3931 +			GETS_DATA *md)
  1.3932 +{
  1.3933 +  unsigned long i;
  1.3934 +  char tmp[MAILTMPLEN+SEARCHSLOP+1];
  1.3935 +  SIZEDTEXT st;
  1.3936 +				/* better not be called unless searching */
  1.3937 +  if (!md->stream->private.search.string) {
  1.3938 +    sprintf (tmp,"Search botch, mbx = %.80s, %s = %lu[%.80s]",
  1.3939 +	     md->stream->mailbox,
  1.3940 +	     (md->flags & FT_UID) ? "UID" : "msg",md->msgno,md->what);
  1.3941 +    fatal (tmp);
  1.3942 +  }
  1.3943 +				/* initially no match for search */
  1.3944 +  md->stream->private.search.result = NIL;
  1.3945 +				/* make sure buffer clear */
  1.3946 +  memset (st.data = (unsigned char *) tmp,'\0',
  1.3947 +	  (size_t) MAILTMPLEN+SEARCHSLOP+1);
  1.3948 +				/* read first buffer */
  1.3949 +  (*f) (stream,st.size = i = min (size,(long) MAILTMPLEN),tmp);
  1.3950 +				/* search for text */
  1.3951 +  if (mail_search_string (&st,NIL,&md->stream->private.search.string))
  1.3952 +    md->stream->private.search.result = T;
  1.3953 +  else if (size -= i) {		/* more to do, blat slop down */
  1.3954 +    memmove (tmp,tmp+MAILTMPLEN-SEARCHSLOP,(size_t) SEARCHSLOP);
  1.3955 +    do {			/* read subsequent buffers one at a time */
  1.3956 +      (*f) (stream,i = min (size,(long) MAILTMPLEN),tmp+SEARCHSLOP);
  1.3957 +      st.size = i + SEARCHSLOP;
  1.3958 +      if (mail_search_string (&st,NIL,&md->stream->private.search.string))
  1.3959 +	md->stream->private.search.result = T;
  1.3960 +      else memmove (tmp,tmp+MAILTMPLEN,(size_t) SEARCHSLOP);
  1.3961 +    }
  1.3962 +    while ((size -= i) && !md->stream->private.search.result);
  1.3963 +  }
  1.3964 +  if (size) {			/* toss out everything after that */
  1.3965 +    do (*f) (stream,i = min (size,(long) MAILTMPLEN),tmp);
  1.3966 +    while (size -= i);
  1.3967 +  }
  1.3968 +  return NIL;
  1.3969 +}
  1.3970 +
  1.3971 +/* Mail parse search criteria
  1.3972 + * Accepts: criteria
  1.3973 + * Returns: search program if parse successful, else NIL
  1.3974 + */
  1.3975 +
  1.3976 +SEARCHPGM *mail_criteria (char *criteria)
  1.3977 +{
  1.3978 +  SEARCHPGM *pgm = NIL;
  1.3979 +  char *criterion,*r,tmp[MAILTMPLEN];
  1.3980 +  int f;
  1.3981 +  if (criteria) {		/* only if criteria defined */
  1.3982 +				/* make writeable copy of criteria */
  1.3983 +    criteria = cpystr (criteria);
  1.3984 +				/* for each criterion */
  1.3985 +    for (pgm = mail_newsearchpgm (), criterion = strtok_r (criteria," ",&r);
  1.3986 +	 criterion; (criterion = strtok_r (NIL," ",&r))) {
  1.3987 +      f = NIL;			/* init then scan the criterion */
  1.3988 +      switch (*ucase (criterion)) {
  1.3989 +      case 'A':			/* possible ALL, ANSWERED */
  1.3990 +	if (!strcmp (criterion+1,"LL")) f = T;
  1.3991 +	else if (!strcmp (criterion+1,"NSWERED")) f = pgm->answered = T;
  1.3992 +	break;
  1.3993 +      case 'B':			/* possible BCC, BEFORE, BODY */
  1.3994 +	if (!strcmp (criterion+1,"CC"))
  1.3995 +	  f = mail_criteria_string (&pgm->bcc,&r);
  1.3996 +	else if (!strcmp (criterion+1,"EFORE"))
  1.3997 +	  f = mail_criteria_date (&pgm->before,&r);
  1.3998 +	else if (!strcmp (criterion+1,"ODY"))
  1.3999 +	  f = mail_criteria_string (&pgm->body,&r);
  1.4000 +	break;
  1.4001 +      case 'C':			/* possible CC */
  1.4002 +	if (!strcmp (criterion+1,"C")) f = mail_criteria_string (&pgm->cc,&r);
  1.4003 +	break;
  1.4004 +      case 'D':			/* possible DELETED */
  1.4005 +	if (!strcmp (criterion+1,"ELETED")) f = pgm->deleted = T;
  1.4006 +	break;
  1.4007 +      case 'F':			/* possible FLAGGED, FROM */
  1.4008 +	if (!strcmp (criterion+1,"LAGGED")) f = pgm->flagged = T;
  1.4009 +	else if (!strcmp (criterion+1,"ROM"))
  1.4010 +	  f = mail_criteria_string (&pgm->from,&r);
  1.4011 +	break;
  1.4012 +      case 'K':			/* possible KEYWORD */
  1.4013 +	if (!strcmp (criterion+1,"EYWORD"))
  1.4014 +	  f = mail_criteria_string (&pgm->keyword,&r);
  1.4015 +	break;
  1.4016 +
  1.4017 +      case 'N':			/* possible NEW */
  1.4018 +	if (!strcmp (criterion+1,"EW")) f = pgm->recent = pgm->unseen = T;
  1.4019 +	break;
  1.4020 +      case 'O':			/* possible OLD, ON */
  1.4021 +	if (!strcmp (criterion+1,"LD")) f = pgm->old = T;
  1.4022 +	else if (!strcmp (criterion+1,"N"))
  1.4023 +	  f = mail_criteria_date (&pgm->on,&r);
  1.4024 +	break;
  1.4025 +      case 'R':			/* possible RECENT */
  1.4026 +	if (!strcmp (criterion+1,"ECENT")) f = pgm->recent = T;
  1.4027 +	break;
  1.4028 +      case 'S':			/* possible SEEN, SINCE, SUBJECT */
  1.4029 +	if (!strcmp (criterion+1,"EEN")) f = pgm->seen = T;
  1.4030 +	else if (!strcmp (criterion+1,"INCE"))
  1.4031 +	  f = mail_criteria_date (&pgm->since,&r);
  1.4032 +	else if (!strcmp (criterion+1,"UBJECT"))
  1.4033 +	  f = mail_criteria_string (&pgm->subject,&r);
  1.4034 +	break;
  1.4035 +      case 'T':			/* possible TEXT, TO */
  1.4036 +	if (!strcmp (criterion+1,"EXT"))
  1.4037 +	  f = mail_criteria_string (&pgm->text,&r);
  1.4038 +	else if (!strcmp (criterion+1,"O"))
  1.4039 +	  f = mail_criteria_string (&pgm->to,&r);
  1.4040 +	break;
  1.4041 +      case 'U':			/* possible UN* */
  1.4042 +	if (criterion[1] == 'N') {
  1.4043 +	  if (!strcmp (criterion+2,"ANSWERED")) f = pgm->unanswered = T;
  1.4044 +	  else if (!strcmp (criterion+2,"DELETED")) f = pgm->undeleted = T;
  1.4045 +	  else if (!strcmp (criterion+2,"FLAGGED")) f = pgm->unflagged = T;
  1.4046 +	  else if (!strcmp (criterion+2,"KEYWORD"))
  1.4047 +	    f = mail_criteria_string (&pgm->unkeyword,&r);
  1.4048 +	  else if (!strcmp (criterion+2,"SEEN")) f = pgm->unseen = T;
  1.4049 +	}
  1.4050 +	break;
  1.4051 +      default:			/* we will barf below */
  1.4052 +	break;
  1.4053 +      }
  1.4054 +      if (!f) {			/* if can't identify criterion */
  1.4055 +	sprintf (tmp,"Unknown search criterion: %.30s",criterion);
  1.4056 +	MM_LOG (tmp,ERROR);
  1.4057 +	mail_free_searchpgm (&pgm);
  1.4058 +	break;
  1.4059 +      }
  1.4060 +    }
  1.4061 +				/* no longer need copy of criteria */
  1.4062 +    fs_give ((void **) &criteria);
  1.4063 +  }
  1.4064 +  return pgm;
  1.4065 +}
  1.4066 +
  1.4067 +/* Parse a date
  1.4068 + * Accepts: pointer to date integer to return
  1.4069 + *	    pointer to strtok state
  1.4070 + * Returns: T if successful, else NIL
  1.4071 + */
  1.4072 +
  1.4073 +int mail_criteria_date (unsigned short *date,char **r)
  1.4074 +{
  1.4075 +  STRINGLIST *s = NIL;
  1.4076 +  MESSAGECACHE elt;
  1.4077 +				/* parse the date and return fn if OK */
  1.4078 +  int ret = (mail_criteria_string (&s,r) &&
  1.4079 +	     mail_parse_date (&elt,(char *) s->text.data) &&
  1.4080 +	     (*date = mail_shortdate (elt.year,elt.month,elt.day))) ?
  1.4081 +	       T : NIL;
  1.4082 +  if (s) mail_free_stringlist (&s);
  1.4083 +  return ret;
  1.4084 +}
  1.4085 +
  1.4086 +/* Calculate shortdate from elt values
  1.4087 + * Accepts: year (0 = BASEYEAR)
  1.4088 + *	    month (1 = January)
  1.4089 + *	    day
  1.4090 + * Returns: shortdate
  1.4091 + */
  1.4092 +
  1.4093 +unsigned short mail_shortdate (unsigned int year,unsigned int month,
  1.4094 +			       unsigned int day)
  1.4095 +{
  1.4096 +  return (year << 9) + (month << 5) + day;
  1.4097 +}
  1.4098 +
  1.4099 +/* Parse a string
  1.4100 + * Accepts: pointer to stringlist
  1.4101 + *	    pointer to strtok state
  1.4102 + * Returns: T if successful, else NIL
  1.4103 + */
  1.4104 +
  1.4105 +int mail_criteria_string (STRINGLIST **s,char **r)
  1.4106 +{
  1.4107 +  unsigned long n;
  1.4108 +  char e,*d,*end = " ",*c = strtok_r (NIL,"",r);
  1.4109 +  if (!c) return NIL;		/* missing argument */
  1.4110 +  switch (*c) {			/* see what the argument is */
  1.4111 +  case '{':			/* literal string */
  1.4112 +    n = strtoul (c+1,&d,10);	/* get its length */
  1.4113 +    if ((*d++ == '}') && (*d++ == '\015') && (*d++ == '\012') &&
  1.4114 +	(!(*(c = d + n)) || (*c == ' '))) {
  1.4115 +      e = *--c;			/* store old delimiter */
  1.4116 +      *c = '\377';		/* make sure not a space */
  1.4117 +      strtok_r (c," ",r);	/* reset the strtok mechanism */
  1.4118 +      *c = e;			/* put character back */
  1.4119 +      break;
  1.4120 +    }
  1.4121 +  case '\0':			/* catch bogons */
  1.4122 +  case ' ':
  1.4123 +    return NIL;
  1.4124 +  case '"':			/* quoted string */
  1.4125 +    if (strchr (c+1,'"')) end = "\"";
  1.4126 +    else return NIL;		/* falls through */
  1.4127 +  default:			/* atomic string */
  1.4128 +    if (d = strtok_r (c,end,r)) n = strlen (d);
  1.4129 +    else return NIL;
  1.4130 +    break;
  1.4131 +  }
  1.4132 +  while (*s) s = &(*s)->next;	/* find tail of list */
  1.4133 +  *s = mail_newstringlist ();	/* make new entry */
  1.4134 +				/* return the data */
  1.4135 +  (*s)->text.data = (unsigned char *) cpystr (d);
  1.4136 +  (*s)->text.size = n;
  1.4137 +  return T;
  1.4138 +}
  1.4139 +
  1.4140 +/* Mail parse set from string
  1.4141 + * Accepts: string to parse
  1.4142 + *	    pointer to updated string pointer for return
  1.4143 + * Returns: set with pointer updated, or NIL if error
  1.4144 + */
  1.4145 +
  1.4146 +SEARCHSET *mail_parse_set (char *s,char **ret)
  1.4147 +{
  1.4148 +  SEARCHSET *cur;
  1.4149 +  SEARCHSET *set = NIL;
  1.4150 +  while (isdigit (*s)) {
  1.4151 +    if (!set) cur = set = mail_newsearchset ();
  1.4152 +    else cur = cur->next = mail_newsearchset ();
  1.4153 +				/* parse value */
  1.4154 +    if (!(cur->first = strtoul (s,&s,10)) ||
  1.4155 +	((*s == ':') && !(isdigit (*++s) && (cur->last = strtoul (s,&s,10)))))
  1.4156 +      break;			/* bad value or range */
  1.4157 +    if (*s == ',') ++s;		/* point to next value if more */
  1.4158 +    else {			/* end of set */
  1.4159 +      *ret = s;			/* set return pointer */
  1.4160 +      return set;		/* return set */
  1.4161 +    }
  1.4162 +  }
  1.4163 +  mail_free_searchset (&set);	/* failure, punt partial set */
  1.4164 +  return NIL;
  1.4165 +}
  1.4166 +
  1.4167 +
  1.4168 +/* Mail append to set
  1.4169 + * Accepts: head of search set or NIL to do nothing
  1.4170 + *	    message to add
  1.4171 + * Returns: tail of search set or NIL if did nothing
  1.4172 + */
  1.4173 +
  1.4174 +SEARCHSET *mail_append_set (SEARCHSET *set,unsigned long msgno)
  1.4175 +{
  1.4176 +  if (set) {			/* find tail */
  1.4177 +    while (set->next) set = set->next;
  1.4178 +				/* start of set if no first member */
  1.4179 +    if (!set->first) set->first = msgno;
  1.4180 +    else if (msgno == (set->last ? set->last : set->first) + 1)
  1.4181 +      set->last = msgno;	/* extend range if 1 past current */
  1.4182 +    else (set = set->next = mail_newsearchset ())->first = msgno;
  1.4183 +  }
  1.4184 +  return set;
  1.4185 +}
  1.4186 +
  1.4187 +/* Mail sort messages
  1.4188 + * Accepts: mail stream
  1.4189 + *	    character set
  1.4190 + *	    search program
  1.4191 + *	    sort program
  1.4192 + *	    option flags
  1.4193 + * Returns: vector of sorted message sequences or NIL if error
  1.4194 + */
  1.4195 +
  1.4196 +unsigned long *mail_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
  1.4197 +			  SORTPGM *pgm,long flags)
  1.4198 +{
  1.4199 +  unsigned long *ret = NIL;
  1.4200 +  if (stream->dtb)		/* do the driver's action */
  1.4201 +    ret = (*(stream->dtb->sort ? stream->dtb->sort : mail_sort_msgs))
  1.4202 +      (stream,charset,spg,pgm,flags);
  1.4203 +				/* flush search/sort programs if requested */
  1.4204 +  if (spg && (flags & SE_FREE)) mail_free_searchpgm (&spg);
  1.4205 +  if (flags & SO_FREE) mail_free_sortpgm (&pgm);
  1.4206 +  return ret;
  1.4207 +}
  1.4208 +
  1.4209 +/* Mail sort messages work routine
  1.4210 + * Accepts: mail stream
  1.4211 + *	    character set
  1.4212 + *	    search program
  1.4213 + *	    sort program
  1.4214 + *	    option flags
  1.4215 + * Returns: vector of sorted message sequences or NIL if error
  1.4216 + */
  1.4217 +
  1.4218 +unsigned long *mail_sort_msgs (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
  1.4219 +			       SORTPGM *pgm,long flags)
  1.4220 +{
  1.4221 +  unsigned long i;
  1.4222 +  SORTCACHE **sc;
  1.4223 +  unsigned long *ret = NIL;
  1.4224 +  if (spg) {			/* only if a search needs to be done */
  1.4225 +    int silent = stream->silent;
  1.4226 +    stream->silent = T;		/* don't pass up mm_searched() events */
  1.4227 +				/* search for messages */
  1.4228 +    mail_search_full (stream,charset,spg,NIL);
  1.4229 +    stream->silent = silent;	/* restore silence state */
  1.4230 +  }
  1.4231 +				/* initialize progress counters */
  1.4232 +  pgm->nmsgs = pgm->progress.cached = 0;
  1.4233 +				/* pass 1: count messages to sort */
  1.4234 +  for (i = 1; i <= stream->nmsgs; ++i)
  1.4235 +    if (mail_elt (stream,i)->searched) pgm->nmsgs++;
  1.4236 +  if (pgm->nmsgs) {		/* pass 2: sort cache */
  1.4237 +    sc = mail_sort_loadcache (stream,pgm);
  1.4238 +				/* pass 3: sort messages */
  1.4239 +    if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags);
  1.4240 +    fs_give ((void **) &sc);	/* don't need sort vector any more */
  1.4241 +  }
  1.4242 +				/* empty sort results */
  1.4243 +  else ret = (unsigned long *) memset (fs_get (sizeof (unsigned long)),0,
  1.4244 +				       sizeof (unsigned long));
  1.4245 +				/* also return via callback if requested */
  1.4246 +  if (mailsortresults) (*mailsortresults) (stream,ret,pgm->nmsgs);
  1.4247 +  return ret;			/* return sort results */
  1.4248 +}
  1.4249 +
  1.4250 +/* Mail sort sortcache vector
  1.4251 + * Accepts: mail stream
  1.4252 + *	    sort program
  1.4253 + *	    sortcache vector
  1.4254 + *	    option flags
  1.4255 + * Returns: vector of sorted message sequences or NIL if error
  1.4256 + */
  1.4257 +
  1.4258 +unsigned long *mail_sort_cache (MAILSTREAM *stream,SORTPGM *pgm,SORTCACHE **sc,
  1.4259 +				long flags)
  1.4260 +{
  1.4261 +  unsigned long i,*ret;
  1.4262 +				/* pass 3: sort messages */
  1.4263 +  qsort ((void *) sc,pgm->nmsgs,sizeof (SORTCACHE *),mail_sort_compare);
  1.4264 +				/* optional post sorting */
  1.4265 +  if (pgm->postsort) (*pgm->postsort) ((void *) sc);
  1.4266 +				/* pass 4: return results */
  1.4267 +  ret = (unsigned long *) fs_get ((pgm->nmsgs+1) * sizeof (unsigned long));
  1.4268 +  if (flags & SE_UID)		/* UID or msgno? */
  1.4269 +    for (i = 0; i < pgm->nmsgs; i++) ret[i] = mail_uid (stream,sc[i]->num);
  1.4270 +  else for (i = 0; i < pgm->nmsgs; i++) ret[i] = sc[i]->num;
  1.4271 +  ret[pgm->nmsgs] = 0;		/* tie off message list */
  1.4272 +  return ret;
  1.4273 +}
  1.4274 +
  1.4275 +/* Mail load sortcache
  1.4276 + * Accepts: mail stream, already searched
  1.4277 + *	    sort program
  1.4278 + * Returns: vector of sortcache pointers matching search
  1.4279 + */
  1.4280 +
  1.4281 +static STRINGLIST maildateline = {{(unsigned char *) "date",4},NIL};
  1.4282 +static STRINGLIST mailrnfromline = {{(unsigned char *) ">from",5},NIL};
  1.4283 +static STRINGLIST mailfromline = {{(unsigned char *) "from",4},
  1.4284 +				    &mailrnfromline};
  1.4285 +static STRINGLIST mailtonline = {{(unsigned char *) "to",2},NIL};
  1.4286 +static STRINGLIST mailccline = {{(unsigned char *) "cc",2},NIL};
  1.4287 +static STRINGLIST mailsubline = {{(unsigned char *) "subject",7},NIL};
  1.4288 +
  1.4289 +SORTCACHE **mail_sort_loadcache (MAILSTREAM *stream,SORTPGM *pgm)
  1.4290 +{
  1.4291 +  char *t,*v,*x,tmp[MAILTMPLEN];
  1.4292 +  SORTPGM *pg;
  1.4293 +  SORTCACHE *s,**sc;
  1.4294 +  MESSAGECACHE *elt,telt;
  1.4295 +  ENVELOPE *env;
  1.4296 +  ADDRESS *adr = NIL;
  1.4297 +  unsigned long i = (pgm->nmsgs) * sizeof (SORTCACHE *);
  1.4298 +  sc = (SORTCACHE **) memset (fs_get ((size_t) i),0,(size_t) i);
  1.4299 +				/* see what needs to be loaded */
  1.4300 +  for (i = 1; !pgm->abort && (i <= stream->nmsgs); i++)
  1.4301 +    if ((elt = mail_elt (stream,i))->searched) {
  1.4302 +      sc[pgm->progress.cached++] =
  1.4303 +	s = (SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE);
  1.4304 +      s->pgm = pgm;		/* note sort program */
  1.4305 +      s->num = i;
  1.4306 +				/* get envelope if cached */
  1.4307 +      if (stream->scache) env = (i == stream->msgno) ? stream->env : NIL;
  1.4308 +      else env = elt->private.msg.env;
  1.4309 +      for (pg = pgm; pg; pg = pg->next) switch (pg->function) {
  1.4310 +      case SORTARRIVAL:		/* sort by arrival date */
  1.4311 +	if (!s->arrival) {
  1.4312 +				/* internal date unknown but can get? */
  1.4313 +	  if (!elt->day && !(stream->dtb->flags & DR_NOINTDATE)) {
  1.4314 +	    sprintf (tmp,"%lu",i);
  1.4315 +	    mail_fetch_fast (stream,tmp,NIL);
  1.4316 +	  }
  1.4317 +				/* wrong thing before 3-Jan-1970 */
  1.4318 +	  s->arrival = elt->day ? mail_longdate (elt) : 1;
  1.4319 +	  s->dirty = T;
  1.4320 +	}
  1.4321 +	break;
  1.4322 +      case SORTSIZE:		/* sort by message size */
  1.4323 +	if (!s->size) {
  1.4324 +	  if (!elt->rfc822_size) {
  1.4325 +	    sprintf (tmp,"%lu",i);
  1.4326 +	    mail_fetch_fast (stream,tmp,NIL);
  1.4327 +	  }
  1.4328 +	  s->size = elt->rfc822_size ? elt->rfc822_size : 1;
  1.4329 +	  s->dirty = T;
  1.4330 +	}
  1.4331 +	break;
  1.4332 +
  1.4333 +      case SORTDATE:		/* sort by date */
  1.4334 +	if (!s->date) {
  1.4335 +	  if (env) t = env->date;
  1.4336 +	  else if ((t = mail_fetch_header (stream,i,NIL,&maildateline,NIL,
  1.4337 +					   FT_INTERNAL | FT_PEEK)) &&
  1.4338 +		   (t = strchr (t,':')))
  1.4339 +	    for (x = ++t; x = strpbrk (x,"\012\015"); x++)
  1.4340 +	      switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
  1.4341 +	      case ' ':		/* erase continuation newlines */
  1.4342 +	      case '\t':
  1.4343 +		memmove (x,v,strlen (v));
  1.4344 +		break;
  1.4345 +	      default:		/* tie off extraneous text */
  1.4346 +		*x = x[1] = '\0';
  1.4347 +	      }
  1.4348 +				/* skip leading whitespace */
  1.4349 +	  if (t) while ((*t == ' ') || (*t == '\t')) t++;
  1.4350 +				/* parse date from Date: header */
  1.4351 +	  if (!(t && mail_parse_date (&telt,t) && 
  1.4352 +		(s->date = mail_longdate (&telt)))) {
  1.4353 +				/* failed, use internal date */
  1.4354 +	    if (!(s->date = s->arrival)) {
  1.4355 +				/* internal date unknown but can get? */
  1.4356 +	      if (!elt->day && !(stream->dtb->flags & DR_NOINTDATE)) {
  1.4357 +		sprintf (tmp,"%lu",i);
  1.4358 +		mail_fetch_fast (stream,tmp,NIL);
  1.4359 +	      }
  1.4360 +				/* wrong thing before 3-Jan-1970 */
  1.4361 +	      s->date = (s->arrival = elt->day ? mail_longdate (elt) : 1);
  1.4362 +	    }
  1.4363 +	  }
  1.4364 +	  s->dirty = T;
  1.4365 +	}
  1.4366 +	break;
  1.4367 +
  1.4368 +      case SORTFROM:		/* sort by first from */
  1.4369 +	if (!s->from) {
  1.4370 +	  if (env) s->from = env->from && env->from->mailbox ?
  1.4371 +	    cpystr (env->from->mailbox) : NIL;
  1.4372 +	  else if ((t = mail_fetch_header (stream,i,NIL,&mailfromline,NIL,
  1.4373 +					   FT_INTERNAL | FT_PEEK)) &&
  1.4374 +		   (t = strchr (t,':'))) {
  1.4375 +	    for (x = ++t; x = strpbrk (x,"\012\015"); x++)
  1.4376 +	      switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
  1.4377 +	      case ' ':		/* erase continuation newlines */
  1.4378 +	      case '\t':
  1.4379 +		memmove (x,v,strlen (v));
  1.4380 +		break;
  1.4381 +	      case 'f':		/* continuation but with extra "From:" */
  1.4382 +	      case 'F':
  1.4383 +		if (v = strchr (v,':')) {
  1.4384 +		  memmove (x,v+1,strlen (v+1));
  1.4385 +		  break;
  1.4386 +		}
  1.4387 +	      default:		/* tie off extraneous text */
  1.4388 +		*x = x[1] = '\0';
  1.4389 +	      }
  1.4390 +	    if (adr = rfc822_parse_address (&adr,adr,&t,BADHOST,0)) {
  1.4391 +	      s->from = adr->mailbox;
  1.4392 +	      adr->mailbox = NIL;
  1.4393 +	      mail_free_address (&adr);
  1.4394 +	    }
  1.4395 +	  }
  1.4396 +	  if (!s->from) s->from = cpystr ("");
  1.4397 +	  s->dirty = T;
  1.4398 +	}
  1.4399 +	break;
  1.4400 +
  1.4401 +      case SORTTO:		/* sort by first to */
  1.4402 +	if (!s->to) {
  1.4403 +	  if (env) s->to = env->to && env->to->mailbox ?
  1.4404 +	    cpystr (env->to->mailbox) : NIL;
  1.4405 +	  else if ((t = mail_fetch_header (stream,i,NIL,&mailtonline,NIL,
  1.4406 +					   FT_INTERNAL | FT_PEEK)) &&
  1.4407 +		   (t = strchr (t,':'))) {
  1.4408 +	    for (x = ++t; x = strpbrk (x,"\012\015"); x++)
  1.4409 +	      switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
  1.4410 +	      case ' ':		/* erase continuation newlines */
  1.4411 +	      case '\t':
  1.4412 +		memmove (x,v,strlen (v));
  1.4413 +		break;
  1.4414 +	      case 't':		/* continuation but with extra "To:" */
  1.4415 +	      case 'T':
  1.4416 +		if (v = strchr (v,':')) {
  1.4417 +		  memmove (x,v+1,strlen (v+1));
  1.4418 +		  break;
  1.4419 +		}
  1.4420 +	      default:		/* tie off extraneous text */
  1.4421 +		*x = x[1] = '\0';
  1.4422 +	      }
  1.4423 +	    if (adr = rfc822_parse_address (&adr,adr,&t,BADHOST,0)) {
  1.4424 +	      s->to = adr->mailbox;
  1.4425 +	      adr->mailbox = NIL;
  1.4426 +	      mail_free_address (&adr);
  1.4427 +	    }
  1.4428 +	  }
  1.4429 +	  if (!s->to) s->to = cpystr ("");
  1.4430 +	  s->dirty = T;
  1.4431 +	}
  1.4432 +	break;
  1.4433 +
  1.4434 +      case SORTCC:		/* sort by first cc */
  1.4435 +	if (!s->cc) {
  1.4436 +	  if (env) s->cc = env->cc && env->cc->mailbox ?
  1.4437 +	    cpystr (env->cc->mailbox) : NIL;
  1.4438 +	  else if ((t = mail_fetch_header (stream,i,NIL,&mailccline,NIL,
  1.4439 +					   FT_INTERNAL | FT_PEEK)) &&
  1.4440 +		   (t = strchr (t,':'))) {
  1.4441 +	    for (x = ++t; x = strpbrk (x,"\012\015"); x++)
  1.4442 +	      switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
  1.4443 +	      case ' ':		/* erase continuation newlines */
  1.4444 +	      case '\t':
  1.4445 +		memmove (x,v,strlen (v));
  1.4446 +		break;
  1.4447 +	      case 't':		/* continuation but with extra "To:" */
  1.4448 +	      case 'T':
  1.4449 +		if (v = strchr (v,':')) {
  1.4450 +		  memmove (x,v+1,strlen (v+1));
  1.4451 +		  break;
  1.4452 +		}
  1.4453 +	      default:		/* tie off extraneous text */
  1.4454 +		*x = x[1] = '\0';
  1.4455 +	      }
  1.4456 +	    if (adr = rfc822_parse_address (&adr,adr,&t,BADHOST,0)) {
  1.4457 +	      s->cc = adr->mailbox;
  1.4458 +	      adr->mailbox = NIL;
  1.4459 +	      mail_free_address (&adr);
  1.4460 +	    }
  1.4461 +	  }
  1.4462 +	  if (!s->cc) s->cc = cpystr ("");
  1.4463 +	  s->dirty = T;
  1.4464 +	}
  1.4465 +	break;
  1.4466 +
  1.4467 +      case SORTSUBJECT:		/* sort by subject */
  1.4468 +	if (!s->subject) {
  1.4469 +				/* get subject from envelope if have one */
  1.4470 +	  if (env) t = env->subject ? env->subject : "";
  1.4471 +				/* otherwise snarf from header text */
  1.4472 +	  else if ((t = mail_fetch_header (stream,i,NIL,&mailsubline,
  1.4473 +					   NIL,FT_INTERNAL | FT_PEEK)) &&
  1.4474 +		   (t = strchr (t,':')))
  1.4475 +	    for (x = ++t; x = strpbrk (x,"\012\015"); x++)
  1.4476 +	      switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
  1.4477 +	      case ' ':		/* erase continuation newlines */
  1.4478 +	      case '\t':
  1.4479 +		memmove (x,v,strlen (v));
  1.4480 +		break;
  1.4481 +	      default:		/* tie off extraneous text */
  1.4482 +		*x = x[1] = '\0';
  1.4483 +	      }
  1.4484 +	  else t = "";		/* empty subject */
  1.4485 +				/* strip and cache subject */
  1.4486 +	  s->refwd = mail_strip_subject (t,&s->subject);
  1.4487 +	  s->dirty = T;
  1.4488 +	}
  1.4489 +	break;
  1.4490 +      default:
  1.4491 +	fatal ("Unknown sort function");
  1.4492 +      }
  1.4493 +    }
  1.4494 +  return sc;
  1.4495 +}
  1.4496 +
  1.4497 +/* Strip subjects of extra spaces and leading and trailing cruft for sorting
  1.4498 + * Accepts: unstripped subject
  1.4499 + *	    pointer to return stripped subject, in cpystr form
  1.4500 + * Returns: T if subject had a re/fwd, NIL otherwise
  1.4501 + */
  1.4502 +
  1.4503 +unsigned int mail_strip_subject (char *t,char **ret)
  1.4504 +{
  1.4505 +  SIZEDTEXT src,dst;
  1.4506 +  unsigned long i,slen;
  1.4507 +  char c,*s,*x;
  1.4508 +  unsigned int refwd = NIL;
  1.4509 +  if (src.size = strlen (t)) {	/* have non-empty subject? */
  1.4510 +    src.data = (unsigned char *) t;
  1.4511 +			/* Step 1 */
  1.4512 +				/* make copy, convert MIME2 if needed */
  1.4513 +    *ret = s = (utf8_mime2text (&src,&dst,U8T_CANONICAL) &&
  1.4514 +		(src.data != dst.data)) ? (char *) dst.data : cpystr (t);
  1.4515 +				/* convert spaces to tab, strip extra spaces */
  1.4516 +    for (x = t = s, c = 'x'; *t; t++) {
  1.4517 +      if (c != ' ') c = *x++ = ((*t == '\t') ? ' ' : *t);
  1.4518 +      else if ((*t != '\t') && (*t != ' ')) c = *x++ = *t;
  1.4519 +    }
  1.4520 +    *x = '\0';			/* tie off string */
  1.4521 +			/* Step 2 */
  1.4522 +    for (slen = dst.size; s; slen = strlen (s))  {
  1.4523 +      for (t = s + slen; t > s; ) switch (t[-1]) {
  1.4524 +      case ' ': case '\t':	/* WSP */
  1.4525 +	*--t = '\0';		/* just remove it */
  1.4526 +	break;
  1.4527 +      case ')':			/* possible "(fwd)" */
  1.4528 +	if ((t >= (s + 5)) && (t[-5] == '(') &&
  1.4529 +	    ((t[-4] == 'F') || (t[-4] == 'f')) &&
  1.4530 +	    ((t[-3] == 'W') || (t[-3] == 'w')) &&
  1.4531 +	    ((t[-2] == 'D') || (t[-2] == 'd'))) {
  1.4532 +	  *(t -= 5) = '\0';	/* remove "(fwd)" */
  1.4533 +	  refwd = T;		/* note a re/fwd */
  1.4534 +	  break;
  1.4535 +	}
  1.4536 +      default:			/* not a subj-trailer */
  1.4537 +	t = s;
  1.4538 +	break;
  1.4539 +      }
  1.4540 +			/* Steps 3-5 */
  1.4541 +      for (t = s; t; ) switch (*s) {
  1.4542 +      case ' ': case '\t':	/* WSP */
  1.4543 +	s = t = mail_strip_subject_wsp (s + 1);
  1.4544 +	break;
  1.4545 +      case 'r': case 'R':	/* possible "re" */
  1.4546 +	if (((s[1] == 'E') || (s[1] == 'e')) &&
  1.4547 +	    (t = mail_strip_subject_wsp (s + 2)) &&
  1.4548 +	    (t = mail_strip_subject_blob (t)) && (*t == ':')) {
  1.4549 +	  s = ++t;		/* found "re" */
  1.4550 +	  refwd = T;		/* definitely a re/fwd at this point */
  1.4551 +	}
  1.4552 +	else t = NIL;		/* found subj-middle */
  1.4553 +	break;
  1.4554 +      case 'f': case 'F':	/* possible "fw" or "fwd" */
  1.4555 +	if (((s[1] == 'w') || (s[1] == 'W')) &&
  1.4556 +	    (((s[2] == 'd') || (s[2] == 'D')) ?
  1.4557 +	     (t = mail_strip_subject_wsp (s + 3)) :
  1.4558 +	     (t = mail_strip_subject_wsp (s + 2))) &&
  1.4559 +	    (t = mail_strip_subject_blob (t)) && (*t == ':')) {
  1.4560 +	  s = ++t;		/* found "fwd" */
  1.4561 +	  refwd = T;		/* definitely a re/fwd at this point */
  1.4562 +	}
  1.4563 +	else t = NIL;		/* found subj-middle */
  1.4564 +	break;
  1.4565 +      case '[':			/* possible subj-blob */
  1.4566 +	if ((t = mail_strip_subject_blob (s)) && *t) s = t;
  1.4567 +	else t = NIL;		/* found subj-middle */
  1.4568 +	break;
  1.4569 +      default:
  1.4570 +	t = NIL;		/* found subj-middle */
  1.4571 +	break;
  1.4572 +      }
  1.4573 +			/* Step 6 */
  1.4574 +				/* Netscape-style "[Fwd: ...]"? */
  1.4575 +      if ((*s == '[') && ((s[1] == 'F') || (s[1] == 'f')) &&
  1.4576 +	  ((s[2] == 'W') || (s[2] == 'w')) &&
  1.4577 +	  ((s[3] == 'D') || (s[3] == 'd')) && (s[4] == ':') &&
  1.4578 +	  (s[i = strlen (s) - 1] == ']')) {
  1.4579 +	s[i] = '\0';		/* flush closing "]" */
  1.4580 +	s += 5;			/* and leading "[Fwd:" */
  1.4581 +	refwd = T;		/* definitely a re/fwd at this point */
  1.4582 +      }
  1.4583 +      else break;		/* don't need to loop back to step 2 */
  1.4584 +    }
  1.4585 +    if (s != (t = *ret)) {	/* removed leading text? */
  1.4586 +      s = *ret = cpystr (s);	/* yes, make a fresh return copy */
  1.4587 +      fs_give ((void **) &t);	/* flush old copy */
  1.4588 +    }
  1.4589 +  }
  1.4590 +  else *ret = cpystr ("");	/* empty subject */
  1.4591 +  return refwd;			/* return re/fwd state */
  1.4592 +}
  1.4593 +
  1.4594 +/* Strip subject wsp helper routine
  1.4595 + * Accepts: text
  1.4596 + * Returns: pointer to text after blob
  1.4597 + */
  1.4598 +
  1.4599 +char *mail_strip_subject_wsp (char *s)
  1.4600 +{
  1.4601 +  while ((*s == ' ') || (*s == '\t')) s++;
  1.4602 +  return s;
  1.4603 +}
  1.4604 +
  1.4605 +
  1.4606 +/* Strip subject blob helper routine
  1.4607 + * Accepts: text
  1.4608 + * Returns: pointer to text after any blob, NIL if blob-like but not blob
  1.4609 + */
  1.4610 +
  1.4611 +char *mail_strip_subject_blob (char *s)
  1.4612 +{
  1.4613 +  if (*s != '[') return s;	/* not a blob, ignore */
  1.4614 +				/* search for end of blob */
  1.4615 +  while (*++s != ']') if ((*s == '[') || !*s) return NIL;
  1.4616 +  return mail_strip_subject_wsp (s + 1);
  1.4617 +}
  1.4618 +
  1.4619 +/* Sort compare messages
  1.4620 + * Accept: first message sort cache element
  1.4621 + *	   second message sort cache element
  1.4622 + * Returns: -1 if a1 < a2, 0 if a1 == a2, 1 if a1 > a2
  1.4623 + */
  1.4624 +
  1.4625 +int mail_sort_compare (const void *a1,const void *a2)
  1.4626 +{
  1.4627 +  int i = 0;
  1.4628 +  SORTCACHE *s1 = *(SORTCACHE **) a1;
  1.4629 +  SORTCACHE *s2 = *(SORTCACHE **) a2;
  1.4630 +  SORTPGM *pgm = s1->pgm;
  1.4631 +  if (!s1->sorted) {		/* this one sorted yet? */
  1.4632 +    s1->sorted = T;
  1.4633 +    pgm->progress.sorted++;	/* another sorted message */
  1.4634 +  }
  1.4635 +  if (!s2->sorted) {		/* this one sorted yet? */
  1.4636 +    s2->sorted = T;
  1.4637 +    pgm->progress.sorted++;	/* another sorted message */
  1.4638 +  }
  1.4639 +  do {
  1.4640 +    switch (pgm->function) {	/* execute search program */
  1.4641 +    case SORTDATE:		/* sort by date */
  1.4642 +      i = compare_ulong (s1->date,s2->date);
  1.4643 +      break;
  1.4644 +    case SORTARRIVAL:		/* sort by arrival date */
  1.4645 +      i = compare_ulong (s1->arrival,s2->arrival);
  1.4646 +      break;
  1.4647 +    case SORTSIZE:		/* sort by message size */
  1.4648 +      i = compare_ulong (s1->size,s2->size);
  1.4649 +      break;
  1.4650 +    case SORTFROM:		/* sort by first from */
  1.4651 +      i = compare_cstring (s1->from,s2->from);
  1.4652 +      break;
  1.4653 +    case SORTTO:		/* sort by first to */
  1.4654 +      i = compare_cstring (s1->to,s2->to);
  1.4655 +      break;
  1.4656 +    case SORTCC:		/* sort by first cc */
  1.4657 +      i = compare_cstring (s1->cc,s2->cc);
  1.4658 +      break;
  1.4659 +    case SORTSUBJECT:		/* sort by subject */
  1.4660 +      i = compare_cstring (s1->subject,s2->subject);
  1.4661 +      break;
  1.4662 +    }
  1.4663 +    if (pgm->reverse) i = -i;	/* flip results if necessary */
  1.4664 +  }
  1.4665 +  while (pgm = i ? NIL : pgm->next);
  1.4666 +				/* return result, avoid 0 if at all possible */
  1.4667 +  return i ? i : compare_ulong (s1->num,s2->num);
  1.4668 +}
  1.4669 +
  1.4670 +/* Return message date as an unsigned long seconds since time began
  1.4671 + * Accepts: message cache pointer
  1.4672 + * Returns: unsigned long of date
  1.4673 + *
  1.4674 + * This routine, like most UNIX systems, is clueless about leap seconds.
  1.4675 + * Thus, it treats 23:59:60 as equivalent to 00:00:00 the next day.
  1.4676 + *
  1.4677 + * This routine forces any early hours on 1-Jan-1970 in oriental timezones
  1.4678 + * to be 1-Jan-1970 00:00:00 UTC, so as to avoid negative longdates.
  1.4679 + */
  1.4680 +
  1.4681 +unsigned long mail_longdate (MESSAGECACHE *elt)
  1.4682 +{
  1.4683 +  unsigned long m = elt->month ? elt->month : 1;
  1.4684 +  unsigned long yr = elt->year + BASEYEAR;
  1.4685 +				/* number of days since time began */
  1.4686 +  unsigned long ret = (elt->day ? (elt->day - 1) : 0)
  1.4687 +    + 30 * (m - 1) + ((m + (m > 8)) / 2)
  1.4688 +#ifndef USEJULIANCALENDAR
  1.4689 +#ifndef USEORTHODOXCALENDAR	/* Gregorian calendar */
  1.4690 +    + ((yr / 400) - (BASEYEAR / 400)) - ((yr / 100) - (BASEYEAR / 100))
  1.4691 +#ifdef Y4KBUGFIX
  1.4692 +    - ((yr / 4000) - (BASEYEAR / 4000))
  1.4693 +#endif
  1.4694 +    - ((m < 3) ?
  1.4695 +       !(yr % 4) && ((yr % 100) || (!(yr % 400)
  1.4696 +#ifdef Y4KBUGFIX
  1.4697 +				    && (yr % 4000)
  1.4698 +#endif
  1.4699 +				    )) : 2)
  1.4700 +#else				/* Orthodox calendar */
  1.4701 +    + ((2*(yr / 900)) - (2*(BASEYEAR / 900)))
  1.4702 +    + (((yr % 900) >= 200) - ((BASEYEAR % 900) >= 200))
  1.4703 +    + (((yr % 900) >= 600) - ((BASEYEAR % 900) >= 600))
  1.4704 +    - ((yr / 100) - (BASEYEAR / 100))
  1.4705 +    - ((m < 3) ?
  1.4706 +       !(yr % 4) && ((yr % 100) || ((yr % 900) == 200) || ((yr % 900) == 600))
  1.4707 +       : 2)
  1.4708 +#endif
  1.4709 +#endif
  1.4710 +    + elt->year * 365 + (((unsigned long) (elt->year + (BASEYEAR % 4))) / 4);
  1.4711 +  ret *= 24; ret += elt->hours;	/* date value in hours */
  1.4712 +  ret *= 60; ret +=elt->minutes;/* date value in minutes */
  1.4713 +  yr = (elt->zhours * 60) + elt->zminutes;
  1.4714 +  if (elt->zoccident) ret += yr;/* occidental timezone, make UTC */
  1.4715 +  else if (ret < yr) return 0;	/* still 31-Dec-1969 in UTC */
  1.4716 +  else ret -= yr;		/* oriental timezone, make UTC */
  1.4717 +  ret *= 60; ret += elt->seconds;
  1.4718 +  return ret;
  1.4719 +}
  1.4720 +
  1.4721 +/* Mail thread messages
  1.4722 + * Accepts: mail stream
  1.4723 + *	    thread type
  1.4724 + *	    character set
  1.4725 + *	    search program
  1.4726 + *	    option flags
  1.4727 + * Returns: thread node tree or NIL if error
  1.4728 + */
  1.4729 +
  1.4730 +THREADNODE *mail_thread (MAILSTREAM *stream,char *type,char *charset,
  1.4731 +			 SEARCHPGM *spg,long flags)
  1.4732 +{
  1.4733 +  THREADNODE *ret = NIL;
  1.4734 +  if (stream->dtb)		/* must have a live driver */
  1.4735 +    ret = stream->dtb->thread ?	/* do driver's action if available */
  1.4736 +      (*stream->dtb->thread) (stream,type,charset,spg,flags) :
  1.4737 +	mail_thread_msgs (stream,type,charset,spg,flags,mail_sort_msgs);
  1.4738 +				/* flush search/sort programs if requested */
  1.4739 +  if (spg && (flags & SE_FREE)) mail_free_searchpgm (&spg);
  1.4740 +  return ret;
  1.4741 +}
  1.4742 +
  1.4743 +
  1.4744 +/* Mail thread messages
  1.4745 + * Accepts: mail stream
  1.4746 + *	    thread type
  1.4747 + *	    character set
  1.4748 + *	    search program
  1.4749 + *	    option flags
  1.4750 + *	    sorter routine
  1.4751 + * Returns: thread node tree or NIL if error
  1.4752 + */
  1.4753 +
  1.4754 +THREADNODE *mail_thread_msgs (MAILSTREAM *stream,char *type,char *charset,
  1.4755 +			      SEARCHPGM *spg,long flags,sorter_t sorter)
  1.4756 +{
  1.4757 +  THREADER *t;
  1.4758 +  for (t = &mailthreadlist; t; t = t->next)
  1.4759 +    if (!compare_cstring (type,t->name)) {
  1.4760 +      THREADNODE *ret = (*t->dispatch) (stream,charset,spg,flags,sorter);
  1.4761 +      if (mailthreadresults) (*mailthreadresults) (stream,ret);
  1.4762 +      return ret;
  1.4763 +    }
  1.4764 +  MM_LOG ("No such thread type",ERROR);
  1.4765 +  return NIL;
  1.4766 +}
  1.4767 +
  1.4768 +/* Mail thread ordered subject
  1.4769 + * Accepts: mail stream
  1.4770 + *	    character set
  1.4771 + *	    search program
  1.4772 + *	    option flags
  1.4773 + *	    sorter routine
  1.4774 + * Returns: thread node tree
  1.4775 + */
  1.4776 +
  1.4777 +THREADNODE *mail_thread_orderedsubject (MAILSTREAM *stream,char *charset,
  1.4778 +					SEARCHPGM *spg,long flags,
  1.4779 +					sorter_t sorter)
  1.4780 +{
  1.4781 +  THREADNODE *thr = NIL;
  1.4782 +  THREADNODE *cur,*top,**tc;
  1.4783 +  SORTPGM pgm,pgm2;
  1.4784 +  SORTCACHE *s;
  1.4785 +  unsigned long i,j,*lst,*ls;
  1.4786 +				/* sort by subject+date */
  1.4787 +  memset (&pgm,0,sizeof (SORTPGM));
  1.4788 +  memset (&pgm2,0,sizeof (SORTPGM));
  1.4789 +  pgm.function = SORTSUBJECT;
  1.4790 +  pgm.next = &pgm2;
  1.4791 +  pgm2.function = SORTDATE;
  1.4792 +  if (lst = (*sorter) (stream,charset,spg,&pgm,flags & ~(SE_FREE | SE_UID))){
  1.4793 +    if (*(ls = lst)) {		/* create thread */
  1.4794 +				/* note first subject */
  1.4795 +      cur = top = thr = mail_newthreadnode
  1.4796 +	((SORTCACHE *) (*mailcache) (stream,*ls++,CH_SORTCACHE));
  1.4797 +				/* note its number */
  1.4798 +      cur->num = (flags & SE_UID) ? mail_uid (stream,*lst) : *lst;
  1.4799 +      i = 1;			/* number of threads */
  1.4800 +      while (*ls) {		/* build tree */
  1.4801 +				/* subjects match? */
  1.4802 +	s = (SORTCACHE *) (*mailcache) (stream,*ls++,CH_SORTCACHE);
  1.4803 +	if (compare_cstring (top->sc->subject,s->subject)) {
  1.4804 +	  i++;			/* have a new thread */
  1.4805 +	  top = top->branch = cur = mail_newthreadnode (s);
  1.4806 +	}
  1.4807 +				/* start a child of the top */
  1.4808 +	else if (cur == top) cur = cur->next = mail_newthreadnode (s);
  1.4809 +				/* sibling of child */
  1.4810 +	else cur = cur->branch = mail_newthreadnode (s);
  1.4811 +				/* set to msgno or UID as needed */
  1.4812 +	cur->num = (flags & SE_UID) ? mail_uid (stream,s->num) : s->num;
  1.4813 +      }
  1.4814 +				/* make threadnode cache */
  1.4815 +      tc = (THREADNODE **) fs_get (i * sizeof (THREADNODE *));
  1.4816 +				/* load threadnode cache */
  1.4817 +      for (j = 0, cur = thr; cur; cur = cur->branch) tc[j++] = cur;
  1.4818 +      if (i != j) fatal ("Threadnode cache confusion");
  1.4819 +      qsort ((void *) tc,i,sizeof (THREADNODE *),mail_thread_compare_date);
  1.4820 +      for (j = 0, --i; j < i; j++) tc[j]->branch = tc[j+1];
  1.4821 +      tc[j]->branch = NIL;	/* end of root */
  1.4822 +      thr = tc[0];		/* head of data */
  1.4823 +      fs_give ((void **) &tc);
  1.4824 +    }
  1.4825 +    fs_give ((void **) &lst);
  1.4826 +  }
  1.4827 +  return thr;
  1.4828 +}
  1.4829 +
  1.4830 +/* Mail thread references
  1.4831 + * Accepts: mail stream
  1.4832 + *	    character set
  1.4833 + *	    search program
  1.4834 + *	    option flags
  1.4835 + *	    sorter routine
  1.4836 + * Returns: thread node tree
  1.4837 + */
  1.4838 +
  1.4839 +#define REFHASHSIZE 1009	/* arbitrary prime for hash table size */
  1.4840 +
  1.4841 +/*  Reference threading container, as described in Jamie Zawinski's web page
  1.4842 + * (http://www.jwz.org/doc/threading.html) for this algorithm.  These are
  1.4843 + * stored as extended data in the hash table (called "id_table" in JWZ's
  1.4844 + * document) and are maintained by the hash table routines.  The hash table
  1.4845 + * routines implement extended data as additional void* words at the end of
  1.4846 + * each bucket, hence these strange macros instead of a struct which would
  1.4847 + * have been more straightforward.
  1.4848 + */
  1.4849 +
  1.4850 +#define THREADLINKS 3		/* number of thread links */
  1.4851 +
  1.4852 +#define CACHE(data) ((SORTCACHE *) (data)[0])
  1.4853 +#define PARENT(data) ((container_t) (data)[1])
  1.4854 +#define SETPARENT(data,value) ((container_t) (data[1] = value))
  1.4855 +#define SIBLING(data) ((container_t) (data)[2])
  1.4856 +#define SETSIBLING(data,value) ((container_t) (data[2] = value))
  1.4857 +#define CHILD(data) ((container_t) (data)[3])
  1.4858 +#define SETCHILD(data,value) ((container_t) (data[3] = value))
  1.4859 +
  1.4860 +THREADNODE *mail_thread_references (MAILSTREAM *stream,char *charset,
  1.4861 +				    SEARCHPGM *spg,long flags,sorter_t sorter)
  1.4862 +{
  1.4863 +  MESSAGECACHE *elt,telt;
  1.4864 +  ENVELOPE *env;
  1.4865 +  SORTCACHE *s;
  1.4866 +  STRINGLIST *st;
  1.4867 +  HASHENT *he;
  1.4868 +  THREADNODE **tc,*cur,*lst,*nxt,*sis,*msg;
  1.4869 +  container_t con,nxc,prc,sib;
  1.4870 +  void **sub;
  1.4871 +  char *t,tmp[MAILTMPLEN];
  1.4872 +  unsigned long j,nmsgs;
  1.4873 +  unsigned long i = stream->nmsgs * sizeof (SORTCACHE *);
  1.4874 +  SORTCACHE **sc = (SORTCACHE **) memset (fs_get ((size_t) i),0,(size_t) i);
  1.4875 +  HASHTAB *ht = hash_create (REFHASHSIZE);
  1.4876 +  THREADNODE *root = NIL;
  1.4877 +  if (spg) {			/* only if a search needs to be done */
  1.4878 +    int silent = stream->silent;
  1.4879 +    stream->silent = T;		/* don't pass up mm_searched() events */
  1.4880 +				/* search for messages */
  1.4881 +    mail_search_full (stream,charset,spg,NIL);
  1.4882 +    stream->silent = silent;	/* restore silence state */
  1.4883 +  }
  1.4884 +
  1.4885 +				/* create SORTCACHE vector of requested msgs */
  1.4886 +  for (i = 1, nmsgs = 0; i <= stream->nmsgs; ++i)
  1.4887 +    if (mail_elt (stream,i)->searched)
  1.4888 +      (sc[nmsgs++] = (SORTCACHE *)(*mailcache)(stream,i,CH_SORTCACHE))->num =i;
  1.4889 +	/* separate pass so can do overview fetch lookahead */
  1.4890 +  for (i = 0; i < nmsgs; ++i) {	/* for each requested message */
  1.4891 +				/* is anything missing in its SORTCACHE? */
  1.4892 +    if (!((s = sc[i])->date && s->subject && s->message_id && s->references)) {
  1.4893 +				/* driver has an overview mechanism? */
  1.4894 +      if (stream->dtb && stream->dtb->overview) {
  1.4895 +				/* yes, find following unloaded entries */
  1.4896 +	for (j = i + 1; (j < nmsgs) && !sc[j]->references; ++j);
  1.4897 +	sprintf (tmp,"%lu",mail_uid (stream,s->num));
  1.4898 +	if (i != --j)		/* end of range different? */
  1.4899 +	  sprintf (tmp + strlen (tmp),":%lu",mail_uid (stream,sc[j]->num));
  1.4900 +				/* load via overview mechanism */
  1.4901 +	mail_fetch_overview (stream,tmp,mail_thread_loadcache);
  1.4902 +      }
  1.4903 +				/* still missing data? */
  1.4904 +      if (!s->date || !s->subject || !s->message_id || !s->references) {
  1.4905 +				/* try to load data from envelope */
  1.4906 +	if (env = mail_fetch_structure (stream,s->num,NIL,NIL)) {
  1.4907 +	  if (!s->date && env->date && mail_parse_date (&telt,env->date))
  1.4908 +	    s->date = mail_longdate (&telt);
  1.4909 +	  if (!s->subject && env->subject)
  1.4910 +	    s->refwd =
  1.4911 +	      mail_strip_subject (env->subject,&s->subject);
  1.4912 +	  if (!s->message_id && env->message_id && *env->message_id)
  1.4913 +	    s->message_id = mail_thread_parse_msgid (env->message_id,NIL);
  1.4914 +	  if (!s->references &&	/* use References: or In-Reply-To: */
  1.4915 +	      !(s->references = 
  1.4916 +		mail_thread_parse_references (env->references,T)))
  1.4917 +	    s->references = mail_thread_parse_references(env->in_reply_to,NIL);
  1.4918 +	}
  1.4919 +				/* last resort */
  1.4920 +	if (!s->date && !(s->date = s->arrival)) {
  1.4921 +				/* internal date unknown but can get? */
  1.4922 +	  if (!(elt = mail_elt (stream,s->num))->day &&
  1.4923 +	      !(stream->dtb->flags & DR_NOINTDATE)) {
  1.4924 +	    sprintf (tmp,"%lu",s->num);
  1.4925 +	    mail_fetch_fast (stream,tmp,NIL);
  1.4926 +	  }
  1.4927 +				/* wrong thing before 3-Jan-1970 */
  1.4928 +	  s->date = (s->arrival = elt->day ? mail_longdate (elt) : 1);
  1.4929 +	}
  1.4930 +	if (!s->subject) s->subject = cpystr ("");
  1.4931 +	if (!s->references) s->references = mail_newstringlist ();
  1.4932 +	s->dirty = T;
  1.4933 +      }
  1.4934 +    }
  1.4935 +
  1.4936 +			/* Step 1 (preliminary) */
  1.4937 +				/* generate unique string */
  1.4938 +    sprintf (tmp,"%s.%lx.%lx@%s",stream->mailbox,stream->uid_validity,
  1.4939 +	     mail_uid (stream,s->num),mylocalhost ());
  1.4940 +				/* flush old unique string if not message-id */
  1.4941 +    if (s->unique && (s->unique != s->message_id))
  1.4942 +      fs_give ((void **) &s->unique);
  1.4943 +    s->unique = s->message_id ?	/* don't permit Message ID duplicates */
  1.4944 +      (hash_lookup (ht,s->message_id) ? cpystr (tmp) : s->message_id) :
  1.4945 +	(s->message_id = cpystr (tmp));
  1.4946 +				/* add unique string to hash table */
  1.4947 +    hash_add (ht,s->unique,s,THREADLINKS);
  1.4948 +  }
  1.4949 +			/* Step 1 */
  1.4950 +  for (i = 0; i < nmsgs; ++i) {	/* for each message in sortcache */
  1.4951 +			/* Step 1A */
  1.4952 +    if ((st = (s = sc[i])->references) && st->text.data)
  1.4953 +      for (con = hash_lookup_and_add (ht,(char *) st->text.data,NIL,
  1.4954 +				      THREADLINKS); st = st->next; con = nxc) {
  1.4955 +	nxc = hash_lookup_and_add (ht,(char *) st->text.data,NIL,THREADLINKS);
  1.4956 +				/* only if no parent & won't introduce loop */
  1.4957 +	if (!PARENT (nxc) && !mail_thread_check_child (con,nxc)) {
  1.4958 +	  SETPARENT (nxc,con);	/* establish parent/child link */
  1.4959 +				/* other children become sibling of this one */
  1.4960 +	  SETSIBLING (nxc,CHILD (con));
  1.4961 +	  SETCHILD (con,nxc);	/* set as child of parent */
  1.4962 +	}
  1.4963 +      }
  1.4964 +    else con = NIL;		/* else message has no ancestors */
  1.4965 +			/* Step 1B */
  1.4966 +    if ((prc = PARENT ((nxc = hash_lookup (ht,s->unique)))) &&
  1.4967 +	(prc != con)) {		/* break links if have a different parent */
  1.4968 +      SETPARENT (nxc,NIL);	/* easy if direct child */
  1.4969 +      if (nxc == CHILD (prc)) SETCHILD (prc,SIBLING (nxc));
  1.4970 +      else {			/* otherwise hunt through sisters */
  1.4971 +	for (sib = CHILD (prc); nxc != SIBLING (sib); sib = SIBLING (sib));
  1.4972 +	SETSIBLING (sib,SIBLING (nxc));
  1.4973 +      }
  1.4974 +      SETSIBLING (nxc,NIL);	/* no more little sisters either */
  1.4975 +      prc = NIL;		/* no more parent set */
  1.4976 +    }
  1.4977 +				/* need to set parent, and parent is good? */
  1.4978 +    if (!prc && !mail_thread_check_child (con,nxc)) {
  1.4979 +      SETPARENT (nxc,con);	/* establish parent/child link */
  1.4980 +      if (con) {		/* if non-root parent, set parent's child */
  1.4981 +	if (CHILD (con)) {	/* have a child already */
  1.4982 +				/* find youngest daughter */
  1.4983 +	  for (con = CHILD (con); SIBLING (con); con = SIBLING (con));
  1.4984 +	  SETSIBLING (con,nxc);	/* add new baby sister */
  1.4985 +	}
  1.4986 +	else SETCHILD (con,nxc);/* set as only child */
  1.4987 +      }
  1.4988 +    }
  1.4989 +  }
  1.4990 +  fs_give ((void **) &sc);	/* finished with sortcache vector */
  1.4991 +
  1.4992 +			/* Step 2 */
  1.4993 +				/* search hash table for parentless messages */
  1.4994 +  for (i = 0, prc = con = NIL; i < ht->size; i++)
  1.4995 +    for (he = ht->table[i]; he; he = he->next)
  1.4996 +      if (!PARENT ((nxc = he->data))) {
  1.4997 +				/* sibling of previous parentless message */
  1.4998 +	if (con) con = SETSIBLING (con,nxc);
  1.4999 +	else prc = con = nxc;	/* first parentless message */
  1.5000 +      }
  1.5001 +  /*  Once the dummy containers are pruned, we no longer need the parent
  1.5002 +   * information, so we can convert the containers to THREADNODEs.  Since
  1.5003 +   * we don't need the id_table any more either, we can reset the hash table
  1.5004 +   * and reuse it as a subject_table.  Resetting the hash table will also
  1.5005 +   * destroy the containers.
  1.5006 +   */
  1.5007 +			/* Step 3 */
  1.5008 +				/* prune dummies, convert to threadnode */
  1.5009 +  root = mail_thread_c2node (stream,mail_thread_prune_dummy (prc,NIL),flags);
  1.5010 +			/* Step 4 */
  1.5011 +				/* make buffer for sorting */
  1.5012 +  tc = (THREADNODE **) fs_get (nmsgs * sizeof (THREADNODE *));
  1.5013 +				/* load threadcache and count nodes to sort */
  1.5014 +  for (i = 0, cur = root; cur ; cur = cur->branch) tc[i++] = cur;
  1.5015 +  if (i > 1) {			/* only if need to sort */
  1.5016 +    qsort ((void *) tc,i,sizeof (THREADNODE *),mail_thread_compare_date);
  1.5017 +				/* relink siblings */
  1.5018 +    for (j = 0, --i; j < i; j++) tc[j]->branch = tc[j+1];
  1.5019 +    tc[j]->branch = NIL;	/* end of root */
  1.5020 +    root = tc[0];		/* establish new root */
  1.5021 +  }
  1.5022 +			/* Step 5A */
  1.5023 +  hash_reset (ht);		/* discard containers, reset ht */
  1.5024 +			/* Step 5B */
  1.5025 +  for (cur = root; cur; cur = cur->branch)
  1.5026 +    if ((t = (nxt = (cur->sc ? cur : cur->next))->sc->subject) && *t) {
  1.5027 +				/* add new subject to hash table */
  1.5028 +      if (!(sub = hash_lookup (ht,t))) hash_add (ht,t,cur,0);
  1.5029 +				/* if one in table not dummy and */
  1.5030 +      else if ((s = (lst = (THREADNODE *) sub[0])->sc) &&
  1.5031 +				/* current dummy, or not re/fwd and table is */
  1.5032 +	       (!cur->sc || (!nxt->sc->refwd && s->refwd)))
  1.5033 +	sub[0] = (void *) cur;	/* replace with this message */
  1.5034 +    }
  1.5035 +
  1.5036 +			/* Step 5C */
  1.5037 +  for (cur = root, sis = NIL; cur; cur = msg) {
  1.5038 +				/* do nothing if current message or no sub */
  1.5039 +    if (!(t = (cur->sc ? cur : cur->next)->sc->subject) || !*t ||
  1.5040 +	((lst = (THREADNODE *) (sub = hash_lookup (ht,t))[0]) == cur))
  1.5041 +      msg = (sis = cur)->branch;
  1.5042 +    else if (!lst->sc) {	/* is message in the table a dummy? */
  1.5043 +				/* find youngest daughter of msg in table */
  1.5044 +      for (msg = lst->next; msg->branch; msg = msg->branch);
  1.5045 +      if (!cur->sc) {		/* current message a dummy? */
  1.5046 +	msg->branch = cur->next;/* current's daughter now dummy's youngest */
  1.5047 +	msg = cur->branch;	/* continue scan at younger sister */
  1.5048 +				/* now delete this node */
  1.5049 +	cur->branch = cur->next = NIL;
  1.5050 +	mail_free_threadnode (&cur);
  1.5051 +      }
  1.5052 +      else {			/* current message not a dummy */
  1.5053 +	msg->branch = cur;	/* append as youngest daughter */
  1.5054 +	msg = cur->branch;	/* continue scan at younger sister */
  1.5055 +	cur->branch = NIL;	/* lose our younger sisters */
  1.5056 +      }
  1.5057 +    }
  1.5058 +    else {			/* no dummies, is current re/fwd, table not? */
  1.5059 +      if (cur->sc->refwd && !lst->sc->refwd) {
  1.5060 +	if (lst->next) {	/* find youngest daughter of msg in table */
  1.5061 +	  for (msg = lst->next; msg->branch; msg = msg->branch);
  1.5062 +	  msg->branch = cur;	/* append as youngest daughter */
  1.5063 +	}
  1.5064 +	else lst->next = cur;	/* no children, so make the eldest daughter */
  1.5065 +      }
  1.5066 +
  1.5067 +      else {			/* no re/fwd, create a new dummy */
  1.5068 +	msg = mail_newthreadnode (NIL);
  1.5069 +	if (lst == root) {	/* msg in table is root? */
  1.5070 +	  root = lst->branch;	/* younger sister becomes new root */
  1.5071 +				/* no longer older sister either */
  1.5072 +	  if (lst == sis) sis = NIL;
  1.5073 +	}
  1.5074 +	else {			/* find older sister of msg in table */
  1.5075 +	  for (nxt = root; lst != nxt->branch; nxt = nxt->branch);
  1.5076 +				/* remove from older sister */
  1.5077 +	  nxt->branch = lst->branch;
  1.5078 +	}
  1.5079 +	msg->next = lst;	/* msg in table becomes child */
  1.5080 +	lst->branch = cur;	/* current now little sister of msg in table */
  1.5081 +	if (sis) {		/* have an elder sister? */
  1.5082 +	  if (sis == lst)	/* rescan if lost her */
  1.5083 +	    for (sis = root; cur != sis->branch; sis = sis->branch);
  1.5084 +	  sis->branch = msg;	/* make dummy younger sister of big sister */
  1.5085 +	}
  1.5086 +	else root = msg;	/* otherwise this is the new root */
  1.5087 +	sub[0] = sis = msg;	/* set new msg in table and new big sister */
  1.5088 +      }
  1.5089 +      msg = cur->branch;	/* continue scan at younger sister */
  1.5090 +      cur->branch = NIL;	/* lose our younger sisters */
  1.5091 +    }
  1.5092 +    if (sis) sis->branch = msg;	/* older sister gets this as younger sister */
  1.5093 +    else root = msg;		/* otherwise this is the new root */
  1.5094 +  }
  1.5095 +  hash_destroy (&ht);		/* finished with hash table */
  1.5096 +			/* Step 6 */
  1.5097 +				/* sort threads */
  1.5098 +  root = mail_thread_sort (root,tc);
  1.5099 +  fs_give ((void **) &tc);	/* finished with sort buffer */
  1.5100 +  return root;			/* return sorted list */
  1.5101 +}
  1.5102 +
  1.5103 +/* Fetch overview callback to load sortcache for threading
  1.5104 + * Accepts: MAIL stream
  1.5105 + *	    UID of this message
  1.5106 + *	    overview of this message
  1.5107 + *	    msgno of this message
  1.5108 + */
  1.5109 +
  1.5110 +void mail_thread_loadcache (MAILSTREAM *stream,unsigned long uid,OVERVIEW *ov,
  1.5111 +			    unsigned long msgno)
  1.5112 +{
  1.5113 +  if (msgno && ov) {		/* just in case */
  1.5114 +    MESSAGECACHE telt;
  1.5115 +    SORTCACHE *s = (SORTCACHE *) (*mailcache) (stream,msgno,CH_SORTCACHE);
  1.5116 +    if (!s->subject && ov->subject) {
  1.5117 +      s->refwd = mail_strip_subject (ov->subject,&s->subject);
  1.5118 +      s->dirty = T;
  1.5119 +    }
  1.5120 +    if (!s->from && ov->from && ov->from->mailbox) {
  1.5121 +      s->from = cpystr (ov->from->mailbox);
  1.5122 +      s->dirty = T;
  1.5123 +    }
  1.5124 +    if (!s->date && ov->date && mail_parse_date (&telt,ov->date)) {
  1.5125 +      s->date = mail_longdate (&telt);
  1.5126 +      s->dirty = T;
  1.5127 +    }
  1.5128 +    if (!s->message_id && ov->message_id) {
  1.5129 +      s->message_id = mail_thread_parse_msgid (ov->message_id,NIL);
  1.5130 +      s->dirty = T;
  1.5131 +    }
  1.5132 +    if (!s->references &&
  1.5133 +	!(s->references = mail_thread_parse_references (ov->references,T))) {
  1.5134 +				/* don't do In-Reply-To with NNTP mailboxes */
  1.5135 +      s->references = mail_newstringlist ();
  1.5136 +      s->dirty = T;
  1.5137 +    }
  1.5138 +    if (!s->size && ov->optional.octets) {
  1.5139 +      s->size = ov->optional.octets;
  1.5140 +      s->dirty = T;
  1.5141 +    }
  1.5142 +  }
  1.5143 +}
  1.5144 +
  1.5145 +/* Thread parse Message ID
  1.5146 + * Accepts: pointer to purported Message ID
  1.5147 + *	    pointer to return pointer
  1.5148 + * Returns: Message ID or NIL, return pointer updated
  1.5149 + */
  1.5150 +
  1.5151 +char *mail_thread_parse_msgid (char *s,char **ss)
  1.5152 +{
  1.5153 +  char *ret = NIL;
  1.5154 +  char *t = NIL;
  1.5155 +  ADDRESS *adr;
  1.5156 +  if (s) {			/* only for non-NIL strings */
  1.5157 +    rfc822_skipws (&s);		/* skip whitespace */
  1.5158 +				/* ignore phrases */
  1.5159 +    if (((*s == '<') || (s = rfc822_parse_phrase (s))) &&
  1.5160 +	(adr = rfc822_parse_routeaddr (s,&t,BADHOST))) {
  1.5161 +				/* make return msgid */
  1.5162 +      if (adr->mailbox && adr->host)
  1.5163 +	sprintf (ret = (char *) fs_get (strlen (adr->mailbox) +
  1.5164 +					strlen (adr->host) + 2),"%s@%s",
  1.5165 +		 adr->mailbox,adr->host);
  1.5166 +      mail_free_address (&adr);	/* don't need temporary address */
  1.5167 +    }
  1.5168 +  }
  1.5169 +  if (ss) *ss = t;		/* update return pointer */
  1.5170 +  return ret;
  1.5171 +}
  1.5172 +
  1.5173 +
  1.5174 +/* Thread parse references
  1.5175 + * Accepts: pointer to purported references
  1.5176 + *	    parse multiple references flag
  1.5177 + * Returns: references or NIL
  1.5178 + */
  1.5179 +
  1.5180 +STRINGLIST *mail_thread_parse_references (char *s,long flag)
  1.5181 +{
  1.5182 +  char *t;
  1.5183 +  STRINGLIST *ret = NIL;
  1.5184 +  STRINGLIST *cur;
  1.5185 +				/* found first reference? */
  1.5186 +  if (t = mail_thread_parse_msgid (s,&s)) {
  1.5187 +    (ret = mail_newstringlist ())->text.data = (unsigned char *) t;
  1.5188 +    ret->text.size = strlen (t);
  1.5189 +    if (flag)			/* parse subsequent references */
  1.5190 +      for (cur = ret; t = mail_thread_parse_msgid (s,&s); cur = cur->next) {
  1.5191 +	(cur->next = mail_newstringlist ())->text.data = (unsigned char *) t;
  1.5192 +	cur->next->text.size = strlen (t);
  1.5193 +      }
  1.5194 +  }
  1.5195 +  return ret;
  1.5196 +}
  1.5197 +
  1.5198 +/* Prune dummy messages
  1.5199 + * Accepts: candidate container to prune
  1.5200 + *	    older sibling of container, if any
  1.5201 + * Returns: container in this position, possibly pruned
  1.5202 + * All children and younger siblings are also pruned
  1.5203 + */
  1.5204 +
  1.5205 +container_t mail_thread_prune_dummy (container_t msg,container_t ane)
  1.5206 +{
  1.5207 +				/* prune container and children */
  1.5208 +  container_t ret = msg ? mail_thread_prune_dummy_work (msg,ane) : NIL;
  1.5209 +				/* prune all younger sisters */
  1.5210 +  if (ret) for (ane = ret; ane && (msg = SIBLING (ane)); ane = msg)
  1.5211 +    msg = mail_thread_prune_dummy_work (msg,ane);
  1.5212 +  return ret;
  1.5213 +}
  1.5214 +
  1.5215 +
  1.5216 +/* Prune dummy messages worker routine
  1.5217 + * Accepts: candidate container to prune
  1.5218 + *	    older sibling of container, if any
  1.5219 + * Returns: container in this position, possibly pruned
  1.5220 + * All children are also pruned
  1.5221 + */
  1.5222 +
  1.5223 +container_t mail_thread_prune_dummy_work (container_t msg,container_t ane)
  1.5224 +{
  1.5225 +  container_t cur;
  1.5226 +				/* get children, if any */
  1.5227 +  container_t nxt = mail_thread_prune_dummy (CHILD (msg),NIL);
  1.5228 +				/* just update children if container has msg */
  1.5229 +  if (CACHE (msg)) SETCHILD (msg,nxt);
  1.5230 +  else if (!nxt) {		/* delete dummy with no children */
  1.5231 +    nxt = SIBLING (msg);	/* get younger sister */
  1.5232 +    if (ane) SETSIBLING (ane,nxt);
  1.5233 +				/* prune younger sister if exists */
  1.5234 +    msg = nxt ? mail_thread_prune_dummy_work (nxt,ane) : NIL;
  1.5235 +  }
  1.5236 +				/* not if parent root & multiple children */
  1.5237 +  else if ((cur = PARENT (msg)) || !SIBLING (nxt)) {
  1.5238 +				/* OK to promote, try younger sister of aunt */
  1.5239 +    if (ane) SETSIBLING (ane,nxt);
  1.5240 +				/* otherwise promote to child of grandmother */
  1.5241 +    else if (cur) SETCHILD (cur,nxt);
  1.5242 +    SETPARENT (nxt,cur);	/* set parent as well */
  1.5243 +				/* look for end of siblings in new container */
  1.5244 +    for (cur = nxt; SIBLING (cur); cur = SIBLING (cur));
  1.5245 +				/* reattach deleted container's siblings */
  1.5246 +    SETSIBLING (cur,SIBLING (msg));
  1.5247 +				/* prune and return new container */
  1.5248 +    msg = mail_thread_prune_dummy_work (nxt,ane);
  1.5249 +  }
  1.5250 +  else SETCHILD (msg,nxt);	/* in case child pruned */
  1.5251 +  return msg;			/* return this message */
  1.5252 +}
  1.5253 +
  1.5254 +/* Test that purported mother is not a child of purported daughter
  1.5255 + * Accepts: mother
  1.5256 + *	    purported daugher
  1.5257 + * Returns: T if circular parentage exists, else NIL
  1.5258 + */
  1.5259 +
  1.5260 +long mail_thread_check_child (container_t mother,container_t daughter)
  1.5261 +{
  1.5262 +  if (mother) {			/* only if mother non-NIL */
  1.5263 +    if (mother == daughter) return T;
  1.5264 +    for (daughter = CHILD (daughter); daughter; daughter = SIBLING (daughter))
  1.5265 +      if (mail_thread_check_child (mother,daughter)) return T;
  1.5266 +  }
  1.5267 +  return NIL;
  1.5268 +}
  1.5269 +
  1.5270 +
  1.5271 +/* Generate threadnodes from containers
  1.5272 + * Accepts: Mail stream
  1.5273 + *	    container
  1.5274 + *	    flags
  1.5275 + * Return: threadnode list
  1.5276 + */
  1.5277 +
  1.5278 +THREADNODE *mail_thread_c2node (MAILSTREAM *stream,container_t con,long flags)
  1.5279 +{
  1.5280 +  THREADNODE *ret,*cur;
  1.5281 +  SORTCACHE *s;
  1.5282 +  container_t nxt;
  1.5283 +				/* for each container */
  1.5284 +  for (ret = cur = NIL; con; con = SIBLING (con)) {
  1.5285 +    s = CACHE (con);		/* yes, get its sortcache */
  1.5286 +				/* create node for it */
  1.5287 +    if (ret) cur = cur->branch = mail_newthreadnode (s);
  1.5288 +    else ret = cur = mail_newthreadnode (s);
  1.5289 +				/* attach sequence or UID for non-dummy */
  1.5290 +    if (s) cur->num = (flags & SE_UID) ? mail_uid (stream,s->num) : s->num;
  1.5291 +				/* attach the children */
  1.5292 +    if (nxt = CHILD (con)) cur->next = mail_thread_c2node (stream,nxt,flags);
  1.5293 +  }
  1.5294 +  return ret;
  1.5295 +}
  1.5296 +
  1.5297 +/* Sort thread tree by date
  1.5298 + * Accepts: thread tree to sort
  1.5299 + *	    qsort vector to sort
  1.5300 + * Returns: sorted thread tree
  1.5301 + */
  1.5302 +
  1.5303 +THREADNODE *mail_thread_sort (THREADNODE *thr,THREADNODE **tc)
  1.5304 +{
  1.5305 +  unsigned long i,j;
  1.5306 +  THREADNODE *cur;
  1.5307 +				/* sort children of each thread */
  1.5308 +  for (cur = thr; cur; cur = cur->branch)
  1.5309 +    if (cur->next) cur->next = mail_thread_sort (cur->next,tc);
  1.5310 +  /* Must do this in a separate pass since recursive call will clobber tc */
  1.5311 +				/* load threadcache and count nodes to sort */
  1.5312 +  for (i = 0, cur = thr; cur; cur = cur->branch) tc[i++] = cur;
  1.5313 +  if (i > 1) {			/* only if need to sort */
  1.5314 +    qsort ((void *) tc,i,sizeof (THREADNODE *),mail_thread_compare_date);
  1.5315 +				/* relink root siblings */
  1.5316 +    for (j = 0, --i; j < i; j++) tc[j]->branch = tc[j+1];
  1.5317 +    tc[j]->branch = NIL;	/* end of root */
  1.5318 +  }
  1.5319 +  return i ? tc[0] : NIL;	/* return new head of list */
  1.5320 +}
  1.5321 +
  1.5322 +
  1.5323 +/* Thread compare date
  1.5324 + * Accept: first message sort cache element
  1.5325 + *	   second message sort cache element
  1.5326 + * Returns: -1 if a1 < a2, 1 if a1 > a2
  1.5327 + *
  1.5328 + * This assumes that a sort cache element is either a message (with a
  1.5329 + * sortcache entry) or a dummy with a message (with sortcache entry) child.
  1.5330 + * This is true of both the ORDEREDSUBJECT (no dummies) and REFERENCES
  1.5331 + * (dummies only at top-level, and with non-dummy children).
  1.5332 + *
  1.5333 + * If a new algorithm allows a dummy parent to have a dummy child, this
  1.5334 + * routine must be changed if it is to be used by that algorithm.
  1.5335 + *
  1.5336 + * Messages with bogus dates are always sorted at the top.
  1.5337 + */
  1.5338 +
  1.5339 +int mail_thread_compare_date (const void *a1,const void *a2)
  1.5340 +{
  1.5341 +  THREADNODE *t1 = *(THREADNODE **) a1;
  1.5342 +  THREADNODE *t2 = *(THREADNODE **) a2;
  1.5343 +  SORTCACHE *s1 = t1->sc ? t1->sc : t1->next->sc;
  1.5344 +  SORTCACHE *s2 = t2->sc ? t2->sc : t2->next->sc;
  1.5345 +  int ret = compare_ulong (s1->date,s2->date);
  1.5346 +				/* use number as final tie-breaker */
  1.5347 +  return ret ? ret : compare_ulong (s1->num,s2->num);
  1.5348 +}
  1.5349 +
  1.5350 +/* Mail parse sequence
  1.5351 + * Accepts: mail stream
  1.5352 + *	    sequence to parse
  1.5353 + * Returns: T if parse successful, else NIL
  1.5354 + */
  1.5355 +
  1.5356 +long mail_sequence (MAILSTREAM *stream,unsigned char *sequence)
  1.5357 +{
  1.5358 +  unsigned long i,j,x;
  1.5359 +  for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->sequence = NIL;
  1.5360 +  while (sequence && *sequence){/* while there is something to parse */
  1.5361 +    if (*sequence == '*') {	/* maximum message */
  1.5362 +      if (stream->nmsgs) i = stream->nmsgs;
  1.5363 +      else {
  1.5364 +	MM_LOG ("No messages, so no maximum message number",ERROR);
  1.5365 +	return NIL;
  1.5366 +      }
  1.5367 +      sequence++;		/* skip past * */
  1.5368 +    }
  1.5369 +				/* parse and validate message number */
  1.5370 +    else if (!isdigit (*sequence)) {
  1.5371 +      MM_LOG ("Syntax error in sequence",ERROR);
  1.5372 +      return NIL;
  1.5373 +    }
  1.5374 +    else if (!(i = strtoul (sequence,(char **) &sequence,10)) ||
  1.5375 +	     (i > stream->nmsgs)) {
  1.5376 +      MM_LOG ("Sequence out of range",ERROR);
  1.5377 +      return NIL;
  1.5378 +    }
  1.5379 +    switch (*sequence) {	/* see what the delimiter is */
  1.5380 +    case ':':			/* sequence range */
  1.5381 +      if (*++sequence == '*') {	/* maximum message */
  1.5382 +	if (stream->nmsgs) j = stream->nmsgs;
  1.5383 +	else {
  1.5384 +	  MM_LOG ("No messages, so no maximum message number",ERROR);
  1.5385 +	  return NIL;
  1.5386 +	}
  1.5387 +	sequence++;		/* skip past * */
  1.5388 +      }
  1.5389 +				/* parse end of range */
  1.5390 +      else if (!(j = strtoul (sequence,(char **) &sequence,10)) ||
  1.5391 +	       (j > stream->nmsgs)) {
  1.5392 +	MM_LOG ("Sequence range invalid",ERROR);
  1.5393 +	return NIL;
  1.5394 +      }
  1.5395 +      if (*sequence && *sequence++ != ',') {
  1.5396 +	MM_LOG ("Sequence range syntax error",ERROR);
  1.5397 +	return NIL;
  1.5398 +      }
  1.5399 +      if (i > j) {		/* swap the range if backwards */
  1.5400 +	x = i; i = j; j = x;
  1.5401 +      }
  1.5402 +				/* mark each item in the sequence */
  1.5403 +      while (i <= j) mail_elt (stream,j--)->sequence = T;
  1.5404 +      break;
  1.5405 +    case ',':			/* single message */
  1.5406 +      ++sequence;		/* skip the delimiter, fall into end case */
  1.5407 +    case '\0':			/* end of sequence, mark this message */
  1.5408 +      mail_elt (stream,i)->sequence = T;
  1.5409 +      break;
  1.5410 +    default:			/* anything else is a syntax error! */
  1.5411 +      MM_LOG ("Sequence syntax error",ERROR);
  1.5412 +      return NIL;
  1.5413 +    }
  1.5414 +  }
  1.5415 +  return T;			/* successfully parsed sequence */
  1.5416 +}
  1.5417 +
  1.5418 +/* Parse flag list
  1.5419 + * Accepts: MAIL stream
  1.5420 + *	    flag list as a character string
  1.5421 + *	    pointer to user flags to return
  1.5422 + * Returns: system flags
  1.5423 + */
  1.5424 +
  1.5425 +long mail_parse_flags (MAILSTREAM *stream,char *flag,unsigned long *uf)
  1.5426 +{
  1.5427 +  char *t,*n,*s,tmp[MAILTMPLEN],msg[MAILTMPLEN];
  1.5428 +  short f = 0;
  1.5429 +  long i,j;
  1.5430 +  *uf = 0;			/* initially no user flags */
  1.5431 +  if (flag && *flag) {		/* no-op if no flag string */
  1.5432 +				/* check if a list and make sure valid */
  1.5433 +    if (((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) ||
  1.5434 +	(strlen (flag) >= MAILTMPLEN)) {
  1.5435 +      MM_LOG ("Bad flag list",ERROR);
  1.5436 +      return NIL;
  1.5437 +    }
  1.5438 +				/* copy the flag string w/o list construct */
  1.5439 +    strncpy (n = tmp,flag+i,(j = strlen (flag) - (2*i)));
  1.5440 +    tmp[j] = '\0';
  1.5441 +    while ((t = n) && *t) {	/* parse the flags */
  1.5442 +				/* find end of flag */
  1.5443 +      if (n = strchr (t,' ')) *n++ = '\0';
  1.5444 +      if (*t == '\\') {		/* system flag? */
  1.5445 +	if (!compare_cstring (t+1,"SEEN")) f |= fSEEN;
  1.5446 +	else if (!compare_cstring (t+1,"DELETED")) f |= fDELETED;
  1.5447 +	else if (!compare_cstring (t+1,"FLAGGED")) f |= fFLAGGED;
  1.5448 +	else if (!compare_cstring (t+1,"ANSWERED")) f |= fANSWERED;
  1.5449 +	else if (!compare_cstring (t+1,"DRAFT")) f |= fDRAFT;
  1.5450 +	else {
  1.5451 +	  sprintf (msg,"Unsupported system flag: %.80s",t);
  1.5452 +	  MM_LOG (msg,WARN);
  1.5453 +	}
  1.5454 +      }
  1.5455 +
  1.5456 +      else {			/* keyword flag */
  1.5457 +	for (i = j = 0;		/* user flag, search through table */
  1.5458 +	     !i && (j < NUSERFLAGS) && (s = stream->user_flags[j]); ++j)
  1.5459 +	  if (!compare_cstring (t,s)) *uf |= i = 1 << j;
  1.5460 +	if (!i) {		/* flag not found, can it be created? */
  1.5461 +	  if (stream->kwd_create && (j < NUSERFLAGS) && *t &&
  1.5462 +	      (strlen (t) <= MAXUSERFLAG)) {
  1.5463 +	    for (s = t; t && *s; s++) switch (*s) {
  1.5464 +	    default:		/* all other characters */
  1.5465 +				/* SPACE, CTL, or not CHAR */
  1.5466 +	      if ((*s > ' ') && (*s < 0x7f)) break;
  1.5467 +	    case '*': case '%':	/* list_wildcards */
  1.5468 +	    case '"': case '\\':/* quoted-specials */
  1.5469 +				/* atom_specials */
  1.5470 +	    case '(': case ')': case '{':
  1.5471 +	    case ']':		/* resp-specials */
  1.5472 +	      sprintf (msg,"Invalid flag: %.80s",t);
  1.5473 +	      MM_LOG (msg,WARN);
  1.5474 +	      t = NIL;
  1.5475 +	    }
  1.5476 +	    if (t) {		/* only if valid */
  1.5477 +	      *uf |= 1 << j;	/* set the bit */
  1.5478 +	      stream->user_flags[j] = cpystr (t);
  1.5479 +				/* if out of user flags */
  1.5480 +	      if (j == NUSERFLAGS - 1) stream->kwd_create = NIL;
  1.5481 +	    }
  1.5482 +	  }
  1.5483 +	  else {
  1.5484 +	    if (*t) sprintf (msg,"Unknown flag: %.80s",t);
  1.5485 +	    else strcpy (msg,"Empty flag invalid");
  1.5486 +	    MM_LOG (msg,WARN);
  1.5487 +	  }
  1.5488 +	}
  1.5489 +      }
  1.5490 +    }
  1.5491 +  }
  1.5492 +  return f;
  1.5493 +}
  1.5494 +
  1.5495 +/* Mail check network stream for usability with new name
  1.5496 + * Accepts: MAIL stream
  1.5497 + *	    candidate new name
  1.5498 + * Returns: T if stream can be used, NIL otherwise
  1.5499 + */
  1.5500 +
  1.5501 +long mail_usable_network_stream (MAILSTREAM *stream,char *name)
  1.5502 +{
  1.5503 +  NETMBX smb,nmb,omb;
  1.5504 +  return (stream && stream->dtb && !(stream->dtb->flags & DR_LOCAL) &&
  1.5505 +	  mail_valid_net_parse (name,&nmb) &&
  1.5506 +	  mail_valid_net_parse (stream->mailbox,&smb) &&
  1.5507 +	  mail_valid_net_parse (stream->original_mailbox,&omb) &&
  1.5508 +	  ((!compare_cstring (smb.host,
  1.5509 +			      trustdns ? tcp_canonical (nmb.host) : nmb.host)&&
  1.5510 +	    !strcmp (smb.service,nmb.service) &&
  1.5511 +	    (!nmb.port || (smb.port == nmb.port)) &&
  1.5512 +	    (nmb.anoflag == stream->anonymous) &&
  1.5513 +	    (!nmb.user[0] || !strcmp (smb.user,nmb.user))) ||
  1.5514 +	   (!compare_cstring (omb.host,nmb.host) &&
  1.5515 +	    !strcmp (omb.service,nmb.service) &&
  1.5516 +	    (!nmb.port || (omb.port == nmb.port)) &&
  1.5517 +	    (nmb.anoflag == stream->anonymous) &&
  1.5518 +	    (!nmb.user[0] || !strcmp (omb.user,nmb.user))))) ? LONGT : NIL;
  1.5519 +}
  1.5520 +
  1.5521 +/* Mail data structure instantiation routines */
  1.5522 +
  1.5523 +
  1.5524 +/* Mail instantiate cache elt
  1.5525 + * Accepts: initial message number
  1.5526 + * Returns: new cache elt
  1.5527 + */
  1.5528 +
  1.5529 +MESSAGECACHE *mail_new_cache_elt (unsigned long msgno)
  1.5530 +{
  1.5531 +  MESSAGECACHE *elt = (MESSAGECACHE *) memset (fs_get (sizeof (MESSAGECACHE)),
  1.5532 +					       0,sizeof (MESSAGECACHE));
  1.5533 +  elt->lockcount = 1;		/* initially only cache references it */
  1.5534 +  elt->msgno = msgno;		/* message number */
  1.5535 +  return elt;
  1.5536 +}
  1.5537 +
  1.5538 +
  1.5539 +/* Mail instantiate envelope
  1.5540 + * Returns: new envelope
  1.5541 + */
  1.5542 +
  1.5543 +ENVELOPE *mail_newenvelope (void)
  1.5544 +{
  1.5545 +  return (ENVELOPE *) memset (fs_get (sizeof (ENVELOPE)),0,sizeof (ENVELOPE));
  1.5546 +}
  1.5547 +
  1.5548 +
  1.5549 +/* Mail instantiate address
  1.5550 + * Returns: new address
  1.5551 + */
  1.5552 +
  1.5553 +ADDRESS *mail_newaddr (void)
  1.5554 +{
  1.5555 +  return (ADDRESS *) memset (fs_get (sizeof (ADDRESS)),0,sizeof (ADDRESS));
  1.5556 +}
  1.5557 +
  1.5558 +/* Mail instantiate body
  1.5559 + * Returns: new body
  1.5560 + */
  1.5561 +
  1.5562 +BODY *mail_newbody (void)
  1.5563 +{
  1.5564 +  return mail_initbody ((BODY *) fs_get (sizeof (BODY)));
  1.5565 +}
  1.5566 +
  1.5567 +
  1.5568 +/* Mail initialize body
  1.5569 + * Accepts: body
  1.5570 + * Returns: body
  1.5571 + */
  1.5572 +
  1.5573 +BODY *mail_initbody (BODY *body)
  1.5574 +{
  1.5575 +  memset ((void *) body,0,sizeof (BODY));
  1.5576 +  body->type = TYPETEXT;	/* content type */
  1.5577 +  body->encoding = ENC7BIT;	/* content encoding */
  1.5578 +  return body;
  1.5579 +}
  1.5580 +
  1.5581 +
  1.5582 +/* Mail instantiate body parameter
  1.5583 + * Returns: new body part
  1.5584 + */
  1.5585 +
  1.5586 +PARAMETER *mail_newbody_parameter (void)
  1.5587 +{
  1.5588 +  return (PARAMETER *) memset (fs_get (sizeof(PARAMETER)),0,sizeof(PARAMETER));
  1.5589 +}
  1.5590 +
  1.5591 +
  1.5592 +/* Mail instantiate body part
  1.5593 + * Returns: new body part
  1.5594 + */
  1.5595 +
  1.5596 +PART *mail_newbody_part (void)
  1.5597 +{
  1.5598 +  PART *part = (PART *) memset (fs_get (sizeof (PART)),0,sizeof (PART));
  1.5599 +  mail_initbody (&part->body);	/* initialize the body */
  1.5600 +  return part;
  1.5601 +}
  1.5602 +
  1.5603 +
  1.5604 +/* Mail instantiate body message part
  1.5605 + * Returns: new body message part
  1.5606 + */
  1.5607 +
  1.5608 +MESSAGE *mail_newmsg (void)
  1.5609 +{
  1.5610 +  return (MESSAGE *) memset (fs_get (sizeof (MESSAGE)),0,sizeof (MESSAGE));
  1.5611 +}
  1.5612 +
  1.5613 +/* Mail instantiate string list
  1.5614 + * Returns: new string list
  1.5615 + */
  1.5616 +
  1.5617 +STRINGLIST *mail_newstringlist (void)
  1.5618 +{
  1.5619 +  return (STRINGLIST *) memset (fs_get (sizeof (STRINGLIST)),0,
  1.5620 +				sizeof (STRINGLIST));
  1.5621 +}
  1.5622 +
  1.5623 +
  1.5624 +/* Mail instantiate new search program
  1.5625 + * Returns: new search program
  1.5626 + */
  1.5627 +
  1.5628 +SEARCHPGM *mail_newsearchpgm (void)
  1.5629 +{
  1.5630 +  return (SEARCHPGM *) memset (fs_get (sizeof(SEARCHPGM)),0,sizeof(SEARCHPGM));
  1.5631 +}
  1.5632 +
  1.5633 +
  1.5634 +/* Mail instantiate new search program
  1.5635 + * Accepts: header line name   
  1.5636 + * Returns: new search program
  1.5637 + */
  1.5638 +
  1.5639 +SEARCHHEADER *mail_newsearchheader (char *line,char *text)
  1.5640 +{
  1.5641 +  SEARCHHEADER *hdr = (SEARCHHEADER *) memset (fs_get (sizeof (SEARCHHEADER)),
  1.5642 +					       0,sizeof (SEARCHHEADER));
  1.5643 +  hdr->line.size = strlen ((char *) (hdr->line.data =
  1.5644 +				     (unsigned char *) cpystr (line)));
  1.5645 +  hdr->text.size = strlen ((char *) (hdr->text.data =
  1.5646 +				     (unsigned char *) cpystr (text)));
  1.5647 +  return hdr;
  1.5648 +}
  1.5649 +
  1.5650 +
  1.5651 +/* Mail instantiate new search set
  1.5652 + * Returns: new search set
  1.5653 + */
  1.5654 +
  1.5655 +SEARCHSET *mail_newsearchset (void)
  1.5656 +{
  1.5657 +  return (SEARCHSET *) memset (fs_get (sizeof(SEARCHSET)),0,sizeof(SEARCHSET));
  1.5658 +}
  1.5659 +
  1.5660 +
  1.5661 +/* Mail instantiate new search or
  1.5662 + * Returns: new search or
  1.5663 + */
  1.5664 +
  1.5665 +SEARCHOR *mail_newsearchor (void)
  1.5666 +{
  1.5667 +  SEARCHOR *or = (SEARCHOR *) memset (fs_get (sizeof (SEARCHOR)),0,
  1.5668 +				      sizeof (SEARCHOR));
  1.5669 +  or->first = mail_newsearchpgm ();
  1.5670 +  or->second = mail_newsearchpgm ();
  1.5671 +  return or;
  1.5672 +}
  1.5673 +
  1.5674 +/* Mail instantiate new searchpgmlist
  1.5675 + * Returns: new searchpgmlist
  1.5676 + */
  1.5677 +
  1.5678 +SEARCHPGMLIST *mail_newsearchpgmlist (void)
  1.5679 +{
  1.5680 +  SEARCHPGMLIST *pgl = (SEARCHPGMLIST *)
  1.5681 +    memset (fs_get (sizeof (SEARCHPGMLIST)),0,sizeof (SEARCHPGMLIST));
  1.5682 +  pgl->pgm = mail_newsearchpgm ();
  1.5683 +  return pgl;
  1.5684 +}
  1.5685 +
  1.5686 +
  1.5687 +/* Mail instantiate new sortpgm
  1.5688 + * Returns: new sortpgm
  1.5689 + */
  1.5690 +
  1.5691 +SORTPGM *mail_newsortpgm (void)
  1.5692 +{
  1.5693 +  return (SORTPGM *) memset (fs_get (sizeof (SORTPGM)),0,sizeof (SORTPGM));
  1.5694 +}
  1.5695 +
  1.5696 +
  1.5697 +/* Mail instantiate new threadnode
  1.5698 + * Accepts: sort cache for thread node
  1.5699 + * Returns: new threadnode
  1.5700 + */
  1.5701 +
  1.5702 +THREADNODE *mail_newthreadnode (SORTCACHE *sc)
  1.5703 +{
  1.5704 +  THREADNODE *thr = (THREADNODE *) memset (fs_get (sizeof (THREADNODE)),0,
  1.5705 +					   sizeof (THREADNODE));
  1.5706 +  if (sc) thr->sc = sc;		/* initialize sortcache */
  1.5707 +  return thr;
  1.5708 +}
  1.5709 +
  1.5710 +
  1.5711 +/* Mail instantiate new acllist
  1.5712 + * Returns: new acllist
  1.5713 + */
  1.5714 +
  1.5715 +ACLLIST *mail_newacllist (void)
  1.5716 +{
  1.5717 +  return (ACLLIST *) memset (fs_get (sizeof (ACLLIST)),0,sizeof (ACLLIST));
  1.5718 +}
  1.5719 +
  1.5720 +
  1.5721 +/* Mail instantiate new quotalist
  1.5722 + * Returns: new quotalist
  1.5723 + */
  1.5724 +
  1.5725 +QUOTALIST *mail_newquotalist (void)
  1.5726 +{
  1.5727 +  return (QUOTALIST *) memset (fs_get (sizeof (QUOTALIST)),0,
  1.5728 +			       sizeof (QUOTALIST));
  1.5729 +}
  1.5730 +
  1.5731 +/* Mail garbage collection routines */
  1.5732 +
  1.5733 +
  1.5734 +/* Mail garbage collect body
  1.5735 + * Accepts: pointer to body pointer
  1.5736 + */
  1.5737 +
  1.5738 +void mail_free_body (BODY **body)
  1.5739 +{
  1.5740 +  if (*body) {			/* only free if exists */
  1.5741 +    mail_free_body_data (*body);/* free its data */
  1.5742 +    fs_give ((void **) body);	/* return body to free storage */
  1.5743 +  }
  1.5744 +}
  1.5745 +
  1.5746 +
  1.5747 +/* Mail garbage collect body data
  1.5748 + * Accepts: body pointer
  1.5749 + */
  1.5750 +
  1.5751 +void mail_free_body_data (BODY *body)
  1.5752 +{
  1.5753 +  switch (body->type) {		/* free contents */
  1.5754 +  case TYPEMULTIPART:		/* multiple part */
  1.5755 +    mail_free_body_part (&body->nested.part);
  1.5756 +    break;
  1.5757 +  case TYPEMESSAGE:		/* encapsulated message */
  1.5758 +    if (body->subtype && !strcmp (body->subtype,"RFC822")) {
  1.5759 +      mail_free_stringlist (&body->nested.msg->lines);
  1.5760 +      mail_gc_msg (body->nested.msg,GC_ENV | GC_TEXTS);
  1.5761 +    }
  1.5762 +    if (body->nested.msg) fs_give ((void **) &body->nested.msg);
  1.5763 +    break;
  1.5764 +  default:
  1.5765 +    break;
  1.5766 +  }
  1.5767 +  if (body->subtype) fs_give ((void **) &body->subtype);
  1.5768 +  mail_free_body_parameter (&body->parameter);
  1.5769 +  if (body->id) fs_give ((void **) &body->id);
  1.5770 +  if (body->description) fs_give ((void **) &body->description);
  1.5771 +  if (body->disposition.type) fs_give ((void **) &body->disposition.type);
  1.5772 +  if (body->disposition.parameter)
  1.5773 +    mail_free_body_parameter (&body->disposition.parameter);
  1.5774 +  if (body->language) mail_free_stringlist (&body->language);
  1.5775 +  if (body->location) fs_give ((void **) &body->location);
  1.5776 +  if (body->mime.text.data) fs_give ((void **) &body->mime.text.data);
  1.5777 +  if (body->contents.text.data) fs_give ((void **) &body->contents.text.data);
  1.5778 +  if (body->md5) fs_give ((void **) &body->md5);
  1.5779 +  if (mailfreebodysparep && body->sparep)
  1.5780 +      (*mailfreebodysparep) (&body->sparep);
  1.5781 +}
  1.5782 +
  1.5783 +/* Mail garbage collect body parameter
  1.5784 + * Accepts: pointer to body parameter pointer
  1.5785 + */
  1.5786 +
  1.5787 +void mail_free_body_parameter (PARAMETER **parameter)
  1.5788 +{
  1.5789 +  if (*parameter) {		/* only free if exists */
  1.5790 +    if ((*parameter)->attribute) fs_give ((void **) &(*parameter)->attribute);
  1.5791 +    if ((*parameter)->value) fs_give ((void **) &(*parameter)->value);
  1.5792 +				/* run down the list as necessary */
  1.5793 +    mail_free_body_parameter (&(*parameter)->next);
  1.5794 +				/* return body part to free storage */
  1.5795 +    fs_give ((void **) parameter);
  1.5796 +  }
  1.5797 +}
  1.5798 +
  1.5799 +
  1.5800 +/* Mail garbage collect body part
  1.5801 + * Accepts: pointer to body part pointer
  1.5802 + */
  1.5803 +
  1.5804 +void mail_free_body_part (PART **part)
  1.5805 +{
  1.5806 +  if (*part) {			/* only free if exists */
  1.5807 +    mail_free_body_data (&(*part)->body);
  1.5808 +				/* run down the list as necessary */
  1.5809 +    mail_free_body_part (&(*part)->next);
  1.5810 +    fs_give ((void **) part);	/* return body part to free storage */
  1.5811 +  }
  1.5812 +}
  1.5813 +
  1.5814 +/* Mail garbage collect message cache
  1.5815 + * Accepts: mail stream
  1.5816 + *
  1.5817 + * The message cache is set to NIL when this function finishes.
  1.5818 + */
  1.5819 +
  1.5820 +void mail_free_cache (MAILSTREAM *stream)
  1.5821 +{
  1.5822 +				/* do driver specific stuff first */
  1.5823 +  mail_gc (stream,GC_ELT | GC_ENV | GC_TEXTS);
  1.5824 +				/* flush the cache */
  1.5825 +  (*mailcache) (stream,(long) 0,CH_INIT);
  1.5826 +}
  1.5827 +
  1.5828 +
  1.5829 +/* Mail garbage collect cache element
  1.5830 + * Accepts: pointer to cache element pointer
  1.5831 + */
  1.5832 +
  1.5833 +void mail_free_elt (MESSAGECACHE **elt)
  1.5834 +{
  1.5835 +				/* only free if exists and no sharers */
  1.5836 +  if (*elt && !--(*elt)->lockcount) {
  1.5837 +    mail_gc_msg (&(*elt)->private.msg,GC_ENV | GC_TEXTS);
  1.5838 +    if (mailfreeeltsparep && (*elt)->sparep)
  1.5839 +      (*mailfreeeltsparep) (&(*elt)->sparep);
  1.5840 +    fs_give ((void **) elt);
  1.5841 +  }
  1.5842 +  else *elt = NIL;		/* else simply drop pointer */
  1.5843 +}
  1.5844 +
  1.5845 +/* Mail garbage collect envelope
  1.5846 + * Accepts: pointer to envelope pointer
  1.5847 + */
  1.5848 +
  1.5849 +void mail_free_envelope (ENVELOPE **env)
  1.5850 +{
  1.5851 +  if (*env) {			/* only free if exists */
  1.5852 +    if ((*env)->remail) fs_give ((void **) &(*env)->remail);
  1.5853 +    mail_free_address (&(*env)->return_path);
  1.5854 +    if ((*env)->date) fs_give ((void **) &(*env)->date);
  1.5855 +    mail_free_address (&(*env)->from);
  1.5856 +    mail_free_address (&(*env)->sender);
  1.5857 +    mail_free_address (&(*env)->reply_to);
  1.5858 +    if ((*env)->subject) fs_give ((void **) &(*env)->subject);
  1.5859 +    mail_free_address (&(*env)->to);
  1.5860 +    mail_free_address (&(*env)->cc);
  1.5861 +    mail_free_address (&(*env)->bcc);
  1.5862 +    if ((*env)->in_reply_to) fs_give ((void **) &(*env)->in_reply_to);
  1.5863 +    if ((*env)->message_id) fs_give ((void **) &(*env)->message_id);
  1.5864 +    if ((*env)->newsgroups) fs_give ((void **) &(*env)->newsgroups);
  1.5865 +    if ((*env)->followup_to) fs_give ((void **) &(*env)->followup_to);
  1.5866 +    if ((*env)->references) fs_give ((void **) &(*env)->references);
  1.5867 +    if (mailfreeenvelopesparep && (*env)->sparep)
  1.5868 +      (*mailfreeenvelopesparep) (&(*env)->sparep);
  1.5869 +    fs_give ((void **) env);	/* return envelope to free storage */
  1.5870 +  }
  1.5871 +}
  1.5872 +
  1.5873 +
  1.5874 +/* Mail garbage collect address
  1.5875 + * Accepts: pointer to address pointer
  1.5876 + */
  1.5877 +
  1.5878 +void mail_free_address (ADDRESS **address)
  1.5879 +{
  1.5880 +  if (*address) {		/* only free if exists */
  1.5881 +    if ((*address)->personal) fs_give ((void **) &(*address)->personal);
  1.5882 +    if ((*address)->adl) fs_give ((void **) &(*address)->adl);
  1.5883 +    if ((*address)->mailbox) fs_give ((void **) &(*address)->mailbox);
  1.5884 +    if ((*address)->host) fs_give ((void **) &(*address)->host);
  1.5885 +    if ((*address)->error) fs_give ((void **) &(*address)->error);
  1.5886 +    if ((*address)->orcpt.type) fs_give ((void **) &(*address)->orcpt.type);
  1.5887 +    if ((*address)->orcpt.addr) fs_give ((void **) &(*address)->orcpt.addr);
  1.5888 +    mail_free_address (&(*address)->next);
  1.5889 +    fs_give ((void **) address);/* return address to free storage */
  1.5890 +  }
  1.5891 +}
  1.5892 +
  1.5893 +
  1.5894 +/* Mail garbage collect stringlist
  1.5895 + * Accepts: pointer to stringlist pointer
  1.5896 + */
  1.5897 +
  1.5898 +void mail_free_stringlist (STRINGLIST **string)
  1.5899 +{
  1.5900 +  if (*string) {		/* only free if exists */
  1.5901 +    if ((*string)->text.data) fs_give ((void **) &(*string)->text.data);
  1.5902 +    mail_free_stringlist (&(*string)->next);
  1.5903 +    fs_give ((void **) string);	/* return string to free storage */
  1.5904 +  }
  1.5905 +}
  1.5906 +
  1.5907 +/* Mail garbage collect searchpgm
  1.5908 + * Accepts: pointer to searchpgm pointer
  1.5909 + */
  1.5910 +
  1.5911 +void mail_free_searchpgm (SEARCHPGM **pgm)
  1.5912 +{
  1.5913 +  if (*pgm) {			/* only free if exists */
  1.5914 +    mail_free_searchset (&(*pgm)->msgno);
  1.5915 +    mail_free_searchset (&(*pgm)->uid);
  1.5916 +    mail_free_searchor (&(*pgm)->or);
  1.5917 +    mail_free_searchpgmlist (&(*pgm)->not);
  1.5918 +    mail_free_searchheader (&(*pgm)->header);
  1.5919 +    mail_free_stringlist (&(*pgm)->bcc);
  1.5920 +    mail_free_stringlist (&(*pgm)->body);
  1.5921 +    mail_free_stringlist (&(*pgm)->cc);
  1.5922 +    mail_free_stringlist (&(*pgm)->from);
  1.5923 +    mail_free_stringlist (&(*pgm)->keyword);
  1.5924 +    mail_free_stringlist (&(*pgm)->subject);
  1.5925 +    mail_free_stringlist (&(*pgm)->text);
  1.5926 +    mail_free_stringlist (&(*pgm)->to);
  1.5927 +    fs_give ((void **) pgm);	/* return program to free storage */
  1.5928 +  }
  1.5929 +}
  1.5930 +
  1.5931 +
  1.5932 +/* Mail garbage collect searchheader
  1.5933 + * Accepts: pointer to searchheader pointer
  1.5934 + */
  1.5935 +
  1.5936 +void mail_free_searchheader (SEARCHHEADER **hdr)
  1.5937 +{
  1.5938 +  if (*hdr) {			/* only free if exists */
  1.5939 +    if ((*hdr)->line.data) fs_give ((void **) &(*hdr)->line.data);
  1.5940 +    if ((*hdr)->text.data) fs_give ((void **) &(*hdr)->text.data);
  1.5941 +    mail_free_searchheader (&(*hdr)->next);
  1.5942 +    fs_give ((void **) hdr);	/* return header to free storage */
  1.5943 +  }
  1.5944 +}
  1.5945 +
  1.5946 +
  1.5947 +/* Mail garbage collect searchset
  1.5948 + * Accepts: pointer to searchset pointer
  1.5949 + */
  1.5950 +
  1.5951 +void mail_free_searchset (SEARCHSET **set)
  1.5952 +{
  1.5953 +  if (*set) {			/* only free if exists */
  1.5954 +    mail_free_searchset (&(*set)->next);
  1.5955 +    fs_give ((void **) set);	/* return set to free storage */
  1.5956 +  }
  1.5957 +}
  1.5958 +
  1.5959 +/* Mail garbage collect searchor
  1.5960 + * Accepts: pointer to searchor pointer
  1.5961 + */
  1.5962 +
  1.5963 +void mail_free_searchor (SEARCHOR **orl)
  1.5964 +{
  1.5965 +  if (*orl) {			/* only free if exists */
  1.5966 +    mail_free_searchpgm (&(*orl)->first);
  1.5967 +    mail_free_searchpgm (&(*orl)->second);
  1.5968 +    mail_free_searchor (&(*orl)->next);
  1.5969 +    fs_give ((void **) orl);	/* return searchor to free storage */
  1.5970 +  }
  1.5971 +}
  1.5972 +
  1.5973 +
  1.5974 +/* Mail garbage collect search program list
  1.5975 + * Accepts: pointer to searchpgmlist pointer
  1.5976 + */
  1.5977 +
  1.5978 +void mail_free_searchpgmlist (SEARCHPGMLIST **pgl)
  1.5979 +{
  1.5980 +  if (*pgl) {			/* only free if exists */
  1.5981 +    mail_free_searchpgm (&(*pgl)->pgm);
  1.5982 +    mail_free_searchpgmlist (&(*pgl)->next);
  1.5983 +    fs_give ((void **) pgl);	/* return searchpgmlist to free storage */
  1.5984 +  }
  1.5985 +}
  1.5986 +
  1.5987 +
  1.5988 +/* Mail garbage collect namespace
  1.5989 + * Accepts: poiner to namespace
  1.5990 + */
  1.5991 +
  1.5992 +void mail_free_namespace (NAMESPACE **n)
  1.5993 +{
  1.5994 +  if (*n) {
  1.5995 +    fs_give ((void **) &(*n)->name);
  1.5996 +    mail_free_namespace (&(*n)->next);
  1.5997 +    mail_free_body_parameter (&(*n)->param);
  1.5998 +    fs_give ((void **) n);	/* return namespace to free storage */
  1.5999 +  }
  1.6000 +}
  1.6001 +
  1.6002 +/* Mail garbage collect sort program
  1.6003 + * Accepts: pointer to sortpgm pointer
  1.6004 + */
  1.6005 +
  1.6006 +void mail_free_sortpgm (SORTPGM **pgm)
  1.6007 +{
  1.6008 +  if (*pgm) {			/* only free if exists */
  1.6009 +    mail_free_sortpgm (&(*pgm)->next);
  1.6010 +    fs_give ((void **) pgm);	/* return sortpgm to free storage */
  1.6011 +  }
  1.6012 +}
  1.6013 +
  1.6014 +
  1.6015 +/* Mail garbage collect thread node
  1.6016 + * Accepts: pointer to threadnode pointer
  1.6017 + */
  1.6018 +
  1.6019 +void mail_free_threadnode (THREADNODE **thr)
  1.6020 +{
  1.6021 +  if (*thr) {			/* only free if exists */
  1.6022 +    mail_free_threadnode (&(*thr)->branch);
  1.6023 +    mail_free_threadnode (&(*thr)->next);
  1.6024 +    fs_give ((void **) thr);	/* return threadnode to free storage */
  1.6025 +  }
  1.6026 +}
  1.6027 +
  1.6028 +
  1.6029 +/* Mail garbage collect acllist
  1.6030 + * Accepts: pointer to acllist pointer
  1.6031 + */
  1.6032 +
  1.6033 +void mail_free_acllist (ACLLIST **al)
  1.6034 +{
  1.6035 +  if (*al) {			/* only free if exists */
  1.6036 +    if ((*al)->identifier) fs_give ((void **) &(*al)->identifier);
  1.6037 +    if ((*al)->rights) fs_give ((void **) &(*al)->rights);
  1.6038 +    mail_free_acllist (&(*al)->next);
  1.6039 +    fs_give ((void **) al);	/* return acllist to free storage */
  1.6040 +  }
  1.6041 +}
  1.6042 +
  1.6043 +
  1.6044 +/* Mail garbage collect quotalist
  1.6045 + * Accepts: pointer to quotalist pointer
  1.6046 + */
  1.6047 +
  1.6048 +void mail_free_quotalist (QUOTALIST **ql)
  1.6049 +{
  1.6050 +  if (*ql) {			/* only free if exists */
  1.6051 +    if ((*ql)->name) fs_give ((void **) &(*ql)->name);
  1.6052 +    mail_free_quotalist (&(*ql)->next);
  1.6053 +    fs_give ((void **) ql);	/* return quotalist to free storage */
  1.6054 +  }
  1.6055 +}
  1.6056 +
  1.6057 +/* Link authenicator
  1.6058 + * Accepts: authenticator to add to list
  1.6059 + */
  1.6060 +
  1.6061 +void auth_link (AUTHENTICATOR *auth)
  1.6062 +{
  1.6063 +  if (!auth->valid || (*auth->valid) ()) {
  1.6064 +    AUTHENTICATOR **a = &mailauthenticators;
  1.6065 +    while (*a) a = &(*a)->next;	/* find end of list of authenticators */
  1.6066 +    *a = auth;			/* put authenticator at the end */
  1.6067 +    auth->next = NIL;		/* this authenticator is the end of the list */
  1.6068 +  }
  1.6069 +}
  1.6070 +
  1.6071 +
  1.6072 +/* Authenticate access
  1.6073 + * Accepts: mechanism name
  1.6074 + *	    responder function
  1.6075 + *	    argument count
  1.6076 + *	    argument vector
  1.6077 + * Returns: authenticated user name or NIL
  1.6078 + */
  1.6079 +
  1.6080 +char *mail_auth (char *mechanism,authresponse_t resp,int argc,char *argv[])
  1.6081 +{
  1.6082 +  AUTHENTICATOR *auth;
  1.6083 +  for (auth = mailauthenticators; auth; auth = auth->next)
  1.6084 +    if (auth->server && !compare_cstring (auth->name,mechanism))
  1.6085 +      return (!(auth->flags & AU_DISABLE) &&
  1.6086 +	      ((auth->flags & AU_SECURE) ||
  1.6087 +	       !mail_parameters (NIL,GET_DISABLEPLAINTEXT,NIL))) ?
  1.6088 +	(*auth->server) (resp,argc,argv) : NIL;
  1.6089 +  return NIL;			/* no authenticator found */
  1.6090 +}
  1.6091 +
  1.6092 +/* Lookup authenticator index
  1.6093 + * Accepts: authenticator index
  1.6094 + * Returns: authenticator, or 0 if not found
  1.6095 + */
  1.6096 +
  1.6097 +AUTHENTICATOR *mail_lookup_auth (unsigned long i)
  1.6098 +{
  1.6099 +  AUTHENTICATOR *auth = mailauthenticators;
  1.6100 +  while (auth && --i) auth = auth->next;
  1.6101 +  return auth;
  1.6102 +}
  1.6103 +
  1.6104 +
  1.6105 +/* Lookup authenticator name
  1.6106 + * Accepts: authenticator name
  1.6107 + *	    required authenticator flags
  1.6108 + * Returns: index in authenticator chain, or 0 if not found
  1.6109 + */
  1.6110 +
  1.6111 +unsigned int mail_lookup_auth_name (char *mechanism,long flags)
  1.6112 +{
  1.6113 +  int i;
  1.6114 +  AUTHENTICATOR *auth;
  1.6115 +  for (i = 1, auth = mailauthenticators; auth; i++, auth = auth->next)
  1.6116 +    if (auth->client && !(flags & ~auth->flags) &&
  1.6117 +	!(auth->flags & AU_DISABLE) && !compare_cstring (auth->name,mechanism))
  1.6118 +      return i;
  1.6119 +  return 0;
  1.6120 +}
  1.6121 +
  1.6122 +/* Standard TCP/IP network driver */
  1.6123 +
  1.6124 +static NETDRIVER tcpdriver = {
  1.6125 +  tcp_open,			/* open connection */
  1.6126 +  tcp_aopen,			/* open preauthenticated connection */
  1.6127 +  tcp_getline,			/* get a line */
  1.6128 +  tcp_getbuffer,		/* get a buffer */
  1.6129 +  tcp_soutr,			/* output pushed data */
  1.6130 +  tcp_sout,			/* output string */
  1.6131 +  tcp_close,			/* close connection */
  1.6132 +  tcp_host,			/* return host name */
  1.6133 +  tcp_remotehost,		/* return remote host name */
  1.6134 +  tcp_port,			/* return port number */
  1.6135 +  tcp_localhost			/* return local host name */
  1.6136 +};
  1.6137 +
  1.6138 +
  1.6139 +/* Network open
  1.6140 + * Accepts: NETMBX specifier to open
  1.6141 + *	    default network driver
  1.6142 + *	    default port
  1.6143 + *	    SSL driver
  1.6144 + *	    SSL service name
  1.6145 + *	    SSL driver port
  1.6146 + * Returns: Network stream if success, else NIL
  1.6147 + */
  1.6148 +
  1.6149 +NETSTREAM *net_open (NETMBX *mb,NETDRIVER *dv,unsigned long port,
  1.6150 +		     NETDRIVER *ssld,char *ssls,unsigned long sslp)
  1.6151 +{
  1.6152 +  NETSTREAM *stream = NIL;
  1.6153 +  char tmp[MAILTMPLEN];
  1.6154 +  unsigned long flags = mb->novalidate ? NET_NOVALIDATECERT : 0;
  1.6155 +  if (strlen (mb->host) >= NETMAXHOST) {
  1.6156 +    sprintf (tmp,"Invalid host name: %.80s",mb->host);
  1.6157 +    MM_LOG (tmp,ERROR);
  1.6158 +  }
  1.6159 +				/* use designated driver if given */
  1.6160 +  else if (dv) stream = net_open_work (dv,mb->host,mb->service,port,mb->port,
  1.6161 +				       flags);
  1.6162 +  else if (mb->sslflag && ssld)	/* use ssl if sslflag lit */
  1.6163 +    stream = net_open_work (ssld,mb->host,ssls,sslp,mb->port,flags);
  1.6164 +				/* if trysslfirst and can open ssl... */
  1.6165 +  else if ((mb->trysslflag || trysslfirst) && ssld &&
  1.6166 +	   (stream = net_open_work (ssld,mb->host,ssls,sslp,mb->port,
  1.6167 +				    flags | NET_SILENT | NET_TRYSSL))) {
  1.6168 +    if (net_sout (stream,"",0)) mb->sslflag = T;
  1.6169 +    else {
  1.6170 +      net_close (stream);	/* flush fake SSL stream */
  1.6171 +      stream = NIL;
  1.6172 +    }
  1.6173 +  }
  1.6174 +				/* default to TCP driver */
  1.6175 +  else stream = net_open_work (&tcpdriver,mb->host,mb->service,port,mb->port,
  1.6176 +			       flags);
  1.6177 +  return stream;
  1.6178 +}
  1.6179 +
  1.6180 +/* Network open worker routine
  1.6181 + * Accepts: network driver
  1.6182 + *	    host name
  1.6183 + *	    service name to look up port
  1.6184 + *	    port number if service name not found
  1.6185 + *	    port number to override service name
  1.6186 + *	    flags (passed on top of port)
  1.6187 + * Returns: Network stream if success, else NIL
  1.6188 + */
  1.6189 +
  1.6190 +NETSTREAM *net_open_work (NETDRIVER *dv,char *host,char *service,
  1.6191 +			  unsigned long port,unsigned long portoverride,
  1.6192 +			  unsigned long flags)
  1.6193 +{
  1.6194 +  NETSTREAM *stream = NIL;
  1.6195 +  void *tstream;
  1.6196 +  if (service && (*service == '*')) {
  1.6197 +    flags |= NET_NOOPENTIMEOUT;	/* mark that no timeout is desired */
  1.6198 +    ++service;			/* no longer need the no timeout indicator */
  1.6199 +  }
  1.6200 +  if (portoverride) {		/* explicit port number? */
  1.6201 +    service = NIL;		/* yes, override service name */
  1.6202 +    port = portoverride;	/* use that instead of default port */
  1.6203 +  }
  1.6204 +  if (tstream = (*dv->open) (host,service,port | flags)) {
  1.6205 +    stream = (NETSTREAM *) fs_get (sizeof (NETSTREAM));
  1.6206 +    stream->stream = tstream;
  1.6207 +    stream->dtb = dv;
  1.6208 +  }
  1.6209 +  return stream;
  1.6210 +}
  1.6211 +
  1.6212 +
  1.6213 +/* Network authenticated open
  1.6214 + * Accepts: network driver
  1.6215 + *	    NETMBX specifier
  1.6216 + *	    service specifier
  1.6217 + *	    return user name buffer
  1.6218 + * Returns: Network stream if success else NIL
  1.6219 + */
  1.6220 +
  1.6221 +NETSTREAM *net_aopen (NETDRIVER *dv,NETMBX *mb,char *service,char *user)
  1.6222 +{
  1.6223 +  NETSTREAM *stream = NIL;
  1.6224 +  void *tstream;
  1.6225 +  if (!dv) dv = &tcpdriver;	/* default to TCP driver */
  1.6226 +  if (tstream = (*dv->aopen) (mb,service,user)) {
  1.6227 +    stream = (NETSTREAM *) fs_get (sizeof (NETSTREAM));
  1.6228 +    stream->stream = tstream;
  1.6229 +    stream->dtb = dv;
  1.6230 +  }
  1.6231 +  return stream;
  1.6232 +}
  1.6233 +
  1.6234 +/* Network receive line
  1.6235 + * Accepts: Network stream
  1.6236 + * Returns: text line string or NIL if failure
  1.6237 + */
  1.6238 +
  1.6239 +char *net_getline (NETSTREAM *stream)
  1.6240 +{
  1.6241 +  return (*stream->dtb->getline) (stream->stream);
  1.6242 +}
  1.6243 +
  1.6244 +
  1.6245 +/* Network receive buffer
  1.6246 + * Accepts: Network stream (must be void * for use as readfn_t)
  1.6247 + *	    size in bytes
  1.6248 + *	    buffer to read into
  1.6249 + * Returns: T if success, NIL otherwise
  1.6250 + */
  1.6251 +
  1.6252 +long net_getbuffer (void *st,unsigned long size,char *buffer)
  1.6253 +{
  1.6254 +  NETSTREAM *stream = (NETSTREAM *) st;
  1.6255 +  return (*stream->dtb->getbuffer) (stream->stream,size,buffer);
  1.6256 +}
  1.6257 +
  1.6258 +
  1.6259 +/* Network send null-terminated string
  1.6260 + * Accepts: Network stream
  1.6261 + *	    string pointer
  1.6262 + * Returns: T if success else NIL
  1.6263 + */
  1.6264 +
  1.6265 +long net_soutr (NETSTREAM *stream,char *string)
  1.6266 +{
  1.6267 +  return (*stream->dtb->soutr) (stream->stream,string);
  1.6268 +}
  1.6269 +
  1.6270 +
  1.6271 +/* Network send string
  1.6272 + * Accepts: Network stream
  1.6273 + *	    string pointer
  1.6274 + *	    byte count
  1.6275 + * Returns: T if success else NIL
  1.6276 + */
  1.6277 +
  1.6278 +long net_sout (NETSTREAM *stream,char *string,unsigned long size)
  1.6279 +{
  1.6280 +  return (*stream->dtb->sout) (stream->stream,string,size);
  1.6281 +}
  1.6282 +
  1.6283 +/* Network close
  1.6284 + * Accepts: Network stream
  1.6285 + */
  1.6286 +
  1.6287 +void net_close (NETSTREAM *stream)
  1.6288 +{
  1.6289 +  if (stream->stream) (*stream->dtb->close) (stream->stream);
  1.6290 +  fs_give ((void **) &stream);
  1.6291 +}
  1.6292 +
  1.6293 +
  1.6294 +/* Network get host name
  1.6295 + * Accepts: Network stream
  1.6296 + * Returns: host name for this stream
  1.6297 + */
  1.6298 +
  1.6299 +char *net_host (NETSTREAM *stream)
  1.6300 +{
  1.6301 +  return (*stream->dtb->host) (stream->stream);
  1.6302 +}
  1.6303 +
  1.6304 +
  1.6305 +/* Network get remote host name
  1.6306 + * Accepts: Network stream
  1.6307 + * Returns: host name for this stream
  1.6308 + */
  1.6309 +
  1.6310 +char *net_remotehost (NETSTREAM *stream)
  1.6311 +{
  1.6312 +  return (*stream->dtb->remotehost) (stream->stream);
  1.6313 +}
  1.6314 +
  1.6315 +/* Network return port for this stream
  1.6316 + * Accepts: Network stream
  1.6317 + * Returns: port number for this stream
  1.6318 + */
  1.6319 +
  1.6320 +unsigned long net_port (NETSTREAM *stream)
  1.6321 +{
  1.6322 +  return (*stream->dtb->port) (stream->stream);
  1.6323 +}
  1.6324 +
  1.6325 +
  1.6326 +/* Network get local host name
  1.6327 + * Accepts: Network stream
  1.6328 + * Returns: local host name
  1.6329 + */
  1.6330 +
  1.6331 +char *net_localhost (NETSTREAM *stream)
  1.6332 +{
  1.6333 +  return (*stream->dtb->localhost) (stream->stream);
  1.6334 +}

UW-IMAP'd extensions by yuuji