shell bypass 403
/* ========================================================================
* Copyright 1988-2008 University of Washington
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* ========================================================================
*/
/*
* Program: Mailbox Access routines
*
* Author: Mark Crispin
* UW Technology
* University of Washington
* Seattle, WA 98195
* Internet: MRC@Washington.EDU
*
* Date: 22 November 1989
* Last Edited: 15 April 2008
*/
#include <ctype.h>
#include <stdio.h>
#include <time.h>
#include "c-client.h"
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";
/* c-client global data */
/* version of this library */
static char *mailcclientversion = CCLIENTVERSION;
/* list of mail drivers */
static DRIVER *maildrivers = NIL;
/* list of authenticators */
static AUTHENTICATOR *mailauthenticators = NIL;
/* SSL driver pointer */
static NETDRIVER *mailssldriver = NIL;
/* pointer to alternate gets function */
static mailgets_t mailgets = NIL;
/* pointer to read progress function */
static readprogress_t mailreadprogress = NIL;
/* mail cache manipulation function */
static mailcache_t mailcache = mm_cache;
/* RFC-822 output generator */
static rfc822out_t mail822out = NIL;
/* RFC-822 output generator (new style) */
static rfc822outfull_t mail822outfull = NIL;
/* SMTP verbose callback */
static smtpverbose_t mailsmtpverbose = mm_dlog;
/* proxy copy routine */
static mailproxycopy_t mailproxycopy = NIL;
/* RFC-822 external line parse */
static parseline_t mailparseline = NIL;
/* RFC-822 external phrase parser */
static parsephrase_t mailparsephrase = NIL;
static kinit_t mailkinit = NIL; /* application kinit callback */
/* note network sent command */
static sendcommand_t mailsendcommand = NIL;
/* newsrc file name decision function */
static newsrcquery_t mailnewsrcquery = NIL;
/* ACL results callback */
static getacl_t mailaclresults = NIL;
/* list rights results callback */
static listrights_t maillistrightsresults = NIL;
/* my rights results callback */
static myrights_t mailmyrightsresults = NIL;
/* quota results callback */
static quota_t mailquotaresults = NIL;
/* quota root results callback */
static quotaroot_t mailquotarootresults = NIL;
/* sorted results callback */
static sortresults_t mailsortresults = NIL;
/* threaded results callback */
static threadresults_t mailthreadresults = NIL;
/* COPY UID results */
static copyuid_t mailcopyuid = NIL;
/* APPEND UID results */
static appenduid_t mailappenduid = NIL;
/* free elt extra stuff callback */
static freeeltsparep_t mailfreeeltsparep = NIL;
/* free envelope extra stuff callback */
static freeenvelopesparep_t mailfreeenvelopesparep = NIL;
/* free body extra stuff callback */
static freebodysparep_t mailfreebodysparep = NIL;
/* free stream extra stuff callback */
static freestreamsparep_t mailfreestreamsparep = NIL;
/* SSL start routine */
static sslstart_t mailsslstart = NIL;
/* SSL certificate query */
static sslcertificatequery_t mailsslcertificatequery = NIL;
/* SSL client certificate */
static sslclientcert_t mailsslclientcert = NIL;
/* SSL client private key */
static sslclientkey_t mailsslclientkey = NIL;
/* SSL failure notify */
static sslfailure_t mailsslfailure = NIL;
/* snarf interval */
static long mailsnarfinterval = 60;
/* snarf preservation */
static long mailsnarfpreserve = NIL;
/* newsrc name uses canonical host */
static long mailnewsrccanon = LONGT;
/* supported threaders */
static THREADER mailthreadordsub = {
"ORDEREDSUBJECT",mail_thread_orderedsubject,NIL
};
static THREADER mailthreadlist = {
"REFERENCES",mail_thread_references,&mailthreadordsub
};
/* server name */
static char *servicename = "unknown";
/* server externally-set authentication ID */
static char *externalauthid = NIL;
static int expungeatping = T; /* mail_ping() may call mm_expunged() */
static int trysslfirst = NIL; /* always try SSL first */
static int notimezones = NIL; /* write timezones in "From " header */
static int trustdns = T; /* do DNS canonicalization */
static int saslusesptrname = T; /* SASL uses name from DNS PTR lookup */
/* trustdns also must be set */
static int debugsensitive = NIL;/* debug telemetry includes sensitive data */
/* Default mail cache handler
* Accepts: pointer to cache handle
* message number
* caching function
* Returns: cache data
*/
void *mm_cache (MAILSTREAM *stream,unsigned long msgno,long op)
{
size_t n;
void *ret = NIL;
unsigned long i;
switch ((int) op) { /* what function? */
case CH_INIT: /* initialize cache */
if (stream->cache) { /* flush old cache contents */
while (stream->cachesize) {
mm_cache (stream,stream->cachesize,CH_FREE);
mm_cache (stream,stream->cachesize--,CH_FREESORTCACHE);
}
fs_give ((void **) &stream->cache);
fs_give ((void **) &stream->sc);
stream->nmsgs = 0; /* can't have any messages now */
}
break;
case CH_SIZE: /* (re-)size the cache */
if (!stream->cache) { /* have a cache already? */
/* no, create new cache */
n = (stream->cachesize = msgno + CACHEINCREMENT) * sizeof (void *);
stream->cache = (MESSAGECACHE **) memset (fs_get (n),0,n);
stream->sc = (SORTCACHE **) memset (fs_get (n),0,n);
}
/* is existing cache size large neough */
else if (msgno > stream->cachesize) {
i = stream->cachesize; /* remember old size */
n = (stream->cachesize = msgno + CACHEINCREMENT) * sizeof (void *);
fs_resize ((void **) &stream->cache,n);
fs_resize ((void **) &stream->sc,n);
while (i < stream->cachesize) {
stream->cache[i] = NIL;
stream->sc[i++] = NIL;
}
}
break;
case CH_MAKEELT: /* return elt, make if necessary */
if (!stream->cache[msgno - 1])
stream->cache[msgno - 1] = mail_new_cache_elt (msgno);
/* falls through */
case CH_ELT: /* return elt */
ret = (void *) stream->cache[msgno - 1];
break;
case CH_SORTCACHE: /* return sortcache entry, make if needed */
if (!stream->sc[msgno - 1]) stream->sc[msgno - 1] =
(SORTCACHE *) memset (fs_get (sizeof (SORTCACHE)),0,sizeof (SORTCACHE));
ret = (void *) stream->sc[msgno - 1];
break;
case CH_FREE: /* free elt */
mail_free_elt (&stream->cache[msgno - 1]);
break;
case CH_FREESORTCACHE:
if (stream->sc[msgno - 1]) {
if (stream->sc[msgno - 1]->from)
fs_give ((void **) &stream->sc[msgno - 1]->from);
if (stream->sc[msgno - 1]->to)
fs_give ((void **) &stream->sc[msgno - 1]->to);
if (stream->sc[msgno - 1]->cc)
fs_give ((void **) &stream->sc[msgno - 1]->cc);
if (stream->sc[msgno - 1]->subject)
fs_give ((void **) &stream->sc[msgno - 1]->subject);
if (stream->sc[msgno - 1]->unique &&
(stream->sc[msgno - 1]->unique != stream->sc[msgno - 1]->message_id))
fs_give ((void **) &stream->sc[msgno - 1]->unique);
if (stream->sc[msgno - 1]->message_id)
fs_give ((void **) &stream->sc[msgno - 1]->message_id);
if (stream->sc[msgno - 1]->references)
mail_free_stringlist (&stream->sc[msgno - 1]->references);
fs_give ((void **) &stream->sc[msgno - 1]);
}
break;
case CH_EXPUNGE: /* expunge cache slot */
for (i = msgno - 1; msgno < stream->nmsgs; i++,msgno++) {
if (stream->cache[i] = stream->cache[msgno])
stream->cache[i]->msgno = msgno;
stream->sc[i] = stream->sc[msgno];
}
stream->cache[i] = NIL; /* top of cache goes away */
stream->sc[i] = NIL;
break;
default:
fatal ("Bad mm_cache op");
break;
}
return ret;
}
/* Dummy string driver for complete in-memory strings */
static void mail_string_init (STRING *s,void *data,unsigned long size);
static char mail_string_next (STRING *s);
static void mail_string_setpos (STRING *s,unsigned long i);
STRINGDRIVER mail_string = {
mail_string_init, /* initialize string structure */
mail_string_next, /* get next byte in string structure */
mail_string_setpos /* set position in string structure */
};
/* Initialize mail string structure for in-memory string
* Accepts: string structure
* pointer to string
* size of string
*/
static void mail_string_init (STRING *s,void *data,unsigned long size)
{
/* set initial string pointers */
s->chunk = s->curpos = (char *) (s->data = data);
/* and sizes */
s->size = s->chunksize = s->cursize = size;
s->data1 = s->offset = 0; /* never any offset */
}
/* Get next character from string
* Accepts: string structure
* Returns: character, string structure chunk refreshed
*/
static char mail_string_next (STRING *s)
{
return *s->curpos++; /* return the last byte */
}
/* Set string pointer position
* Accepts: string structure
* new position
*/
static void mail_string_setpos (STRING *s,unsigned long i)
{
s->curpos = s->chunk + i; /* set new position */
s->cursize = s->chunksize - i;/* and new size */
}
/* Mail routines
*
* mail_xxx routines are the interface between this module and the outside
* world. Only these routines should be referenced by external callers.
*
* Note that there is an important difference between a "sequence" and a
* "message #" (msgno). A sequence is a string representing a sequence in
* {"n", "n:m", or combination separated by commas} format, whereas a msgno
* is a single integer.
*
*/
/* Mail version check
* Accepts: version
*/
void mail_versioncheck (char *version)
{
/* attempt to protect again wrong .h */
if (strcmp (version,mailcclientversion)) {
char tmp[MAILTMPLEN];
sprintf (tmp,"c-client library version skew, app=%.100s library=%.100s",
version,mailcclientversion);
fatal (tmp);
}
}
/* Mail link driver
* Accepts: driver to add to list
*/
void mail_link (DRIVER *driver)
{
DRIVER **d = &maildrivers;
while (*d) d = &(*d)->next; /* find end of list of drivers */
*d = driver; /* put driver at the end */
driver->next = NIL; /* this driver is the end of the list */
}
/* Mail manipulate driver parameters
* Accepts: mail stream
* function code
* function-dependent value
* Returns: function-dependent return value
*/
void *mail_parameters (MAILSTREAM *stream,long function,void *value)
{
void *r,*ret = NIL;
DRIVER *d;
AUTHENTICATOR *a;
switch ((int) function) {
case SET_INBOXPATH:
fatal ("SET_INBOXPATH not permitted");
case GET_INBOXPATH:
if ((stream || (stream = mail_open (NIL,"INBOX",OP_PROTOTYPE))) &&
stream->dtb) ret = (*stream->dtb->parameters) (function,value);
break;
case SET_THREADERS:
fatal ("SET_THREADERS not permitted");
case GET_THREADERS: /* use stream dtb instead of global */
ret = (stream && stream->dtb) ?
/* KLUDGE ALERT: note stream passed as value */
(*stream->dtb->parameters) (function,stream) : (void *) &mailthreadlist;
break;
case SET_NAMESPACE:
fatal ("SET_NAMESPACE not permitted");
break;
case SET_NEWSRC: /* too late on open stream */
if (stream && stream->dtb && (stream != ((*stream->dtb->open) (NIL))))
fatal ("SET_NEWSRC not permitted");
else ret = env_parameters (function,value);
break;
case GET_NAMESPACE:
case GET_NEWSRC: /* use stream dtb instead of environment */
ret = (stream && stream->dtb) ?
/* KLUDGE ALERT: note stream passed as value */
(*stream->dtb->parameters) (function,stream) :
env_parameters (function,value);
break;
case ENABLE_DEBUG:
fatal ("ENABLE_DEBUG not permitted");
case DISABLE_DEBUG:
fatal ("DISABLE_DEBUG not permitted");
case SET_DIRFMTTEST:
fatal ("SET_DIRFMTTEST not permitted");
case GET_DIRFMTTEST:
if (!(stream && stream->dtb &&
(ret = (*stream->dtb->parameters) (function,NIL))))
fatal ("GET_DIRFMTTEST not permitted");
break;
case SET_DRIVERS:
fatal ("SET_DRIVERS not permitted");
case GET_DRIVERS: /* always return global */
ret = (void *) maildrivers;
break;
case SET_DRIVER:
fatal ("SET_DRIVER not permitted");
case GET_DRIVER:
for (d = maildrivers; d && compare_cstring (d->name,(char *) value);
d = d->next);
ret = (void *) d;
break;
case ENABLE_DRIVER:
for (d = maildrivers; d && compare_cstring (d->name,(char *) value);
d = d->next);
if (ret = (void *) d) d->flags &= ~DR_DISABLE;
break;
case DISABLE_DRIVER:
for (d = maildrivers; d && compare_cstring (d->name,(char *) value);
d = d->next);
if (ret = (void *) d) d->flags |= DR_DISABLE;
break;
case ENABLE_AUTHENTICATOR:
for (a = mailauthenticators;/* scan authenticators */
a && compare_cstring (a->name,(char *) value); a = a->next);
if (ret = (void *) a) a->flags &= ~AU_DISABLE;
break;
case DISABLE_AUTHENTICATOR:
for (a = mailauthenticators;/* scan authenticators */
a && compare_cstring (a->name,(char *) value); a = a->next);
if (ret = (void *) a) a->flags |= AU_DISABLE;
break;
case UNHIDE_AUTHENTICATOR:
for (a = mailauthenticators;/* scan authenticators */
a && compare_cstring (a->name,(char *) value); a = a->next);
if (ret = (void *) a) a->flags &= ~AU_HIDE;
break;
case HIDE_AUTHENTICATOR:
for (a = mailauthenticators;/* scan authenticators */
a && compare_cstring (a->name,(char *) value); a = a->next);
if (ret = (void *) a) a->flags |= AU_HIDE;
break;
case SET_EXTERNALAUTHID:
if (value) { /* setting external authentication ID */
externalauthid = cpystr ((char *) value);
mail_parameters (NIL,UNHIDE_AUTHENTICATOR,"EXTERNAL");
}
else { /* clearing external authentication ID */
if (externalauthid) fs_give ((void **) &externalauthid);
mail_parameters (NIL,HIDE_AUTHENTICATOR,"EXTERNAL");
}
case GET_EXTERNALAUTHID:
ret = (void *) externalauthid;
break;
case SET_GETS:
mailgets = (mailgets_t) value;
case GET_GETS:
ret = (void *) mailgets;
break;
case SET_READPROGRESS:
mailreadprogress = (readprogress_t) value;
case GET_READPROGRESS:
ret = (void *) mailreadprogress;
break;
case SET_CACHE:
mailcache = (mailcache_t) value;
case GET_CACHE:
ret = (void *) mailcache;
break;
case SET_RFC822OUTPUT:
mail822out = (rfc822out_t) value;
case GET_RFC822OUTPUT:
ret = (void *) mail822out;
break;
case SET_RFC822OUTPUTFULL:
mail822outfull = (rfc822outfull_t) value;
case GET_RFC822OUTPUTFULL:
ret = (void *) mail822outfull;
break;
case SET_SMTPVERBOSE:
mailsmtpverbose = (smtpverbose_t) value;
case GET_SMTPVERBOSE:
ret = (void *) mailsmtpverbose;
break;
case SET_MAILPROXYCOPY:
mailproxycopy = (mailproxycopy_t) value;
case GET_MAILPROXYCOPY:
ret = (void *) mailproxycopy;
break;
case SET_PARSELINE:
mailparseline = (parseline_t) value;
case GET_PARSELINE:
ret = (void *) mailparseline;
break;
case SET_PARSEPHRASE:
mailparsephrase = (parsephrase_t) value;
case GET_PARSEPHRASE:
ret = (void *) mailparsephrase;
break;
case SET_NEWSRCQUERY:
mailnewsrcquery = (newsrcquery_t) value;
case GET_NEWSRCQUERY:
ret = (void *) mailnewsrcquery;
break;
case SET_NEWSRCCANONHOST:
mailnewsrccanon = (long) value;
case GET_NEWSRCCANONHOST:
ret = (void *) mailnewsrccanon;
break;
case SET_COPYUID:
mailcopyuid = (copyuid_t) value;
case GET_COPYUID:
ret = (void *) mailcopyuid;
break;
case SET_APPENDUID:
mailappenduid = (appenduid_t) value;
case GET_APPENDUID:
ret = (void *) mailappenduid;
break;
case SET_FREEENVELOPESPAREP:
mailfreeenvelopesparep = (freeenvelopesparep_t) value;
case GET_FREEENVELOPESPAREP:
ret = (void *) mailfreeenvelopesparep;
break;
case SET_FREEELTSPAREP:
mailfreeeltsparep = (freeeltsparep_t) value;
case GET_FREEELTSPAREP:
ret = (void *) mailfreeeltsparep;
break;
case SET_FREESTREAMSPAREP:
mailfreestreamsparep = (freestreamsparep_t) value;
case GET_FREESTREAMSPAREP:
ret = (void *) mailfreestreamsparep;
break;
case SET_FREEBODYSPAREP:
mailfreebodysparep = (freebodysparep_t) value;
case GET_FREEBODYSPAREP:
ret = (void *) mailfreebodysparep;
break;
case SET_SSLSTART:
mailsslstart = (sslstart_t) value;
case GET_SSLSTART:
ret = (void *) mailsslstart;
break;
case SET_SSLCERTIFICATEQUERY:
mailsslcertificatequery = (sslcertificatequery_t) value;
case GET_SSLCERTIFICATEQUERY:
ret = (void *) mailsslcertificatequery;
break;
case SET_SSLCLIENTCERT:
mailsslclientcert = (sslclientcert_t) value;
case GET_SSLCLIENTCERT:
ret = (void *) mailsslclientcert;
break;
case SET_SSLCLIENTKEY:
mailsslclientkey = (sslclientkey_t) value;
case GET_SSLCLIENTKEY:
ret = (void *) mailsslclientkey;
break;
case SET_SSLFAILURE:
mailsslfailure = (sslfailure_t) value;
case GET_SSLFAILURE:
ret = (void *) mailsslfailure;
break;
case SET_KINIT:
mailkinit = (kinit_t) value;
case GET_KINIT:
ret = (void *) mailkinit;
break;
case SET_SENDCOMMAND:
mailsendcommand = (sendcommand_t) value;
case GET_SENDCOMMAND:
ret = (void *) mailsendcommand;
break;
case SET_SERVICENAME:
servicename = (char *) value;
case GET_SERVICENAME:
ret = (void *) servicename;
break;
case SET_EXPUNGEATPING:
expungeatping = (value ? T : NIL);
case GET_EXPUNGEATPING:
ret = (void *) (expungeatping ? VOIDT : NIL);
break;
case SET_SORTRESULTS:
mailsortresults = (sortresults_t) value;
case GET_SORTRESULTS:
ret = (void *) mailsortresults;
break;
case SET_THREADRESULTS:
mailthreadresults = (threadresults_t) value;
case GET_THREADRESULTS:
ret = (void *) mailthreadresults;
break;
case SET_SSLDRIVER:
mailssldriver = (NETDRIVER *) value;
case GET_SSLDRIVER:
ret = (void *) mailssldriver;
break;
case SET_TRYSSLFIRST:
trysslfirst = (value ? T : NIL);
case GET_TRYSSLFIRST:
ret = (void *) (trysslfirst ? VOIDT : NIL);
break;
case SET_NOTIMEZONES:
notimezones = (value ? T : NIL);
case GET_NOTIMEZONES:
ret = (void *) (notimezones ? VOIDT : NIL);
break;
case SET_TRUSTDNS:
trustdns = (value ? T : NIL);
case GET_TRUSTDNS:
ret = (void *) (trustdns ? VOIDT : NIL);
break;
case SET_SASLUSESPTRNAME:
saslusesptrname = (value ? T : NIL);
case GET_SASLUSESPTRNAME:
ret = (void *) (saslusesptrname ? VOIDT : NIL);
break;
case SET_DEBUGSENSITIVE:
debugsensitive = (value ? T : NIL);
case GET_DEBUGSENSITIVE:
ret = (void *) (debugsensitive ? VOIDT : NIL);
break;
case SET_ACL:
mailaclresults = (getacl_t) value;
case GET_ACL:
ret = (void *) mailaclresults;
break;
case SET_LISTRIGHTS:
maillistrightsresults = (listrights_t) value;
case GET_LISTRIGHTS:
ret = (void *) maillistrightsresults;
break;
case SET_MYRIGHTS:
mailmyrightsresults = (myrights_t) value;
case GET_MYRIGHTS:
ret = (void *) mailmyrightsresults;
break;
case SET_QUOTA:
mailquotaresults = (quota_t) value;
case GET_QUOTA:
ret = (void *) mailquotaresults;
break;
case SET_QUOTAROOT:
mailquotarootresults = (quotaroot_t) value;
case GET_QUOTAROOT:
ret = (void *) mailquotarootresults;
break;
case SET_SNARFINTERVAL:
mailsnarfinterval = (long) value;
case GET_SNARFINTERVAL:
ret = (void *) mailsnarfinterval;
break;
case SET_SNARFPRESERVE:
mailsnarfpreserve = (long) value;
case GET_SNARFPRESERVE:
ret = (void *) mailsnarfpreserve;
break;
case SET_SNARFMAILBOXNAME:
if (stream) { /* have a stream? */
if (stream->snarf.name) fs_give ((void **) &stream->snarf.name);
stream->snarf.name = cpystr ((char *) value);
}
else fatal ("SET_SNARFMAILBOXNAME with no stream");
case GET_SNARFMAILBOXNAME:
if (stream) ret = (void *) stream->snarf.name;
break;
default:
if (r = smtp_parameters (function,value)) ret = r;
if (r = env_parameters (function,value)) ret = r;
if (r = tcp_parameters (function,value)) ret = r;
if (stream && stream->dtb) {/* if have stream, do for its driver only */
if (r = (*stream->dtb->parameters) (function,value)) ret = r;
}
/* else do all drivers */
else for (d = maildrivers; d; d = d->next)
if (r = (d->parameters) (function,value)) ret = r;
break;
}
return ret;
}
/* Mail validate mailbox name
* Accepts: MAIL stream
* mailbox name
* purpose string for error message
* Return: driver factory on success, NIL on failure
*/
DRIVER *mail_valid (MAILSTREAM *stream,char *mailbox,char *purpose)
{
char tmp[MAILTMPLEN];
DRIVER *factory = NIL;
/* never allow names with newlines */
if (strpbrk (mailbox,"\015\012")) {
if (purpose) { /* if want an error message */
sprintf (tmp,"Can't %s with such a name",purpose);
MM_LOG (tmp,ERROR);
}
return NIL;
}
/* validate name, find driver factory */
if (strlen (mailbox) < (NETMAXHOST+(NETMAXUSER*2)+NETMAXMBX+NETMAXSRV+50))
for (factory = maildrivers; factory &&
((factory->flags & DR_DISABLE) ||
((factory->flags & DR_LOCAL) && (*mailbox == '{')) ||
!(*factory->valid) (mailbox));
factory = factory->next);
/* validate factory against non-dummy stream */
if (factory && stream && stream->dtb && (stream->dtb != factory) &&
strcmp (stream->dtb->name,"dummy"))
/* factory invalid; if dummy, use stream */
factory = strcmp (factory->name,"dummy") ? NIL : stream->dtb;
if (!factory && purpose) { /* if want an error message */
sprintf (tmp,"Can't %s %.80s: %s",purpose,mailbox,(*mailbox == '{') ?
"invalid remote specification" : "no such mailbox");
MM_LOG (tmp,ERROR);
}
return factory; /* return driver factory */
}
/* Mail validate network mailbox name
* Accepts: mailbox name
* mailbox driver to validate against
* pointer to where to return host name if non-NIL
* pointer to where to return mailbox name if non-NIL
* Returns: driver on success, NIL on failure
*/
DRIVER *mail_valid_net (char *name,DRIVER *drv,char *host,char *mailbox)
{
NETMBX mb;
if (!mail_valid_net_parse (name,&mb) || strcmp (mb.service,drv->name))
return NIL;
if (host) strcpy (host,mb.host);
if (mailbox) strcpy (mailbox,mb.mailbox);
return drv;
}
/* Mail validate network mailbox name
* Accepts: mailbox name
* NETMBX structure to return values
* Returns: T on success, NIL on failure
*/
long mail_valid_net_parse (char *name,NETMBX *mb)
{
return mail_valid_net_parse_work (name,mb,"imap");
}
/* Mail validate network mailbox name worker routine
* Accepts: mailbox name
* NETMBX structure to return values
* default service
* Returns: T on success, NIL on failure
*/
long mail_valid_net_parse_work (char *name,NETMBX *mb,char *service)
{
int i,j;
char c,*s,*t,*v,tmp[MAILTMPLEN],arg[MAILTMPLEN];
/* initialize structure */
memset (mb,'\0',sizeof (NETMBX));
/* must have host specification */
if (*name++ != '{') return NIL;
if (*name == '[') { /* if domain literal, find its ending */
if (!((v = strpbrk (name,"]}")) && (*v++ == ']'))) return NIL;
}
/* find end of host name */
else if (!(v = strpbrk (name,"/:}"))) return NIL;
/* validate length, find mailbox part */
if (!((i = v - name) && (i < NETMAXHOST) && (t = strchr (v,'}')) &&
((j = t - v) < MAILTMPLEN) && (strlen (t+1) < (size_t) NETMAXMBX)))
return NIL; /* invalid mailbox */
strncpy (mb->host,name,i); /* set host name */
strncpy (mb->orighost,name,i);
mb->host[i] = mb->orighost[i] = '\0';
strcpy (mb->mailbox,t+1); /* set mailbox name */
if (t - v) { /* any switches or port specification? */
strncpy (t = tmp,v,j); /* copy it */
tmp[j] = '\0'; /* tie it off */
c = *t++; /* get first delimiter */
do switch (c) { /* act based upon the character */
case ':': /* port specification */
if (mb->port || !(mb->port = strtoul (t,&t,10))) return NIL;
c = t ? *t++ : '\0'; /* get delimiter, advance pointer */
break;
case '/': /* switch */
/* find delimiter */
if (t = strpbrk (s = t,"/:=")) {
c = *t; /* remember delimiter for later */
*t++ = '\0'; /* tie off switch name */
}
else c = '\0'; /* no delimiter */
if (c == '=') { /* parse switches which take arguments */
if (*t == '"') { /* quoted string? */
for (v = arg,i = 0,++t; (c = *t++) != '"';) {
if (!c) return NIL; /* unterminated string */
/* quote next character */
if (c == '\\') c = *t++;
if (!c) return NIL; /* can't quote NUL either */
arg[i++] = c;
}
c = *t++; /* remember delimiter for later */
arg[i] = '\0'; /* tie off argument */
}
else { /* non-quoted argument */
if (t = strpbrk (v = t,"/:")) {
c = *t; /* remember delimiter for later */
*t++ = '\0'; /* tie off switch name */
}
else c = '\0'; /* no delimiter */
i = strlen (v); /* length of argument */
}
if (!compare_cstring (s,"service") && (i < NETMAXSRV) && !*mb->service)
lcase (strcpy (mb->service,v));
else if (!compare_cstring (s,"user") && (i < NETMAXUSER) && !*mb->user)
strcpy (mb->user,v);
else if (!compare_cstring (s,"authuser") && (i < NETMAXUSER) &&
!*mb->authuser) strcpy (mb->authuser,v);
else return NIL;
}
else { /* non-argument switch */
if (!compare_cstring (s,"anonymous")) mb->anoflag = T;
else if (!compare_cstring (s,"debug")) mb->dbgflag = T;
else if (!compare_cstring (s,"readonly")) mb->readonlyflag = T;
else if (!compare_cstring (s,"secure")) mb->secflag = T;
else if (!compare_cstring (s,"norsh")) mb->norsh = T;
else if (!compare_cstring (s,"loser")) mb->loser = T;
else if (!compare_cstring (s,"tls") && !mb->notlsflag)
mb->tlsflag = T;
else if (!compare_cstring (s,"tls-sslv23") && !mb->notlsflag)
mb->tlssslv23 = mb->tlsflag = T;
else if (!compare_cstring (s,"notls") && !mb->tlsflag)
mb->notlsflag = T;
else if (!compare_cstring (s,"tryssl"))
mb->trysslflag = mailssldriver? T : NIL;
else if (mailssldriver && !compare_cstring (s,"ssl") && !mb->tlsflag)
mb->sslflag = mb->notlsflag = T;
else if (mailssldriver && !compare_cstring (s,"novalidate-cert"))
mb->novalidate = T;
/* hack for compatibility with the past */
else if (mailssldriver && !compare_cstring (s,"validate-cert"));
/* service switches below here */
else if (*mb->service) return NIL;
else if (!compare_cstring (s,"imap") ||
!compare_cstring (s,"nntp") ||
!compare_cstring (s,"pop3") ||
!compare_cstring (s,"smtp") ||
!compare_cstring (s,"submit"))
lcase (strcpy (mb->service,s));
else if (!compare_cstring (s,"imap2") ||
!compare_cstring (s,"imap2bis") ||
!compare_cstring (s,"imap4") ||
!compare_cstring (s,"imap4rev1"))
strcpy (mb->service,"imap");
else if (!compare_cstring (s,"pop"))
strcpy (mb->service,"pop3");
else return NIL; /* invalid non-argument switch */
}
break;
default: /* anything else is bogus */
return NIL;
} while (c); /* see if anything more to parse */
}
/* default mailbox name */
if (!*mb->mailbox) strcpy (mb->mailbox,"INBOX");
/* default service name */
if (!*mb->service) strcpy (mb->service,service);
/* /norsh only valid if imap */
if (mb->norsh && strcmp (mb->service,"imap")) return NIL;
return T;
}
/* Mail scan mailboxes for string
* Accepts: mail stream
* reference
* pattern to search
* contents to search
*/
void mail_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
{
int remote = ((*pat == '{') || (ref && *ref == '{'));
DRIVER *d;
if (ref && (strlen (ref) > NETMAXMBX)) {
char tmp[MAILTMPLEN];
sprintf (tmp,"Invalid LIST reference specification: %.80s",ref);
MM_LOG (tmp,ERROR);
return;
}
if (strlen (pat) > NETMAXMBX) {
char tmp[MAILTMPLEN];
sprintf (tmp,"Invalid LIST pattern specification: %.80s",pat);
MM_LOG (tmp,ERROR);
return;
}
if (*pat == '{') ref = NIL; /* ignore reference if pattern is remote */
if (stream) { /* if have a stream, do it for that stream */
if ((d = stream->dtb) && d->scan &&
!(((d->flags & DR_LOCAL) && remote)))
(*d->scan) (stream,ref,pat,contents);
}
/* otherwise do for all DTB's */
else for (d = maildrivers; d; d = d->next)
if (d->scan && !((d->flags & DR_DISABLE) ||
((d->flags & DR_LOCAL) && remote)))
(d->scan) (NIL,ref,pat,contents);
}
/* Mail list mailboxes
* Accepts: mail stream
* reference
* pattern to search
*/
void mail_list (MAILSTREAM *stream,char *ref,char *pat)
{
int remote = ((*pat == '{') || (ref && *ref == '{'));
DRIVER *d = maildrivers;
if (ref && (strlen (ref) > NETMAXMBX)) {
char tmp[MAILTMPLEN];
sprintf (tmp,"Invalid LIST reference specification: %.80s",ref);
MM_LOG (tmp,ERROR);
return;
}
if (strlen (pat) > NETMAXMBX) {
char tmp[MAILTMPLEN];
sprintf (tmp,"Invalid LIST pattern specification: %.80s",pat);
MM_LOG (tmp,ERROR);
return;
}
if (*pat == '{') ref = NIL; /* ignore reference if pattern is remote */
if (stream && stream->dtb) { /* if have a stream, do it for that stream */
if (!(((d = stream->dtb)->flags & DR_LOCAL) && remote))
(*d->list) (stream,ref,pat);
}
/* otherwise do for all DTB's */
else do if (!((d->flags & DR_DISABLE) ||
((d->flags & DR_LOCAL) && remote)))
(d->list) (NIL,ref,pat);
while (d = d->next); /* until at the end */
}
/* Mail list subscribed mailboxes
* Accepts: mail stream
* pattern to search
*/
void mail_lsub (MAILSTREAM *stream,char *ref,char *pat)
{
int remote = ((*pat == '{') || (ref && *ref == '{'));
DRIVER *d = maildrivers;
if (ref && (strlen (ref) > NETMAXMBX)) {
char tmp[MAILTMPLEN];
sprintf (tmp,"Invalid LSUB reference specification: %.80s",ref);
MM_LOG (tmp,ERROR);
return;
}
if (strlen (pat) > NETMAXMBX) {
char tmp[MAILTMPLEN];
sprintf (tmp,"Invalid LSUB pattern specification: %.80s",pat);
MM_LOG (tmp,ERROR);
return;
}
if (*pat == '{') ref = NIL; /* ignore reference if pattern is remote */
if (stream && stream->dtb) { /* if have a stream, do it for that stream */
if (!(((d = stream->dtb)->flags & DR_LOCAL) && remote))
(*d->lsub) (stream,ref,pat);
}
/* otherwise do for all DTB's */
else do if (!((d->flags & DR_DISABLE) ||
((d->flags & DR_LOCAL) && remote)))
(d->lsub) (NIL,ref,pat);
while (d = d->next); /* until at the end */
}
/* Mail subscribe to mailbox
* Accepts: mail stream
* mailbox to add to subscription list
* Returns: T on success, NIL on failure
*/
long mail_subscribe (MAILSTREAM *stream,char *mailbox)
{
DRIVER *factory = mail_valid (stream,mailbox,"subscribe to mailbox");
return factory ?
(factory->subscribe ?
(*factory->subscribe) (stream,mailbox) : sm_subscribe (mailbox)) : NIL;
}
/* Mail unsubscribe to mailbox
* Accepts: mail stream
* mailbox to delete from subscription list
* Returns: T on success, NIL on failure
*/
long mail_unsubscribe (MAILSTREAM *stream,char *mailbox)
{
DRIVER *factory = mail_valid (stream,mailbox,NIL);
return (factory && factory->unsubscribe) ?
(*factory->unsubscribe) (stream,mailbox) : sm_unsubscribe (mailbox);
}
/* Mail create mailbox
* Accepts: mail stream
* mailbox name to create
* Returns: T on success, NIL on failure
*/
long mail_create (MAILSTREAM *stream,char *mailbox)
{
MAILSTREAM *ts;
char *s,*t,tmp[MAILTMPLEN];
size_t i;
DRIVER *d;
/* never allow names with newlines */
if (s = strpbrk (mailbox,"\015\012")) {
MM_LOG ("Can't create mailbox with such a name",ERROR);
return NIL;
}
if (strlen (mailbox) >= (NETMAXHOST+(NETMAXUSER*2)+NETMAXMBX+NETMAXSRV+50)) {
sprintf (tmp,"Can't create %.80s: %s",mailbox,(*mailbox == '{') ?
"invalid remote specification" : "no such mailbox");
MM_LOG (tmp,ERROR);
return NIL;
}
/* create of INBOX invalid */
if (!compare_cstring (mailbox,"INBOX")) {
MM_LOG ("Can't create INBOX",ERROR);
return NIL;
}
/* validate name */
if (s = mail_utf7_valid (mailbox)) {
sprintf (tmp,"Can't create %s: %.80s",s,mailbox);
MM_LOG (tmp,ERROR);
return NIL;
}
/* see if special driver hack */
if ((mailbox[0] == '#') && ((mailbox[1] == 'd') || (mailbox[1] == 'D')) &&
((mailbox[2] == 'r') || (mailbox[2] == 'R')) &&
((mailbox[3] == 'i') || (mailbox[3] == 'I')) &&
((mailbox[4] == 'v') || (mailbox[4] == 'V')) &&
((mailbox[5] == 'e') || (mailbox[5] == 'E')) &&
((mailbox[6] == 'r') || (mailbox[6] == 'R')) && (mailbox[7] == '.')) {
/* copy driver until likely delimiter */
if ((s = strpbrk (t = mailbox+8,"/\\:")) && (i = s - t)) {
strncpy (tmp,t,i);
tmp[i] = '\0';
}
else {
sprintf (tmp,"Can't create mailbox %.80s: bad driver syntax",mailbox);
MM_LOG (tmp,ERROR);
return NIL;
}
for (d = maildrivers; d && strcmp (d->name,tmp); d = d->next);
if (d) mailbox = ++s; /* skip past driver specification */
else {
sprintf (tmp,"Can't create mailbox %.80s: unknown driver",mailbox);
MM_LOG (tmp,ERROR);
return NIL;
}
}
/* use stream if one given or deterministic */
else if ((stream && stream->dtb) ||
(((*mailbox == '{') || (*mailbox == '#')) &&
(stream = mail_open (NIL,mailbox,OP_PROTOTYPE | OP_SILENT))))
d = stream->dtb;
else if ((*mailbox != '{') && (ts = default_proto (NIL))) d = ts->dtb;
else { /* failed utterly */
sprintf (tmp,"Can't create mailbox %.80s: indeterminate format",mailbox);
MM_LOG (tmp,ERROR);
return NIL;
}
return (*d->create) (stream,mailbox);
}
/* Mail delete mailbox
* Accepts: mail stream
* mailbox name to delete
* Returns: T on success, NIL on failure
*/
long mail_delete (MAILSTREAM *stream,char *mailbox)
{
DRIVER *dtb = mail_valid (stream,mailbox,"delete mailbox");
if (!dtb) return NIL;
if (((mailbox[0] == 'I') || (mailbox[0] == 'i')) &&
((mailbox[1] == 'N') || (mailbox[1] == 'n')) &&
((mailbox[2] == 'B') || (mailbox[2] == 'b')) &&
((mailbox[3] == 'O') || (mailbox[3] == 'o')) &&
((mailbox[4] == 'X') || (mailbox[4] == 'x')) && !mailbox[5]) {
MM_LOG ("Can't delete INBOX",ERROR);
return NIL;
}
return SAFE_DELETE (dtb,stream,mailbox);
}
/* Mail rename mailbox
* Accepts: mail stream
* old mailbox name
* new mailbox name
* Returns: T on success, NIL on failure
*/
long mail_rename (MAILSTREAM *stream,char *old,char *newname)
{
char *s,tmp[MAILTMPLEN];
DRIVER *dtb = mail_valid (stream,old,"rename mailbox");
if (!dtb) return NIL;
/* validate name */
if (s = mail_utf7_valid (newname)) {
sprintf (tmp,"Can't rename to %s: %.80s",s,newname);
MM_LOG (tmp,ERROR);
return NIL;
}
if ((*old != '{') && (*old != '#') && mail_valid (NIL,newname,NIL)) {
sprintf (tmp,"Can't rename %.80s: mailbox %.80s already exists",
old,newname);
MM_LOG (tmp,ERROR);
return NIL;
}
return SAFE_RENAME (dtb,stream,old,newname);
}
/* Validate mailbox as Modified UTF-7
* Accepts: candidate mailbox name
* Returns: error string if error, NIL if valid
*/
char *mail_utf7_valid (char *mailbox)
{
char *s;
for (s = mailbox; *s; s++) { /* make sure valid name */
/* reserved for future use with UTF-8 */
if (*s & 0x80) return "mailbox name with 8-bit octet";
/* validate modified UTF-7 */
else if (*s == '&') while (*++s != '-') switch (*s) {
case '\0':
return "unterminated modified UTF-7 name";
case '+': /* valid modified BASE64 */
case ',':
break; /* all OK so far */
default: /* must be alphanumeric */
if (!isalnum (*s)) return "invalid modified UTF-7 name";
break;
}
}
return NIL; /* all OK */
}
/* Mail status of mailbox
* Accepts: mail stream if open on this mailbox
* mailbox name
* status flags
* Returns: T on success, NIL on failure
*/
long mail_status (MAILSTREAM *stream,char *mbx,long flags)
{
DRIVER *dtb = mail_valid (stream,mbx,"get status of mailbox");
if (!dtb) return NIL; /* only if valid */
if (stream && ((dtb != stream->dtb) ||
((dtb->flags & DR_LOCAL) && strcmp (mbx,stream->mailbox) &&
strcmp (mbx,stream->original_mailbox))))
stream = NIL; /* stream not suitable */
return SAFE_STATUS (dtb,stream,mbx,flags);
}
/* Mail status of mailbox default handler
* Accepts: mail stream
* mailbox name
* status flags
* Returns: T on success, NIL on failure
*/
long mail_status_default (MAILSTREAM *stream,char *mbx,long flags)
{
MAILSTATUS status;
unsigned long i;
MAILSTREAM *tstream = NIL;
/* make temporary stream (unless this mbx) */
if (!stream && !(stream = tstream =
mail_open (NIL,mbx,OP_READONLY|OP_SILENT))) return NIL;
status.flags = flags; /* return status values */
status.messages = stream->nmsgs;
status.recent = stream->recent;
if (flags & SA_UNSEEN) /* must search to get unseen messages */
for (i = 1,status.unseen = 0; i <= stream->nmsgs; i++)
if (!mail_elt (stream,i)->seen) status.unseen++;
status.uidnext = stream->uid_last + 1;
status.uidvalidity = stream->uid_validity;
MM_STATUS(stream,mbx,&status);/* pass status to main program */
if (tstream) mail_close (tstream);
return T; /* success */
}
/* Mail open
* Accepts: candidate stream for recycling
* mailbox name
* open options
* Returns: stream to use on success, NIL on failure
*/
MAILSTREAM *mail_open (MAILSTREAM *stream,char *name,long options)
{
int i;
char c,*s,tmp[MAILTMPLEN];
NETMBX mb;
DRIVER *d;
switch (name[0]) { /* see if special handling */
case '#': /* possible special hacks */
if (((name[1] == 'M') || (name[1] == 'm')) &&
((name[2] == 'O') || (name[2] == 'o')) &&
((name[3] == 'V') || (name[3] == 'v')) &&
((name[4] == 'E') || (name[4] == 'e')) && (c = name[5]) &&
(s = strchr (name+6,c)) && (i = s - (name + 6)) && (i < MAILTMPLEN)) {
if (stream = mail_open (stream,s+1,options)) {
strncpy (tmp,name+6,i); /* copy snarf mailbox name */
tmp[i] = '\0'; /* tie off name */
mail_parameters (stream,SET_SNARFMAILBOXNAME,(void *) tmp);
stream->snarf.options = options;
mail_ping (stream); /* do initial snarf */
/* punt if can't do initial snarf */
if (!stream->snarf.time) stream = mail_close (stream);
}
return stream;
}
/* special POP hack */
else if (((name[1] == 'P') || (name[1] == 'p')) &&
((name[2] == 'O') || (name[2] == 'o')) &&
((name[3] == 'P') || (name[3] == 'p')) &&
mail_valid_net_parse_work (name+4,&mb,"pop3") &&
!strcmp (mb.service,"pop3") && !mb.anoflag && !mb.readonlyflag) {
if (stream = mail_open (stream,mb.mailbox,options)) {
sprintf (tmp,"{%.255s",mb.host);
if (mb.port) sprintf (tmp + strlen (tmp),":%lu",mb.port);
if (mb.user[0]) sprintf (tmp + strlen (tmp),"/user=%.64s",mb.user);
if (mb.dbgflag) strcat (tmp,"/debug");
if (mb.secflag) strcat (tmp,"/secure");
if (mb.tlsflag) strcat (tmp,"/tls");
if (mb.notlsflag) strcat (tmp,"/notls");
if (mb.sslflag) strcat (tmp,"/ssl");
if (mb.trysslflag) strcat (tmp,"/tryssl");
if (mb.novalidate) strcat (tmp,"/novalidate-cert");
strcat (tmp,"/pop3/loser}");
mail_parameters (stream,SET_SNARFMAILBOXNAME,(void *) tmp);
mail_ping (stream); /* do initial snarf */
}
return stream; /* return local mailbox stream */
}
else if ((options & OP_PROTOTYPE) &&
((name[1] == 'D') || (name[1] == 'd')) &&
((name[2] == 'R') || (name[2] == 'r')) &&
((name[3] == 'I') || (name[3] == 'i')) &&
((name[4] == 'V') || (name[4] == 'v')) &&
((name[5] == 'E') || (name[5] == 'e')) &&
((name[6] == 'R') || (name[6] == 'r')) && (name[7] == '.')) {
sprintf (tmp,"%.80s",name+8);
/* tie off name at likely delimiter */
if (s = strpbrk (tmp,"/\\:")) *s++ = '\0';
else {
sprintf (tmp,"Can't resolve mailbox %.80s: bad driver syntax",name);
MM_LOG (tmp,ERROR);
return mail_close (stream);
}
for (d = maildrivers; d && compare_cstring (d->name,tmp); d = d->next);
if (d) return (*d->open) (NIL);
sprintf (tmp,"Can't resolve mailbox %.80s: unknown driver",name);
MM_LOG (tmp,ERROR);
return mail_close (stream);
}
/* fall through to default case */
default: /* not special hack (but could be # name */
d = mail_valid (NIL,name,(options & OP_SILENT) ?
(char *) NIL : "open mailbox");
}
return d ? mail_open_work (d,stream,name,options) : stream;
}
/* Mail open worker routine
* Accepts: factory
* candidate stream for recycling
* mailbox name
* open options
* Returns: stream to use on success, NIL on failure
*/
MAILSTREAM *mail_open_work (DRIVER *d,MAILSTREAM *stream,char *name,
long options)
{
int i;
char tmp[MAILTMPLEN];
NETMBX mb;
if (options & OP_PROTOTYPE) return (*d->open) (NIL);
/* name is copied here in case the caller does a re-open using
* stream->mailbox or stream->original_mailbox as the argument.
*/
name = cpystr (name); /* make copy of name */
if (stream) { /* recycling requested? */
if ((stream->dtb == d) && (d->flags & DR_RECYCLE) &&
((d->flags & DR_HALFOPEN) || !(options & OP_HALFOPEN)) &&
mail_usable_network_stream (stream,name)) {
/* yes, checkpoint if needed */
if (d->flags & DR_XPOINT) mail_check (stream);
mail_free_cache (stream); /* clean up stream */
if (stream->mailbox) fs_give ((void **) &stream->mailbox);
if (stream->original_mailbox)
fs_give ((void **) &stream->original_mailbox);
/* flush user flags */
for (i = 0; i < NUSERFLAGS; i++)
if (stream->user_flags[i]) fs_give ((void **) &stream->user_flags[i]);
}
else { /* stream not recycleable, babble if net */
if (!stream->silent && stream->dtb && !(stream->dtb->flags&DR_LOCAL) &&
mail_valid_net_parse (stream->mailbox,&mb)) {
sprintf (tmp,"Closing connection to %.80s",mb.host);
MM_LOG (tmp,(long) NIL);
}
/* flush the old stream */
stream = mail_close (stream);
}
}
/* check if driver does not support halfopen */
else if ((options & OP_HALFOPEN) && !(d->flags & DR_HALFOPEN)) {
fs_give ((void **) &name);
return NIL;
}
/* instantiate new stream if not recycling */
if (!stream) (*mailcache) (stream = (MAILSTREAM *)
memset (fs_get (sizeof (MAILSTREAM)),0,
sizeof (MAILSTREAM)),(long) 0,CH_INIT);
stream->dtb = d; /* set dispatch */
/* set mailbox name */
stream->mailbox = cpystr (stream->original_mailbox = name);
/* initialize stream flags */
stream->inbox = stream->lock = NIL;
stream->debug = (options & OP_DEBUG) ? T : NIL;
stream->rdonly = (options & OP_READONLY) ? T : NIL;
stream->anonymous = (options & OP_ANONYMOUS) ? T : NIL;
stream->scache = (options & OP_SHORTCACHE) ? T : NIL;
stream->silent = (options & OP_SILENT) ? T : NIL;
stream->halfopen = (options & OP_HALFOPEN) ? T : NIL;
stream->secure = (options & OP_SECURE) ? T : NIL;
stream->tryssl = (options & OP_TRYSSL) ? T : NIL;
stream->mulnewsrc = (options & OP_MULNEWSRC) ? T : NIL;
stream->nokod = (options & OP_NOKOD) ? T : NIL;
stream->sniff = (options & OP_SNIFF) ? T : NIL;
stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
stream->perm_answered = stream->perm_draft = stream->kwd_create = NIL;
stream->uid_nosticky = (d->flags & DR_NOSTICKY) ? T : NIL;
stream->uid_last = 0; /* default UID validity */
stream->uid_validity = (unsigned long) time (0);
/* have driver open, flush if failed */
return ((*d->open) (stream)) ? stream : mail_close (stream);
}
/* Mail close
* Accepts: mail stream
* close options
* Returns: NIL, always
*/
MAILSTREAM *mail_close_full (MAILSTREAM *stream,long options)
{
int i;
if (stream) { /* make sure argument given */
/* do the driver's close action */
if (stream->dtb) (*stream->dtb->close) (stream,options);
stream->dtb = NIL; /* resign driver */
if (stream->mailbox) fs_give ((void **) &stream->mailbox);
if (stream->original_mailbox)
fs_give ((void **) &stream->original_mailbox);
if (stream->snarf.name) fs_give ((void **) &stream->snarf.name);
stream->sequence++; /* invalidate sequence */
/* flush user flags */
for (i = 0; i < NUSERFLAGS; i++)
if (stream->user_flags[i]) fs_give ((void **) &stream->user_flags[i]);
mail_free_cache (stream); /* finally free the stream's storage */
if (mailfreestreamsparep && stream->sparep)
(*mailfreestreamsparep) (&stream->sparep);
if (!stream->use) fs_give ((void **) &stream);
}
return NIL;
}
/* Mail make handle
* Accepts: mail stream
* Returns: handle
*
* Handles provide a way to have multiple pointers to a stream yet allow the
* stream's owner to nuke it or recycle it.
*/
MAILHANDLE *mail_makehandle (MAILSTREAM *stream)
{
MAILHANDLE *handle = (MAILHANDLE *) fs_get (sizeof (MAILHANDLE));
handle->stream = stream; /* copy stream */
/* and its sequence */
handle->sequence = stream->sequence;
stream->use++; /* let stream know another handle exists */
return handle;
}
/* Mail release handle
* Accepts: Mail handle
*/
void mail_free_handle (MAILHANDLE **handle)
{
MAILSTREAM *s;
if (*handle) { /* only free if exists */
/* resign stream, flush unreferenced zombies */
if ((!--(s = (*handle)->stream)->use) && !s->dtb) fs_give ((void **) &s);
fs_give ((void **) handle); /* now flush the handle */
}
}
/* Mail get stream handle
* Accepts: Mail handle
* Returns: mail stream or NIL if stream gone
*/
MAILSTREAM *mail_stream (MAILHANDLE *handle)
{
MAILSTREAM *s = handle->stream;
return (s->dtb && (handle->sequence == s->sequence)) ? s : NIL;
}
/* Mail fetch cache element
* Accepts: mail stream
* message # to fetch
* Returns: cache element of this message
* Can also be used to create cache elements for new messages.
*/
MESSAGECACHE *mail_elt (MAILSTREAM *stream,unsigned long msgno)
{
if (msgno < 1 || msgno > stream->nmsgs) {
char tmp[MAILTMPLEN];
sprintf (tmp,"Bad msgno %lu in mail_elt, nmsgs = %lu, mbx=%.80s",
msgno,stream->nmsgs,stream->mailbox ? stream->mailbox : "???");
fatal (tmp);
}
return (MESSAGECACHE *) (*mailcache) (stream,msgno,CH_MAKEELT);
}
/* Mail fetch fast information
* Accepts: mail stream
* sequence
* option flags
*
* Generally, mail_fetch_structure is preferred
*/
void mail_fetch_fast (MAILSTREAM *stream,char *sequence,long flags)
{
/* do the driver's action */
if (stream->dtb && stream->dtb->fast)
(*stream->dtb->fast) (stream,sequence,flags);
}
/* Mail fetch flags
* Accepts: mail stream
* sequence
* option flags
*/
void mail_fetch_flags (MAILSTREAM *stream,char *sequence,long flags)
{
/* do the driver's action */
if (stream->dtb && stream->dtb->msgflags)
(*stream->dtb->msgflags) (stream,sequence,flags);
}
/* Mail fetch message overview
* Accepts: mail stream
* UID sequence to fetch
* pointer to overview return function
*/
void mail_fetch_overview (MAILSTREAM *stream,char *sequence,overview_t ofn)
{
if (stream->dtb && mail_uid_sequence (stream,sequence) &&
!(stream->dtb->overview && (*stream->dtb->overview) (stream,ofn)) &&
mail_ping (stream))
mail_fetch_overview_default (stream,ofn);
}
/* Mail fetch message overview using sequence numbers instead of UIDs
* Accepts: mail stream
* sequence to fetch
* pointer to overview return function
*/
void mail_fetch_overview_sequence (MAILSTREAM *stream,char *sequence,
overview_t ofn)
{
if (stream->dtb && mail_sequence (stream,sequence) &&
!(stream->dtb->overview && (*stream->dtb->overview) (stream,ofn)) &&
mail_ping (stream))
mail_fetch_overview_default (stream,ofn);
}
/* Mail fetch message overview default handler
* Accepts: mail stream with sequence bits lit
* pointer to overview return function
*/
void mail_fetch_overview_default (MAILSTREAM *stream,overview_t ofn)
{
MESSAGECACHE *elt;
ENVELOPE *env;
OVERVIEW ov;
unsigned long i;
ov.optional.lines = 0;
ov.optional.xref = NIL;
for (i = 1; i <= stream->nmsgs; i++)
if (((elt = mail_elt (stream,i))->sequence) &&
(env = mail_fetch_structure (stream,i,NIL,NIL)) && ofn) {
ov.subject = env->subject;
ov.from = env->from;
ov.date = env->date;
ov.message_id = env->message_id;
ov.references = env->references;
ov.optional.octets = elt->rfc822_size;
(*ofn) (stream,mail_uid (stream,i),&ov,i);
}
}
/* Mail fetch message structure
* Accepts: mail stream
* message # to fetch
* pointer to return body
* option flags
* Returns: envelope of this message, body returned in body value
*
* Fetches the "fast" information as well
*/
ENVELOPE *mail_fetch_structure (MAILSTREAM *stream,unsigned long msgno,
BODY **body,long flags)
{
ENVELOPE **env;
BODY **b;
MESSAGECACHE *elt;
char c,*s,*hdr;
unsigned long hdrsize;
STRING bs;
/* do the driver's action if specified */
if (stream->dtb && stream->dtb->structure)
return (*stream->dtb->structure) (stream,msgno,body,flags);
if (flags & FT_UID) { /* UID form of call */
if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
else return NIL; /* must get UID/msgno map first */
}
elt = mail_elt (stream,msgno);/* get elt for real message number */
if (stream->scache) { /* short caching */
if (msgno != stream->msgno){/* garbage collect if not same message */
mail_gc (stream,GC_ENV | GC_TEXTS);
stream->msgno = msgno; /* this is the current message now */
}
env = &stream->env; /* get pointers to envelope and body */
b = &stream->body;
}
else { /* get pointers to elt envelope and body */
env = &elt->private.msg.env;
b = &elt->private.msg.body;
}
if (stream->dtb && ((body && !*b) || !*env || (*env)->incomplete)) {
mail_free_envelope (env); /* flush old envelope and body */
mail_free_body (b);
/* see if need to fetch the whole thing */
if (body || !elt->rfc822_size) {
s = (*stream->dtb->header) (stream,msgno,&hdrsize,flags & ~FT_INTERNAL);
/* make copy in case body fetch smashes it */
hdr = (char *) memcpy (fs_get ((size_t) hdrsize+1),s,(size_t) hdrsize);
hdr[hdrsize] = '\0'; /* tie off header */
(*stream->dtb->text) (stream,msgno,&bs,(flags & ~FT_INTERNAL) | FT_PEEK);
if (!elt->rfc822_size) elt->rfc822_size = hdrsize + SIZE (&bs);
if (body) /* only parse body if requested */
rfc822_parse_msg (env,b,hdr,hdrsize,&bs,BADHOST,stream->dtb->flags);
else
rfc822_parse_msg (env,NIL,hdr,hdrsize,NIL,BADHOST,stream->dtb->flags);
fs_give ((void **) &hdr); /* flush header */
}
else { /* can save memory doing it this way */
hdr = (*stream->dtb->header) (stream,msgno,&hdrsize,flags | FT_INTERNAL);
if (hdrsize) { /* in case null header */
c = hdr[hdrsize]; /* preserve what's there */
hdr[hdrsize] = '\0'; /* tie off header */
rfc822_parse_msg (env,NIL,hdr,hdrsize,NIL,BADHOST,stream->dtb->flags);
hdr[hdrsize] = c; /* restore in case cached data */
}
else *env = mail_newenvelope ();
}
}
/* if need date, have date in envelope? */
if (!elt->day && *env && (*env)->date) mail_parse_date (elt,(*env)->date);
/* sigh, fill in bogus default */
if (!elt->day) elt->day = elt->month = 1;
if (body) *body = *b; /* return the body */
return *env; /* return the envelope */
}
/* Mail mark single message (internal use only)
* Accepts: mail stream
* elt to mark
* fetch flags
*/
static void markseen (MAILSTREAM *stream,MESSAGECACHE *elt,long flags)
{
unsigned long i;
char sequence[20];
MESSAGECACHE *e;
/* non-peeking and needs to set \Seen? */
if (!(flags & FT_PEEK) && !elt->seen) {
if (stream->dtb->flagmsg){ /* driver wants per-message call? */
elt->valid = NIL; /* do pre-alteration driver call */
(*stream->dtb->flagmsg) (stream,elt);
/* set seen, do post-alteration driver call */
elt->seen = elt->valid = T;
(*stream->dtb->flagmsg) (stream,elt);
}
if (stream->dtb->flag) { /* driver wants one-time call? */
/* better safe than sorry, save seq bits */
for (i = 1; i <= stream->nmsgs; i++) {
e = mail_elt (stream,i);
e->private.sequence = e->sequence;
}
/* call driver to set the message */
sprintf (sequence,"%lu",elt->msgno);
(*stream->dtb->flag) (stream,sequence,"\\Seen",ST_SET);
/* restore sequence bits */
for (i = 1; i <= stream->nmsgs; i++) {
e = mail_elt (stream,i);
e->sequence = e->private.sequence;
}
}
/* notify mail program of flag change */
MM_FLAGS (stream,elt->msgno);
}
}
/* Mail fetch message
* Accepts: mail stream
* message # to fetch
* pointer to returned length
* flags
* Returns: message text
*/
char *mail_fetch_message (MAILSTREAM *stream,unsigned long msgno,
unsigned long *len,long flags)
{
GETS_DATA md;
SIZEDTEXT *t;
STRING bs;
MESSAGECACHE *elt;
char *s,*u;
unsigned long i,j;
if (len) *len = 0; /* default return size */
if (flags & FT_UID) { /* UID form of call */
if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
else return ""; /* must get UID/msgno map first */
}
/* initialize message data identifier */
INIT_GETS (md,stream,msgno,"",0,0);
/* is data already cached? */
if ((t = &(elt = mail_elt (stream,msgno))->private.msg.full.text)->data) {
markseen (stream,elt,flags);/* mark message seen */
return mail_fetch_text_return (&md,t,len);
}
if (!stream->dtb) return ""; /* not in cache, must have live driver */
if (stream->dtb->msgdata) return
((*stream->dtb->msgdata) (stream,msgno,"",0,0,NIL,flags) && t->data) ?
mail_fetch_text_return (&md,t,len) : "";
/* ugh, have to do this the crufty way */
u = mail_fetch_header (stream,msgno,NIL,NIL,&i,flags);
/* copy in case text method stomps on it */
s = (char *) memcpy (fs_get ((size_t) i),u,(size_t) i);
if ((*stream->dtb->text) (stream,msgno,&bs,flags)) {
t = &stream->text; /* build combined copy */
if (t->data) fs_give ((void **) &t->data);
t->data = (unsigned char *) fs_get ((t->size = i + SIZE (&bs)) + 1);
if (!elt->rfc822_size) elt->rfc822_size = t->size;
else if (elt->rfc822_size != t->size) {
char tmp[MAILTMPLEN];
sprintf (tmp,"Calculated RFC822.SIZE (%lu) != reported size (%lu)",
t->size,elt->rfc822_size);
mm_log (tmp,WARN); /* bug trap */
}
memcpy (t->data,s,(size_t) i);
for (u = (char *) t->data + i, j = SIZE (&bs); j;) {
memcpy (u,bs.curpos,bs.cursize);
u += bs.cursize; /* update text */
j -= bs.cursize;
bs.curpos += (bs.cursize -1);
bs.cursize = 0;
(*bs.dtb->next) (&bs); /* advance to next buffer's worth */
}
*u = '\0'; /* tie off data */
u = mail_fetch_text_return (&md,t,len);
}
else u = "";
fs_give ((void **) &s); /* finished with copy of header */
return u;
}
/* Mail fetch message header
* Accepts: mail stream
* message # to fetch
* MIME section specifier (#.#.#...#)
* list of lines to fetch
* pointer to returned length
* flags
* Returns: message header in RFC822 format
*
* Note: never calls a mailgets routine
*/
char *mail_fetch_header (MAILSTREAM *stream,unsigned long msgno,char *section,
STRINGLIST *lines,unsigned long *len,long flags)
{
STRING bs;
BODY *b = NIL;
SIZEDTEXT *t = NIL,rt;
MESSAGE *m = NIL;
MESSAGECACHE *elt;
char tmp[MAILTMPLEN];
if (len) *len = 0; /* default return size */
if (section && (strlen (section) > (MAILTMPLEN - 20))) return "";
if (flags & FT_UID) { /* UID form of call */
if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
else return ""; /* must get UID/msgno map first */
}
elt = mail_elt (stream,msgno);/* get cache data */
if (section && *section) { /* nested body header wanted? */
if (!((b = mail_body (stream,msgno,section)) &&
(b->type == TYPEMESSAGE) && !strcmp (b->subtype,"RFC822")))
return ""; /* lose if no body or not MESSAGE/RFC822 */
m = b->nested.msg; /* point to nested message */
}
/* else top-level message header wanted */
else m = &elt->private.msg;
if (m->header.text.data && mail_match_lines (lines,m->lines,flags)) {
if (lines) textcpy (t = &stream->text,&m->header.text);
else t = &m->header.text; /* in cache, and cache is valid */
markseen (stream,elt,flags);/* mark message seen */
}
else if (stream->dtb) { /* not in cache, has live driver? */
if (stream->dtb->msgdata) { /* has driver section fetch? */
/* build driver section specifier */
if (section && *section) sprintf (tmp,"%s.HEADER",section);
else strcpy (tmp,"HEADER");
if ((*stream->dtb->msgdata) (stream,msgno,tmp,0,0,lines,flags)) {
t = &m->header.text; /* fetch data */
/* don't need to postprocess lines */
if (m->lines) lines = NIL;
else if (lines) textcpy (t = &stream->text,&m->header.text);
}
}
else if (b) { /* nested body wanted? */
if (stream->private.search.text) {
rt.data = (unsigned char *) stream->private.search.text +
b->nested.msg->header.offset;
rt.size = b->nested.msg->header.text.size;
t = &rt;
}
else if ((*stream->dtb->text) (stream,msgno,&bs,flags & ~FT_INTERNAL)) {
if ((bs.dtb->next == mail_string_next) && !lines) {
rt.data = (unsigned char *) bs.curpos + b->nested.msg->header.offset;
rt.size = b->nested.msg->header.text.size;
if (stream->private.search.string)
stream->private.search.text = bs.curpos;
t = &rt; /* special hack to avoid extra copy */
}
else textcpyoffstring (t = &stream->text,&bs,
b->nested.msg->header.offset,
b->nested.msg->header.text.size);
}
}
else { /* top-level header fetch */
/* mark message seen */
markseen (stream,elt,flags);
if (rt.data = (unsigned char *)
(*stream->dtb->header) (stream,msgno,&rt.size,flags)) {
/* make a safe copy if need to filter */
if (lines) textcpy (t = &stream->text,&rt);
else t = &rt; /* top level header */
}
}
}
if (!t || !t->data) return "";/* error if no string */
/* filter headers if requested */
if (lines) t->size = mail_filter ((char *) t->data,t->size,lines,flags);
if (len) *len = t->size; /* return size if requested */
return (char *) t->data; /* and text */
}
/* Mail fetch message text
* Accepts: mail stream
* message # to fetch
* MIME section specifier (#.#.#...#)
* pointer to returned length
* flags
* Returns: message text
*/
char *mail_fetch_text (MAILSTREAM *stream,unsigned long msgno,char *section,
unsigned long *len,long flags)
{
GETS_DATA md;
PARTTEXT *p;
STRING bs;
MESSAGECACHE *elt;
BODY *b = NIL;
char tmp[MAILTMPLEN];
unsigned long i;
if (len) *len = 0; /* default return size */
memset (&stream->private.string,NIL,sizeof (STRING));
if (section && (strlen (section) > (MAILTMPLEN - 20))) return "";
if (flags & FT_UID) { /* UID form of call */
if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
else return ""; /* must get UID/msgno map first */
}
elt = mail_elt (stream,msgno);/* get cache data */
if (section && *section) { /* nested body text wanted? */
if (!((b = mail_body (stream,msgno,section)) &&
(b->type == TYPEMESSAGE) && !strcmp (b->subtype,"RFC822")))
return ""; /* lose if no body or not MESSAGE/RFC822 */
p = &b->nested.msg->text; /* point at nested message */
/* build IMAP-format section specifier */
sprintf (tmp,"%s.TEXT",section);
flags &= ~FT_INTERNAL; /* can't win with this set */
}
else { /* top-level message text wanted */
p = &elt->private.msg.text;
strcpy (tmp,"TEXT");
}
/* initialize message data identifier */
INIT_GETS (md,stream,msgno,section,0,0);
if (p->text.data) { /* is data already cached? */
markseen (stream,elt,flags);/* mark message seen */
return mail_fetch_text_return (&md,&p->text,len);
}
if (!stream->dtb) return ""; /* not in cache, must have live driver */
if (stream->dtb->msgdata) return
((*stream->dtb->msgdata) (stream,msgno,tmp,0,0,NIL,flags) && p->text.data)?
mail_fetch_text_return (&md,&p->text,len) : "";
if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) return "";
if (section && *section) { /* nested is more complex */
SETPOS (&bs,p->offset);
i = p->text.size; /* just want this much */
}
else i = SIZE (&bs); /* want entire text */
return mail_fetch_string_return (&md,&bs,i,len,flags);
}
/* Mail fetch message body part MIME headers
* Accepts: mail stream
* message # to fetch
* MIME section specifier (#.#.#...#)
* pointer to returned length
* flags
* Returns: message text
*/
char *mail_fetch_mime (MAILSTREAM *stream,unsigned long msgno,char *section,
unsigned long *len,long flags)
{
PARTTEXT *p;
STRING bs;
BODY *b;
char tmp[MAILTMPLEN];
if (len) *len = 0; /* default return size */
if (section && (strlen (section) > (MAILTMPLEN - 20))) return "";
if (flags & FT_UID) { /* UID form of call */
if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
else return ""; /* must get UID/msgno map first */
}
flags &= ~FT_INTERNAL; /* can't win with this set */
if (!(section && *section && (b = mail_body (stream,msgno,section))))
return ""; /* not valid section */
/* in cache? */
if ((p = &b->mime)->text.data) {
/* mark message seen */
markseen (stream,mail_elt (stream,msgno),flags);
if (len) *len = p->text.size;
return (char *) p->text.data;
}
if (!stream->dtb) return ""; /* not in cache, must have live driver */
if (stream->dtb->msgdata) { /* has driver fetch? */
/* build driver section specifier */
sprintf (tmp,"%s.MIME",section);
if ((*stream->dtb->msgdata) (stream,msgno,tmp,0,0,NIL,flags) &&
p->text.data) {
if (len) *len = p->text.size;
return (char *) p->text.data;
}
else return "";
}
if (len) *len = b->mime.text.size;
if (!b->mime.text.size) { /* empty MIME header -- mark seen anyway */
markseen (stream,mail_elt (stream,msgno),flags);
return "";
}
/* have to get it from offset */
if (stream->private.search.text)
return stream->private.search.text + b->mime.offset;
if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) {
if (len) *len = 0;
return "";
}
if (bs.dtb->next == mail_string_next) {
if (stream->private.search.string) stream->private.search.text = bs.curpos;
return bs.curpos + b->mime.offset;
}
return textcpyoffstring (&stream->text,&bs,b->mime.offset,b->mime.text.size);
}
/* Mail fetch message body part
* Accepts: mail stream
* message # to fetch
* MIME section specifier (#.#.#...#)
* pointer to returned length
* flags
* Returns: message body
*/
char *mail_fetch_body (MAILSTREAM *stream,unsigned long msgno,char *section,
unsigned long *len,long flags)
{
GETS_DATA md;
PARTTEXT *p;
STRING bs;
BODY *b;
SIZEDTEXT *t;
char *s,tmp[MAILTMPLEN];
memset (&stream->private.string,NIL,sizeof (STRING));
if (!(section && *section)) /* top-level text wanted? */
return mail_fetch_message (stream,msgno,len,flags);
else if (strlen (section) > (MAILTMPLEN - 20)) return "";
flags &= ~FT_INTERNAL; /* can't win with this set */
/* initialize message data identifier */
INIT_GETS (md,stream,msgno,section,0,0);
/* kludge for old section 0 header */
if (!strcmp (s = strcpy (tmp,section),"0") ||
((s = strstr (tmp,".0")) && !s[2])) {
SIZEDTEXT ht;
*s = '\0'; /* tie off section */
/* this silly way so it does mailgets */
ht.data = (unsigned char *) mail_fetch_header (stream,msgno,
tmp[0] ? tmp : NIL,NIL,
&ht.size,flags);
/* may have UIDs here */
md.flags = (flags & FT_UID) ? MG_UID : NIL;
return mail_fetch_text_return (&md,&ht,len);
}
if (len) *len = 0; /* default return size */
if (flags & FT_UID) { /* UID form of call */
if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
else return ""; /* must get UID/msgno map first */
}
/* must have body */
if (!(b = mail_body (stream,msgno,section))) return "";
/* have cached text? */
if ((t = &(p = &b->contents)->text)->data) {
/* mark message seen */
markseen (stream,mail_elt (stream,msgno),flags);
return mail_fetch_text_return (&md,t,len);
}
if (!stream->dtb) return ""; /* not in cache, must have live driver */
if (stream->dtb->msgdata) return
((*stream->dtb->msgdata)(stream,msgno,section,0,0,NIL,flags) && t->data) ?
mail_fetch_text_return (&md,t,len) : "";
if (len) *len = t->size;
if (!t->size) { /* empty body part -- mark seen anyway */
markseen (stream,mail_elt (stream,msgno),flags);
return "";
}
/* copy body from stringstruct offset */
if (stream->private.search.text)
return stream->private.search.text + p->offset;
if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) {
if (len) *len = 0;
return "";
}
if (bs.dtb->next == mail_string_next) {
if (stream->private.search.string) stream->private.search.text = bs.curpos;
return bs.curpos + p->offset;
}
SETPOS (&bs,p->offset);
return mail_fetch_string_return (&md,&bs,t->size,len,flags);
}
/* Mail fetch partial message text
* Accepts: mail stream
* message # to fetch
* MIME section specifier (#.#.#...#)
* offset of first designed byte or 0 to start at beginning
* maximum number of bytes or 0 for all bytes
* flags
* Returns: T if successful, else NIL
*/
long mail_partial_text (MAILSTREAM *stream,unsigned long msgno,char *section,
unsigned long first,unsigned long last,long flags)
{
GETS_DATA md;
PARTTEXT *p = NIL;
MESSAGECACHE *elt;
STRING bs;
BODY *b;
char tmp[MAILTMPLEN];
unsigned long i;
if (!mailgets) fatal ("mail_partial_text() called without a mailgets!");
if (section && (strlen (section) > (MAILTMPLEN - 20))) return NIL;
if (flags & FT_UID) { /* UID form of call */
if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
else return NIL; /* must get UID/msgno map first */
}
elt = mail_elt (stream,msgno);/* get cache data */
flags &= ~FT_INTERNAL; /* bogus if this is set */
if (section && *section) { /* nested body text wanted? */
if (!((b = mail_body (stream,msgno,section)) &&
(b->type == TYPEMESSAGE) && !strcmp (b->subtype,"RFC822")))
return NIL; /* lose if no body or not MESSAGE/RFC822 */
p = &b->nested.msg->text; /* point at nested message */
/* build IMAP-format section specifier */
sprintf (tmp,"%s.TEXT",section);
}
else { /* else top-level message text wanted */
p = &elt->private.msg.text;
strcpy (tmp,"TEXT");
}
/* initialize message data identifier */
INIT_GETS (md,stream,msgno,tmp,first,last);
if (p->text.data) { /* is data already cached? */
INIT (&bs,mail_string,p->text.data,i = p->text.size);
markseen (stream,elt,flags);/* mark message seen */
}
else { /* else get data from driver */
if (!stream->dtb) return NIL;
if (stream->dtb->msgdata) /* driver will handle this */
return (*stream->dtb->msgdata) (stream,msgno,tmp,first,last,NIL,flags);
if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) return NIL;
if (section && *section) { /* nexted if more complex */
SETPOS (&bs,p->offset); /* offset stringstruct to data */
i = p->text.size; /* maximum size of data */
}
else i = SIZE (&bs); /* just want this much */
}
if (i <= first) i = first = 0;/* first byte is beyond end of text */
/* truncate as needed */
else { /* offset and truncate */
SETPOS (&bs,first + GETPOS (&bs));
i -= first; /* reduced size */
if (last && (i > last)) i = last;
}
/* do the mailgets thing */
(*mailgets) (mail_read,&bs,i,&md);
return T; /* success */
}
/* Mail fetch partial message body part
* Accepts: mail stream
* message # to fetch
* MIME section specifier (#.#.#...#)
* offset of first designed byte or 0 to start at beginning
* maximum number of bytes or 0 for all bytes
* flags
* Returns: T if successful, else NIL
*/
long mail_partial_body (MAILSTREAM *stream,unsigned long msgno,char *section,
unsigned long first,unsigned long last,long flags)
{
GETS_DATA md;
PARTTEXT *p;
STRING bs;
BODY *b;
SIZEDTEXT *t;
unsigned long i;
if (!(section && *section)) /* top-level text wanted? */
return mail_partial_text (stream,msgno,NIL,first,last,flags);
if (!mailgets) fatal ("mail_partial_body() called without a mailgets!");
if (flags & FT_UID) { /* UID form of call */
if (msgno = mail_msgno (stream,msgno)) flags &= ~FT_UID;
else return NIL; /* must get UID/msgno map first */
}
/* must have body */
if (!(b = mail_body (stream,msgno,section))) return NIL;
flags &= ~FT_INTERNAL; /* bogus if this is set */
/* initialize message data identifier */
INIT_GETS (md,stream,msgno,section,first,last);
/* have cached text? */
if ((t = &(p = &b->contents)->text)->data) {
/* mark message seen */
markseen (stream,mail_elt (stream,msgno),flags);
INIT (&bs,mail_string,t->data,i = t->size);
}
else { /* else get data from driver */
if (!stream->dtb) return NIL;
if (stream->dtb->msgdata) /* driver will handle this */
return (*stream->dtb->msgdata) (stream,msgno,section,first,last,NIL,
flags);
if (!(*stream->dtb->text) (stream,msgno,&bs,flags)) return NIL;
if (section && *section) { /* nexted if more complex */
SETPOS (&bs,p->offset); /* offset stringstruct to data */
i = t->size; /* maximum size of data */
}
else i = SIZE (&bs); /* just want this much */
}
if (i <= first) i = first = 0;/* first byte is beyond end of text */
else { /* offset and truncate */
SETPOS (&bs,first + GETPOS (&bs));
i -= first; /* reduced size */
if (last && (i > last)) i = last;
}
/* do the mailgets thing */
(*mailgets) (mail_read,&bs,i,&md);
return T; /* success */
}
/* Mail return message text
* Accepts: identifier data
* sized text
* pointer to returned length
* Returns: text
*/
char *mail_fetch_text_return (GETS_DATA *md,SIZEDTEXT *t,unsigned long *len)
{
STRING bs;
if (len) *len = t->size; /* return size */
if (t->size && mailgets) { /* have to do the mailgets thing? */
/* silly but do it anyway for consistency */
INIT (&bs,mail_string,t->data,t->size);
return (*mailgets) (mail_read,&bs,t->size,md);
}
return t->size ? (char *) t->data : "";
}
/* Mail return message string
* Accepts: identifier data
* stringstruct
* text length
* pointer to returned length
* flags
* Returns: text, or NIL if stringstruct returned
*/
char *mail_fetch_string_return (GETS_DATA *md,STRING *bs,unsigned long i,
unsigned long *len,long flags)
{
char *ret = NIL;
if (len) *len = i; /* return size */
/* return stringstruct hack */
if (flags & FT_RETURNSTRINGSTRUCT) {
memcpy (&md->stream->private.string,bs,sizeof (STRING));
SETPOS (&md->stream->private.string,GETPOS (&md->stream->private.string));
}
/* have to do the mailgets thing? */
else if (mailgets) ret = (*mailgets) (mail_read,bs,i,md);
/* special hack to avoid extra copy */
else if (bs->dtb->next == mail_string_next) ret = bs->curpos;
/* make string copy in memory */
else ret = textcpyoffstring (&md->stream->text,bs,GETPOS (bs),i);
return ret;
}
/* Read data from stringstruct
* Accepts: stringstruct
* size of data to read
* buffer to read into
* Returns: T, always, stringstruct updated
*/
long mail_read (void *stream,unsigned long size,char *buffer)
{
unsigned long i;
STRING *s = (STRING *) stream;
while (size) { /* until satisfied */
memcpy (buffer,s->curpos,i = min (s->cursize,size));
buffer += i; /* update buffer */
size -= i; /* note that we read this much */
s->curpos += --i; /* advance that many spaces minus 1 */
s->cursize -= i;
SNX (s); /* now use SNX to advance the last byte */
}
return T;
}
/* Mail fetch UID
* Accepts: mail stream
* message number
* Returns: UID or zero if dead stream
*/
unsigned long mail_uid (MAILSTREAM *stream,unsigned long msgno)
{
unsigned long uid = mail_elt (stream,msgno)->private.uid;
return uid ? uid :
(stream->dtb && stream->dtb->uid) ? (*stream->dtb->uid) (stream,msgno) : 0;
}
/* Mail fetch msgno from UID
* Accepts: mail stream
* UID
* Returns: msgno or zero if failed
*/
unsigned long mail_msgno (MAILSTREAM *stream,unsigned long uid)
{
unsigned long msgno,delta,first,firstuid,last,lastuid,middle,miduid;
if (stream->dtb) { /* active stream? */
if (stream->dtb->msgno) /* direct way */
return (*stream->dtb->msgno) (stream,uid);
else if (stream->dtb->uid) {/* indirect way */
/* Placeholder for now, since currently there are no drivers which
* have a uid method but not a msgno method
*/
for (msgno = 1; msgno <= stream->nmsgs; msgno++)
if ((*stream->dtb->uid) (stream,msgno) == uid) return msgno;
}
/* binary search since have full map */
else for (first = 1,last = stream->nmsgs, delta = (first <= last) ? 1 : 0;
delta &&
(uid >= (firstuid = mail_elt (stream,first)->private.uid)) &&
(uid <= (lastuid = mail_elt (stream,last)->private.uid));) {
/* done if match at an endpoint */
if (uid == firstuid) return first;
if (uid == lastuid) return last;
/* have anything between endpoints? */
if (delta = ((last - first) / 2)) {
if ((miduid = mail_elt (stream,middle = first + delta)->private.uid)
== uid)
return middle; /* found match in middle */
else if (uid < miduid) last = middle - 1;
else first = middle + 1;
}
}
}
else { /* dead stream, do linear search for UID */
for (msgno = 1; msgno <= stream->nmsgs; msgno++)
if (mail_elt (stream,msgno)->private.uid == uid) return msgno;
}
return 0; /* didn't find the UID anywhere */
}
/* Mail fetch From string for menu
* Accepts: destination string
* mail stream
* message # to fetch
* desired string length
* Returns: string of requested length
*/
void mail_fetchfrom (char *s,MAILSTREAM *stream,unsigned long msgno,
long length)
{
char *t;
char tmp[MAILTMPLEN];
ENVELOPE *env = mail_fetchenvelope (stream,msgno);
ADDRESS *adr = env ? env->from : NIL;
memset (s,' ',(size_t)length);/* fill it with spaces */
s[length] = '\0'; /* tie off with null */
/* get first from address from envelope */
while (adr && !adr->host) adr = adr->next;
if (adr) { /* if a personal name exists use it */
if (!(t = adr->personal))
sprintf (t = tmp,"%.256s@%.256s",adr->mailbox,adr->host);
memcpy (s,t,(size_t) min (length,(long) strlen (t)));
}
}
/* Mail fetch Subject string for menu
* Accepts: destination string
* mail stream
* message # to fetch
* desired string length
* Returns: string of no more than requested length
*/
void mail_fetchsubject (char *s,MAILSTREAM *stream,unsigned long msgno,
long length)
{
ENVELOPE *env = mail_fetchenvelope (stream,msgno);
memset (s,'\0',(size_t) length+1);
/* copy subject from envelope */
if (env && env->subject) strncpy (s,env->subject,(size_t) length);
else *s = ' '; /* if no subject then just a space */
}
/* Mail modify flags
* Accepts: mail stream
* sequence
* flag(s)
* option flags
*/
void mail_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
{
MESSAGECACHE *elt;
unsigned long i,uf;
long f;
short nf;
if (!stream->dtb) return; /* no-op if no stream */
if ((stream->dtb->flagmsg || !stream->dtb->flag) &&
((flags & ST_UID) ? mail_uid_sequence (stream,sequence) :
mail_sequence (stream,sequence)) &&
((f = mail_parse_flags (stream,flag,&uf)) || uf))
for (i = 1,nf = (flags & ST_SET) ? T : NIL; i <= stream->nmsgs; i++)
if ((elt = mail_elt (stream,i))->sequence) {
struct { /* old flags */
unsigned int valid : 1;
unsigned int seen : 1;
unsigned int deleted : 1;
unsigned int flagged : 1;
unsigned int answered : 1;
unsigned int draft : 1;
unsigned long user_flags;
} old;
old.valid = elt->valid; old.seen = elt->seen;
old.deleted = elt->deleted; old.flagged = elt->flagged;
old.answered = elt->answered; old.draft = elt->draft;
old.user_flags = elt->user_flags;
elt->valid = NIL; /* prepare for flag alteration */
if (stream->dtb->flagmsg) (*stream->dtb->flagmsg) (stream,elt);
if (f&fSEEN) elt->seen = nf;
if (f&fDELETED) elt->deleted = nf;
if (f&fFLAGGED) elt->flagged = nf;
if (f&fANSWERED) elt->answered = nf;
if (f&fDRAFT) elt->draft = nf;
/* user flags */
if (flags & ST_SET) elt->user_flags |= uf;
else elt->user_flags &= ~uf;
elt->valid = T; /* flags now altered */
if ((old.valid != elt->valid) || (old.seen != elt->seen) ||
(old.deleted != elt->deleted) || (old.flagged != elt->flagged) ||
(old.answered != elt->answered) || (old.draft != elt->draft) ||
(old.user_flags != elt->user_flags))
MM_FLAGS (stream,elt->msgno);
if (stream->dtb->flagmsg) (*stream->dtb->flagmsg) (stream,elt);
}
/* call driver once */
if (stream->dtb->flag) (*stream->dtb->flag) (stream,sequence,flag,flags);
}
/* Mail search for messages
* Accepts: mail stream
* character set
* search program
* option flags
* Returns: T if successful, NIL if dead stream, NIL searchpgm or bad charset
*/
long mail_search_full (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,
long flags)
{
unsigned long i;
long ret = NIL;
if (!(flags & SE_RETAIN)) /* clear search vector unless retaining */
for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = NIL;
if (pgm && stream->dtb) /* must have a search program and driver */
ret = (*(stream->dtb->search ? stream->dtb->search : mail_search_default))
(stream,charset,pgm,flags);
/* flush search program if requested */
if (flags & SE_FREE) mail_free_searchpgm (&pgm);
return ret;
}
/* Mail search for messages default handler
* Accepts: mail stream
* character set
* search program
* option flags
* Returns: T if successful, NIL if bad charset
*/
long mail_search_default (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,
long flags)
{
unsigned long i;
char *msg;
/* make sure that charset is good */
if (msg = utf8_badcharset (charset)) {
MM_LOG (msg,ERROR); /* output error */
fs_give ((void **) &msg);
return NIL;
}
utf8_searchpgm (pgm,charset);
for (i = 1; i <= stream->nmsgs; ++i)
if (mail_search_msg (stream,i,NIL,pgm)) {
if (flags & SE_UID) mm_searched (stream,mail_uid (stream,i));
else { /* mark as searched, notify mail program */
mail_elt (stream,i)->searched = T;
if (!stream->silent) mm_searched (stream,i);
}
}
return LONGT; /* search completed */
}
/* Mail ping mailbox
* Accepts: mail stream
* Returns: stream if still open else NIL
*/
long mail_ping (MAILSTREAM *stream)
{
unsigned long i,n,uf,len;
char *s,*f,tmp[MAILTMPLEN],flags[MAILTMPLEN];
MAILSTREAM *snarf;
MESSAGECACHE *elt;
STRING bs;
long ret;
/* do driver action */
if ((ret = ((stream && stream->dtb) ? (stream->dtb->ping) (stream) : NIL)) &&
stream->snarf.name && /* time to snarf? */
/* prohibit faster than once/min */
(time (0) > (time_t) (stream->snarf.time + min(60,mailsnarfinterval))) &&
(snarf = mail_open (NIL,stream->snarf.name,
stream->snarf.options | OP_SILENT))) {
if ((n = snarf->nmsgs) && /* yes, have messages to snarf? */
mail_search_full (snarf,NIL,mail_criteria ("UNDELETED"),SE_FREE)) {
for (i = 1; ret && (i <= n); i++) /* for each message */
if ((elt = mail_elt (snarf,i))->searched &&
(s = mail_fetch_message (snarf,i,&len,FT_PEEK)) && len) {
INIT (&bs,mail_string,s,len);
if (mailsnarfpreserve) {
/* yes, make sure have fast data */
if (!elt->valid || !elt->day) {
sprintf (tmp,"%lu",n);
mail_fetch_fast (snarf,tmp,NIL);
}
/* initialize flag string */
memset (flags,0,MAILTMPLEN);
/* output system flags except \Deleted */
if (elt->seen) strcat (flags," \\Seen");
if (elt->flagged) strcat (flags," \\Flagged");
if (elt->answered) strcat (flags," \\Answered");
if (elt->draft) strcat (flags," \\Draft");
/* any user flags? */
for (uf = elt->user_flags,s = flags + strlen (flags);
uf && (f = stream->user_flags[find_rightmost_bit (&uf)]) &&
((MAILTMPLEN - (s - tmp)) > (long) (2 + strlen (f)));
s += strlen (s)) sprintf (s," %s",f);
ret = mail_append_full (stream,stream->mailbox,flags + 1,
mail_date (tmp,elt),&bs);
}
else ret = mail_append (stream,stream->mailbox,&bs);
if (ret) { /* did snarf succeed? */
/* driver has per-message (or no) flag call */
if (snarf->dtb->flagmsg || !snarf->dtb->flag) {
elt->valid = NIL; /* prepare for flag alteration */
if (snarf->dtb->flagmsg) (*snarf->dtb->flagmsg) (snarf,elt);
/* flags now altered */
elt->deleted = elt->seen = elt->valid = T;
if (snarf->dtb->flagmsg) (*snarf->dtb->flagmsg) (snarf,elt);
}
/* driver has one-time flag call */
if (snarf->dtb->flag) {
sprintf (tmp,"%lu",i);
(*snarf->dtb->flag) (snarf,tmp,"\\Deleted \\Seen",ST_SET);
}
}
else { /* copy failed */
sprintf (tmp,"Unable to move message %lu from %s mailbox",
i,snarf->dtb->name);
mm_log (tmp,WARN);
}
}
}
/* expunge the messages */
mail_close_full (snarf,n ? CL_EXPUNGE : NIL);
stream->snarf.time = (unsigned long) time (0);
/* Even if the snarf failed, we don't want to return NIL if the stream
* is still alive. Or at least that's what we currently think.
*/
/* redo the driver's action */
ret = stream->dtb ? (*stream->dtb->ping) (stream) : NIL;
}
return ret;
}
/* Mail check mailbox
* Accepts: mail stream
*/
void mail_check (MAILSTREAM *stream)
{
/* do the driver's action */
if (stream->dtb) (*stream->dtb->check) (stream);
}
/* Mail expunge mailbox
* Accepts: mail stream
* sequence to expunge if non-NIL
* expunge options
* Returns: T on success, NIL on failure
*/
long mail_expunge_full (MAILSTREAM *stream,char *sequence,long options)
{
/* do the driver's action */
return stream->dtb ? (*stream->dtb->expunge) (stream,sequence,options) : NIL;
}
/* Mail copy message(s)
* Accepts: mail stream
* sequence
* destination mailbox
* flags
*/
long mail_copy_full (MAILSTREAM *stream,char *sequence,char *mailbox,
long options)
{
return stream->dtb ?
SAFE_COPY (stream->dtb,stream,sequence,mailbox,options) : NIL;
}
/* Append data package to use for old single-message mail_append() interface */
typedef struct mail_append_package {
char *flags; /* initial flags */
char *date; /* message internal date */
STRING *message; /* stringstruct of message */
} APPENDPACKAGE;
/* Single append message string
* Accepts: mail stream
* package pointer (cast as a void *)
* pointer to return initial flags
* pointer to return message internal date
* pointer to return stringstruct of message to append
* Returns: T, always
*/
static long mail_append_single (MAILSTREAM *stream,void *data,char **flags,
char **date,STRING **message)
{
APPENDPACKAGE *ap = (APPENDPACKAGE *) data;
*flags = ap->flags; /* get desired data from the package */
*date = ap->date;
*message = ap->message;
ap->message = NIL; /* so next callback puts a stop to it */
return LONGT; /* always return success */
}
/* Mail append message string
* Accepts: mail stream
* destination mailbox
* initial flags
* message internal date
* stringstruct of message to append
* Returns: T on success, NIL on failure
*/
long mail_append_full (MAILSTREAM *stream,char *mailbox,char *flags,char *date,
STRING *message)
{
APPENDPACKAGE ap;
ap.flags = flags; /* load append package */
ap.date = date;
ap.message = message;
return mail_append_multiple (stream,mailbox,mail_append_single,(void *) &ap);
}
/* Mail append message(s)
* Accepts: mail stream
* destination mailbox
* append data callback
* arbitrary data for callback use
* Returns: T on success, NIL on failure
*/
long mail_append_multiple (MAILSTREAM *stream,char *mailbox,append_t af,
void *data)
{
char *s,tmp[MAILTMPLEN];
DRIVER *d = NIL;
long ret = NIL;
/* never allow names with newlines */
if (strpbrk (mailbox,"\015\012"))
MM_LOG ("Can't append to mailbox with such a name",ERROR);
else if (strlen (mailbox) >=
(NETMAXHOST+(NETMAXUSER*2)+NETMAXMBX+NETMAXSRV+50)) {
sprintf (tmp,"Can't append %.80s: %s",mailbox,(*mailbox == '{') ?
"invalid remote specification" : "no such mailbox");
MM_LOG (tmp,ERROR);
}
/* special driver hack? */
else if (!strncmp (lcase (strcpy (tmp,mailbox)),"#driver.",8)) {
/* yes, tie off name at likely delimiter */
if (!(s = strpbrk (tmp+8,"/\\:"))) {
sprintf (tmp,"Can't append to mailbox %.80s: bad driver syntax",mailbox);
MM_LOG (tmp,ERROR);
return NIL;
}
*s++ = '\0'; /* tie off at delimiter */
if (!(d = (DRIVER *) mail_parameters (NIL,GET_DRIVER,tmp+8))) {
sprintf (tmp,"Can't append to mailbox %.80s: unknown driver",mailbox);
MM_LOG (tmp,ERROR);
}
else ret = SAFE_APPEND (d,stream,mailbox + (s - tmp),af,data);
}
else if (d = mail_valid (stream,mailbox,NIL))
ret = SAFE_APPEND (d,stream,mailbox,af,data);
/* No driver, try for TRYCREATE if no stream. Note that we use the
* createProto here, not the appendProto, since the dummy driver already
* took care of the appendProto case. Otherwise, if appendProto is set to
* NIL, we won't get a TRYCREATE.
*/
else if (!stream && (stream = default_proto (NIL)) && stream->dtb &&
SAFE_APPEND (stream->dtb,stream,mailbox,af,data))
/* timing race? */
MM_NOTIFY (stream,"Append validity confusion",WARN);
/* generate error message */
else mail_valid (stream,mailbox,"append to mailbox");
return ret;
}
/* Mail garbage collect stream
* Accepts: mail stream
* garbage collection flags
*/
void mail_gc (MAILSTREAM *stream,long gcflags)
{
MESSAGECACHE *elt;
unsigned long i;
/* do the driver's action first */
if (stream->dtb && stream->dtb->gc) (*stream->dtb->gc) (stream,gcflags);
stream->msgno = 0; /* nothing cached now */
if (gcflags & GC_ENV) { /* garbage collect envelopes? */
if (stream->env) mail_free_envelope (&stream->env);
if (stream->body) mail_free_body (&stream->body);
}
if (gcflags & GC_TEXTS) { /* free texts */
if (stream->text.data) fs_give ((void **) &stream->text.data);
stream->text.size = 0;
}
/* garbage collect per-message stuff */
for (i = 1; i <= stream->nmsgs; i++)
if (elt = (MESSAGECACHE *) (*mailcache) (stream,i,CH_ELT))
mail_gc_msg (&elt->private.msg,gcflags);
}
/* Mail garbage collect message
* Accepts: message structure
* garbage collection flags
*/
void mail_gc_msg (MESSAGE *msg,long gcflags)
{
if (gcflags & GC_ENV) { /* garbage collect envelopes? */
mail_free_envelope (&msg->env);
mail_free_body (&msg->body);
}
if (gcflags & GC_TEXTS) { /* garbage collect texts */
if (msg->full.text.data) fs_give ((void **) &msg->full.text.data);
if (msg->header.text.data) {
mail_free_stringlist (&msg->lines);
fs_give ((void **) &msg->header.text.data);
}
if (msg->text.text.data) fs_give ((void **) &msg->text.text.data);
/* now GC all body components */
if (msg->body) mail_gc_body (msg->body);
}
}
/* Mail garbage collect texts in BODY structure
* Accepts: BODY structure
*/
void mail_gc_body (BODY *body)
{
PART *part;
switch (body->type) { /* free contents */
case TYPEMULTIPART: /* multiple part */
for (part = body->nested.part; part; part = part->next)
mail_gc_body (&part->body);
break;
case TYPEMESSAGE: /* encapsulated message */
if (body->subtype && !strcmp (body->subtype,"RFC822")) {
mail_free_stringlist (&body->nested.msg->lines);
mail_gc_msg (body->nested.msg,GC_TEXTS);
}
break;
default:
break;
}
if (body->mime.text.data) fs_give ((void **) &body->mime.text.data);
if (body->contents.text.data) fs_give ((void **) &body->contents.text.data);
}
/* Mail get body part
* Accepts: mail stream
* message number
* section specifier
* Returns: pointer to body
*/
BODY *mail_body (MAILSTREAM *stream,unsigned long msgno,unsigned char *section)
{
BODY *b = NIL;
PART *pt;
unsigned long i;
/* make sure have a body */
if (section && *section && mail_fetchstructure (stream,msgno,&b) && b)
while (*section) { /* find desired section */
if (isdigit (*section)) { /* get section specifier */
/* make sure what follows is valid */
if (!(i = strtoul (section,(char **) §ion,10)) ||
(*section && ((*section++ != '.') || !*section))) return NIL;
/* multipart content? */
if (b->type == TYPEMULTIPART) {
/* yes, find desired part */
if (pt = b->nested.part) while (--i && (pt = pt->next));
if (!pt) return NIL; /* bad specifier */
b = &pt->body; /* note new body */
}
/* otherwise must be section 1 */
else if (i != 1) return NIL;
/* need to go down further? */
if (*section) switch (b->type) {
case TYPEMULTIPART: /* multipart */
break;
case TYPEMESSAGE: /* embedded message */
if (!strcmp (b->subtype,"RFC822")) {
b = b->nested.msg->body;
break;
}
default: /* bogus subpart specification */
return NIL;
}
}
else return NIL; /* unknown section specifier */
}
return b;
}
/* Mail output date from elt fields
* Accepts: character string to write into
* elt to get data data from
* Returns: the character string
*/
const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
char *mail_date (char *string,MESSAGECACHE *elt)
{
sprintf (string,"%2d-%s-%d %02d:%02d:%02d %c%02d%02d",
elt->day ? elt->day : 1,
months[elt->month ? (elt->month - 1) : 0],
elt->year + BASEYEAR,elt->hours,elt->minutes,elt->seconds,
elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes);
return string;
}
/* Mail output extended-ctime format date from elt fields
* Accepts: character string to write into
* elt to get data data from
* Returns: the character string
*/
char *mail_cdate (char *string,MESSAGECACHE *elt)
{
char *fmt = "%s %s %2d %02d:%02d:%02d %4d %s%02d%02d\n";
int d = elt->day ? elt->day : 1;
int m = elt->month ? (elt->month - 1) : 0;
int y = elt->year + BASEYEAR;
const char *s = months[m];
if (m < 2) { /* if before March, */
m += 10; /* January = month 10 of previous year */
y--;
}
else m -= 2; /* March is month 0 */
sprintf (string,fmt,days[(int) (d + 2 + ((7 + 31 * m) / 12)
#ifndef USEJULIANCALENDAR
#ifndef USEORTHODOXCALENDAR /* Gregorian calendar */
+ (y / 400)
#ifdef Y4KBUGFIX
- (y / 4000)
#endif
#else /* Orthodox calendar */
+ (2 * (y / 900)) + ((y % 900) >= 200)
+ ((y % 900) >= 600)
#endif
- (y / 100)
#endif
+ y + (y / 4)) % 7],
s,d,elt->hours,elt->minutes,elt->seconds,elt->year + BASEYEAR,
elt->zoccident ? "-" : "+",elt->zhours,elt->zminutes);
return string;
}
/* Mail parse date into elt fields
* Accepts: elt to write into
* date string to parse
* Returns: T if parse successful, else NIL
* This routine parses dates as follows:
* . leading three alphas followed by comma and space are ignored
* . date accepted in format: mm/dd/yy, mm/dd/yyyy, dd-mmm-yy, dd-mmm-yyyy,
* dd mmm yy, dd mmm yyyy, yyyy-mm-dd, yyyymmdd
* . two and three digit years interpreted according to RFC 2822 rules
* . mandatory end of string if yyyy-mm-dd or yyyymmdd; otherwise optional
* space followed by time:
* . time accepted in format hh:mm:ss or hh:mm
* . end of string accepted
* . timezone accepted: hyphen followed by symbolic timezone, or space
* followed by signed numeric timezone or symbolic timezone
* Examples of normal input:
* . IMAP date-only (SEARCH):
* dd-mmm-yyyy
* . IMAP date-time (INTERNALDATE):
* dd-mmm-yyyy hh:mm:ss +zzzz
* . RFC-822:
* www, dd mmm yy hh:mm:ss zzz
* . RFC-2822:
* www, dd mmm yyyy hh:mm:ss +zzzz
*/
long mail_parse_date (MESSAGECACHE *elt,unsigned char *s)
{
unsigned long d,m,y;
int mi,ms;
struct tm *t;
time_t tn;
char tmp[MAILTMPLEN];
static unsigned long maxyear = 0;
if (!maxyear) { /* know the end of time yet? */
MESSAGECACHE tmpelt;
memset (&tmpelt,0xff,sizeof (MESSAGECACHE));
maxyear = BASEYEAR + tmpelt.year;
}
/* clear elt */
elt->zoccident = elt->zhours = elt->zminutes =
elt->hours = elt->minutes = elt->seconds =
elt->day = elt->month = elt->year = 0;
/* make a writeable uppercase copy */
if (s && *s && (strlen (s) < (size_t)MAILTMPLEN)) s = ucase (strcpy (tmp,s));
else return NIL;
/* skip over possible day of week */
if (isalpha (*s) && isalpha (s[1]) && isalpha (s[2]) && (s[3] == ',') &&
(s[4] == ' ')) s += 5;
while (*s == ' ') s++; /* parse first number (probable month) */
if (!(m = strtoul (s,(char **) &s,10))) return NIL;
switch (*s) { /* different parse based on delimiter */
case '/': /* mm/dd/yy format */
if (isdigit (*++s) && (d = strtoul (s,(char **) &s,10)) &&
(*s == '/') && isdigit (*++s)) {
y = strtoul (s,(char **) &s,10);
if (*s == '\0') break; /* must end here */
}
return NIL; /* bogon */
case ' ': /* dd mmm yy format */
while (s[1] == ' ') s++; /* slurp extra whitespace */
case '-':
if (isdigit (s[1])) { /* possible ISO 8601 date format? */
y = m; /* yes, first number is year */
/* get month and day */
if ((m = strtoul (s+1,(char **) &s,10)) && (*s++ == '-') &&
(d = strtoul (s,(char **) &s,10)) && !*s) break;
return NIL; /* syntax error or time present */
}
d = m; /* dd-mmm-yy[yy], so first number is a day */
/* make sure string long enough! */
if (strlen (s) < (size_t) 5) return NIL;
/* Some compilers don't allow `<<' and/or longs in case statements. */
/* slurp up the month string */
ms = ((s[1] - 'A') * 1024) + ((s[2] - 'A') * 32) + (s[3] - 'A');
switch (ms) { /* determine the month */
case (('J'-'A') * 1024) + (('A'-'A') * 32) + ('N'-'A'): m = 1; break;
case (('F'-'A') * 1024) + (('E'-'A') * 32) + ('B'-'A'): m = 2; break;
case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('R'-'A'): m = 3; break;
case (('A'-'A') * 1024) + (('P'-'A') * 32) + ('R'-'A'): m = 4; break;
case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('Y'-'A'): m = 5; break;
case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('N'-'A'): m = 6; break;
case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('L'-'A'): m = 7; break;
case (('A'-'A') * 1024) + (('U'-'A') * 32) + ('G'-'A'): m = 8; break;
case (('S'-'A') * 1024) + (('E'-'A') * 32) + ('P'-'A'): m = 9; break;
case (('O'-'A') * 1024) + (('C'-'A') * 32) + ('T'-'A'): m = 10; break;
case (('N'-'A') * 1024) + (('O'-'A') * 32) + ('V'-'A'): m = 11; break;
case (('D'-'A') * 1024) + (('E'-'A') * 32) + ('C'-'A'): m = 12; break;
default: return NIL; /* unknown month */
}
if (s[4] == *s) s += 5; /* advance to year */
else { /* first three were OK, possibly full name */
mi = *s; /* note delimiter, skip alphas */
for (s += 4; isalpha (*s); s++);
/* error if delimiter not here */
if (mi != *s++) return NIL;
}
while (*s == ' ') s++; /* parse year */
if (isdigit (*s)) { /* must be a digit here */
y = strtoul (s,(char **) &s,10);
if (*s == '\0' || *s == ' ') break;
}
case '\0': /* ISO 8601 compact date */
if (m < (BASEYEAR * 10000)) return NIL;
y = m / 10000; /* get year */
d = (m %= 10000) % 100; /* get day */
m /= 100; /* and month */
break;
default:
return NIL; /* unknown date format */
}
/* minimal validity check of date */
if ((d > 31) || (m > 12)) return NIL;
if (y < 49) y += 2000; /* RFC 2282 rules for two digit years 00-49 */
else if (y < 999) y += 1900; /* 2-digit years 50-99 and 3-digit years */
/* reject prehistoric and far future years */
if ((y < BASEYEAR) || (y > maxyear)) return NIL;
/* set values in elt */
elt->day = d; elt->month = m; elt->year = y - BASEYEAR;
ms = '\0'; /* initially no time zone string */
if (*s) { /* time specification present? */
/* parse time */
d = strtoul (s+1,(char **) &s,10);
if (*s != ':') return NIL;
m = strtoul (++s,(char **) &s,10);
y = (*s == ':') ? strtoul (++s,(char **) &s,10) : 0;
/* validity check time */
if ((d > 23) || (m > 59) || (y > 60)) return NIL;
/* set values in elt */
elt->hours = d; elt->minutes = m; elt->seconds = y;
switch (*s) { /* time zone specifier? */
case ' ': /* numeric time zone */
while (s[1] == ' ') s++; /* slurp extra whitespace */
if (!isalpha (s[1])) { /* treat as '-' case if alphabetic */
/* test for sign character */
if ((elt->zoccident = (*++s == '-')) || (*s == '+')) s++;
/* validate proper timezone */
if (isdigit(*s) && isdigit(s[1]) && isdigit(s[2]) && (s[2] < '6') &&
isdigit(s[3])) {
elt->zhours = (*s - '0') * 10 + (s[1] - '0');
elt->zminutes = (s[2] - '0') * 10 + (s[3] - '0');
}
return T; /* all done! */
}
/* falls through */
case '-': /* symbolic time zone */
if (!(ms = *++s)) ms = 'Z';
else if (*++s) { /* multi-character? */
ms -= 'A'; ms *= 1024; /* yes, make compressed three-byte form */
ms += ((*s++ - 'A') * 32);
if (*s) ms += *s++ - 'A';
if (*s) ms = '\0'; /* more than three characters */
}
default: /* ignore anything else */
break;
}
}
/* This is not intended to be a comprehensive list of all possible
* timezone strings. Such a list would be impractical. Rather, this
* listing is intended to incorporate all military, North American, and
* a few special cases such as Japan and the major European zone names,
* such as what might be expected to be found in a Tenex format mailbox
* and spewed from an IMAP server. The trend is to migrate to numeric
* timezones which lack the flavor but also the ambiguity of the names.
*
* RFC-822 only recognizes UT, GMT, 1-letter military timezones, and the
* 4 CONUS timezones and their summer time variants. [Sorry, Canadian
* Atlantic Provinces, Alaska, and Hawaii.]
*/
switch (ms) { /* determine the timezone */
/* Universal */
case (('U'-'A')*1024)+(('T'-'A')*32):
#ifndef STRICT_RFC822_TIMEZONES
case (('U'-'A')*1024)+(('T'-'A')*32)+'C'-'A':
#endif
/* Greenwich */
case (('G'-'A')*1024)+(('M'-'A')*32)+'T'-'A':
case 'Z': elt->zhours = 0; break;
/* oriental (from Greenwich) timezones */
#ifndef STRICT_RFC822_TIMEZONES
/* Middle Europe */
case (('M'-'A')*1024)+(('E'-'A')*32)+'T'-'A':
#endif
#ifdef BRITISH_SUMMER_TIME
/* British Summer */
case (('B'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
#endif
case 'A': elt->zhours = 1; break;
#ifndef STRICT_RFC822_TIMEZONES
/* Eastern Europe */
case (('E'-'A')*1024)+(('E'-'A')*32)+'T'-'A':
#endif
case 'B': elt->zhours = 2; break;
case 'C': elt->zhours = 3; break;
case 'D': elt->zhours = 4; break;
case 'E': elt->zhours = 5; break;
case 'F': elt->zhours = 6; break;
case 'G': elt->zhours = 7; break;
case 'H': elt->zhours = 8; break;
#ifndef STRICT_RFC822_TIMEZONES
/* Japan */
case (('J'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
#endif
case 'I': elt->zhours = 9; break;
case 'K': elt->zhours = 10; break;
case 'L': elt->zhours = 11; break;
case 'M': elt->zhours = 12; break;
/* occidental (from Greenwich) timezones */
case 'N': elt->zoccident = 1; elt->zhours = 1; break;
case 'O': elt->zoccident = 1; elt->zhours = 2; break;
#ifndef STRICT_RFC822_TIMEZONES
case (('A'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
#endif
case 'P': elt->zoccident = 1; elt->zhours = 3; break;
#ifdef NEWFOUNDLAND_STANDARD_TIME
/* Newfoundland */
case (('N'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
elt->zoccident = 1; elt->zhours = 3; elt->zminutes = 30; break;
#endif
#ifndef STRICT_RFC822_TIMEZONES
/* Atlantic */
case (('A'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
#endif
/* CONUS */
case (('E'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
case 'Q': elt->zoccident = 1; elt->zhours = 4; break;
/* Eastern */
case (('E'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
case (('C'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
case 'R': elt->zoccident = 1; elt->zhours = 5; break;
/* Central */
case (('C'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
case (('M'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
case 'S': elt->zoccident = 1; elt->zhours = 6; break;
/* Mountain */
case (('M'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
case (('P'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
case 'T': elt->zoccident = 1; elt->zhours = 7; break;
/* Pacific */
case (('P'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
#ifndef STRICT_RFC822_TIMEZONES
case (('Y'-'A')*1024)+(('D'-'A')*32)+'T'-'A':
#endif
case 'U': elt->zoccident = 1; elt->zhours = 8; break;
#ifndef STRICT_RFC822_TIMEZONES
/* Yukon */
case (('Y'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
#endif
case 'V': elt->zoccident = 1; elt->zhours = 9; break;
#ifndef STRICT_RFC822_TIMEZONES
/* Hawaii */
case (('H'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
#endif
case 'W': elt->zoccident = 1; elt->zhours = 10; break;
/* Nome/Bering/Samoa */
#ifdef NOME_STANDARD_TIME
case (('N'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
#endif
#ifdef BERING_STANDARD_TIME
case (('B'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
#endif
#ifdef SAMOA_STANDARD_TIME
case (('S'-'A')*1024)+(('S'-'A')*32)+'T'-'A':
#endif
case 'X': elt->zoccident = 1; elt->zhours = 11; break;
case 'Y': elt->zoccident = 1; elt->zhours = 12; break;
default: /* unknown time zones treated as local */
tn = time (0); /* time now... */
t = localtime (&tn); /* get local minutes since midnight */
mi = t->tm_hour * 60 + t->tm_min;
ms = t->tm_yday; /* note Julian day */
if (t = gmtime (&tn)) { /* minus UTC minutes since midnight */
mi -= t->tm_hour * 60 + t->tm_min;
/* ms can be one of:
* 36x local time is December 31, UTC is January 1, offset -24 hours
* 1 local time is 1 day ahead of UTC, offset +24 hours
* 0 local time is same day as UTC, no offset
* -1 local time is 1 day behind UTC, offset -24 hours
* -36x local time is January 1, UTC is December 31, offset +24 hours
*/
if (ms -= t->tm_yday) /* correct offset if different Julian day */
mi += ((ms < 0) == (abs (ms) == 1)) ? -24*60 : 24*60;
if (mi < 0) { /* occidental? */
mi = abs (mi); /* yup, make positive number */
elt->zoccident = 1; /* and note west of UTC */
}
elt->zhours = mi / 60; /* now break into hours and minutes */
elt->zminutes = mi % 60;
}
break;
}
return T;
}
/* Mail n messages exist
* Accepts: mail stream
* number of messages
*/
void mail_exists (MAILSTREAM *stream,unsigned long nmsgs)
{
char tmp[MAILTMPLEN];
if (nmsgs > MAXMESSAGES) {
sprintf (tmp,"Mailbox has more messages (%lu) exist than maximum (%lu)",
nmsgs,MAXMESSAGES);
mm_log (tmp,ERROR);
nmsgs = MAXMESSAGES; /* cap to maximum */
/* probably will crash in mail_elt() soon enough... */
}
/* make sure cache is large enough */
(*mailcache) (stream,nmsgs,CH_SIZE);
stream->nmsgs = nmsgs; /* update stream status */
/* notify main program of change */
if (!stream->silent) MM_EXISTS (stream,nmsgs);
}
/* Mail n messages are recent
* Accepts: mail stream
* number of recent messages
*/
void mail_recent (MAILSTREAM *stream,unsigned long recent)
{
char tmp[MAILTMPLEN];
if (recent <= stream->nmsgs) stream->recent = recent;
else {
sprintf (tmp,"Non-existent recent message(s) %lu, nmsgs=%lu",
recent,stream->nmsgs);
mm_log (tmp,ERROR);
}
}
/* Mail message n is expunged
* Accepts: mail stream
* message #
*/
void mail_expunged (MAILSTREAM *stream,unsigned long msgno)
{
char tmp[MAILTMPLEN];
MESSAGECACHE *elt;
if (msgno > stream->nmsgs) {
sprintf (tmp,"Expunge of non-existent message %lu, nmsgs=%lu",
msgno,stream->nmsgs);
mm_log (tmp,ERROR);
}
else {
elt = (MESSAGECACHE *) (*mailcache) (stream,msgno,CH_ELT);
/* notify main program of change */
if (!stream->silent) MM_EXPUNGED (stream,msgno);
if (elt) { /* if an element is there */
elt->msgno = 0; /* invalidate its message number and free */
(*mailcache) (stream,msgno,CH_FREE);
(*mailcache) (stream,msgno,CH_FREESORTCACHE);
}
/* expunge the slot */
(*mailcache) (stream,msgno,CH_EXPUNGE);
--stream->nmsgs; /* update stream status */
if (stream->msgno) { /* have stream pointers? */
/* make sure the short cache is nuked */
if (stream->scache) mail_gc (stream,GC_ENV | GC_TEXTS);
else stream->msgno = 0; /* make sure invalidated in any case */
}
}
}
/* Mail stream status routines */
/* Mail lock stream
* Accepts: mail stream
*/
void mail_lock (MAILSTREAM *stream)
{
if (stream->lock) {
char tmp[MAILTMPLEN];
sprintf (tmp,"Lock when already locked, mbx=%.80s",
stream->mailbox ? stream->mailbox : "???");
fatal (tmp);
}
else stream->lock = T; /* lock stream */
}
/* Mail unlock stream
* Accepts: mail stream
*/
void mail_unlock (MAILSTREAM *stream)
{
if (!stream->lock) fatal ("Unlock when not locked");
else stream->lock = NIL; /* unlock stream */
}
/* Mail turn on debugging telemetry
* Accepts: mail stream
*/
void mail_debug (MAILSTREAM *stream)
{
stream->debug = T; /* turn on debugging telemetry */
if (stream->dtb) (*stream->dtb->parameters) (ENABLE_DEBUG,stream);
}
/* Mail turn off debugging telemetry
* Accepts: mail stream
*/
void mail_nodebug (MAILSTREAM *stream)
{
stream->debug = NIL; /* turn off debugging telemetry */
if (stream->dtb) (*stream->dtb->parameters) (DISABLE_DEBUG,stream);
}
/* Mail log to debugging telemetry
* Accepts: message
* flag that data is "sensitive"
*/
void mail_dlog (char *string,long flag)
{
mm_dlog ((debugsensitive || !flag) ? string : "<suppressed>");
}
/* Mail parse UID sequence
* Accepts: mail stream
* sequence to parse
* Returns: T if parse successful, else NIL
*/
long mail_uid_sequence (MAILSTREAM *stream,unsigned char *sequence)
{
unsigned long i,j,k,x,y;
for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->sequence = NIL;
while (sequence && *sequence){/* while there is something to parse */
if (*sequence == '*') { /* maximum message */
i = stream->nmsgs ? mail_uid (stream,stream->nmsgs) : stream->uid_last;
sequence++; /* skip past * */
}
/* parse and validate message number */
/* parse and validate message number */
else if (!isdigit (*sequence)) {
MM_LOG ("Syntax error in sequence",ERROR);
return NIL;
}
else if (!(i = strtoul (sequence,(char **) &sequence,10))) {
MM_LOG ("UID may not be zero",ERROR);
return NIL;
}
switch (*sequence) { /* see what the delimiter is */
case ':': /* sequence range */
if (*++sequence == '*') { /* maximum message */
j = stream->nmsgs ? mail_uid (stream,stream->nmsgs) : stream->uid_last;
sequence++; /* skip past * */
}
/* parse end of range */
else if (!(j = strtoul (sequence,(char **) &sequence,10))) {
MM_LOG ("UID sequence range invalid",ERROR);
return NIL;
}
if (*sequence && *sequence++ != ',') {
MM_LOG ("UID sequence range syntax error",ERROR);
return NIL;
}
if (i > j) { /* swap the range if backwards */
x = i; i = j; j = x;
}
x = mail_msgno (stream,i);/* get msgnos */
y = mail_msgno (stream,j);/* for both UIDS (don't && it) */
/* easy if both UIDs valid */
if (x && y) while (x <= y) mail_elt (stream,x++)->sequence = T;
/* start UID valid, end is not */
else if (x) while ((x <= stream->nmsgs) && (mail_uid (stream,x) <= j))
mail_elt (stream,x++)->sequence = T;
/* end UID valid, start is not */
else if (y) for (x = 1; x <= y; x++) {
if (mail_uid (stream,x) >= i) mail_elt (stream,x)->sequence = T;
}
/* neither is valid, ugh */
else for (x = 1; x <= stream->nmsgs; x++)
if (((k = mail_uid (stream,x)) >= i) && (k <= j))
mail_elt (stream,x)->sequence = T;
break;
case ',': /* single message */
++sequence; /* skip the delimiter, fall into end case */
case '\0': /* end of sequence, mark this message */
if (x = mail_msgno (stream,i)) mail_elt (stream,x)->sequence = T;
break;
default: /* anything else is a syntax error! */
MM_LOG ("UID sequence syntax error",ERROR);
return NIL;
}
}
return T; /* successfully parsed sequence */
}
/* Mail see if line list matches that in cache
* Accepts: candidate line list
* cached line list
* matching flags
* Returns: T if match, NIL if no match
*/
long mail_match_lines (STRINGLIST *lines,STRINGLIST *msglines,long flags)
{
unsigned long i;
unsigned char *s,*t;
STRINGLIST *m;
if (!msglines) return T; /* full header is in cache */
/* need full header but filtered in cache */
if ((flags & FT_NOT) || !lines) return NIL;
do { /* make sure all present & accounted for */
for (m = msglines; m; m = m->next) if (lines->text.size == m->text.size) {
for (s = lines->text.data,t = m->text.data,i = lines->text.size;
i && !compare_uchar (*s,*t); s++,t++,i--);
if (!i) break; /* this line matches */
}
if (!m) return NIL; /* didn't find in the list */
}
while (lines = lines->next);
return T; /* all lines found */
}
/* Mail filter text by header lines
* Accepts: text to filter, with trailing null
* length of text
* list of lines
* fetch flags
* Returns: new text size, text overwritten
*/
unsigned long mail_filter (char *text,unsigned long len,STRINGLIST *lines,
long flags)
{
STRINGLIST *hdrs;
int notfound;
unsigned long i;
char c,*s,*e,*t,tmp[MAILTMPLEN];
char *src = text;
char *dst = src;
char *end = text + len;
text[len] = '\012'; /* guard against running off buffer */
while (src < end) { /* process header */
/* slurp header line name */
for (s = src,e = s + MAILTMPLEN - 1,e = (e < end ? e : end),t = tmp;
(s < e) && ((c = (*s ? *s : (*s = ' '))) != ':') &&
((c > ' ') ||
((c != ' ') && (c != '\t') && (c != '\015') && (c != '\012')));
*t++ = *s++);
*t = '\0'; /* tie off */
notfound = T; /* not found yet */
if (i = t - tmp) /* see if found in header */
for (hdrs = lines; hdrs && notfound; hdrs = hdrs->next)
if ((hdrs->text.size == i) && !compare_csizedtext (tmp,&hdrs->text))
notfound = NIL;
/* skip header line if not wanted */
if (i && ((flags & FT_NOT) ? !notfound : notfound))
while (((*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
(*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
(*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
(*src++ != '\012')) || ((*src == ' ') || (*src == '\t')));
else if (src == dst) { /* copy to self */
while (((*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
(*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
(*src++ != '\012') && (*src++ != '\012') && (*src++ != '\012') &&
(*src++ != '\012')) || ((*src == ' ') || (*src == '\t')));
dst = src; /* update destination */
}
else { /* copy line and any continuation line */
while ((((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') &&
((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') &&
((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') &&
((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012') &&
((*dst++ = *src++) != '\012') && ((*dst++ = *src++) != '\012'))||
((*src == ' ') || (*src == '\t')));
/* in case hit the guard LF */
if (src > end) dst -= (src - end);
}
}
*dst = '\0'; /* tie off destination */
return dst - text;
}
/* Local mail search message
* Accepts: MAIL stream
* message number
* optional section specification
* search program
* Returns: T if found, NIL otherwise
*/
long mail_search_msg (MAILSTREAM *stream,unsigned long msgno,char *section,
SEARCHPGM *pgm)
{
unsigned short d;
char tmp[MAILTMPLEN];
MESSAGECACHE *elt = mail_elt (stream,msgno);
SEARCHHEADER *hdr;
SEARCHOR *or;
SEARCHPGMLIST *not;
unsigned long now = (unsigned long) time (0);
if (pgm->msgno || pgm->uid) { /* message set searches */
SEARCHSET *set;
/* message sequences */
if (pgm->msgno) { /* inside this message sequence set */
for (set = pgm->msgno; set; set = set->next)
if (set->last ? ((set->first <= set->last) ?
((msgno >= set->first) && (msgno <= set->last)) :
((msgno >= set->last) && (msgno <= set->first))) :
msgno == set->first) break;
if (!set) return NIL; /* not found within sequence */
}
if (pgm->uid) { /* inside this unique identifier set */
unsigned long uid = mail_uid (stream,msgno);
for (set = pgm->uid; set; set = set->next)
if (set->last ? ((set->first <= set->last) ?
((uid >= set->first) && (uid <= set->last)) :
((uid >= set->last) && (uid <= set->first))) :
uid == set->first) break;
if (!set) return NIL; /* not found within sequence */
}
}
/* Fast data searches */
/* need to fetch fast data? */
if ((!elt->rfc822_size && (pgm->larger || pgm->smaller)) ||
(!elt->year && (pgm->before || pgm->on || pgm->since ||
pgm->older || pgm->younger)) ||
(!elt->valid && (pgm->answered || pgm->unanswered ||
pgm->deleted || pgm->undeleted ||
pgm->draft || pgm->undraft ||
pgm->flagged || pgm->unflagged ||
pgm->recent || pgm->old ||
pgm->seen || pgm->unseen ||
pgm->keyword || pgm->unkeyword))) {
unsigned long i;
MESSAGECACHE *ielt;
for (i = elt->msgno; /* find last unloaded message in range */
(i < stream->nmsgs) && (ielt = mail_elt (stream,i+1)) &&
((!ielt->rfc822_size && (pgm->larger || pgm->smaller)) ||
(!ielt->year && (pgm->before || pgm->on || pgm->since ||
pgm->older || pgm->younger)) ||
(!ielt->valid && (pgm->answered || pgm->unanswered ||
pgm->deleted || pgm->undeleted ||
pgm->draft || pgm->undraft ||
pgm->flagged || pgm->unflagged ||
pgm->recent || pgm->old ||
pgm->seen || pgm->unseen ||
pgm->keyword || pgm->unkeyword))); ++i);
if (i == elt->msgno) sprintf (tmp,"%lu",elt->msgno);
else sprintf (tmp,"%lu:%lu",elt->msgno,i);
mail_fetch_fast (stream,tmp,NIL);
}
/* size ranges */
if ((pgm->larger && (elt->rfc822_size <= pgm->larger)) ||
(pgm->smaller && (elt->rfc822_size >= pgm->smaller))) return NIL;
/* message flags */
if ((pgm->answered && !elt->answered) ||
(pgm->unanswered && elt->answered) ||
(pgm->deleted && !elt->deleted) ||
(pgm->undeleted && elt->deleted) ||
(pgm->draft && !elt->draft) ||
(pgm->undraft && elt->draft) ||
(pgm->flagged && !elt->flagged) ||
(pgm->unflagged && elt->flagged) ||
(pgm->recent && !elt->recent) ||
(pgm->old && elt->recent) ||
(pgm->seen && !elt->seen) ||
(pgm->unseen && elt->seen)) return NIL;
/* keywords */
if ((pgm->keyword && !mail_search_keyword (stream,elt,pgm->keyword,LONGT)) ||
(pgm->unkeyword && !mail_search_keyword (stream,elt,pgm->unkeyword,NIL)))
return NIL;
/* internal date ranges */
if (pgm->before || pgm->on || pgm->since) {
d = mail_shortdate (elt->year,elt->month,elt->day);
if (pgm->before && (d >= pgm->before)) return NIL;
if (pgm->on && (d != pgm->on)) return NIL;
if (pgm->since && (d < pgm->since)) return NIL;
}
if (pgm->older || pgm->younger) {
unsigned long msgd = mail_longdate (elt);
if (pgm->older && msgd > (now - pgm->older)) return NIL;
if (pgm->younger && msgd < (now - pgm->younger)) return NIL;
}
/* envelope searches */
if (pgm->sentbefore || pgm->senton || pgm->sentsince ||
pgm->bcc || pgm->cc || pgm->from || pgm->to || pgm->subject ||
pgm->return_path || pgm->sender || pgm->reply_to || pgm->in_reply_to ||
pgm->message_id || pgm->newsgroups || pgm->followup_to ||
pgm->references) {
ENVELOPE *env;
MESSAGECACHE delt;
if (section) { /* use body part envelope */
BODY *body = mail_body (stream,msgno,section);
env = (body && (body->type == TYPEMESSAGE) && body->subtype &&
!strcmp (body->subtype,"RFC822")) ? body->nested.msg->env : NIL;
}
else { /* use top level envelope if no section */
if (pgm->header && !stream->scache && !(stream->dtb->flags & DR_LOCAL))
mail_fetch_header(stream,msgno,NIL,NIL,NIL,FT_PEEK|FT_SEARCHLOOKAHEAD);
env = mail_fetchenvelope (stream,msgno);
}
if (!env) return NIL; /* no envelope obtained */
/* sent date ranges */
if ((pgm->sentbefore || pgm->senton || pgm->sentsince) &&
(!mail_parse_date (&delt,env->date) ||
!(d = mail_shortdate (delt.year,delt.month,delt.day)) ||
(pgm->sentbefore && (d >= pgm->sentbefore)) ||
(pgm->senton && (d != pgm->senton)) ||
(pgm->sentsince && (d < pgm->sentsince)))) return NIL;
/* search headers */
if ((pgm->bcc && !mail_search_addr (env->bcc,pgm->bcc)) ||
(pgm->cc && !mail_search_addr (env->cc,pgm->cc)) ||
(pgm->from && !mail_search_addr (env->from,pgm->from)) ||
(pgm->to && !mail_search_addr (env->to,pgm->to)) ||
(pgm->subject && !mail_search_header_text (env->subject,pgm->subject)))
return NIL;
/* These criteria are not supported by IMAP and have to be emulated */
if ((pgm->return_path &&
!mail_search_addr (env->return_path,pgm->return_path)) ||
(pgm->sender && !mail_search_addr (env->sender,pgm->sender)) ||
(pgm->reply_to && !mail_search_addr (env->reply_to,pgm->reply_to)) ||
(pgm->in_reply_to &&
!mail_search_header_text (env->in_reply_to,pgm->in_reply_to)) ||
(pgm->message_id &&
!mail_search_header_text (env->message_id,pgm->message_id)) ||
(pgm->newsgroups &&
!mail_search_header_text (env->newsgroups,pgm->newsgroups)) ||
(pgm->followup_to &&
!mail_search_header_text (env->followup_to,pgm->followup_to)) ||
(pgm->references &&
!mail_search_header_text (env->references,pgm->references)))
return NIL;
}
/* search header lines */
for (hdr = pgm->header; hdr; hdr = hdr->next) {
char *t,*e,*v;
SIZEDTEXT s;
STRINGLIST sth,stc;
sth.next = stc.next = NIL; /* only one at a time */
sth.text.data = hdr->line.data;
sth.text.size = hdr->line.size;
/* get the header text */
if ((t = mail_fetch_header (stream,msgno,NIL,&sth,&s.size,
FT_INTERNAL | FT_PEEK |
(section ? NIL : FT_SEARCHLOOKAHEAD))) &&
strchr (t,':')) {
if (hdr->text.size) { /* anything matches empty search string */
/* non-empty, copy field data */
s.data = (unsigned char *) fs_get (s.size + 1);
/* for each line */
for (v = (char *) s.data, e = t + s.size; t < e;) switch (*t) {
default: /* non-continuation, skip leading field name */
while ((t < e) && (*t++ != ':'));
if ((t < e) && (*t == ':')) t++;
case '\t': case ' ': /* copy field data */
while ((t < e) && (*t != '\015') && (*t != '\012')) *v++ = *t++;
*v++ = '\n'; /* tie off line */
while (((*t == '\015') || (*t == '\012')) && (t < e)) t++;
}
/* calculate true size */
s.size = v - (char *) s.data;
*v = '\0'; /* tie off results */
stc.text.data = hdr->text.data;
stc.text.size = hdr->text.size;
/* search header */
if (mail_search_header (&s,&stc)) fs_give ((void **) &s.data);
else { /* search failed */
fs_give ((void **) &s.data);
return NIL;
}
}
}
else return NIL; /* no matching header text */
}
/* search strings */
if ((pgm->text && !mail_search_text (stream,msgno,section,pgm->text,LONGT))||
(pgm->body && !mail_search_text (stream,msgno,section,pgm->body,NIL)))
return NIL;
/* logical conditions */
for (or = pgm->or; or; or = or->next)
if (!(mail_search_msg (stream,msgno,section,or->first) ||
mail_search_msg (stream,msgno,section,or->second))) return NIL;
for (not = pgm->not; not; not = not->next)
if (mail_search_msg (stream,msgno,section,not->pgm)) return NIL;
return T;
}
/* Mail search message header null-terminated text
* Accepts: header text
* strings to search
* Returns: T if search found a match
*/
long mail_search_header_text (char *s,STRINGLIST *st)
{
SIZEDTEXT h;
/* have any text? */
if (h.data = (unsigned char *) s) {
h.size = strlen (s); /* yes, get its size */
return mail_search_header (&h,st);
}
return NIL;
}
/* Mail search message header
* Accepts: header as sized text
* strings to search
* Returns: T if search found a match
*/
long mail_search_header (SIZEDTEXT *hdr,STRINGLIST *st)
{
SIZEDTEXT h;
long ret = LONGT;
/* make UTF-8 version of header */
utf8_mime2text (hdr,&h,U8T_CANONICAL);
while (h.size && ((h.data[h.size-1]=='\015') || (h.data[h.size-1]=='\012')))
--h.size; /* slice off trailing newlines */
do if (h.size ? /* search non-empty string */
!ssearch (h.data,h.size,st->text.data,st->text.size) : st->text.size)
ret = NIL;
while (ret && (st = st->next));
if (h.data != hdr->data) fs_give ((void **) &h.data);
return ret;
}
/* Mail search message body
* Accepts: MAIL stream
* message number
* optional section specification
* string list
* flags
* Returns: T if search found a match
*/
long mail_search_text (MAILSTREAM *stream,unsigned long msgno,char *section,
STRINGLIST *st,long flags)
{
BODY *body;
long ret = NIL;
STRINGLIST *s = mail_newstringlist ();
mailgets_t omg = mailgets;
if (stream->dtb->flags & DR_LOWMEM) mailgets = mail_search_gets;
/* strings to search */
for (stream->private.search.string = s; st;) {
s->text.data = st->text.data;
s->text.size = st->text.size;
if (st = st->next) s = s->next = mail_newstringlist ();
}
stream->private.search.text = NIL;
if (flags) { /* want header? */
SIZEDTEXT s,t;
s.data = (unsigned char *)
mail_fetch_header (stream,msgno,section,NIL,&s.size,FT_INTERNAL|FT_PEEK);
utf8_mime2text (&s,&t,U8T_CANONICAL);
ret = mail_search_string_work (&t,&stream->private.search.string);
if (t.data != s.data) fs_give ((void **) &t.data);
}
if (!ret) { /* still looking for match? */
/* no section, get top-level body */
if (!section) mail_fetchstructure (stream,msgno,&body);
/* get body of nested message */
else if ((body = mail_body (stream,msgno,section)) &&
(body->type == TYPEMULTIPART) && body->subtype &&
!strcmp (body->subtype,"RFC822")) body = body->nested.msg->body;
if (body) ret = mail_search_body (stream,msgno,body,NIL,1,flags);
}
mailgets = omg; /* restore former gets routine */
/* clear searching */
for (s = stream->private.search.string; s; s = s->next) s->text.data = NIL;
mail_free_stringlist (&stream->private.search.string);
stream->private.search.text = NIL;
return ret;
}
/* Mail search message body text parts
* Accepts: MAIL stream
* message number
* current body pointer
* hierarchical level prefix
* position at current hierarchical level
* string list
* flags
* Returns: T if search found a match
*/
long mail_search_body (MAILSTREAM *stream,unsigned long msgno,BODY *body,
char *prefix,unsigned long section,long flags)
{
long ret = NIL;
unsigned long i;
char *s,*t,sect[MAILTMPLEN];
SIZEDTEXT st,h;
PART *part;
PARAMETER *param;
if (prefix && (strlen (prefix) > (MAILTMPLEN - 20))) return NIL;
sprintf (sect,"%s%lu",prefix ? prefix : "",section++);
if (flags && prefix) { /* want to search MIME header too? */
st.data = (unsigned char *) mail_fetch_mime (stream,msgno,sect,&st.size,
FT_INTERNAL | FT_PEEK);
if (stream->dtb->flags & DR_LOWMEM) ret = stream->private.search.result;
else {
/* make UTF-8 version of header */
utf8_mime2text (&st,&h,U8T_CANONICAL);
ret = mail_search_string_work (&h,&stream->private.search.string);
if (h.data != st.data) fs_give ((void **) &h.data);
}
}
if (!ret) switch (body->type) {
case TYPEMULTIPART:
/* extend prefix if not first time */
s = prefix ? strcat (sect,".") : "";
for (i = 1,part = body->nested.part; part && !ret; i++,part = part->next)
ret = mail_search_body (stream,msgno,&part->body,s,i,flags);
break;
case TYPEMESSAGE:
if (!strcmp (body->subtype,"RFC822")) {
if (flags) { /* want to search nested message header? */
st.data = (unsigned char *)
mail_fetch_header (stream,msgno,sect,NIL,&st.size,
FT_INTERNAL | FT_PEEK);
if (stream->dtb->flags & DR_LOWMEM) ret =stream->private.search.result;
else {
/* make UTF-8 version of header */
utf8_mime2text (&st,&h,U8T_CANONICAL);
ret = mail_search_string_work (&h,&stream->private.search.string);
if (h.data != st.data) fs_give ((void **) &h.data);
}
}
if (body = body->nested.msg->body)
ret = (body->type == TYPEMULTIPART) ?
mail_search_body (stream,msgno,body,(prefix ? prefix : ""),
section - 1,flags) :
mail_search_body (stream,msgno,body,strcat (sect,"."),1,flags);
break;
}
/* non-MESSAGE/RFC822 falls into text case */
case TYPETEXT:
s = mail_fetch_body (stream,msgno,sect,&i,FT_INTERNAL | FT_PEEK);
if (stream->dtb->flags & DR_LOWMEM) ret = stream->private.search.result;
else {
for (t = NIL,param = body->parameter; param && !t; param = param->next)
if (!strcmp (param->attribute,"CHARSET")) t = param->value;
switch (body->encoding) { /* what encoding? */
case ENCBASE64:
if (st.data = (unsigned char *)
rfc822_base64 ((unsigned char *) s,i,&st.size)) {
ret = mail_search_string (&st,t,&stream->private.search.string);
fs_give ((void **) &st.data);
}
break;
case ENCQUOTEDPRINTABLE:
if (st.data = rfc822_qprint ((unsigned char *) s,i,&st.size)) {
ret = mail_search_string (&st,t,&stream->private.search.string);
fs_give ((void **) &st.data);
}
break;
default:
st.data = (unsigned char *) s;
st.size = i;
ret = mail_search_string (&st,t,&stream->private.search.string);
break;
}
}
break;
}
return ret;
}
/* Mail search text
* Accepts: sized text to search
* character set of sized text
* string list of search keys
* Returns: T if search found a match
*/
long mail_search_string (SIZEDTEXT *s,char *charset,STRINGLIST **st)
{
SIZEDTEXT u;
long ret;
STRINGLIST **sc = st;
/* convert to UTF-8 as best we can */
if (!utf8_text (s,charset,&u,U8T_CANONICAL))
utf8_text (s,NIL,&u,U8T_CANONICAL);
ret = mail_search_string_work (&u,st);
if (u.data != s->data) fs_give ((void **) &u.data);
return ret;
}
/* Mail search text worker routine
* Accepts: sized text to search
* string list of search keys
* Returns: T if search found a match
*/
long mail_search_string_work (SIZEDTEXT *s,STRINGLIST **st)
{
void *t;
STRINGLIST **sc = st;
while (*sc) { /* run down criteria list */
if (ssearch (s->data,s->size,(*sc)->text.data,(*sc)->text.size)) {
t = (void *) (*sc); /* found one, need to flush this */
*sc = (*sc)->next; /* remove it from the list */
fs_give (&t); /* flush the buffer */
}
else sc = &(*sc)->next; /* move to next in list */
}
return *st ? NIL : LONGT;
}
/* Mail search keyword
* Accepts: MAIL stream
* elt to get flags from
* keyword list
* T for keyword search, NIL for unkeyword search
* Returns: T if search found a match
*/
long mail_search_keyword (MAILSTREAM *stream,MESSAGECACHE *elt,STRINGLIST *st,
long flag)
{
int i,j;
unsigned long f = 0;
unsigned long tf;
do {
for (i = 0; (j = (i < NUSERFLAGS) && stream->user_flags[i]); ++i)
if (!compare_csizedtext (stream->user_flags[i],&st->text)) {
f |= (1 << i);
break;
}
if (flag && !j) return NIL;
} while (st = st->next);
tf = elt->user_flags & f; /* get set flags which match */
return flag ? (f == tf) : !tf;
}
/* Mail search an address list
* Accepts: address list
* string list
* Returns: T if search found a match
*/
#define SEARCHBUFLEN (size_t) 2000
#define SEARCHBUFSLOP (size_t) 5
long mail_search_addr (ADDRESS *adr,STRINGLIST *st)
{
ADDRESS *a,tadr;
SIZEDTEXT txt;
char tmp[SENDBUFLEN + 1];
size_t i = SEARCHBUFLEN;
size_t k;
long ret = NIL;
if (adr) {
txt.data = (unsigned char *) fs_get (i + SEARCHBUFSLOP);
/* never an error or next */
tadr.error = NIL,tadr.next = NIL;
/* write address list */
for (txt.size = 0,a = adr; a; a = a->next) {
k = (tadr.mailbox = a->mailbox) ? 4 + 2*strlen (a->mailbox) : 3;
if (tadr.personal = a->personal) k += 3 + 2*strlen (a->personal);
if (tadr.adl = a->adl) k += 3 + 2*strlen (a->adl);
if (tadr.host = a->host) k += 3 + 2*strlen (a->host);
if (tadr.personal || tadr.adl) k += 2;
if (k < (SENDBUFLEN-10)) {/* ignore ridiculous addresses */
tmp[0] = '\0';
rfc822_write_address (tmp,&tadr);
/* resize buffer if necessary */
if (((k = strlen (tmp)) + txt.size) > i)
fs_resize ((void **) &txt.data,SEARCHBUFSLOP + (i += SEARCHBUFLEN));
/* add new address */
memcpy (txt.data + txt.size,tmp,k);
txt.size += k;
/* another address follows */
if (a->next) txt.data[txt.size++] = ',';
}
}
txt.data[txt.size] = '\0'; /* tie off string */
ret = mail_search_header (&txt,st);
fs_give ((void **) &txt.data);
}
return ret;
}
/* Get string for low-memory searching
* Accepts: readin function pointer
* stream to use
* number of bytes
* gets data packet
* mail stream
* message number
* descriptor string
* option flags
* Returns: NIL, always
*/
#define SEARCHSLOP 128
char *mail_search_gets (readfn_t f,void *stream,unsigned long size,
GETS_DATA *md)
{
unsigned long i;
char tmp[MAILTMPLEN+SEARCHSLOP+1];
SIZEDTEXT st;
/* better not be called unless searching */
if (!md->stream->private.search.string) {
sprintf (tmp,"Search botch, mbx = %.80s, %s = %lu[%.80s]",
md->stream->mailbox,
(md->flags & FT_UID) ? "UID" : "msg",md->msgno,md->what);
fatal (tmp);
}
/* initially no match for search */
md->stream->private.search.result = NIL;
/* make sure buffer clear */
memset (st.data = (unsigned char *) tmp,'\0',
(size_t) MAILTMPLEN+SEARCHSLOP+1);
/* read first buffer */
(*f) (stream,st.size = i = min (size,(long) MAILTMPLEN),tmp);
/* search for text */
if (mail_search_string (&st,NIL,&md->stream->private.search.string))
md->stream->private.search.result = T;
else if (size -= i) { /* more to do, blat slop down */
memmove (tmp,tmp+MAILTMPLEN-SEARCHSLOP,(size_t) SEARCHSLOP);
do { /* read subsequent buffers one at a time */
(*f) (stream,i = min (size,(long) MAILTMPLEN),tmp+SEARCHSLOP);
st.size = i + SEARCHSLOP;
if (mail_search_string (&st,NIL,&md->stream->private.search.string))
md->stream->private.search.result = T;
else memmove (tmp,tmp+MAILTMPLEN,(size_t) SEARCHSLOP);
}
while ((size -= i) && !md->stream->private.search.result);
}
if (size) { /* toss out everything after that */
do (*f) (stream,i = min (size,(long) MAILTMPLEN),tmp);
while (size -= i);
}
return NIL;
}
/* Mail parse search criteria
* Accepts: criteria
* Returns: search program if parse successful, else NIL
*/
SEARCHPGM *mail_criteria (char *criteria)
{
SEARCHPGM *pgm = NIL;
char *criterion,*r,tmp[MAILTMPLEN];
int f;
if (criteria) { /* only if criteria defined */
/* make writeable copy of criteria */
criteria = cpystr (criteria);
/* for each criterion */
for (pgm = mail_newsearchpgm (), criterion = strtok_r (criteria," ",&r);
criterion; (criterion = strtok_r (NIL," ",&r))) {
f = NIL; /* init then scan the criterion */
switch (*ucase (criterion)) {
case 'A': /* possible ALL, ANSWERED */
if (!strcmp (criterion+1,"LL")) f = T;
else if (!strcmp (criterion+1,"NSWERED")) f = pgm->answered = T;
break;
case 'B': /* possible BCC, BEFORE, BODY */
if (!strcmp (criterion+1,"CC"))
f = mail_criteria_string (&pgm->bcc,&r);
else if (!strcmp (criterion+1,"EFORE"))
f = mail_criteria_date (&pgm->before,&r);
else if (!strcmp (criterion+1,"ODY"))
f = mail_criteria_string (&pgm->body,&r);
break;
case 'C': /* possible CC */
if (!strcmp (criterion+1,"C")) f = mail_criteria_string (&pgm->cc,&r);
break;
case 'D': /* possible DELETED */
if (!strcmp (criterion+1,"ELETED")) f = pgm->deleted = T;
break;
case 'F': /* possible FLAGGED, FROM */
if (!strcmp (criterion+1,"LAGGED")) f = pgm->flagged = T;
else if (!strcmp (criterion+1,"ROM"))
f = mail_criteria_string (&pgm->from,&r);
break;
case 'K': /* possible KEYWORD */
if (!strcmp (criterion+1,"EYWORD"))
f = mail_criteria_string (&pgm->keyword,&r);
break;
case 'N': /* possible NEW */
if (!strcmp (criterion+1,"EW")) f = pgm->recent = pgm->unseen = T;
break;
case 'O': /* possible OLD, ON */
if (!strcmp (criterion+1,"LD")) f = pgm->old = T;
else if (!strcmp (criterion+1,"N"))
f = mail_criteria_date (&pgm->on,&r);
break;
case 'R': /* possible RECENT */
if (!strcmp (criterion+1,"ECENT")) f = pgm->recent = T;
break;
case 'S': /* possible SEEN, SINCE, SUBJECT */
if (!strcmp (criterion+1,"EEN")) f = pgm->seen = T;
else if (!strcmp (criterion+1,"INCE"))
f = mail_criteria_date (&pgm->since,&r);
else if (!strcmp (criterion+1,"UBJECT"))
f = mail_criteria_string (&pgm->subject,&r);
break;
case 'T': /* possible TEXT, TO */
if (!strcmp (criterion+1,"EXT"))
f = mail_criteria_string (&pgm->text,&r);
else if (!strcmp (criterion+1,"O"))
f = mail_criteria_string (&pgm->to,&r);
break;
case 'U': /* possible UN* */
if (criterion[1] == 'N') {
if (!strcmp (criterion+2,"ANSWERED")) f = pgm->unanswered = T;
else if (!strcmp (criterion+2,"DELETED")) f = pgm->undeleted = T;
else if (!strcmp (criterion+2,"FLAGGED")) f = pgm->unflagged = T;
else if (!strcmp (criterion+2,"KEYWORD"))
f = mail_criteria_string (&pgm->unkeyword,&r);
else if (!strcmp (criterion+2,"SEEN")) f = pgm->unseen = T;
}
break;
default: /* we will barf below */
break;
}
if (!f) { /* if can't identify criterion */
sprintf (tmp,"Unknown search criterion: %.30s",criterion);
MM_LOG (tmp,ERROR);
mail_free_searchpgm (&pgm);
break;
}
}
/* no longer need copy of criteria */
fs_give ((void **) &criteria);
}
return pgm;
}
/* Parse a date
* Accepts: pointer to date integer to return
* pointer to strtok state
* Returns: T if successful, else NIL
*/
int mail_criteria_date (unsigned short *date,char **r)
{
STRINGLIST *s = NIL;
MESSAGECACHE elt;
/* parse the date and return fn if OK */
int ret = (mail_criteria_string (&s,r) &&
mail_parse_date (&elt,(char *) s->text.data) &&
(*date = mail_shortdate (elt.year,elt.month,elt.day))) ?
T : NIL;
if (s) mail_free_stringlist (&s);
return ret;
}
/* Calculate shortdate from elt values
* Accepts: year (0 = BASEYEAR)
* month (1 = January)
* day
* Returns: shortdate
*/
unsigned short mail_shortdate (unsigned int year,unsigned int month,
unsigned int day)
{
return (year << 9) + (month << 5) + day;
}
/* Parse a string
* Accepts: pointer to stringlist
* pointer to strtok state
* Returns: T if successful, else NIL
*/
int mail_criteria_string (STRINGLIST **s,char **r)
{
unsigned long n;
char e,*d,*end = " ",*c = strtok_r (NIL,"",r);
if (!c) return NIL; /* missing argument */
switch (*c) { /* see what the argument is */
case '{': /* literal string */
n = strtoul (c+1,&d,10); /* get its length */
if ((*d++ == '}') && (*d++ == '\015') && (*d++ == '\012') &&
(!(*(c = d + n)) || (*c == ' '))) {
e = *--c; /* store old delimiter */
*c = '\377'; /* make sure not a space */
strtok_r (c," ",r); /* reset the strtok mechanism */
*c = e; /* put character back */
break;
}
case '\0': /* catch bogons */
case ' ':
return NIL;
case '"': /* quoted string */
if (strchr (c+1,'"')) end = "\"";
else return NIL; /* falls through */
default: /* atomic string */
if (d = strtok_r (c,end,r)) n = strlen (d);
else return NIL;
break;
}
while (*s) s = &(*s)->next; /* find tail of list */
*s = mail_newstringlist (); /* make new entry */
/* return the data */
(*s)->text.data = (unsigned char *) cpystr (d);
(*s)->text.size = n;
return T;
}
/* Mail parse set from string
* Accepts: string to parse
* pointer to updated string pointer for return
* Returns: set with pointer updated, or NIL if error
*/
SEARCHSET *mail_parse_set (char *s,char **ret)
{
SEARCHSET *cur;
SEARCHSET *set = NIL;
while (isdigit (*s)) {
if (!set) cur = set = mail_newsearchset ();
else cur = cur->next = mail_newsearchset ();
/* parse value */
if (!(cur->first = strtoul (s,&s,10)) ||
((*s == ':') && !(isdigit (*++s) && (cur->last = strtoul (s,&s,10)))))
break; /* bad value or range */
if (*s == ',') ++s; /* point to next value if more */
else { /* end of set */
*ret = s; /* set return pointer */
return set; /* return set */
}
}
mail_free_searchset (&set); /* failure, punt partial set */
return NIL;
}
/* Mail append to set
* Accepts: head of search set or NIL to do nothing
* message to add
* Returns: tail of search set or NIL if did nothing
*/
SEARCHSET *mail_append_set (SEARCHSET *set,unsigned long msgno)
{
if (set) { /* find tail */
while (set->next) set = set->next;
/* start of set if no first member */
if (!set->first) set->first = msgno;
else if (msgno == (set->last ? set->last : set->first) + 1)
set->last = msgno; /* extend range if 1 past current */
else (set = set->next = mail_newsearchset ())->first = msgno;
}
return set;
}
/* Mail sort messages
* Accepts: mail stream
* character set
* search program
* sort program
* option flags
* Returns: vector of sorted message sequences or NIL if error
*/
unsigned long *mail_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
SORTPGM *pgm,long flags)
{
unsigned long *ret = NIL;
if (stream->dtb) /* do the driver's action */
ret = (*(stream->dtb->sort ? stream->dtb->sort : mail_sort_msgs))
(stream,charset,spg,pgm,flags);
/* flush search/sort programs if requested */
if (spg && (flags & SE_FREE)) mail_free_searchpgm (&spg);
if (flags & SO_FREE) mail_free_sortpgm (&pgm);
return ret;
}
/* Mail sort messages work routine
* Accepts: mail stream
* character set
* search program
* sort program
* option flags
* Returns: vector of sorted message sequences or NIL if error
*/
unsigned long *mail_sort_msgs (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
SORTPGM *pgm,long flags)
{
unsigned long i;
SORTCACHE **sc;
unsigned long *ret = NIL;
if (spg) { /* only if a search needs to be done */
int silent = stream->silent;
stream->silent = T; /* don't pass up mm_searched() events */
/* search for messages */
mail_search_full (stream,charset,spg,NIL);
stream->silent = silent; /* restore silence state */
}
/* initialize progress counters */
pgm->nmsgs = pgm->progress.cached = 0;
/* pass 1: count messages to sort */
for (i = 1; i <= stream->nmsgs; ++i)
if (mail_elt (stream,i)->searched) pgm->nmsgs++;
if (pgm->nmsgs) { /* pass 2: sort cache */
sc = mail_sort_loadcache (stream,pgm);
/* pass 3: sort messages */
if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags);
fs_give ((void **) &sc); /* don't need sort vector any more */
}
/* empty sort results */
else ret = (unsigned long *) memset (fs_get (sizeof (unsigned long)),0,
sizeof (unsigned long));
/* also return via callback if requested */
if (mailsortresults) (*mailsortresults) (stream,ret,pgm->nmsgs);
return ret; /* return sort results */
}
/* Mail sort sortcache vector
* Accepts: mail stream
* sort program
* sortcache vector
* option flags
* Returns: vector of sorted message sequences or NIL if error
*/
unsigned long *mail_sort_cache (MAILSTREAM *stream,SORTPGM *pgm,SORTCACHE **sc,
long flags)
{
unsigned long i,*ret;
/* pass 3: sort messages */
qsort ((void *) sc,pgm->nmsgs,sizeof (SORTCACHE *),mail_sort_compare);
/* optional post sorting */
if (pgm->postsort) (*pgm->postsort) ((void *) sc);
/* pass 4: return results */
ret = (unsigned long *) fs_get ((pgm->nmsgs+1) * sizeof (unsigned long));
if (flags & SE_UID) /* UID or msgno? */
for (i = 0; i < pgm->nmsgs; i++) ret[i] = mail_uid (stream,sc[i]->num);
else for (i = 0; i < pgm->nmsgs; i++) ret[i] = sc[i]->num;
ret[pgm->nmsgs] = 0; /* tie off message list */
return ret;
}
/* Mail load sortcache
* Accepts: mail stream, already searched
* sort program
* Returns: vector of sortcache pointers matching search
*/
static STRINGLIST maildateline = {{(unsigned char *) "date",4},NIL};
static STRINGLIST mailrnfromline = {{(unsigned char *) ">from",5},NIL};
static STRINGLIST mailfromline = {{(unsigned char *) "from",4},
&mailrnfromline};
static STRINGLIST mailtonline = {{(unsigned char *) "to",2},NIL};
static STRINGLIST mailccline = {{(unsigned char *) "cc",2},NIL};
static STRINGLIST mailsubline = {{(unsigned char *) "subject",7},NIL};
SORTCACHE **mail_sort_loadcache (MAILSTREAM *stream,SORTPGM *pgm)
{
char *t,*v,*x,tmp[MAILTMPLEN];
SORTPGM *pg;
SORTCACHE *s,**sc;
MESSAGECACHE *elt,telt;
ENVELOPE *env;
ADDRESS *adr = NIL;
unsigned long i = (pgm->nmsgs) * sizeof (SORTCACHE *);
sc = (SORTCACHE **) memset (fs_get ((size_t) i),0,(size_t) i);
/* see what needs to be loaded */
for (i = 1; !pgm->abort && (i <= stream->nmsgs); i++)
if ((elt = mail_elt (stream,i))->searched) {
sc[pgm->progress.cached++] =
s = (SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE);
s->pgm = pgm; /* note sort program */
s->num = i;
/* get envelope if cached */
if (stream->scache) env = (i == stream->msgno) ? stream->env : NIL;
else env = elt->private.msg.env;
for (pg = pgm; pg; pg = pg->next) switch (pg->function) {
case SORTARRIVAL: /* sort by arrival date */
if (!s->arrival) {
/* internal date unknown but can get? */
if (!elt->day && !(stream->dtb->flags & DR_NOINTDATE)) {
sprintf (tmp,"%lu",i);
mail_fetch_fast (stream,tmp,NIL);
}
/* wrong thing before 3-Jan-1970 */
s->arrival = elt->day ? mail_longdate (elt) : 1;
s->dirty = T;
}
break;
case SORTSIZE: /* sort by message size */
if (!s->size) {
if (!elt->rfc822_size) {
sprintf (tmp,"%lu",i);
mail_fetch_fast (stream,tmp,NIL);
}
s->size = elt->rfc822_size ? elt->rfc822_size : 1;
s->dirty = T;
}
break;
case SORTDATE: /* sort by date */
if (!s->date) {
if (env) t = env->date;
else if ((t = mail_fetch_header (stream,i,NIL,&maildateline,NIL,
FT_INTERNAL | FT_PEEK)) &&
(t = strchr (t,':')))
for (x = ++t; x = strpbrk (x,"\012\015"); x++)
switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
case ' ': /* erase continuation newlines */
case '\t':
memmove (x,v,strlen (v));
break;
default: /* tie off extraneous text */
*x = x[1] = '\0';
}
/* skip leading whitespace */
if (t) while ((*t == ' ') || (*t == '\t')) t++;
/* parse date from Date: header */
if (!(t && mail_parse_date (&telt,t) &&
(s->date = mail_longdate (&telt)))) {
/* failed, use internal date */
if (!(s->date = s->arrival)) {
/* internal date unknown but can get? */
if (!elt->day && !(stream->dtb->flags & DR_NOINTDATE)) {
sprintf (tmp,"%lu",i);
mail_fetch_fast (stream,tmp,NIL);
}
/* wrong thing before 3-Jan-1970 */
s->date = (s->arrival = elt->day ? mail_longdate (elt) : 1);
}
}
s->dirty = T;
}
break;
case SORTFROM: /* sort by first from */
if (!s->from) {
if (env) s->from = env->from && env->from->mailbox ?
cpystr (env->from->mailbox) : NIL;
else if ((t = mail_fetch_header (stream,i,NIL,&mailfromline,NIL,
FT_INTERNAL | FT_PEEK)) &&
(t = strchr (t,':'))) {
for (x = ++t; x = strpbrk (x,"\012\015"); x++)
switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
case ' ': /* erase continuation newlines */
case '\t':
memmove (x,v,strlen (v));
break;
case 'f': /* continuation but with extra "From:" */
case 'F':
if (v = strchr (v,':')) {
memmove (x,v+1,strlen (v+1));
break;
}
default: /* tie off extraneous text */
*x = x[1] = '\0';
}
if (adr = rfc822_parse_address (&adr,adr,&t,BADHOST,0)) {
s->from = adr->mailbox;
adr->mailbox = NIL;
mail_free_address (&adr);
}
}
if (!s->from) s->from = cpystr ("");
s->dirty = T;
}
break;
case SORTTO: /* sort by first to */
if (!s->to) {
if (env) s->to = env->to && env->to->mailbox ?
cpystr (env->to->mailbox) : NIL;
else if ((t = mail_fetch_header (stream,i,NIL,&mailtonline,NIL,
FT_INTERNAL | FT_PEEK)) &&
(t = strchr (t,':'))) {
for (x = ++t; x = strpbrk (x,"\012\015"); x++)
switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
case ' ': /* erase continuation newlines */
case '\t':
memmove (x,v,strlen (v));
break;
case 't': /* continuation but with extra "To:" */
case 'T':
if (v = strchr (v,':')) {
memmove (x,v+1,strlen (v+1));
break;
}
default: /* tie off extraneous text */
*x = x[1] = '\0';
}
if (adr = rfc822_parse_address (&adr,adr,&t,BADHOST,0)) {
s->to = adr->mailbox;
adr->mailbox = NIL;
mail_free_address (&adr);
}
}
if (!s->to) s->to = cpystr ("");
s->dirty = T;
}
break;
case SORTCC: /* sort by first cc */
if (!s->cc) {
if (env) s->cc = env->cc && env->cc->mailbox ?
cpystr (env->cc->mailbox) : NIL;
else if ((t = mail_fetch_header (stream,i,NIL,&mailccline,NIL,
FT_INTERNAL | FT_PEEK)) &&
(t = strchr (t,':'))) {
for (x = ++t; x = strpbrk (x,"\012\015"); x++)
switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
case ' ': /* erase continuation newlines */
case '\t':
memmove (x,v,strlen (v));
break;
case 't': /* continuation but with extra "To:" */
case 'T':
if (v = strchr (v,':')) {
memmove (x,v+1,strlen (v+1));
break;
}
default: /* tie off extraneous text */
*x = x[1] = '\0';
}
if (adr = rfc822_parse_address (&adr,adr,&t,BADHOST,0)) {
s->cc = adr->mailbox;
adr->mailbox = NIL;
mail_free_address (&adr);
}
}
if (!s->cc) s->cc = cpystr ("");
s->dirty = T;
}
break;
case SORTSUBJECT: /* sort by subject */
if (!s->subject) {
/* get subject from envelope if have one */
if (env) t = env->subject ? env->subject : "";
/* otherwise snarf from header text */
else if ((t = mail_fetch_header (stream,i,NIL,&mailsubline,
NIL,FT_INTERNAL | FT_PEEK)) &&
(t = strchr (t,':')))
for (x = ++t; x = strpbrk (x,"\012\015"); x++)
switch (*(v = ((*x == '\015') && (x[1] == '\012')) ? x+2 : x+1)){
case ' ': /* erase continuation newlines */
case '\t':
memmove (x,v,strlen (v));
break;
default: /* tie off extraneous text */
*x = x[1] = '\0';
}
else t = ""; /* empty subject */
/* strip and cache subject */
s->refwd = mail_strip_subject (t,&s->subject);
s->dirty = T;
}
break;
default:
fatal ("Unknown sort function");
}
}
return sc;
}
/* Strip subjects of extra spaces and leading and trailing cruft for sorting
* Accepts: unstripped subject
* pointer to return stripped subject, in cpystr form
* Returns: T if subject had a re/fwd, NIL otherwise
*/
unsigned int mail_strip_subject (char *t,char **ret)
{
SIZEDTEXT src,dst;
unsigned long i,slen;
char c,*s,*x;
unsigned int refwd = NIL;
if (src.size = strlen (t)) { /* have non-empty subject? */
src.data = (unsigned char *) t;
/* Step 1 */
/* make copy, convert MIME2 if needed */
*ret = s = (utf8_mime2text (&src,&dst,U8T_CANONICAL) &&
(src.data != dst.data)) ? (char *) dst.data : cpystr (t);
/* convert spaces to tab, strip extra spaces */
for (x = t = s, c = 'x'; *t; t++) {
if (c != ' ') c = *x++ = ((*t == '\t') ? ' ' : *t);
else if ((*t != '\t') && (*t != ' ')) c = *x++ = *t;
}
*x = '\0'; /* tie off string */
/* Step 2 */
for (slen = dst.size; s; slen = strlen (s)) {
for (t = s + slen; t > s; ) switch (t[-1]) {
case ' ': case '\t': /* WSP */
*--t = '\0'; /* just remove it */
break;
case ')': /* possible "(fwd)" */
if ((t >= (s + 5)) && (t[-5] == '(') &&
((t[-4] == 'F') || (t[-4] == 'f')) &&
((t[-3] == 'W') || (t[-3] == 'w')) &&
((t[-2] == 'D') || (t[-2] == 'd'))) {
*(t -= 5) = '\0'; /* remove "(fwd)" */
refwd = T; /* note a re/fwd */
break;
}
default: /* not a subj-trailer */
t = s;
break;
}
/* Steps 3-5 */
for (t = s; t; ) switch (*s) {
case ' ': case '\t': /* WSP */
s = t = mail_strip_subject_wsp (s + 1);
break;
case 'r': case 'R': /* possible "re" */
if (((s[1] == 'E') || (s[1] == 'e')) &&
(t = mail_strip_subject_wsp (s + 2)) &&
(t = mail_strip_subject_blob (t)) && (*t == ':')) {
s = ++t; /* found "re" */
refwd = T; /* definitely a re/fwd at this point */
}
else t = NIL; /* found subj-middle */
break;
case 'f': case 'F': /* possible "fw" or "fwd" */
if (((s[1] == 'w') || (s[1] == 'W')) &&
(((s[2] == 'd') || (s[2] == 'D')) ?
(t = mail_strip_subject_wsp (s + 3)) :
(t = mail_strip_subject_wsp (s + 2))) &&
(t = mail_strip_subject_blob (t)) && (*t == ':')) {
s = ++t; /* found "fwd" */
refwd = T; /* definitely a re/fwd at this point */
}
else t = NIL; /* found subj-middle */
break;
case '[': /* possible subj-blob */
if ((t = mail_strip_subject_blob (s)) && *t) s = t;
else t = NIL; /* found subj-middle */
break;
default:
t = NIL; /* found subj-middle */
break;
}
/* Step 6 */
/* Netscape-style "[Fwd: ...]"? */
if ((*s == '[') && ((s[1] == 'F') || (s[1] == 'f')) &&
((s[2] == 'W') || (s[2] == 'w')) &&
((s[3] == 'D') || (s[3] == 'd')) && (s[4] == ':') &&
(s[i = strlen (s) - 1] == ']')) {
s[i] = '\0'; /* flush closing "]" */
s += 5; /* and leading "[Fwd:" */
refwd = T; /* definitely a re/fwd at this point */
}
else break; /* don't need to loop back to step 2 */
}
if (s != (t = *ret)) { /* removed leading text? */
s = *ret = cpystr (s); /* yes, make a fresh return copy */
fs_give ((void **) &t); /* flush old copy */
}
}
else *ret = cpystr (""); /* empty subject */
return refwd; /* return re/fwd state */
}
/* Strip subject wsp helper routine
* Accepts: text
* Returns: pointer to text after blob
*/
char *mail_strip_subject_wsp (char *s)
{
while ((*s == ' ') || (*s == '\t')) s++;
return s;
}
/* Strip subject blob helper routine
* Accepts: text
* Returns: pointer to text after any blob, NIL if blob-like but not blob
*/
char *mail_strip_subject_blob (char *s)
{
if (*s != '[') return s; /* not a blob, ignore */
/* search for end of blob */
while (*++s != ']') if ((*s == '[') || !*s) return NIL;
return mail_strip_subject_wsp (s + 1);
}
/* Sort compare messages
* Accept: first message sort cache element
* second message sort cache element
* Returns: -1 if a1 < a2, 0 if a1 == a2, 1 if a1 > a2
*/
int mail_sort_compare (const void *a1,const void *a2)
{
int i = 0;
SORTCACHE *s1 = *(SORTCACHE **) a1;
SORTCACHE *s2 = *(SORTCACHE **) a2;
SORTPGM *pgm = s1->pgm;
if (!s1->sorted) { /* this one sorted yet? */
s1->sorted = T;
pgm->progress.sorted++; /* another sorted message */
}
if (!s2->sorted) { /* this one sorted yet? */
s2->sorted = T;
pgm->progress.sorted++; /* another sorted message */
}
do {
switch (pgm->function) { /* execute search program */
case SORTDATE: /* sort by date */
i = compare_ulong (s1->date,s2->date);
break;
case SORTARRIVAL: /* sort by arrival date */
i = compare_ulong (s1->arrival,s2->arrival);
break;
case SORTSIZE: /* sort by message size */
i = compare_ulong (s1->size,s2->size);
break;
case SORTFROM: /* sort by first from */
i = compare_cstring (s1->from,s2->from);
break;
case SORTTO: /* sort by first to */
i = compare_cstring (s1->to,s2->to);
break;
case SORTCC: /* sort by first cc */
i = compare_cstring (s1->cc,s2->cc);
break;
case SORTSUBJECT: /* sort by subject */
i = compare_cstring (s1->subject,s2->subject);
break;
}
if (pgm->reverse) i = -i; /* flip results if necessary */
}
while (pgm = i ? NIL : pgm->next);
/* return result, avoid 0 if at all possible */
return i ? i : compare_ulong (s1->num,s2->num);
}
/* Return message date as an unsigned long seconds since time began
* Accepts: message cache pointer
* Returns: unsigned long of date
*
* This routine, like most UNIX systems, is clueless about leap seconds.
* Thus, it treats 23:59:60 as equivalent to 00:00:00 the next day.
*
* This routine forces any early hours on 1-Jan-1970 in oriental timezones
* to be 1-Jan-1970 00:00:00 UTC, so as to avoid negative longdates.
*/
unsigned long mail_longdate (MESSAGECACHE *elt)
{
unsigned long m = elt->month ? elt->month : 1;
unsigned long yr = elt->year + BASEYEAR;
/* number of days since time began */
unsigned long ret = (elt->day ? (elt->day - 1) : 0)
+ 30 * (m - 1) + ((m + (m > 8)) / 2)
#ifndef USEJULIANCALENDAR
#ifndef USEORTHODOXCALENDAR /* Gregorian calendar */
+ ((yr / 400) - (BASEYEAR / 400)) - ((yr / 100) - (BASEYEAR / 100))
#ifdef Y4KBUGFIX
- ((yr / 4000) - (BASEYEAR / 4000))
#endif
- ((m < 3) ?
!(yr % 4) && ((yr % 100) || (!(yr % 400)
#ifdef Y4KBUGFIX
&& (yr % 4000)
#endif
)) : 2)
#else /* Orthodox calendar */
+ ((2*(yr / 900)) - (2*(BASEYEAR / 900)))
+ (((yr % 900) >= 200) - ((BASEYEAR % 900) >= 200))
+ (((yr % 900) >= 600) - ((BASEYEAR % 900) >= 600))
- ((yr / 100) - (BASEYEAR / 100))
- ((m < 3) ?
!(yr % 4) && ((yr % 100) || ((yr % 900) == 200) || ((yr % 900) == 600))
: 2)
#endif
#endif
+ elt->year * 365 + (((unsigned long) (elt->year + (BASEYEAR % 4))) / 4);
ret *= 24; ret += elt->hours; /* date value in hours */
ret *= 60; ret +=elt->minutes;/* date value in minutes */
yr = (elt->zhours * 60) + elt->zminutes;
if (elt->zoccident) ret += yr;/* occidental timezone, make UTC */
else if (ret < yr) return 0; /* still 31-Dec-1969 in UTC */
else ret -= yr; /* oriental timezone, make UTC */
ret *= 60; ret += elt->seconds;
return ret;
}
/* Mail thread messages
* Accepts: mail stream
* thread type
* character set
* search program
* option flags
* Returns: thread node tree or NIL if error
*/
THREADNODE *mail_thread (MAILSTREAM *stream,char *type,char *charset,
SEARCHPGM *spg,long flags)
{
THREADNODE *ret = NIL;
if (stream->dtb) /* must have a live driver */
ret = stream->dtb->thread ? /* do driver's action if available */
(*stream->dtb->thread) (stream,type,charset,spg,flags) :
mail_thread_msgs (stream,type,charset,spg,flags,mail_sort_msgs);
/* flush search/sort programs if requested */
if (spg && (flags & SE_FREE)) mail_free_searchpgm (&spg);
return ret;
}
/* Mail thread messages
* Accepts: mail stream
* thread type
* character set
* search program
* option flags
* sorter routine
* Returns: thread node tree or NIL if error
*/
THREADNODE *mail_thread_msgs (MAILSTREAM *stream,char *type,char *charset,
SEARCHPGM *spg,long flags,sorter_t sorter)
{
THREADER *t;
for (t = &mailthreadlist; t; t = t->next)
if (!compare_cstring (type,t->name)) {
THREADNODE *ret = (*t->dispatch) (stream,charset,spg,flags,sorter);
if (mailthreadresults) (*mailthreadresults) (stream,ret);
return ret;
}
MM_LOG ("No such thread type",ERROR);
return NIL;
}
/* Mail thread ordered subject
* Accepts: mail stream
* character set
* search program
* option flags
* sorter routine
* Returns: thread node tree
*/
THREADNODE *mail_thread_orderedsubject (MAILSTREAM *stream,char *charset,
SEARCHPGM *spg,long flags,
sorter_t sorter)
{
THREADNODE *thr = NIL;
THREADNODE *cur,*top,**tc;
SORTPGM pgm,pgm2;
SORTCACHE *s;
unsigned long i,j,*lst,*ls;
/* sort by subject+date */
memset (&pgm,0,sizeof (SORTPGM));
memset (&pgm2,0,sizeof (SORTPGM));
pgm.function = SORTSUBJECT;
pgm.next = &pgm2;
pgm2.function = SORTDATE;
if (lst = (*sorter) (stream,charset,spg,&pgm,flags & ~(SE_FREE | SE_UID))){
if (*(ls = lst)) { /* create thread */
/* note first subject */
cur = top = thr = mail_newthreadnode
((SORTCACHE *) (*mailcache) (stream,*ls++,CH_SORTCACHE));
/* note its number */
cur->num = (flags & SE_UID) ? mail_uid (stream,*lst) : *lst;
i = 1; /* number of threads */
while (*ls) { /* build tree */
/* subjects match? */
s = (SORTCACHE *) (*mailcache) (stream,*ls++,CH_SORTCACHE);
if (compare_cstring (top->sc->subject,s->subject)) {
i++; /* have a new thread */
top = top->branch = cur = mail_newthreadnode (s);
}
/* start a child of the top */
else if (cur == top) cur = cur->next = mail_newthreadnode (s);
/* sibling of child */
else cur = cur->branch = mail_newthreadnode (s);
/* set to msgno or UID as needed */
cur->num = (flags & SE_UID) ? mail_uid (stream,s->num) : s->num;
}
/* make threadnode cache */
tc = (THREADNODE **) fs_get (i * sizeof (THREADNODE *));
/* load threadnode cache */
for (j = 0, cur = thr; cur; cur = cur->branch) tc[j++] = cur;
if (i != j) fatal ("Threadnode cache confusion");
qsort ((void *) tc,i,sizeof (THREADNODE *),mail_thread_compare_date);
for (j = 0, --i; j < i; j++) tc[j]->branch = tc[j+1];
tc[j]->branch = NIL; /* end of root */
thr = tc[0]; /* head of data */
fs_give ((void **) &tc);
}
fs_give ((void **) &lst);
}
return thr;
}
/* Mail thread references
* Accepts: mail stream
* character set
* search program
* option flags
* sorter routine
* Returns: thread node tree
*/
#define REFHASHSIZE 1009 /* arbitrary prime for hash table size */
/* Reference threading container, as described in Jamie Zawinski's web page
* (http://www.jwz.org/doc/threading.html) for this algorithm. These are
* stored as extended data in the hash table (called "id_table" in JWZ's
* document) and are maintained by the hash table routines. The hash table
* routines implement extended data as additional void* words at the end of
* each bucket, hence these strange macros instead of a struct which would
* have been more straightforward.
*/
#define THREADLINKS 3 /* number of thread links */
#define CACHE(data) ((SORTCACHE *) (data)[0])
#define PARENT(data) ((container_t) (data)[1])
#define SETPARENT(data,value) ((container_t) (data[1] = value))
#define SIBLING(data) ((container_t) (data)[2])
#define SETSIBLING(data,value) ((container_t) (data[2] = value))
#define CHILD(data) ((container_t) (data)[3])
#define SETCHILD(data,value) ((container_t) (data[3] = value))
THREADNODE *mail_thread_references (MAILSTREAM *stream,char *charset,
SEARCHPGM *spg,long flags,sorter_t sorter)
{
MESSAGECACHE *elt,telt;
ENVELOPE *env;
SORTCACHE *s;
STRINGLIST *st;
HASHENT *he;
THREADNODE **tc,*cur,*lst,*nxt,*sis,*msg;
container_t con,nxc,prc,sib;
void **sub;
char *t,tmp[MAILTMPLEN];
unsigned long j,nmsgs;
unsigned long i = stream->nmsgs * sizeof (SORTCACHE *);
SORTCACHE **sc = (SORTCACHE **) memset (fs_get ((size_t) i),0,(size_t) i);
HASHTAB *ht = hash_create (REFHASHSIZE);
THREADNODE *root = NIL;
if (spg) { /* only if a search needs to be done */
int silent = stream->silent;
stream->silent = T; /* don't pass up mm_searched() events */
/* search for messages */
mail_search_full (stream,charset,spg,NIL);
stream->silent = silent; /* restore silence state */
}
/* create SORTCACHE vector of requested msgs */
for (i = 1, nmsgs = 0; i <= stream->nmsgs; ++i)
if (mail_elt (stream,i)->searched)
(sc[nmsgs++] = (SORTCACHE *)(*mailcache)(stream,i,CH_SORTCACHE))->num =i;
/* separate pass so can do overview fetch lookahead */
for (i = 0; i < nmsgs; ++i) { /* for each requested message */
/* is anything missing in its SORTCACHE? */
if (!((s = sc[i])->date && s->subject && s->message_id && s->references)) {
/* driver has an overview mechanism? */
if (stream->dtb && stream->dtb->overview) {
/* yes, find following unloaded entries */
for (j = i + 1; (j < nmsgs) && !sc[j]->references; ++j);
sprintf (tmp,"%lu",mail_uid (stream,s->num));
if (i != --j) /* end of range different? */
sprintf (tmp + strlen (tmp),":%lu",mail_uid (stream,sc[j]->num));
/* load via overview mechanism */
mail_fetch_overview (stream,tmp,mail_thread_loadcache);
}
/* still missing data? */
if (!s->date || !s->subject || !s->message_id || !s->references) {
/* try to load data from envelope */
if (env = mail_fetch_structure (stream,s->num,NIL,NIL)) {
if (!s->date && env->date && mail_parse_date (&telt,env->date))
s->date = mail_longdate (&telt);
if (!s->subject && env->subject)
s->refwd =
mail_strip_subject (env->subject,&s->subject);
if (!s->message_id && env->message_id && *env->message_id)
s->message_id = mail_thread_parse_msgid (env->message_id,NIL);
if (!s->references && /* use References: or In-Reply-To: */
!(s->references =
mail_thread_parse_references (env->references,T)))
s->references = mail_thread_parse_references(env->in_reply_to,NIL);
}
/* last resort */
if (!s->date && !(s->date = s->arrival)) {
/* internal date unknown but can get? */
if (!(elt = mail_elt (stream,s->num))->day &&
!(stream->dtb->flags & DR_NOINTDATE)) {
sprintf (tmp,"%lu",s->num);
mail_fetch_fast (stream,tmp,NIL);
}
/* wrong thing before 3-Jan-1970 */
s->date = (s->arrival = elt->day ? mail_longdate (elt) : 1);
}
if (!s->subject) s->subject = cpystr ("");
if (!s->references) s->references = mail_newstringlist ();
s->dirty = T;
}
}
/* Step 1 (preliminary) */
/* generate unique string */
sprintf (tmp,"%s.%lx.%lx@%s",stream->mailbox,stream->uid_validity,
mail_uid (stream,s->num),mylocalhost ());
/* flush old unique string if not message-id */
if (s->unique && (s->unique != s->message_id))
fs_give ((void **) &s->unique);
s->unique = s->message_id ? /* don't permit Message ID duplicates */
(hash_lookup (ht,s->message_id) ? cpystr (tmp) : s->message_id) :
(s->message_id = cpystr (tmp));
/* add unique string to hash table */
hash_add (ht,s->unique,s,THREADLINKS);
}
/* Step 1 */
for (i = 0; i < nmsgs; ++i) { /* for each message in sortcache */
/* Step 1A */
if ((st = (s = sc[i])->references) && st->text.data)
for (con = hash_lookup_and_add (ht,(char *) st->text.data,NIL,
THREADLINKS); st = st->next; con = nxc) {
nxc = hash_lookup_and_add (ht,(char *) st->text.data,NIL,THREADLINKS);
/* only if no parent & won't introduce loop */
if (!PARENT (nxc) && !mail_thread_check_child (con,nxc)) {
SETPARENT (nxc,con); /* establish parent/child link */
/* other children become sibling of this one */
SETSIBLING (nxc,CHILD (con));
SETCHILD (con,nxc); /* set as child of parent */
}
}
else con = NIL; /* else message has no ancestors */
/* Step 1B */
if ((prc = PARENT ((nxc = hash_lookup (ht,s->unique)))) &&
(prc != con)) { /* break links if have a different parent */
SETPARENT (nxc,NIL); /* easy if direct child */
if (nxc == CHILD (prc)) SETCHILD (prc,SIBLING (nxc));
else { /* otherwise hunt through sisters */
for (sib = CHILD (prc); nxc != SIBLING (sib); sib = SIBLING (sib));
SETSIBLING (sib,SIBLING (nxc));
}
SETSIBLING (nxc,NIL); /* no more little sisters either */
prc = NIL; /* no more parent set */
}
/* need to set parent, and parent is good? */
if (!prc && !mail_thread_check_child (con,nxc)) {
SETPARENT (nxc,con); /* establish parent/child link */
if (con) { /* if non-root parent, set parent's child */
if (CHILD (con)) { /* have a child already */
/* find youngest daughter */
for (con = CHILD (con); SIBLING (con); con = SIBLING (con));
SETSIBLING (con,nxc); /* add new baby sister */
}
else SETCHILD (con,nxc);/* set as only child */
}
}
}
fs_give ((void **) &sc); /* finished with sortcache vector */
/* Step 2 */
/* search hash table for parentless messages */
for (i = 0, prc = con = NIL; i < ht->size; i++)
for (he = ht->table[i]; he; he = he->next)
if (!PARENT ((nxc = he->data))) {
/* sibling of previous parentless message */
if (con) con = SETSIBLING (con,nxc);
else prc = con = nxc; /* first parentless message */
}
/* Once the dummy containers are pruned, we no longer need the parent
* information, so we can convert the containers to THREADNODEs. Since
* we don't need the id_table any more either, we can reset the hash table
* and reuse it as a subject_table. Resetting the hash table will also
* destroy the containers.
*/
/* Step 3 */
/* prune dummies, convert to threadnode */
root = mail_thread_c2node (stream,mail_thread_prune_dummy (prc,NIL),flags);
/* Step 4 */
/* make buffer for sorting */
tc = (THREADNODE **) fs_get (nmsgs * sizeof (THREADNODE *));
/* load threadcache and count nodes to sort */
for (i = 0, cur = root; cur ; cur = cur->branch) tc[i++] = cur;
if (i > 1) { /* only if need to sort */
qsort ((void *) tc,i,sizeof (THREADNODE *),mail_thread_compare_date);
/* relink siblings */
for (j = 0, --i; j < i; j++) tc[j]->branch = tc[j+1];
tc[j]->branch = NIL; /* end of root */
root = tc[0]; /* establish new root */
}
/* Step 5A */
hash_reset (ht); /* discard containers, reset ht */
/* Step 5B */
for (cur = root; cur; cur = cur->branch)
if ((t = (nxt = (cur->sc ? cur : cur->next))->sc->subject) && *t) {
/* add new subject to hash table */
if (!(sub = hash_lookup (ht,t))) hash_add (ht,t,cur,0);
/* if one in table not dummy and */
else if ((s = (lst = (THREADNODE *) sub[0])->sc) &&
/* current dummy, or not re/fwd and table is */
(!cur->sc || (!nxt->sc->refwd && s->refwd)))
sub[0] = (void *) cur; /* replace with this message */
}
/* Step 5C */
for (cur = root, sis = NIL; cur; cur = msg) {
/* do nothing if current message or no sub */
if (!(t = (cur->sc ? cur : cur->next)->sc->subject) || !*t ||
((lst = (THREADNODE *) (sub = hash_lookup (ht,t))[0]) == cur))
msg = (sis = cur)->branch;
else if (!lst->sc) { /* is message in the table a dummy? */
/* find youngest daughter of msg in table */
for (msg = lst->next; msg->branch; msg = msg->branch);
if (!cur->sc) { /* current message a dummy? */
msg->branch = cur->next;/* current's daughter now dummy's youngest */
msg = cur->branch; /* continue scan at younger sister */
/* now delete this node */
cur->branch = cur->next = NIL;
mail_free_threadnode (&cur);
}
else { /* current message not a dummy */
msg->branch = cur; /* append as youngest daughter */
msg = cur->branch; /* continue scan at younger sister */
cur->branch = NIL; /* lose our younger sisters */
}
}
else { /* no dummies, is current re/fwd, table not? */
if (cur->sc->refwd && !lst->sc->refwd) {
if (lst->next) { /* find youngest daughter of msg in table */
for (msg = lst->next; msg->branch; msg = msg->branch);
msg->branch = cur; /* append as youngest daughter */
}
else lst->next = cur; /* no children, so make the eldest daughter */
}
else { /* no re/fwd, create a new dummy */
msg = mail_newthreadnode (NIL);
if (lst == root) { /* msg in table is root? */
root = lst->branch; /* younger sister becomes new root */
/* no longer older sister either */
if (lst == sis) sis = NIL;
}
else { /* find older sister of msg in table */
for (nxt = root; lst != nxt->branch; nxt = nxt->branch);
/* remove from older sister */
nxt->branch = lst->branch;
}
msg->next = lst; /* msg in table becomes child */
lst->branch = cur; /* current now little sister of msg in table */
if (sis) { /* have an elder sister? */
if (sis == lst) /* rescan if lost her */
for (sis = root; cur != sis->branch; sis = sis->branch);
sis->branch = msg; /* make dummy younger sister of big sister */
}
else root = msg; /* otherwise this is the new root */
sub[0] = sis = msg; /* set new msg in table and new big sister */
}
msg = cur->branch; /* continue scan at younger sister */
cur->branch = NIL; /* lose our younger sisters */
}
if (sis) sis->branch = msg; /* older sister gets this as younger sister */
else root = msg; /* otherwise this is the new root */
}
hash_destroy (&ht); /* finished with hash table */
/* Step 6 */
/* sort threads */
root = mail_thread_sort (root,tc);
fs_give ((void **) &tc); /* finished with sort buffer */
return root; /* return sorted list */
}
/* Fetch overview callback to load sortcache for threading
* Accepts: MAIL stream
* UID of this message
* overview of this message
* msgno of this message
*/
void mail_thread_loadcache (MAILSTREAM *stream,unsigned long uid,OVERVIEW *ov,
unsigned long msgno)
{
if (msgno && ov) { /* just in case */
MESSAGECACHE telt;
SORTCACHE *s = (SORTCACHE *) (*mailcache) (stream,msgno,CH_SORTCACHE);
if (!s->subject && ov->subject) {
s->refwd = mail_strip_subject (ov->subject,&s->subject);
s->dirty = T;
}
if (!s->from && ov->from && ov->from->mailbox) {
s->from = cpystr (ov->from->mailbox);
s->dirty = T;
}
if (!s->date && ov->date && mail_parse_date (&telt,ov->date)) {
s->date = mail_longdate (&telt);
s->dirty = T;
}
if (!s->message_id && ov->message_id) {
s->message_id = mail_thread_parse_msgid (ov->message_id,NIL);
s->dirty = T;
}
if (!s->references &&
!(s->references = mail_thread_parse_references (ov->references,T))) {
/* don't do In-Reply-To with NNTP mailboxes */
s->references = mail_newstringlist ();
s->dirty = T;
}
if (!s->size && ov->optional.octets) {
s->size = ov->optional.octets;
s->dirty = T;
}
}
}
/* Thread parse Message ID
* Accepts: pointer to purported Message ID
* pointer to return pointer
* Returns: Message ID or NIL, return pointer updated
*/
char *mail_thread_parse_msgid (char *s,char **ss)
{
char *ret = NIL;
char *t = NIL;
ADDRESS *adr;
if (s) { /* only for non-NIL strings */
rfc822_skipws (&s); /* skip whitespace */
/* ignore phrases */
if (((*s == '<') || (s = rfc822_parse_phrase (s))) &&
(adr = rfc822_parse_routeaddr (s,&t,BADHOST))) {
/* make return msgid */
if (adr->mailbox && adr->host)
sprintf (ret = (char *) fs_get (strlen (adr->mailbox) +
strlen (adr->host) + 2),"%s@%s",
adr->mailbox,adr->host);
mail_free_address (&adr); /* don't need temporary address */
}
}
if (ss) *ss = t; /* update return pointer */
return ret;
}
/* Thread parse references
* Accepts: pointer to purported references
* parse multiple references flag
* Returns: references or NIL
*/
STRINGLIST *mail_thread_parse_references (char *s,long flag)
{
char *t;
STRINGLIST *ret = NIL;
STRINGLIST *cur;
/* found first reference? */
if (t = mail_thread_parse_msgid (s,&s)) {
(ret = mail_newstringlist ())->text.data = (unsigned char *) t;
ret->text.size = strlen (t);
if (flag) /* parse subsequent references */
for (cur = ret; t = mail_thread_parse_msgid (s,&s); cur = cur->next) {
(cur->next = mail_newstringlist ())->text.data = (unsigned char *) t;
cur->next->text.size = strlen (t);
}
}
return ret;
}
/* Prune dummy messages
* Accepts: candidate container to prune
* older sibling of container, if any
* Returns: container in this position, possibly pruned
* All children and younger siblings are also pruned
*/
container_t mail_thread_prune_dummy (container_t msg,container_t ane)
{
/* prune container and children */
container_t ret = msg ? mail_thread_prune_dummy_work (msg,ane) : NIL;
/* prune all younger sisters */
if (ret) for (ane = ret; ane && (msg = SIBLING (ane)); ane = msg)
msg = mail_thread_prune_dummy_work (msg,ane);
return ret;
}
/* Prune dummy messages worker routine
* Accepts: candidate container to prune
* older sibling of container, if any
* Returns: container in this position, possibly pruned
* All children are also pruned
*/
container_t mail_thread_prune_dummy_work (container_t msg,container_t ane)
{
container_t cur;
/* get children, if any */
container_t nxt = mail_thread_prune_dummy (CHILD (msg),NIL);
/* just update children if container has msg */
if (CACHE (msg)) SETCHILD (msg,nxt);
else if (!nxt) { /* delete dummy with no children */
nxt = SIBLING (msg); /* get younger sister */
if (ane) SETSIBLING (ane,nxt);
/* prune younger sister if exists */
msg = nxt ? mail_thread_prune_dummy_work (nxt,ane) : NIL;
}
/* not if parent root & multiple children */
else if ((cur = PARENT (msg)) || !SIBLING (nxt)) {
/* OK to promote, try younger sister of aunt */
if (ane) SETSIBLING (ane,nxt);
/* otherwise promote to child of grandmother */
else if (cur) SETCHILD (cur,nxt);
SETPARENT (nxt,cur); /* set parent as well */
/* look for end of siblings in new container */
for (cur = nxt; SIBLING (cur); cur = SIBLING (cur));
/* reattach deleted container's siblings */
SETSIBLING (cur,SIBLING (msg));
/* prune and return new container */
msg = mail_thread_prune_dummy_work (nxt,ane);
}
else SETCHILD (msg,nxt); /* in case child pruned */
return msg; /* return this message */
}
/* Test that purported mother is not a child of purported daughter
* Accepts: mother
* purported daugher
* Returns: T if circular parentage exists, else NIL
*/
long mail_thread_check_child (container_t mother,container_t daughter)
{
if (mother) { /* only if mother non-NIL */
if (mother == daughter) return T;
for (daughter = CHILD (daughter); daughter; daughter = SIBLING (daughter))
if (mail_thread_check_child (mother,daughter)) return T;
}
return NIL;
}
/* Generate threadnodes from containers
* Accepts: Mail stream
* container
* flags
* Return: threadnode list
*/
THREADNODE *mail_thread_c2node (MAILSTREAM *stream,container_t con,long flags)
{
THREADNODE *ret,*cur;
SORTCACHE *s;
container_t nxt;
/* for each container */
for (ret = cur = NIL; con; con = SIBLING (con)) {
s = CACHE (con); /* yes, get its sortcache */
/* create node for it */
if (ret) cur = cur->branch = mail_newthreadnode (s);
else ret = cur = mail_newthreadnode (s);
/* attach sequence or UID for non-dummy */
if (s) cur->num = (flags & SE_UID) ? mail_uid (stream,s->num) : s->num;
/* attach the children */
if (nxt = CHILD (con)) cur->next = mail_thread_c2node (stream,nxt,flags);
}
return ret;
}
/* Sort thread tree by date
* Accepts: thread tree to sort
* qsort vector to sort
* Returns: sorted thread tree
*/
THREADNODE *mail_thread_sort (THREADNODE *thr,THREADNODE **tc)
{
unsigned long i,j;
THREADNODE *cur;
/* sort children of each thread */
for (cur = thr; cur; cur = cur->branch)
if (cur->next) cur->next = mail_thread_sort (cur->next,tc);
/* Must do this in a separate pass since recursive call will clobber tc */
/* load threadcache and count nodes to sort */
for (i = 0, cur = thr; cur; cur = cur->branch) tc[i++] = cur;
if (i > 1) { /* only if need to sort */
qsort ((void *) tc,i,sizeof (THREADNODE *),mail_thread_compare_date);
/* relink root siblings */
for (j = 0, --i; j < i; j++) tc[j]->branch = tc[j+1];
tc[j]->branch = NIL; /* end of root */
}
return i ? tc[0] : NIL; /* return new head of list */
}
/* Thread compare date
* Accept: first message sort cache element
* second message sort cache element
* Returns: -1 if a1 < a2, 1 if a1 > a2
*
* This assumes that a sort cache element is either a message (with a
* sortcache entry) or a dummy with a message (with sortcache entry) child.
* This is true of both the ORDEREDSUBJECT (no dummies) and REFERENCES
* (dummies only at top-level, and with non-dummy children).
*
* If a new algorithm allows a dummy parent to have a dummy child, this
* routine must be changed if it is to be used by that algorithm.
*
* Messages with bogus dates are always sorted at the top.
*/
int mail_thread_compare_date (const void *a1,const void *a2)
{
THREADNODE *t1 = *(THREADNODE **) a1;
THREADNODE *t2 = *(THREADNODE **) a2;
SORTCACHE *s1 = t1->sc ? t1->sc : t1->next->sc;
SORTCACHE *s2 = t2->sc ? t2->sc : t2->next->sc;
int ret = compare_ulong (s1->date,s2->date);
/* use number as final tie-breaker */
return ret ? ret : compare_ulong (s1->num,s2->num);
}
/* Mail parse sequence
* Accepts: mail stream
* sequence to parse
* Returns: T if parse successful, else NIL
*/
long mail_sequence (MAILSTREAM *stream,unsigned char *sequence)
{
unsigned long i,j,x;
for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->sequence = NIL;
while (sequence && *sequence){/* while there is something to parse */
if (*sequence == '*') { /* maximum message */
if (stream->nmsgs) i = stream->nmsgs;
else {
MM_LOG ("No messages, so no maximum message number",ERROR);
return NIL;
}
sequence++; /* skip past * */
}
/* parse and validate message number */
else if (!isdigit (*sequence)) {
MM_LOG ("Syntax error in sequence",ERROR);
return NIL;
}
else if (!(i = strtoul (sequence,(char **) &sequence,10)) ||
(i > stream->nmsgs)) {
MM_LOG ("Sequence out of range",ERROR);
return NIL;
}
switch (*sequence) { /* see what the delimiter is */
case ':': /* sequence range */
if (*++sequence == '*') { /* maximum message */
if (stream->nmsgs) j = stream->nmsgs;
else {
MM_LOG ("No messages, so no maximum message number",ERROR);
return NIL;
}
sequence++; /* skip past * */
}
/* parse end of range */
else if (!(j = strtoul (sequence,(char **) &sequence,10)) ||
(j > stream->nmsgs)) {
MM_LOG ("Sequence range invalid",ERROR);
return NIL;
}
if (*sequence && *sequence++ != ',') {
MM_LOG ("Sequence range syntax error",ERROR);
return NIL;
}
if (i > j) { /* swap the range if backwards */
x = i; i = j; j = x;
}
/* mark each item in the sequence */
while (i <= j) mail_elt (stream,j--)->sequence = T;
break;
case ',': /* single message */
++sequence; /* skip the delimiter, fall into end case */
case '\0': /* end of sequence, mark this message */
mail_elt (stream,i)->sequence = T;
break;
default: /* anything else is a syntax error! */
MM_LOG ("Sequence syntax error",ERROR);
return NIL;
}
}
return T; /* successfully parsed sequence */
}
/* Parse flag list
* Accepts: MAIL stream
* flag list as a character string
* pointer to user flags to return
* Returns: system flags
*/
long mail_parse_flags (MAILSTREAM *stream,char *flag,unsigned long *uf)
{
char *t,*n,*s,tmp[MAILTMPLEN],msg[MAILTMPLEN];
short f = 0;
long i,j;
*uf = 0; /* initially no user flags */
if (flag && *flag) { /* no-op if no flag string */
/* check if a list and make sure valid */
if (((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) ||
(strlen (flag) >= MAILTMPLEN)) {
MM_LOG ("Bad flag list",ERROR);
return NIL;
}
/* copy the flag string w/o list construct */
strncpy (n = tmp,flag+i,(j = strlen (flag) - (2*i)));
tmp[j] = '\0';
while ((t = n) && *t) { /* parse the flags */
/* find end of flag */
if (n = strchr (t,' ')) *n++ = '\0';
if (*t == '\\') { /* system flag? */
if (!compare_cstring (t+1,"SEEN")) f |= fSEEN;
else if (!compare_cstring (t+1,"DELETED")) f |= fDELETED;
else if (!compare_cstring (t+1,"FLAGGED")) f |= fFLAGGED;
else if (!compare_cstring (t+1,"ANSWERED")) f |= fANSWERED;
else if (!compare_cstring (t+1,"DRAFT")) f |= fDRAFT;
else {
sprintf (msg,"Unsupported system flag: %.80s",t);
MM_LOG (msg,WARN);
}
}
else { /* keyword flag */
for (i = j = 0; /* user flag, search through table */
!i && (j < NUSERFLAGS) && (s = stream->user_flags[j]); ++j)
if (!compare_cstring (t,s)) *uf |= i = 1 << j;
if (!i) { /* flag not found, can it be created? */
if (stream->kwd_create && (j < NUSERFLAGS) && *t &&
(strlen (t) <= MAXUSERFLAG)) {
for (s = t; t && *s; s++) switch (*s) {
default: /* all other characters */
/* SPACE, CTL, or not CHAR */
if ((*s > ' ') && (*s < 0x7f)) break;
case '*': case '%': /* list_wildcards */
case '"': case '\\':/* quoted-specials */
/* atom_specials */
case '(': case ')': case '{':
case ']': /* resp-specials */
sprintf (msg,"Invalid flag: %.80s",t);
MM_LOG (msg,WARN);
t = NIL;
}
if (t) { /* only if valid */
*uf |= 1 << j; /* set the bit */
stream->user_flags[j] = cpystr (t);
/* if out of user flags */
if (j == NUSERFLAGS - 1) stream->kwd_create = NIL;
}
}
else {
if (*t) sprintf (msg,"Unknown flag: %.80s",t);
else strcpy (msg,"Empty flag invalid");
MM_LOG (msg,WARN);
}
}
}
}
}
return f;
}
/* Mail check network stream for usability with new name
* Accepts: MAIL stream
* candidate new name
* Returns: T if stream can be used, NIL otherwise
*/
long mail_usable_network_stream (MAILSTREAM *stream,char *name)
{
NETMBX smb,nmb,omb;
return (stream && stream->dtb && !(stream->dtb->flags & DR_LOCAL) &&
mail_valid_net_parse (name,&nmb) &&
mail_valid_net_parse (stream->mailbox,&smb) &&
mail_valid_net_parse (stream->original_mailbox,&omb) &&
((!compare_cstring (smb.host,
trustdns ? tcp_canonical (nmb.host) : nmb.host)&&
!strcmp (smb.service,nmb.service) &&
(!nmb.port || (smb.port == nmb.port)) &&
(nmb.anoflag == stream->anonymous) &&
(!nmb.user[0] || !strcmp (smb.user,nmb.user))) ||
(!compare_cstring (omb.host,nmb.host) &&
!strcmp (omb.service,nmb.service) &&
(!nmb.port || (omb.port == nmb.port)) &&
(nmb.anoflag == stream->anonymous) &&
(!nmb.user[0] || !strcmp (omb.user,nmb.user))))) ? LONGT : NIL;
}
/* Mail data structure instantiation routines */
/* Mail instantiate cache elt
* Accepts: initial message number
* Returns: new cache elt
*/
MESSAGECACHE *mail_new_cache_elt (unsigned long msgno)
{
MESSAGECACHE *elt = (MESSAGECACHE *) memset (fs_get (sizeof (MESSAGECACHE)),
0,sizeof (MESSAGECACHE));
elt->lockcount = 1; /* initially only cache references it */
elt->msgno = msgno; /* message number */
return elt;
}
/* Mail instantiate envelope
* Returns: new envelope
*/
ENVELOPE *mail_newenvelope (void)
{
return (ENVELOPE *) memset (fs_get (sizeof (ENVELOPE)),0,sizeof (ENVELOPE));
}
/* Mail instantiate address
* Returns: new address
*/
ADDRESS *mail_newaddr (void)
{
return (ADDRESS *) memset (fs_get (sizeof (ADDRESS)),0,sizeof (ADDRESS));
}
/* Mail instantiate body
* Returns: new body
*/
BODY *mail_newbody (void)
{
return mail_initbody ((BODY *) fs_get (sizeof (BODY)));
}
/* Mail initialize body
* Accepts: body
* Returns: body
*/
BODY *mail_initbody (BODY *body)
{
memset ((void *) body,0,sizeof (BODY));
body->type = TYPETEXT; /* content type */
body->encoding = ENC7BIT; /* content encoding */
return body;
}
/* Mail instantiate body parameter
* Returns: new body part
*/
PARAMETER *mail_newbody_parameter (void)
{
return (PARAMETER *) memset (fs_get (sizeof(PARAMETER)),0,sizeof(PARAMETER));
}
/* Mail instantiate body part
* Returns: new body part
*/
PART *mail_newbody_part (void)
{
PART *part = (PART *) memset (fs_get (sizeof (PART)),0,sizeof (PART));
mail_initbody (&part->body); /* initialize the body */
return part;
}
/* Mail instantiate body message part
* Returns: new body message part
*/
MESSAGE *mail_newmsg (void)
{
return (MESSAGE *) memset (fs_get (sizeof (MESSAGE)),0,sizeof (MESSAGE));
}
/* Mail instantiate string list
* Returns: new string list
*/
STRINGLIST *mail_newstringlist (void)
{
return (STRINGLIST *) memset (fs_get (sizeof (STRINGLIST)),0,
sizeof (STRINGLIST));
}
/* Mail instantiate new search program
* Returns: new search program
*/
SEARCHPGM *mail_newsearchpgm (void)
{
return (SEARCHPGM *) memset (fs_get (sizeof(SEARCHPGM)),0,sizeof(SEARCHPGM));
}
/* Mail instantiate new search program
* Accepts: header line name
* Returns: new search program
*/
SEARCHHEADER *mail_newsearchheader (char *line,char *text)
{
SEARCHHEADER *hdr = (SEARCHHEADER *) memset (fs_get (sizeof (SEARCHHEADER)),
0,sizeof (SEARCHHEADER));
hdr->line.size = strlen ((char *) (hdr->line.data =
(unsigned char *) cpystr (line)));
hdr->text.size = strlen ((char *) (hdr->text.data =
(unsigned char *) cpystr (text)));
return hdr;
}
/* Mail instantiate new search set
* Returns: new search set
*/
SEARCHSET *mail_newsearchset (void)
{
return (SEARCHSET *) memset (fs_get (sizeof(SEARCHSET)),0,sizeof(SEARCHSET));
}
/* Mail instantiate new search or
* Returns: new search or
*/
SEARCHOR *mail_newsearchor (void)
{
SEARCHOR *or = (SEARCHOR *) memset (fs_get (sizeof (SEARCHOR)),0,
sizeof (SEARCHOR));
or->first = mail_newsearchpgm ();
or->second = mail_newsearchpgm ();
return or;
}
/* Mail instantiate new searchpgmlist
* Returns: new searchpgmlist
*/
SEARCHPGMLIST *mail_newsearchpgmlist (void)
{
SEARCHPGMLIST *pgl = (SEARCHPGMLIST *)
memset (fs_get (sizeof (SEARCHPGMLIST)),0,sizeof (SEARCHPGMLIST));
pgl->pgm = mail_newsearchpgm ();
return pgl;
}
/* Mail instantiate new sortpgm
* Returns: new sortpgm
*/
SORTPGM *mail_newsortpgm (void)
{
return (SORTPGM *) memset (fs_get (sizeof (SORTPGM)),0,sizeof (SORTPGM));
}
/* Mail instantiate new threadnode
* Accepts: sort cache for thread node
* Returns: new threadnode
*/
THREADNODE *mail_newthreadnode (SORTCACHE *sc)
{
THREADNODE *thr = (THREADNODE *) memset (fs_get (sizeof (THREADNODE)),0,
sizeof (THREADNODE));
if (sc) thr->sc = sc; /* initialize sortcache */
return thr;
}
/* Mail instantiate new acllist
* Returns: new acllist
*/
ACLLIST *mail_newacllist (void)
{
return (ACLLIST *) memset (fs_get (sizeof (ACLLIST)),0,sizeof (ACLLIST));
}
/* Mail instantiate new quotalist
* Returns: new quotalist
*/
QUOTALIST *mail_newquotalist (void)
{
return (QUOTALIST *) memset (fs_get (sizeof (QUOTALIST)),0,
sizeof (QUOTALIST));
}
/* Mail garbage collection routines */
/* Mail garbage collect body
* Accepts: pointer to body pointer
*/
void mail_free_body (BODY **body)
{
if (*body) { /* only free if exists */
mail_free_body_data (*body);/* free its data */
fs_give ((void **) body); /* return body to free storage */
}
}
/* Mail garbage collect body data
* Accepts: body pointer
*/
void mail_free_body_data (BODY *body)
{
switch (body->type) { /* free contents */
case TYPEMULTIPART: /* multiple part */
mail_free_body_part (&body->nested.part);
break;
case TYPEMESSAGE: /* encapsulated message */
if (body->subtype && !strcmp (body->subtype,"RFC822")) {
mail_free_stringlist (&body->nested.msg->lines);
mail_gc_msg (body->nested.msg,GC_ENV | GC_TEXTS);
}
if (body->nested.msg) fs_give ((void **) &body->nested.msg);
break;
default:
break;
}
if (body->subtype) fs_give ((void **) &body->subtype);
mail_free_body_parameter (&body->parameter);
if (body->id) fs_give ((void **) &body->id);
if (body->description) fs_give ((void **) &body->description);
if (body->disposition.type) fs_give ((void **) &body->disposition.type);
if (body->disposition.parameter)
mail_free_body_parameter (&body->disposition.parameter);
if (body->language) mail_free_stringlist (&body->language);
if (body->location) fs_give ((void **) &body->location);
if (body->mime.text.data) fs_give ((void **) &body->mime.text.data);
if (body->contents.text.data) fs_give ((void **) &body->contents.text.data);
if (body->md5) fs_give ((void **) &body->md5);
if (mailfreebodysparep && body->sparep)
(*mailfreebodysparep) (&body->sparep);
}
/* Mail garbage collect body parameter
* Accepts: pointer to body parameter pointer
*/
void mail_free_body_parameter (PARAMETER **parameter)
{
if (*parameter) { /* only free if exists */
if ((*parameter)->attribute) fs_give ((void **) &(*parameter)->attribute);
if ((*parameter)->value) fs_give ((void **) &(*parameter)->value);
/* run down the list as necessary */
mail_free_body_parameter (&(*parameter)->next);
/* return body part to free storage */
fs_give ((void **) parameter);
}
}
/* Mail garbage collect body part
* Accepts: pointer to body part pointer
*/
void mail_free_body_part (PART **part)
{
if (*part) { /* only free if exists */
mail_free_body_data (&(*part)->body);
/* run down the list as necessary */
mail_free_body_part (&(*part)->next);
fs_give ((void **) part); /* return body part to free storage */
}
}
/* Mail garbage collect message cache
* Accepts: mail stream
*
* The message cache is set to NIL when this function finishes.
*/
void mail_free_cache (MAILSTREAM *stream)
{
/* do driver specific stuff first */
mail_gc (stream,GC_ELT | GC_ENV | GC_TEXTS);
/* flush the cache */
(*mailcache) (stream,(long) 0,CH_INIT);
}
/* Mail garbage collect cache element
* Accepts: pointer to cache element pointer
*/
void mail_free_elt (MESSAGECACHE **elt)
{
/* only free if exists and no sharers */
if (*elt && !--(*elt)->lockcount) {
mail_gc_msg (&(*elt)->private.msg,GC_ENV | GC_TEXTS);
if (mailfreeeltsparep && (*elt)->sparep)
(*mailfreeeltsparep) (&(*elt)->sparep);
fs_give ((void **) elt);
}
else *elt = NIL; /* else simply drop pointer */
}
/* Mail garbage collect envelope
* Accepts: pointer to envelope pointer
*/
void mail_free_envelope (ENVELOPE **env)
{
if (*env) { /* only free if exists */
if ((*env)->remail) fs_give ((void **) &(*env)->remail);
mail_free_address (&(*env)->return_path);
if ((*env)->date) fs_give ((void **) &(*env)->date);
mail_free_address (&(*env)->from);
mail_free_address (&(*env)->sender);
mail_free_address (&(*env)->reply_to);
if ((*env)->subject) fs_give ((void **) &(*env)->subject);
mail_free_address (&(*env)->to);
mail_free_address (&(*env)->cc);
mail_free_address (&(*env)->bcc);
if ((*env)->in_reply_to) fs_give ((void **) &(*env)->in_reply_to);
if ((*env)->message_id) fs_give ((void **) &(*env)->message_id);
if ((*env)->newsgroups) fs_give ((void **) &(*env)->newsgroups);
if ((*env)->followup_to) fs_give ((void **) &(*env)->followup_to);
if ((*env)->references) fs_give ((void **) &(*env)->references);
if (mailfreeenvelopesparep && (*env)->sparep)
(*mailfreeenvelopesparep) (&(*env)->sparep);
fs_give ((void **) env); /* return envelope to free storage */
}
}
/* Mail garbage collect address
* Accepts: pointer to address pointer
*/
void mail_free_address (ADDRESS **address)
{
if (*address) { /* only free if exists */
if ((*address)->personal) fs_give ((void **) &(*address)->personal);
if ((*address)->adl) fs_give ((void **) &(*address)->adl);
if ((*address)->mailbox) fs_give ((void **) &(*address)->mailbox);
if ((*address)->host) fs_give ((void **) &(*address)->host);
if ((*address)->error) fs_give ((void **) &(*address)->error);
if ((*address)->orcpt.type) fs_give ((void **) &(*address)->orcpt.type);
if ((*address)->orcpt.addr) fs_give ((void **) &(*address)->orcpt.addr);
mail_free_address (&(*address)->next);
fs_give ((void **) address);/* return address to free storage */
}
}
/* Mail garbage collect stringlist
* Accepts: pointer to stringlist pointer
*/
void mail_free_stringlist (STRINGLIST **string)
{
if (*string) { /* only free if exists */
if ((*string)->text.data) fs_give ((void **) &(*string)->text.data);
mail_free_stringlist (&(*string)->next);
fs_give ((void **) string); /* return string to free storage */
}
}
/* Mail garbage collect searchpgm
* Accepts: pointer to searchpgm pointer
*/
void mail_free_searchpgm (SEARCHPGM **pgm)
{
if (*pgm) { /* only free if exists */
mail_free_searchset (&(*pgm)->msgno);
mail_free_searchset (&(*pgm)->uid);
mail_free_searchor (&(*pgm)->or);
mail_free_searchpgmlist (&(*pgm)->not);
mail_free_searchheader (&(*pgm)->header);
mail_free_stringlist (&(*pgm)->bcc);
mail_free_stringlist (&(*pgm)->body);
mail_free_stringlist (&(*pgm)->cc);
mail_free_stringlist (&(*pgm)->from);
mail_free_stringlist (&(*pgm)->keyword);
mail_free_stringlist (&(*pgm)->subject);
mail_free_stringlist (&(*pgm)->text);
mail_free_stringlist (&(*pgm)->to);
fs_give ((void **) pgm); /* return program to free storage */
}
}
/* Mail garbage collect searchheader
* Accepts: pointer to searchheader pointer
*/
void mail_free_searchheader (SEARCHHEADER **hdr)
{
if (*hdr) { /* only free if exists */
if ((*hdr)->line.data) fs_give ((void **) &(*hdr)->line.data);
if ((*hdr)->text.data) fs_give ((void **) &(*hdr)->text.data);
mail_free_searchheader (&(*hdr)->next);
fs_give ((void **) hdr); /* return header to free storage */
}
}
/* Mail garbage collect searchset
* Accepts: pointer to searchset pointer
*/
void mail_free_searchset (SEARCHSET **set)
{
if (*set) { /* only free if exists */
mail_free_searchset (&(*set)->next);
fs_give ((void **) set); /* return set to free storage */
}
}
/* Mail garbage collect searchor
* Accepts: pointer to searchor pointer
*/
void mail_free_searchor (SEARCHOR **orl)
{
if (*orl) { /* only free if exists */
mail_free_searchpgm (&(*orl)->first);
mail_free_searchpgm (&(*orl)->second);
mail_free_searchor (&(*orl)->next);
fs_give ((void **) orl); /* return searchor to free storage */
}
}
/* Mail garbage collect search program list
* Accepts: pointer to searchpgmlist pointer
*/
void mail_free_searchpgmlist (SEARCHPGMLIST **pgl)
{
if (*pgl) { /* only free if exists */
mail_free_searchpgm (&(*pgl)->pgm);
mail_free_searchpgmlist (&(*pgl)->next);
fs_give ((void **) pgl); /* return searchpgmlist to free storage */
}
}
/* Mail garbage collect namespace
* Accepts: poiner to namespace
*/
void mail_free_namespace (NAMESPACE **n)
{
if (*n) {
fs_give ((void **) &(*n)->name);
mail_free_namespace (&(*n)->next);
mail_free_body_parameter (&(*n)->param);
fs_give ((void **) n); /* return namespace to free storage */
}
}
/* Mail garbage collect sort program
* Accepts: pointer to sortpgm pointer
*/
void mail_free_sortpgm (SORTPGM **pgm)
{
if (*pgm) { /* only free if exists */
mail_free_sortpgm (&(*pgm)->next);
fs_give ((void **) pgm); /* return sortpgm to free storage */
}
}
/* Mail garbage collect thread node
* Accepts: pointer to threadnode pointer
*/
void mail_free_threadnode (THREADNODE **thr)
{
if (*thr) { /* only free if exists */
mail_free_threadnode (&(*thr)->branch);
mail_free_threadnode (&(*thr)->next);
fs_give ((void **) thr); /* return threadnode to free storage */
}
}
/* Mail garbage collect acllist
* Accepts: pointer to acllist pointer
*/
void mail_free_acllist (ACLLIST **al)
{
if (*al) { /* only free if exists */
if ((*al)->identifier) fs_give ((void **) &(*al)->identifier);
if ((*al)->rights) fs_give ((void **) &(*al)->rights);
mail_free_acllist (&(*al)->next);
fs_give ((void **) al); /* return acllist to free storage */
}
}
/* Mail garbage collect quotalist
* Accepts: pointer to quotalist pointer
*/
void mail_free_quotalist (QUOTALIST **ql)
{
if (*ql) { /* only free if exists */
if ((*ql)->name) fs_give ((void **) &(*ql)->name);
mail_free_quotalist (&(*ql)->next);
fs_give ((void **) ql); /* return quotalist to free storage */
}
}
/* Link authenicator
* Accepts: authenticator to add to list
*/
void auth_link (AUTHENTICATOR *auth)
{
if (!auth->valid || (*auth->valid) ()) {
AUTHENTICATOR **a = &mailauthenticators;
while (*a) a = &(*a)->next; /* find end of list of authenticators */
*a = auth; /* put authenticator at the end */
auth->next = NIL; /* this authenticator is the end of the list */
}
}
/* Authenticate access
* Accepts: mechanism name
* responder function
* argument count
* argument vector
* Returns: authenticated user name or NIL
*/
char *mail_auth (char *mechanism,authresponse_t resp,int argc,char *argv[])
{
AUTHENTICATOR *auth;
for (auth = mailauthenticators; auth; auth = auth->next)
if (auth->server && !compare_cstring (auth->name,mechanism))
return (!(auth->flags & AU_DISABLE) &&
((auth->flags & AU_SECURE) ||
!mail_parameters (NIL,GET_DISABLEPLAINTEXT,NIL))) ?
(*auth->server) (resp,argc,argv) : NIL;
return NIL; /* no authenticator found */
}
/* Lookup authenticator index
* Accepts: authenticator index
* Returns: authenticator, or 0 if not found
*/
AUTHENTICATOR *mail_lookup_auth (unsigned long i)
{
AUTHENTICATOR *auth = mailauthenticators;
while (auth && --i) auth = auth->next;
return auth;
}
/* Lookup authenticator name
* Accepts: authenticator name
* required authenticator flags
* Returns: index in authenticator chain, or 0 if not found
*/
unsigned int mail_lookup_auth_name (char *mechanism,long flags)
{
int i;
AUTHENTICATOR *auth;
for (i = 1, auth = mailauthenticators; auth; i++, auth = auth->next)
if (auth->client && !(flags & ~auth->flags) &&
!(auth->flags & AU_DISABLE) && !compare_cstring (auth->name,mechanism))
return i;
return 0;
}
/* Standard TCP/IP network driver */
static NETDRIVER tcpdriver = {
tcp_open, /* open connection */
tcp_aopen, /* open preauthenticated connection */
tcp_getline, /* get a line */
tcp_getbuffer, /* get a buffer */
tcp_soutr, /* output pushed data */
tcp_sout, /* output string */
tcp_close, /* close connection */
tcp_host, /* return host name */
tcp_remotehost, /* return remote host name */
tcp_port, /* return port number */
tcp_localhost /* return local host name */
};
/* Network open
* Accepts: NETMBX specifier to open
* default network driver
* default port
* SSL driver
* SSL service name
* SSL driver port
* Returns: Network stream if success, else NIL
*/
NETSTREAM *net_open (NETMBX *mb,NETDRIVER *dv,unsigned long port,
NETDRIVER *ssld,char *ssls,unsigned long sslp)
{
NETSTREAM *stream = NIL;
char tmp[MAILTMPLEN];
unsigned long flags = mb->novalidate ? NET_NOVALIDATECERT : 0;
if (strlen (mb->host) >= NETMAXHOST) {
sprintf (tmp,"Invalid host name: %.80s",mb->host);
MM_LOG (tmp,ERROR);
}
/* use designated driver if given */
else if (dv) stream = net_open_work (dv,mb->host,mb->service,port,mb->port,
flags);
else if (mb->sslflag && ssld) /* use ssl if sslflag lit */
stream = net_open_work (ssld,mb->host,ssls,sslp,mb->port,flags);
/* if trysslfirst and can open ssl... */
else if ((mb->trysslflag || trysslfirst) && ssld &&
(stream = net_open_work (ssld,mb->host,ssls,sslp,mb->port,
flags | NET_SILENT | NET_TRYSSL))) {
if (net_sout (stream,"",0)) mb->sslflag = T;
else {
net_close (stream); /* flush fake SSL stream */
stream = NIL;
}
}
/* default to TCP driver */
else stream = net_open_work (&tcpdriver,mb->host,mb->service,port,mb->port,
flags);
return stream;
}
/* Network open worker routine
* Accepts: network driver
* host name
* service name to look up port
* port number if service name not found
* port number to override service name
* flags (passed on top of port)
* Returns: Network stream if success, else NIL
*/
NETSTREAM *net_open_work (NETDRIVER *dv,char *host,char *service,
unsigned long port,unsigned long portoverride,
unsigned long flags)
{
NETSTREAM *stream = NIL;
void *tstream;
if (service && (*service == '*')) {
flags |= NET_NOOPENTIMEOUT; /* mark that no timeout is desired */
++service; /* no longer need the no timeout indicator */
}
if (portoverride) { /* explicit port number? */
service = NIL; /* yes, override service name */
port = portoverride; /* use that instead of default port */
}
if (tstream = (*dv->open) (host,service,port | flags)) {
stream = (NETSTREAM *) fs_get (sizeof (NETSTREAM));
stream->stream = tstream;
stream->dtb = dv;
}
return stream;
}
/* Network authenticated open
* Accepts: network driver
* NETMBX specifier
* service specifier
* return user name buffer
* Returns: Network stream if success else NIL
*/
NETSTREAM *net_aopen (NETDRIVER *dv,NETMBX *mb,char *service,char *user)
{
NETSTREAM *stream = NIL;
void *tstream;
if (!dv) dv = &tcpdriver; /* default to TCP driver */
if (tstream = (*dv->aopen) (mb,service,user)) {
stream = (NETSTREAM *) fs_get (sizeof (NETSTREAM));
stream->stream = tstream;
stream->dtb = dv;
}
return stream;
}
/* Network receive line
* Accepts: Network stream
* Returns: text line string or NIL if failure
*/
char *net_getline (NETSTREAM *stream)
{
return (*stream->dtb->getline) (stream->stream);
}
/* Network receive buffer
* Accepts: Network stream (must be void * for use as readfn_t)
* size in bytes
* buffer to read into
* Returns: T if success, NIL otherwise
*/
long net_getbuffer (void *st,unsigned long size,char *buffer)
{
NETSTREAM *stream = (NETSTREAM *) st;
return (*stream->dtb->getbuffer) (stream->stream,size,buffer);
}
/* Network send null-terminated string
* Accepts: Network stream
* string pointer
* Returns: T if success else NIL
*/
long net_soutr (NETSTREAM *stream,char *string)
{
return (*stream->dtb->soutr) (stream->stream,string);
}
/* Network send string
* Accepts: Network stream
* string pointer
* byte count
* Returns: T if success else NIL
*/
long net_sout (NETSTREAM *stream,char *string,unsigned long size)
{
return (*stream->dtb->sout) (stream->stream,string,size);
}
/* Network close
* Accepts: Network stream
*/
void net_close (NETSTREAM *stream)
{
if (stream->stream) (*stream->dtb->close) (stream->stream);
fs_give ((void **) &stream);
}
/* Network get host name
* Accepts: Network stream
* Returns: host name for this stream
*/
char *net_host (NETSTREAM *stream)
{
return (*stream->dtb->host) (stream->stream);
}
/* Network get remote host name
* Accepts: Network stream
* Returns: host name for this stream
*/
char *net_remotehost (NETSTREAM *stream)
{
return (*stream->dtb->remotehost) (stream->stream);
}
/* Network return port for this stream
* Accepts: Network stream
* Returns: port number for this stream
*/
unsigned long net_port (NETSTREAM *stream)
{
return (*stream->dtb->port) (stream->stream);
}
/* Network get local host name
* Accepts: Network stream
* Returns: local host name
*/
char *net_localhost (NETSTREAM *stream)
{
return (*stream->dtb->localhost) (stream->stream);
}