/*
* Program: IMAP4 server
*
* Author: Mark Crispin
* Networks and Distributed Computing
* Computing & Communications
* University of Washington
* Administration Building, AG-44
* Seattle, WA 98195
* Internet: MRC@CAC.Washington.EDU
*
* Date: 5 November 1990
* Last Edited: 28 December 1995
*
* Copyright 1995 by the University of Washington
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose and without fee is hereby granted, provided
* that the above copyright notice appears in all copies and that both the
* above copyright notice and this permission notice appear in supporting
* documentation, and that the name of the University of Washington not be
* used in advertising or publicity pertaining to distribution of the software
* without specific, written prior permission. This software is made
* available "as is", and
* THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
* WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
* NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
* (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
/* Parameter files */
#include "mail.h"
#include "osdep.h"
#include "rfc822.h"
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
extern int errno; /* just in case */
#include <sys/stat.h>
#include "misc.h"
/* Autologout timer */
#define TIMEOUT 60*30
/* Login tries */
#define LOGTRY 3
/* Length of literal stack (I hope 20 is plenty) */
#define LITSTKLEN 20
/* Size of temporary buffers */
#define TMPLEN 8192
/* Server states */
#define LOGIN 0
#define SELECT 1
#define OPEN 2
#define LOGOUT 3
/* Global storage */
char *version = "8.1(133)"; /* version number of this server */
time_t alerttime = 0; /* time of last alert */
int state = LOGIN; /* server state */
int mackludge = 0; /* MacMS kludge */
int trycreate = 0; /* saw a trycreate */
int finding = NIL; /* doing old FIND command */
int anonymous = 0; /* non-zero if anonymous */
int logtry = LOGTRY; /* login tries */
long kodcount = 0; /* set if KOD has happened already */
MAILSTREAM *stream = NIL; /* mailbox stream */
DRIVER *curdriver = NIL; /* note current driver */
MAILSTREAM *tstream = NIL; /* temporary mailbox stream */
unsigned long nmsgs =0xffffffff;/* last reported # of messages and recent */
unsigned long recent = 0xffffffff;
char *host = NIL; /* local host name */
char *user = NIL; /* user name */
char *pass = NIL; /* password */
char cmdbuf[TMPLEN]; /* command buffer */
char *tag; /* tag portion of command */
char *cmd; /* command portion of command */
char *arg; /* pointer to current argument of command */
char *lsterr = NIL; /* last error message from c-client */
char *response = NIL; /* command response */
int litsp = 0; /* literal stack pointer */
char *litstk[LITSTKLEN]; /* stack to hold literals */
/* Response texts */
char *win = "%s OK %s completed\015\012";
char *altwin = "%s OK %s\015\012";
char *lose = "%s NO %s failed: %s\015\012";
char *losetry = "%s NO [TRYCREATE] %s failed: %s\015\012";
char *misarg = "%s BAD Missing required argument to %s\015\012";
char *badfnd = "%s BAD FIND option unrecognized: %s\015\012";
char *noques = "%s NO FIND %s with ? or %% wildcard not supported\015\012";
char *badarg = "%s BAD Argument given to %s when none expected\015\012";
char *badseq = "%s BAD Bogus sequence in %s\015\012";
char *badatt = "%s BAD Bogus attribute list in %s\015\012";
char *badlit = "%s BAD Bogus literal count in %s\015\012";
char *toobig = "%s BAD Command line too long\015\012";
char *nulcmd = "* BAD Null command\015\012";
char *argrdy = "+ Ready for argument\015\012";
/* Function prototypes */
void main (int argc,char *argv[]);
void new_flags (MAILSTREAM *stream,char *tmp);
void clkint ();
void kodint ();
void slurp (char *s,int n);
char inchar (void);
void *flush (void);
char *parse_astring (char **arg,unsigned long *i,char *del);
char *snarf (char **arg);
char *snarf_list (char **arg);
STRINGLIST *parse_stringlist (char **s,int *list);
long parse_criteria (SEARCHPGM *pgm,char **arg,unsigned long maxmsg,
unsigned long maxuid);
long parse_criterion (SEARCHPGM *pgm,char **arg,unsigned long msgmsg,
unsigned long maxuid);
long crit_date (unsigned short *date,char **arg);
long crit_date_work (unsigned short *date,char **arg);
long crit_set (SEARCHSET **set,char **arg,unsigned long maxima);
long crit_number (unsigned long *number,char **arg);
long crit_string (STRINGLIST **string,char **arg);
void fetch (char *t,unsigned long uid);
void fetch_bodystructure (unsigned long i,STRINGLIST *a);
void fetch_body (unsigned long i,STRINGLIST *a);
void fetch_body_part (unsigned long i,STRINGLIST *a);
void fetch_body_part_peek (unsigned long i,STRINGLIST *a);
void fetch_envelope (unsigned long i,STRINGLIST *a);
void fetch_encoding (unsigned long i,STRINGLIST *a);
void changed_flags (unsigned long i,int f);
void fetch_rfc822_header_lines (unsigned long i,STRINGLIST *a);
void fetch_rfc822_header_lines_not (unsigned long i,STRINGLIST *a);
void fetch_flags (unsigned long i,STRINGLIST *a);
void put_flag (int *c,char *s);
void fetch_internaldate (unsigned long i,STRINGLIST *a);
void fetch_uid (unsigned long i,STRINGLIST *a);
void fetch_rfc822 (unsigned long i,STRINGLIST *a);
void fetch_rfc822_peek (unsigned long i,STRINGLIST *a);
void fetch_rfc822_header (unsigned long i,STRINGLIST *a);
void fetch_rfc822_size (unsigned long i,STRINGLIST *a);
void fetch_rfc822_text (unsigned long i,STRINGLIST *a);
void fetch_rfc822_text_peek (unsigned long i,STRINGLIST *a);
void penv (ENVELOPE *env);
void pbodystructure (BODY *body);
void pbody (BODY *body);
void pstring (char *s);
void paddr (ADDRESS *a);
long cstring (char *s);
long caddr (ADDRESS *a);
long nameok (char *ref,char *name);
char *imap_responder (void *challenge,unsigned long clen,unsigned long *rlen);
void mm_list_work (char *what,char delimiter,char *name,long attributes);
/* Main program */
void main (int argc,char *argv[])
{
unsigned long i,uid;
char *s,*t,*u,*v,tmp[MAILTMPLEN];
struct stat sbuf;
#include "linkage.c"
openlog ("imapd",LOG_PID,LOG_MAIL);
host = cpystr (mylocalhost());/* get local host name */
rfc822_date (cmdbuf); /* get date/time now */
s = myusername_full (&i); /* get user name and flags */
switch (i) {
case MU_NOTLOGGEDIN:
t = "OK"; /* not logged in, ordinary startup */
break;
case MU_ANONYMOUS:
anonymous = T; /* anonymous user, fall into default */
case MU_LOGGEDIN:
t = "PREAUTH"; /* already logged in, pre-authorized */
user = cpystr (s); /* copy user name */
pass = cpystr ("*"); /* set fake password */
state = SELECT; /* enter select state */
syslog (LOG_INFO,"Preauthenticated user=%.80s host=%.80s",
anonymous ? "ANONYMOUS" : user,
tcp_clienthost (tmp));
break;
default:
fatal ("Unknown state from myusername_full()");
}
printf ("* %s %s IMAP4 Service %s at %s\015\012",t,host,version,cmdbuf);
fflush (stdout); /* dump output buffer */
server_traps (clkint,kodint); /* set up traps */
do { /* command processing loop */
slurp (cmdbuf,TMPLEN-1); /* slurp command */
/* no more last error or literal */
if (lsterr) fs_give ((void **) &lsterr);
while (litsp) fs_give ((void **) &litstk[--litsp]);
/* find end of line */
if (!strchr (cmdbuf,'\012')) {
if (t = strchr (cmdbuf,' ')) *t = '\0';
flush (); /* flush excess */
printf (response,t ? cmdbuf : "*");
}
else if (!(tag = strtok (cmdbuf," \015\012"))) fputs (nulcmd,stdout);
else if (!(cmd = strtok (NIL," \015\012")))
printf ("%s BAD Missing command\015\012",tag);
else { /* parse command */
response = win; /* set default response */
finding = NIL; /* no longer FINDing */
ucase (cmd); /* canonicalize command case */
/* UID command? */
if (!strcmp (cmd,"UID") && strtok (NIL," \015\012")) {
uid = T; /* a UID command */
cmd[3] = ' '; /* restore the space delimiter */
ucase (cmd); /* make sure command all uppercase */
}
else uid = NIL; /* not a UID command */
/* snarf argument */
arg = strtok (NIL,"\015\012");
/* LOGOUT command always valid */
if (!strcmp (cmd,"LOGOUT")) {
if (state == OPEN) mail_close (stream);
stream = NIL;
printf("* BYE %s IMAP4 server terminating connection\015\012",host);
state = LOGOUT;
}
/* kludge for MacMS */
else if ((!strcmp (cmd,"VERSION")) && (s = snarf (&arg)) && (!arg) &&
((i = strtoul (s,NIL,10)) == 4)) {
mackludge = T;
fputs ("* OK [MacMS] The MacMS kludges are enabled\015\012",stdout);
}
else if (!strcmp (cmd,"CAPABILITY")) {
AUTHENTICATOR *auth;
fputs ("* CAPABILITY IMAP4 STATUS SCAN",stdout);
for (auth = authenticators; auth; auth = auth->next)
printf (" AUTH-%s",auth->name);
fputs ("\015\012",stdout);
}
/* dispatch depending upon state */
else if (strcmp (cmd,"NOOP")) switch (state) {
case LOGIN: /* waiting to get logged in */
/* new style authentication */
if (!strcmp (cmd,"AUTHENTICATE")) {
fs_give ((void **) &user);
fs_give ((void **) &pass);
/* single argument */
if (!(s = snarf (&arg))) response = misarg;
else if (arg) response = badarg;
else if (user = cpystr (mail_auth (s,imap_responder,argc,argv))) {
state = SELECT;
syslog (LOG_INFO,"Authenticated user=%.80s host=%.80s",
user,tcp_clienthost (tmp));
}
else {
response = "%s NO Authentication failure\015\012";
sleep (3); /* slow the cracker down */
if (!--logtry) {
fputs ("* BYE Too many authentication failures\015\012",stdout);
state = LOGOUT;
syslog (LOG_INFO,"Excessive authentication failures host=%.80s",
tcp_clienthost (tmp));
}
else syslog (LOG_INFO,"Authentication failure host=%.80s",
tcp_clienthost (tmp));
}
}
/* plaintext login with password */
else if (!strcmp (cmd,"LOGIN")) {
if (user) fs_give ((void **) &user);
if (pass) fs_give ((void **) &pass);
/* two arguments */
if (!((user = cpystr (snarf (&arg))) &&
(pass = cpystr (snarf (&arg))))) response = misarg;
else if (arg) response = badarg;
/* see if username and password are OK */
else if (server_login (user,pass,argc,argv)) {
state = SELECT;
syslog (LOG_INFO,"Login user=%.80s host=%.80s",user,
tcp_clienthost(tmp));
}
/* nope, see if we allow anonymous */
else if (!stat (ANOFILE,&sbuf) && !strcmp (user,"anonymous") &&
anonymous_login ()) {
anonymous = T; /* note we are anonymous, login as nobody */
myusername (); /* give the environment a kick */
state = SELECT; /* make selected */
syslog (LOG_INFO,"Login anonymous=%.80s host=%.80s",pass,
tcp_clienthost(tmp));
}
else {
response = "%s NO Bad %s user name and/or password\015\012";
sleep (3); /* slow the cracker down */
if (!--logtry) {
fputs ("* BYE Too many login failures\015\012",stdout);
state = LOGOUT;
syslog(LOG_INFO,"Excessive login failures user=%.80s host=%.80s",
user,tcp_clienthost (tmp));
}
else syslog (LOG_INFO,"Login failure user=%.80s host=%.80s",
user,tcp_clienthost (tmp));
}
}
else response = "%s BAD Command unrecognized/login please: %s\015\012";
break;
case OPEN: /* valid only when mailbox open */
/* fetch mailbox attributes */
if (!strcmp (cmd,"FETCH") || !strcmp (cmd,"UID FETCH")) {
if (!(arg && (s = strtok (arg," ")) && (t = strtok(NIL,"\015\012"))))
response = misarg;
else if (uid ? mail_uid_sequence (stream,s) :
mail_sequence (stream,s)) fetch (t,uid);
else response = badseq;
}
/* fetch partial mailbox attributes */
else if (!strcmp (cmd,"PARTIAL")) {
unsigned long msgno,start,count,size,txtsize;
if (!(arg && (msgno = strtoul (arg,&s,10)) && (t = strtok (s," ")) &&
(s = strtok (NIL,"\015\012")) && (start = strtoul (s,&s,10)) &&
(count = strtoul (s,&s,10)))) response = misarg;
else if (s && *s) response = badarg;
else if (msgno > stream->nmsgs) response = badseq;
else { /* looks good */
int sf = mail_elt (stream,msgno)->seen;
u = s = NIL; /* no strings yet */
if (!strcmp (ucase (t),"RFC822")) {
/* have to make a temporary buffer for this */
mail_fetchtext_full (stream,msgno,&txtsize,NIL);
v = mail_fetchheader_full (stream,msgno,NIL,&size,NIL);
s = u = (char *) fs_get (size + txtsize + 1);
u[size + txtsize] = '\0';
memcpy (u,v,size);
v = mail_fetchtext_full (stream,msgno,&txtsize,NIL);
memcpy (u + size,v,txtsize);
}
else if (!strcmp (ucase (t),"RFC822.PEEK")) {
mail_fetchtext_full (stream,msgno,&txtsize,FT_PEEK);
v = mail_fetchheader_full (stream,msgno,NIL,&size,NIL);
s = u = (char *) fs_get (size + txtsize + 1);
u[size + txtsize] = '\0';
memcpy (u,v,size);
v = mail_fetchtext_full (stream,msgno,&txtsize,FT_PEEK);
memcpy (u + size,v,txtsize);
}
else if (!strcmp (t,"RFC822.HEADER"))
s = mail_fetchheader_full (stream,msgno,NIL,&size,NIL);
else if (!strcmp (t,"RFC822.TEXT"))
s = mail_fetchtext_full (stream,msgno,&size,NIL);
else if (!strcmp (t,"RFC822.TEXT.PEEK"))
s = mail_fetchtext_full (stream,msgno,&size,FT_PEEK);
else if (*t == 'B' && t[1] == 'O' && t[2] == 'D' && t[3] == 'Y') {
if (t[4] == '[' && (v = strchr (t += 5,']')) && !v[1]) {
*v = '\0'; /* tie off body part */
s = mail_fetchbody (stream,msgno,t,&size);
}
else if (t[4] == '.' && t[5] == 'P' && t[6] == 'E' &&
t[7] == 'E' && t[8] == 'K' && t[9] == '[' &&
(v = strchr (t += 10,']')) && !v[1]) {
*v = '\0'; /* tie off body part */
s = mail_fetchbody_full (stream,msgno,t,&size,FT_PEEK);
}
}
if (s) { /* got a string back? */
if (size <= --start) s = NIL;
else { /* have string we can make smaller */
s += start; /* this is the first byte */
/* tie off as appropriate */
if (count < (size - start)) s[count] = '\0';
}
printf ("* %lu FETCH (%s ",msgno,t);
pstring (s); /* write the string */
changed_flags (msgno,sf);
fputs (")\015\012",stdout);
if (u) fs_give ((void **) &u);
}
else response = badatt;
}
}
/* store mailbox attributes */
else if (!strcmp (cmd,"STORE") || !strcmp (cmd,"UID STORE")) {
void (*f) () = NIL;
long flags = uid ? ST_UID : NIL;
/* must have three arguments */
if (!(arg && (s = strtok (arg," ")) && (v = strtok (NIL," ")) &&
(t = strtok (NIL,"\015\012")))) response = misarg;
else { /* see if silent store wanted */
if ((u = strchr (ucase (v),'.')) && !strcmp (u,".SILENT")) {
flags |= ST_SILENT;
*u = '\0';
}
if (!strcmp (v,"+FLAGS")) f = mail_setflag_full;
else if (!strcmp (v,"-FLAGS")) f = mail_clearflag_full;
else if (!strcmp (v,"FLAGS")) {
tmp[0] = '('; /* start new flag list */
tmp[1] = '\0';
for (i = 0; i < NUSERFLAGS; i++)
if (v = stream->user_flags[i]) strcat (strcat (tmp,v)," ");
/* append system flags to list */
strcat (tmp,"\\Answered \\Flagged \\Deleted \\Draft \\Seen)");
/* gross, but rarely if ever done */
mail_clearflag_full (stream,s,tmp,flags);
f = mail_setflag_full;
}
else response = badatt;
if (f) { /* if a function was selected */
for (i = 0; i < NUSERFLAGS && stream->user_flags[i]; ++i);
(*f) (stream,s,t,flags);
/* note if any new keywords appeared */
if (i < NUSERFLAGS && stream->user_flags[i])
new_flags (stream,tmp);
/* return flags if silence not wanted */
if (!(flags & ST_SILENT) &&
(uid ? mail_uid_sequence (stream,s) :
mail_sequence (stream,s))) fetch ("FLAGS",uid);
}
}
}
/* check for new mail */
else if (!strcmp (cmd,"CHECK")) {
/* no arguments */
if (arg) response = badarg;
else if (anonymous) mail_ping (stream);
else mail_check (stream);
}
/* expunge deleted messages */
else if (!(anonymous || strcmp (cmd,"EXPUNGE"))) {
/* no arguments */
if (arg) response = badarg;
else mail_expunge (stream);
}
else if (!anonymous && /* copy message(s) */
(!strcmp (cmd,"COPY") || !strcmp (cmd,"UID COPY"))) {
trycreate = NIL; /* no trycreate status */
if (!((s = snarf (&arg)) && (t = snarf (&arg)))) response = misarg;
else if (arg) response = badarg;
else if (mackludge) { /* MacMS wants implicit create */
if (!(mail_copy_full (stream,s,t,uid ? CP_UID : NIL) ||
(trycreate && mail_create (stream,t) &&
mail_copy_full (stream,s,t,uid ? CP_UID : NIL)))) {
response = lose;
if (!lsterr) lsterr = cpystr ("No such destination mailbox");
}
}
else if (!mail_copy_full (stream,s,t,uid ? CP_UID : NIL)) {
response = trycreate ? losetry : lose;
if (!lsterr) lsterr = cpystr ("No such destination mailbox");
}
}
/* close mailbox */
else if (!strcmp (cmd,"CLOSE")) {
/* no arguments */
if (arg) response = badarg;
else {
stream = mail_close_full (stream,anonymous ? NIL : CL_EXPUNGE);
state = SELECT; /* no longer opened */
}
}
/* search mailbox */
else if (!strcmp (cmd,"SEARCH") || !strcmp (cmd,"UID SEARCH")) {
char *charset = NIL;
SEARCHPGM *pgm;
/* one or more arguments required */
if (!arg) response = misarg;
/* character set specified? */
else if ((arg[0] == 'C' || arg[0] == 'c') &&
(arg[1] == 'H' || arg[1] == 'h') &&
(arg[2] == 'A' || arg[2] == 'a') &&
(arg[3] == 'R' || arg[3] == 'r') &&
(arg[4] == 'S' || arg[4] == 's') &&
(arg[5] == 'E' || arg[5] == 'e') &&
(arg[6] == 'T' || arg[6] == 't') &&
(arg[7] == ' ' || arg[7] == ' ')) {
arg += 8; /* yes, skip over CHARSET token */
if (s = snarf (&arg)) charset = cpystr (s);
else { /* missing character set */
response = misarg;
break;
}
}
/* must have arguments here */
if (!(arg && *arg)) response = misarg;
else if (parse_criteria (pgm = mail_newsearchpgm (),&arg,
stream->nmsgs,stream->nmsgs ?
mail_uid (stream,stream->nmsgs) : 0) &&
!*arg) { /* parse criteria */
mail_search_full (stream,charset,pgm,SE_FREE);
if (charset) fs_give ((void **) &charset);
if (response == win || response == altwin) {
/* output search results */
fputs ("* SEARCH",stdout);
for (i = 1; i <= stream->nmsgs; ++i)
if (mail_elt (stream,i)->searched)
printf (" %lu",uid ? mail_uid (stream,i) : i);
fputs ("\015\012",stdout);
}
}
else {
response = "%s BAD Bogus criteria list in %s\015\012";
mail_free_searchpgm (&pgm);
}
}
else /* fall into select case */
case SELECT: /* valid whenever logged in */
/* select new mailbox */
if (!(strcmp (cmd,"SELECT") && strcmp (cmd,"EXAMINE"))) {
/* single argument */
if (!(s = snarf (&arg))) response = misarg;
else if (arg) response = badarg;
else if (nameok (NIL,s)) {
long oopts = (anonymous ? OP_ANONYMOUS + OP_READONLY : NIL) +
((*cmd == 'E') ? OP_READONLY : NIL);
DRIVER *factory = mail_valid (NIL,s,NIL);
curdriver = NIL; /* no drivers known */
/* force update */
nmsgs = recent = 0xffffffff;
if (factory && !strcmp (factory->name,"phile") &&
(stream = mail_open (stream,s,oopts | OP_SILENT)) &&
((response == win) || (response == altwin))) {
BODY *b;
/* see if proxy open */
if ((mail_elt (stream,1)->rfc822_size < 400) &&
mail_fetchstructure (stream,1,&b) &&
(b->type == TYPETEXT) &&
strcpy (tmp,mail_fetchtext (stream,1)) &&
(tmp[0] == '{')) {
/* nuke any trailing newline */
if (s = strpbrk (tmp,"\r\n")) *s = '\0';
/* try to open proxy */
if ((tstream = mail_open (NIL,tmp,oopts | OP_SILENT)) &&
((response == win) || (response == altwin)) &&
tstream->nmsgs) {
/* got it, close the link */
mail_close (stream);
stream = tstream;
tstream = NIL;
}
/* now give the exists event */
stream->silent = NIL;
mm_exists (stream,stream->nmsgs);
}
}
/* open stream normally then */
else stream = mail_open (stream,s,oopts);
if (stream && ((response == win) || (response == altwin))) {
kodcount = 0; /* initialize KOD count */
state = OPEN; /* note state open */
/* note readonly/readwrite */
response = stream->rdonly ?
"%s OK [READ-ONLY] %s completed\015\012" :
"%s OK [READ-WRITE] %s completed\015\012";
if (anonymous)
syslog (LOG_INFO,"Anonymous select of %.80s host=%.80s",
stream->mailbox,tcp_clienthost (tmp));
}
else { /* failed */
state = SELECT; /* no mailbox open now */
response = lose; /* open failed */
}
}
}
/* APPEND message to mailbox */
else if (!(anonymous || strcmp (cmd,"APPEND"))) {
u = v = NIL; /* init flags/date */
/* parse mailbox name */
if ((s = snarf (&arg)) && arg) {
if (*arg == '(') { /* parse optional flag list */
u = ++arg; /* pointer to flag list contents */
while (*arg && (*arg != ')')) arg++;
if (*arg) *arg++ = '\0';
if (*arg == ' ') arg++;
}
/* parse optional date */
if (*arg == '"') v = snarf (&arg);
/* parse message */
if (!arg || (*arg != '{'))
response = "%s BAD Missing literal in %s\015\012";
else if (!(isdigit (arg[1]) && (i = strtoul (arg+1,NIL,10))))
response = "%s NO Empty message to %s\015\012";
else if (!(t = snarf (&arg))) response = misarg;
else if (arg) response = badarg;
else { /* append the data */
STRING st;
INIT (&st,mail_string,(void *) t,i);
trycreate = NIL; /* no trycreate status */
if (!mail_append_full (NIL,s,u,v,&st)) {
response = trycreate ? losetry : lose;
if (!lsterr) lsterr = cpystr ("No such destination mailbox");
}
}
}
else response = misarg;
}
/* list mailboxes */
else if (!strcmp (cmd,"LIST")) {
/* get reference and mailbox argument */
if (!((s = snarf (&arg)) && (t = snarf_list (&arg))))
response = misarg;
else if (arg) response = badarg;
/* make sure anonymous can't do bad things */
else if (nameok (s,t)) {
/* do the list */
tstream = (*s == '{') ? mail_open (NIL,s,OP_HALFOPEN) : NIL;
mail_list (tstream,s,t);
if (tstream) mail_close (tstream);
tstream = NIL; /* no more temporary stream */
}
}
/* scan mailboxes */
else if (!strcmp (cmd,"SCAN")) {
/* get arguments */
if (!((s = snarf (&arg)) && (t = snarf_list (&arg)) &&
(u = snarf (&arg)))) response = misarg;
else if (arg) response = badarg;
/* make sure anonymous can't do bad things */
else if (nameok (s,t)) {
/* do the list */
tstream = (*s == '{') ? mail_open (NIL,s,OP_HALFOPEN) : NIL;
mail_scan (tstream,s,t,u);
if (tstream) mail_close (tstream);
tstream = NIL; /* no more temporary stream */
}
}
/* list subscribed mailboxes */
/* list subscribed mailboxes */
else if (!(anonymous || strcmp (cmd,"LSUB"))) {
/* get reference and mailbox argument */
if (!((s = snarf (&arg)) && (t = snarf_list (&arg))))
response = misarg;
else if (arg) response = badarg;
/* make sure anonymous can't do bad things */
else if (nameok (s,t)) {
mail_lsub (NIL,s,t);/* do the lsub */
if (tstream = (*s == '{') ? mail_open (NIL,s,OP_HALFOPEN) : NIL) {
mail_lsub (tstream,s,t);
mail_close (tstream);
tstream = NIL; /* no more temporary stream */
}
}
}
/* find mailboxes */
else if (!strcmp (cmd,"FIND")) {
response = "%s OK FIND %s completed\015\012";
/* get subcommand and true argument */
if (!(arg && (s = strtok (arg," \015\012")) && (cmd = ucase (s)) &&
(arg = strtok (NIL,"\015\012")) && (s = snarf_list (&arg))))
response = misarg; /* missing required argument */
else if (arg) response = badarg;
/* punt on single-char wildcards */
else if (strpbrk (s,"%?")) response = noques;
else if (nameok (NIL,s)) {
finding = T; /* note that we are FINDing */
for (t = s; *t; t++) if (*t == '*') *t = '%';
/* dispatch based on type */
if (!strcmp (cmd,"MAILBOXES")) {
if (!anonymous) {
mail_lsub (NIL,NIL,s);
if ((*s == '{') && (tstream = mail_open (NIL,s,OP_HALFOPEN)))
mail_lsub (tstream,NIL,s);
}
}
else if (!strcmp (cmd,"ALL.MAILBOXES")) {
mail_list (NIL,NIL,s);
if ((*s == '{') && (tstream = mail_open (NIL,s,OP_HALFOPEN)))
mail_list (tstream,NIL,s);
}
else response = badfnd;
if (tstream) mail_close (tstream);
tstream = NIL; /* no more temporary stream */
}
}
/* status of mailbox */
else if (!strcmp (cmd,"STATUS")) {
long flags = NIL;
if (!((s = snarf (&arg)) && arg && (*arg++ == '(') &&
(t = strchr (arg,')')) && (t - arg) && !t[1]))
response = misarg;
else {
*t = '\0'; /* tie off flag string */
/* read flags */
t = strtok (ucase (arg)," ");
do { /* parse each one; unknown generate warning */
if (!strcmp (t,"MESSAGES")) flags |= SA_MESSAGES;
else if (!strcmp (t,"RECENT")) flags |= SA_RECENT;
else if (!strcmp (t,"UNSEEN")) flags |= SA_UNSEEN;
else if (!strcmp (t,"UID-NEXT")) flags |= SA_UIDNEXT;
else if (!strcmp (t,"UID-VALIDITY")) flags |= SA_UIDVALIDITY;
else printf ("* NO Unknown status flag %s\015\012",t);
} while (t = strtok (NIL," "));
if (state == OPEN) mail_ping (stream);
if (!mail_status (stream,s,flags)) response = lose;
}
}
/* subscribe to mailbox */
else if (!(anonymous || strcmp (cmd,"SUBSCRIBE"))) {
/* get <mailbox> or MAILBOX <mailbox> */
if (!(s = snarf (&arg))) response = misarg;
else if (arg && strcmp (ucase (strcpy (tmp,s)),"MAILBOX"))
response = badarg;
else if (arg && !(s = snarf (&arg))) response = misarg;
else if (arg) response = badarg;
else mail_subscribe (NIL,s);
}
/* unsubscribe to mailbox */
else if (!(anonymous || strcmp (cmd,"UNSUBSCRIBE"))) {
/* get <mailbox> or MAILBOX <mailbox> */
if (!(s = snarf (&arg))) response = misarg;
else if (arg && strcmp (ucase (strcpy (tmp,s)),"MAILBOX"))
response = badarg;
else if (arg && !(s = snarf (&arg))) response = misarg;
else if (arg) response = badarg;
else mail_unsubscribe (NIL,s);
}
/* create mailbox */
else if (!(anonymous || strcmp (cmd,"CREATE"))) {
if (!(s = snarf (&arg))) response = misarg;
else if (arg) response = badarg;
else mail_create (NIL,s);
}
/* delete mailbox */
else if (!(anonymous || strcmp (cmd,"DELETE"))) {
if (!(s = snarf (&arg))) response = misarg;
else if (arg) response = badarg;
else mail_delete (NIL,s);
}
/* rename mailbox */
else if (!(anonymous || strcmp (cmd,"RENAME"))) {
if (!((s = snarf (&arg)) && (t = snarf (&arg)))) response = misarg;
else if (arg) response = badarg;
else mail_rename (NIL,s,t);
}
else response = "%s BAD Command unrecognized: %s\015\012";
break;
default:
response = "%s BAD Server in unknown state for %s command\015\012";
break;
}
if (state == OPEN) { /* mailbox open? */
char *saveresponse = response;
char *savelsterr = lsterr;
/* make sure stream still alive */
if (!mail_ping (stream)) {
printf ("* BYE %s Fatal mailbox error: %s\015\012",host,
lsterr ? lsterr : "<unknown>");
state = LOGOUT; /* go away */
syslog (LOG_INFO,
"Fatal mailbox error user=%.80s host=%.80s mbx=%.80s: %.80s",
user ? user : "???",tcp_clienthost (tmp),
(stream && stream->mailbox) ? stream->mailbox : "???",
lsterr ? lsterr : "<unknown>");
}
else { /* did driver change? */
if (curdriver != stream->dtb) {
printf ("* OK [UIDVALIDITY %lu] UID validity status\015\012",
stream->uid_validity);
/* send mailbox flags */
new_flags (stream,tmp);
/* note readonly/write if possible change */
if (curdriver) printf ("* OK [READ-%s] Mailbox status\015\012",
stream->rdonly ? "ONLY" : "WRITE");
curdriver = stream->dtb;
}
/* change in recent messages? */
if (recent != stream->recent)
printf ("* %lu RECENT\015\012",(recent = stream->recent));
/* output changed flags */
for (i = 1; i <= stream->nmsgs; i++) if(mail_elt (stream,i)->spare2){
printf ("* %lu FETCH (",i);
fetch_flags (i,NIL);
fputs (")\015\012",stdout);
}
}
lsterr = savelsterr;
response = saveresponse;
}
/* output any new alerts */
if (!stat (ALERTFILE,&sbuf) && (sbuf.st_mtime > alerttime)) {
FILE *alf = fopen (ALERTFILE,"r");
int c,lc = '\012';
if (alf) { /* only if successful */
while ((c = getc (alf)) != EOF) {
if (lc == '\012') fputs ("* OK [ALERT] ",stdout);
switch (c) { /* output character */
case '\012':
fputs ("\015\012",stdout);
case '\0': /* flush nulls */
break;
default:
putchar (c); /* output all other characters */
break;
}
lc = c; /* note previous character */
}
fclose (alf);
if (lc != '\012') fputs ("\015\012",stdout);
alerttime = sbuf.st_mtime;
}
}
/* get text for alternative win message now */
if (response == altwin) cmd = lsterr;
/* output response */
printf (response,tag,cmd,lsterr ? lsterr : "<unknown>");
}
fflush (stdout); /* make sure output blatted */
} while (state != LOGOUT); /* until logged out */
syslog (LOG_INFO,"Logout user=%.80s host=%.80s",user ? user : "???",
tcp_clienthost (tmp));
exit (0); /* all done */
}
/* Send flags for stream
* Accepts: MAIL stream
* scratch buffer
*/
void new_flags (MAILSTREAM *stream,char *tmp)
{
int i;
tmp[0] = '('; /* start new flag list */
tmp[1] = '\0';
for (i = 0; i < NUSERFLAGS; i++)
if (stream->user_flags[i]) strcat (strcat (tmp,stream->user_flags[i])," ");
/* append system flags to list */
strcat (tmp,"\\Answered \\Flagged \\Deleted \\Draft \\Seen)");
/* output list of flags */
printf ("* FLAGS %s\015\012* OK [PERMANENTFLAGS (",tmp);
tmp[0] = tmp[1] ='\0'; /* write permanent flags */
for (i = 0; i < NUSERFLAGS; i++)
if ((stream->perm_user_flags & (1 << i)) && stream->user_flags[i])
strcat (strcat (tmp," "),stream->user_flags[i]);
if (stream->kwd_create) strcat (tmp," \\*");
if (stream->perm_answered) strcat (tmp," \\Answered");
if (stream->perm_flagged) strcat (tmp," \\Flagged");
if (stream->perm_deleted) strcat (tmp," \\Deleted");
if (stream->perm_draft) strcat (tmp," \\Draft");
if (stream->perm_seen) strcat (tmp," \\Seen");
printf ("%s)] Permanent flags\015\012",tmp+1);
}
/* Clock interrupt
*/
void clkint ()
{
char tmp[MAILTMPLEN];
fputs ("* BYE Autologout; idle for too long\015\012",stdout);
syslog (LOG_INFO,"Autologout user=%.80s host=%.80s",user ? user : "???",
tcp_clienthost (tmp));
fflush (stdout); /* make sure output blatted */
/* try to gracefully close the stream */
if (state == OPEN) mail_close (stream);
stream = NIL;
exit (0); /* die die die */
}
/* Kiss Of Death interrupt
*/
void kodint ()
{
char *s;
if (!kodcount++) { /* only do this once please */
/* must be open for this to work */
if ((state == OPEN) && stream && stream->dtb && (s = stream->dtb->name) &&
(!strcmp (s,"bezerk") || !strcmp (s,"mbox") || !strcmp (s,"mmdf"))) {
fputs("* OK [READ-ONLY] Now READ-ONLY, mailbox lock surrendered\015\012",
stdout);
stream->rdonly = T; /* make the stream readonly */
mail_ping (stream); /* cause it to stick! */
}
else fputs ("* NO Unexpected KOD interrupt received, ignored\015\012",
stdout);
fflush (stdout); /* make sure output blatted */
}
}
/* Slurp a command line
* Accepts: buffer pointer
* buffer size
*/
void slurp (char *s,int n)
{
alarm (TIMEOUT); /* get a command under timeout */
errno = 0; /* clear error */
while (!fgets (s,n,stdin)) { /* read buffer */
if (errno==EINTR) errno = 0;/* ignore if some interrupt */
else {
syslog (LOG_INFO,
"Connection broken while reading line user=%.80s host=%.80s",
user ? user : "???",tcp_clienthost (s));
_exit (1);
}
}
alarm (0); /* make sure timeout disabled */
}
/* Read a character
* Returns: character
*/
char inchar (void)
{
char c,tmp[MAILTMPLEN];
while ((c = getchar ()) == EOF) {
if (errno==EINTR) errno = 0;/* ignore if some interrupt */
else {
syslog (LOG_INFO,
"Connection broken while reading char user=%.80s host=%.80s",
user ? user : "???",tcp_clienthost (tmp));
_exit (1);
}
}
return c;
}
/* Flush until newline seen
* Returns: NIL, always
*/
void *flush (void)
{
while (inchar () != '\012'); /* slurp until we find newline */
response = toobig;
return NIL;
}
/* Parse an IMAP astring
* Accepts: pointer to argument text pointer
* pointer to returned size
* pointer to returned delimiter
* Returns: argument
*/
char *parse_astring (char **arg,unsigned long *size,char *del)
{
unsigned long i;
char c,*s,*t,*v;
if (!*arg) return NIL; /* better be an argument */
switch (**arg) { /* see what the argument is */
case '\0': /* catch bogons */
case ' ':
return NIL;
case '"': /* hunt for trailing quote */
for (s = t = v = *arg + 1; (c = *t++) != '"'; *v++ = c) {
if (c == '\\') c = *t++; /* quote next character */
if (!c) return NIL; /* unterminated quoted string! */
}
*v = '\0'; /* tie off string */
*size = v - s; /* return size */
break;
case '{': /* literal string */
s = *arg + 1; /* get size */
if (!(isdigit (*s) && (*size = i = strtoul (s,&t,10)) && t && (*t == '}')
&& !t[1])) return NIL;/* validate end of literal */
if (litsp >= LITSTKLEN) { /* make sure don't overflow stack */
mm_notify (NIL,"Too many literals in command",ERROR);
return NIL;
}
fputs (argrdy,stdout); /* tell client ready for argument */
fflush (stdout); /* dump output buffer */
/* get a literal buffer */
s = v = litstk[litsp++] = (char *) fs_get (i+1);
alarm (TIMEOUT); /* start timeout */
while (i--) *v++ = inchar ();
alarm (0); /* stop timeout */
*v++ = NIL; /* make sure string tied off */
/* get new command tail */
slurp ((*arg = v = t),TMPLEN - (t - cmdbuf) - 1);
if (!strchr (t,'\012')) return flush ();
/* reset strtok mechanism, tie off if done */
if (!strtok (t,"\015\012")) *t = '\0';
break;
default: /* atom */
for (s = t = *arg, i = 0;
(*t > ' ') && (*t != '(') && (*t != ')') && (*t != '{') &&
(*t != '%') && (*t != '*') && (*t != '"') && (*t != '\\'); ++t,++i);
*size = i; /* return size */
break;
}
if (*del = *t) { /* have a delimiter? */
*t++ = '\0'; /* yes, stomp on it */
*arg = t; /* update argument pointer */
}
else *arg = NIL; /* no more arguments */
return s;
}
/* Snarf a command argument (simple jacket into parse_astring())
* Accepts: pointer to argument text pointer
* Returns: argument
*/
char *snarf (char **arg)
{
unsigned long i;
char c;
char *s = parse_astring (arg,&i,&c);
return ((c == ' ') || !c) ? s : NIL;
}
/* Snarf a list command argument (simple jacket into parse_astring())
* Accepts: pointer to argument text pointer
* Returns: argument
*/
char *snarf_list (char **arg)
{
unsigned long i;
char c;
char *s,*t;
if (!*arg) return NIL; /* better be an argument */
switch (**arg) {
case '\0': /* catch bogons */
case ' ':
return NIL;
case '"': /* quoted string? */
case '{': /* or literal? */
s = parse_astring (arg,&i,&c);
break;
default: /* atom and/or wildcard chars */
for (s = t = *arg, i = 0;
(*t > ' ') && (*t != '(') && (*t != ')') && (*t != '{') &&
(*t != '"') && (*t != '\\'); ++t,++i);
if (c = *t) { /* have a delimiter? */
*t++ = '\0'; /* stomp on it */
*arg = t; /* update argument pointer */
}
else *arg = NIL;
break;
}
return ((c == ' ') || !c) ? s : NIL;
}
/* Get a list of header lines
* Accepts: pointer to string pointer
* pointer to list flag
* Returns: string list
*/
STRINGLIST *parse_stringlist (char **s,int *list)
{
char c = ' ',*t;
unsigned long i;
STRINGLIST *ret = NIL,*cur = NIL;
if (*s && **s == '(') { /* proper list? */
++*s; /* for each item in list */
while ((c == ' ') && (t = parse_astring (s,&i,&c))) {
/* get new block */
if (cur) cur = cur->next = mail_newstringlist ();
else cur = ret = mail_newstringlist ();
cur->text = (char *) fs_get (i + 1);
memcpy (cur->text,t,i); /* note text */
cur->size = i; /* and size */
}
/* must be end of list */
if (c != ')') mail_free_stringlist (&ret);
}
if (t = *s) { /* need to reload strtok() state? */
/* end of a list? */
if (*list && (*t == ')') && !t[1]) *list = NIL;
else {
*--t = ' '; /* patch a space back in */
*--t = 'x'; /* and a hokey character before that */
t = strtok (t," "); /* reset to *s */
}
}
return ret;
}
/* Parse search criteria
* Accepts: search program to write criteria into
* pointer to argument text pointer
* maximum message number
* maximum UID
* Returns: T if success, NIL if error
*/
long parse_criteria (SEARCHPGM *pgm,char **arg,unsigned long maxmsg,
unsigned long maxuid)
{
if (arg && *arg) { /* must be an argument */
/* parse criteria */
do if (!parse_criterion (pgm,arg,maxmsg,maxuid)) return NIL;
/* as long as a space delimiter */
while (**arg == ' ' && (*arg)++);
/* failed if not end of criteria */
if (**arg && **arg != ')') return NIL;
}
return T; /* success */
}
/* Parse a search criterion
* Accepts: search program to write criterion into
* pointer to argument text pointer
* maximum message number
* maximum UID
* Returns: T if success, NIL if error
*/
long parse_criterion (SEARCHPGM *pgm,char **arg,unsigned long maxmsg,
unsigned long maxuid)
{
unsigned long i;
char c = NIL,*s,*t,*v,*tail,*del;
SEARCHSET **set;
SEARCHPGMLIST **not;
SEARCHOR **or;
SEARCHHEADER **hdr;
long ret = NIL;
if (!(arg && *arg)); /* better be an argument */
else if (**arg == '(') { /* list of criteria? */
(*arg)++; /* yes, parse the criteria */
if (parse_criteria (pgm,arg,maxmsg,maxuid) && **arg == ')') {
(*arg)++; /* skip closing paren */
ret = T; /* successful parse of list */
}
}
else { /* find end of criterion */
if (!(tail = strpbrk ((s = *arg)," )"))) tail = *arg + strlen (*arg);
c = *(del = tail); /* remember the delimiter */
*del = '\0'; /* tie off criterion */
switch (*ucase (s)) { /* dispatch based on character */
case '*': /* sequence */
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
if (*(set = &pgm->msgno)){/* already a sequence? */
/* silly, but not as silly as the client! */
for (not = &pgm->not; *not; not = &(*not)->next);
*not = mail_newsearchpgmlist ();
set = &((*not)->pgm->not = mail_newsearchpgmlist ())->pgm->msgno;
}
ret = crit_set (set,&s,maxmsg) && (tail == s);
break;
case 'A': /* possible ALL, ANSWERED */
if (!strcmp (s+1,"LL")) ret = T;
else if (!strcmp (s+1,"NSWERED")) ret = pgm->answered = T;
break;
case 'B': /* possible BCC, BEFORE, BODY */
if (!strcmp (s+1,"CC") && c == ' ' && *++tail)
ret = crit_string (&pgm->bcc,&tail);
else if (!strcmp (s+1,"EFORE") && c == ' ' && *++tail)
ret = crit_date (&pgm->before,&tail);
else if (!strcmp (s+1,"ODY") && c == ' ' && *++tail)
ret = crit_string (&pgm->body,&tail);
break;
case 'C': /* possible CC */
if (!strcmp (s+1,"C") && c == ' ' && *++tail)
ret = crit_string (&pgm->cc,&tail);
break;
case 'D': /* possible DELETED */
if (!strcmp (s+1,"ELETED")) ret = pgm->deleted = T;
if (!strcmp (s+1,"RAFT")) ret = pgm->draft = T;
break;
case 'F': /* possible FLAGGED, FROM */
if (!strcmp (s+1,"LAGGED")) ret = pgm->flagged = T;
else if (!strcmp (s+1,"ROM") && c == ' ' && *++tail)
ret = crit_string (&pgm->from,&tail);
break;
case 'H': /* possible HEADER */
if (!strcmp (s+1,"EADER") && c == ' ' && *(v = tail + 1) &&
(s = parse_astring (&v,&i,&c)) && i && c == ' ' &&
(t = parse_astring (&v,&i,&c)) && i) {
for (hdr = &pgm->header; *hdr; hdr = &(*hdr)->next);
*hdr = mail_newsearchheader (s);
(*hdr)->text = cpystr (t);
/* update tail, restore delimiter */
*(tail = v ? v - 1 : t + i) = c;
ret = T; /* success */
}
break;
case 'K': /* possible KEYWORD */
if (!strcmp (s+1,"EYWORD") && c == ' ' && *++tail)
ret = crit_string (&pgm->keyword,&tail);
break;
case 'L':
if (!strcmp (s+1,"ARGER") && c == ' ' && *++tail)
ret = crit_number (&pgm->larger,&tail);
break;
case 'N': /* possible NEW, NOT */
if (!strcmp (s+1,"EW")) ret = pgm->recent = pgm->unseen = T;
else if (!strcmp (s+1,"OT") && c == ' ' && *++tail) {
for (not = &pgm->not; *not; not = &(*not)->next);
*not = mail_newsearchpgmlist ();
ret = parse_criterion ((*not)->pgm,&tail,maxmsg,maxuid);
}
break;
case 'O': /* possible OLD, ON */
if (!strcmp (s+1,"LD")) ret = pgm->old = T;
else if (!strcmp (s+1,"N") && c == ' ' && *++tail)
ret = crit_date (&pgm->on,&tail);
else if (!strcmp (s+1,"R") && c == ' ') {
for (or = &pgm->or; *or; or = &(*or)->next);
*or = mail_newsearchor ();
ret = *++tail && parse_criterion ((*or)->first,&tail,maxmsg,maxuid) &&
*tail == ' ' && *++tail &&
parse_criterion ((*or)->second,&tail,maxmsg,maxuid);
}
break;
case 'R': /* possible RECENT */
if (!strcmp (s+1,"ECENT")) ret = pgm->recent = T;
break;
case 'S': /* possible SEEN, SINCE, SUBJECT */
if (!strcmp (s+1,"EEN")) ret = pgm->seen = T;
else if (!strcmp (s+1,"ENTBEFORE") && c == ' ' && *++tail)
ret = crit_date (&pgm->sentbefore,&tail);
else if (!strcmp (s+1,"ENTON") && c == ' ' && *++tail)
ret = crit_date (&pgm->senton,&tail);
else if (!strcmp (s+1,"ENTSINCE") && c == ' ' && *++tail)
ret = crit_date (&pgm->sentsince,&tail);
else if (!strcmp (s+1,"INCE") && c == ' ' && *++tail)
ret = crit_date (&pgm->since,&tail);
else if (!strcmp (s+1,"MALLER") && c == ' ' && *++tail)
ret = crit_number (&pgm->smaller,&tail);
else if (!strcmp (s+1,"UBJECT") && c == ' ' && *++tail)
ret = crit_string (&pgm->subject,&tail);
break;
case 'T': /* possible TEXT, TO */
if (!strcmp (s+1,"EXT") && c == ' ' && *++tail)
ret = crit_string (&pgm->text,&tail);
else if (!strcmp (s+1,"O") && c == ' ' && *++tail)
ret = crit_string (&pgm->to,&tail);
break;
case 'U': /* possible UID, UN* */
if (!strcmp (s+1,"ID") && c== ' ' && *++tail) {
if (*(set = &pgm->uid)){/* already a sequence? */
/* silly, but not as silly as the client! */
for (not = &pgm->not; *not; not = &(*not)->next);
*not = mail_newsearchpgmlist ();
set = &((*not)->pgm->not = mail_newsearchpgmlist ())->pgm->uid;
}
ret = crit_set (set,&tail,maxuid);
}
else if (!strcmp (s+1,"NANSWERED")) ret = pgm->unanswered = T;
else if (!strcmp (s+1,"NDELETED")) ret = pgm->undeleted = T;
else if (!strcmp (s+1,"NDRAFT")) ret = pgm->undraft = T;
else if (!strcmp (s+1,"NFLAGGED")) ret = pgm->unflagged = T;
else if (!strcmp (s+1,"NKEYWORD") && c == ' ' && *++tail)
ret = crit_string (&pgm->unkeyword,&tail);
else if (!strcmp (s+1,"NSEEN")) ret = pgm->unseen = T;
break;
default: /* oh dear */
break;
}
if (ret) { /* only bother if success */
*del = c; /* restore delimiter */
*arg = tail; /* update argument pointer */
}
}
return ret; /* return more to come */
}
/* Parse a search date criterion
* Accepts: date to write into
* pointer to argument text pointer
* Returns: T if success, NIL if error
*/
long crit_date (unsigned short *date,char **arg)
{
/* handle quoted form */
if (**arg != '"') return crit_date_work (date,arg);
(*arg)++; /* skip past opening quote */
if (!(crit_date_work (date,arg) && (**arg == '"'))) return NIL;
(*arg)++; /* skip closing quote */
return T;
}
/* Worker routine to parse a search date criterion
* Accepts: date to write into
* pointer to argument text pointer
* Returns: T if success, NIL if error
*/
long crit_date_work (unsigned short *date,char **arg)
{
int d,m,y;
if (isdigit (**arg)) { /* day */
d = *(*arg)++ - '0'; /* first digit */
if (isdigit (**arg)) { /* if a second digit */
d *= 10; /* slide over first digit */
d += *(*arg)++ - '0'; /* second digit */
}
if ((**arg == '-') && (y = *++(*arg))) {
m = (y >= 'a' ? y - 'a' : y - 'A') * 1024;
if ((y = *++(*arg))) {
m += (y >= 'a' ? y - 'a' : y - 'A') * 32;
if ((y = *++(*arg))) {
m += (y >= 'a' ? y - 'a' : y - 'A');
switch (m) { /* determine the month */
case (('J'-'A') * 1024) + (('A'-'A') * 32) + ('N'-'A'): m = 1; break;
case (('F'-'A') * 1024) + (('E'-'A') * 32) + ('B'-'A'): m = 2; break;
case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('R'-'A'): m = 3; break;
case (('A'-'A') * 1024) + (('P'-'A') * 32) + ('R'-'A'): m = 4; break;
case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('Y'-'A'): m = 5; break;
case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('N'-'A'): m = 6; break;
case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('L'-'A'): m = 7; break;
case (('A'-'A') * 1024) + (('U'-'A') * 32) + ('G'-'A'): m = 8; break;
case (('S'-'A') * 1024) + (('E'-'A') * 32) + ('P'-'A'): m = 9; break;
case (('O'-'A') * 1024) + (('C'-'A') * 32) + ('T'-'A'): m = 10;break;
case (('N'-'A') * 1024) + (('O'-'A') * 32) + ('V'-'A'): m = 11;break;
case (('D'-'A') * 1024) + (('E'-'A') * 32) + ('C'-'A'): m = 12;break;
default: return NIL;
}
if ((*++(*arg) == '-') && isdigit (*++(*arg))) {
y = 0; /* init year */
do {
y *= 10; /* add this number */
y += *(*arg)++ - '0';
}
while (isdigit (**arg));
/* minimal validity check of date */
if (d < 1 || d > 31 || m < 1 || m > 12 || y < 0) return NIL;
/* Tenex/ARPAnet began in 1969 */
if (y < 100) y += (y >= (BASEYEAR - 1900)) ? 1900 : 2000;
/* return value */
*date = ((y - BASEYEAR) << 9) + (m << 5) + d;
return T; /* success */
}
}
}
}
}
return NIL; /* else error */
}
/* Parse a search set criterion
* Accepts: set to write into
* pointer to argument text pointer
* maximum value permitted
* Returns: T if success, NIL if error
*/
long crit_set (SEARCHSET **set,char **arg,unsigned long maxima)
{
unsigned long i;
*set = mail_newsearchset (); /* instantiate a new search set */
if (**arg == '*') { /* maxnum? */
(*arg)++; /* skip past that number */
(*set)->first = maxima;
}
else if (crit_number (&i,arg) && i) (*set)->first = i;
else return NIL; /* bogon */
switch (**arg) { /* decide based on delimiter */
case ':': /* sequence range */
if (*++(*arg) == '*') { /* maxnum? */
(*arg)++; /* skip past that number */
(*set)->last -= maxima;
}
else if (crit_number (&i,arg) && i) {
if (i < (*set)->first) { /* backwards range */
(*set)->last = (*set)->first;
(*set)->first = i;
}
else (*set)->last = i; /* set last number */
}
else return NIL; /* bogon */
if (**arg != ',') break; /* drop into comma case if comma seen */
case ',':
(*arg)++; /* skip past delimiter */
return crit_set (&(*set)->next,arg,maxima);
default:
break;
}
return T; /* return success */
}
/* Parse a search number criterion
* Accepts: number to write into
* pointer to argument text pointer
* Returns: T if success, NIL if error
*/
long crit_number (unsigned long *number,char **arg)
{
if (!isdigit (**arg)) return NIL;
*number = 0;
while (isdigit (**arg)) { /* found a digit? */
*number *= 10; /* add a decade */
*number += *(*arg)++ - '0'; /* add number */
}
return T;
}
/* Parse a search string criterion
* Accepts: date to write into
* pointer to argument text pointer
* Returns: T if success, NIL if error
*/
long crit_string (STRINGLIST **string,char **arg)
{
unsigned long i;
char c;
char *s = parse_astring (arg,&i,&c);
if (!s) return NIL;
/* find tail of list */
while (*string) string = &(*string)->next;
*string = mail_newstringlist ();
(*string)->text = (char *) fs_get (i + 1);
memcpy ((*string)->text,s,i);
(*string)->text[i] = '\0';
(*string)->size = i;
/* if end of arguments, wrap it up here */
if (!*arg) *arg = (*string)->text + i;
else (*--(*arg) = c); /* back up pointer, restore delimiter */
return T;
}
/* Fetch message data
* Accepts: string of data items to be fetched
* UID fetch flag
*/
#define MAXFETCH 100
void fetch (char *t,unsigned long uid)
{
char *s,*v;
unsigned long i,k;
BODY *b;
int list = NIL;
int parse_envs = NIL;
int parse_bodies = NIL;
void *doing_uid = NIL;
void (*f[MAXFETCH + 2]) ();
STRINGLIST *fa[MAXFETCH + 2];
/* process macros */
if (!strcmp (ucase (t),"ALL"))
strcpy (t,"(FLAGS INTERNALDATE RFC822.SIZE ENVELOPE)");
else if (!strcmp (t,"FULL"))
strcpy (t,"(FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY)");
else if (!strcmp (t,"FAST")) strcpy (t,"(FLAGS INTERNALDATE RFC822.SIZE)");
if (list = (*t == '(')) t++; /* skip open paren */
k = 0; /* initial index */
if (s = strtok (t," ")) do { /* parse attribute list */
fa[k] = NIL;
if (list && (i = strlen (s)) && (s[i-1] == ')')) {
list = NIL; /* done with list */
s[i-1] = '\0'; /* tie off last item */
}
if (!strcmp (s,"BODY")) { /* we will need to parse bodies */
parse_envs = parse_bodies = T;
f[k++] = fetch_body;
}
else if (!strcmp (s,"BODYSTRUCTURE")) {
parse_envs = parse_bodies = T;
f[k++] = fetch_bodystructure;
}
else if (!strcmp (s,"ENVELOPE")) {
parse_envs = T; /* we will need to parse envelopes */
f[k++] = fetch_envelope;
}
else if (!strcmp (s,"FLAGS")) f[k++] = fetch_flags;
else if (!strcmp (s,"INTERNALDATE")) f[k++] = fetch_internaldate;
else if (!strcmp (s,"RFC822")) f[k++] = fetch_rfc822;
else if (!strcmp (s,"RFC822.PEEK")) f[k++] = fetch_rfc822_peek;
else if (!strcmp (s,"RFC822.HEADER")) f[k++] = fetch_rfc822_header;
else if (!strcmp (s,"RFC822.SIZE")) f[k++] = fetch_rfc822_size;
else if (!strcmp (s,"RFC822.TEXT")) f[k++] = fetch_rfc822_text;
else if (!strcmp (s,"RFC822.TEXT.PEEK")) f[k++] = fetch_rfc822_text_peek;
else if (!strcmp (s,"UID")) doing_uid = (void *) (f[k++] = fetch_uid);
else if (*s == 'B' && s[1] == 'O' && s[2] == 'D' && s[3] == 'Y' &&
s[4] == '[' && (v = strchr (s + 5,']')) && !(*v = v[1])) {
/* we will need to parse bodies */
parse_envs = parse_bodies = T;
/* set argument */
(fa[k] = mail_newstringlist ())->text = cpystr (s+5);
fa[k]->size = strlen (s+5);
f[k++] = fetch_body_part;
}
else if (*s == 'B' && s[1] == 'O' && s[2] == 'D' && s[3] == 'Y' &&
s[4] == '.' && s[5] == 'P' && s[6] == 'E' && s[7] == 'E' &&
s[8] == 'K' && s[9] == '[' && (v = strchr (s + 10,']')) &&
!(*v = v[1])) { /* we will need to parse bodies */
parse_envs = parse_bodies = T;
/* set argument */
(fa[k] = mail_newstringlist ())->text = cpystr (s+10);
fa[k]->size = strlen (s+10);
f[k++] = fetch_body_part_peek;
}
else if (!strcmp (s,"RFC822.HEADER.LINES") &&
(v = strtok (NIL,"\015\012")) &&
(fa[k] = parse_stringlist (&v,&list)))
f[k++] = fetch_rfc822_header_lines;
else if (!strcmp (s,"RFC822.HEADER.LINES.NOT") &&
(v = strtok (NIL,"\015\012")) &&
(fa[k] = parse_stringlist (&v,&list)))
f[k++] = fetch_rfc822_header_lines_not;
else { /* unknown attribute */
response = badatt;
return;
}
} while ((s = strtok (NIL," ")) && (k < MAXFETCH) && list);
else {
response = misarg; /* missing attribute list */
return;
}
if (s) { /* too many attributes? */
response = "%s BAD Excessively complex FETCH attribute list\015\012";
return;
}
if (list) { /* too many attributes? */
response = "%s BAD Unterminated FETCH attribute list\015\012";
return;
}
if (uid && !doing_uid) { /* UID fetch must fetch UIDs */
fa[k] = NIL;
f[k++] = fetch_uid; /* implicitly fetch UIDs on UID fetch */
}
f[k] = NIL; /* tie off attribute list */
/* c-client clobbers sequence, use spare */
for (i = 1; i <= stream->nmsgs; i++)
mail_elt (stream,i)->spare = mail_elt (stream,i)->sequence;
/* for each requested message */
for (i = 1; i <= stream->nmsgs; i++) if (mail_elt (stream,i)->spare) {
/* parse envelope, set body, do warnings */
if (parse_envs) mail_fetchstructure (stream,i,parse_bodies ? &b : NIL);
printf ("* %lu FETCH (",i); /* leader */
(*f[0]) (i,fa[0]); /* do first attribute */
for (k = 1; f[k]; k++) { /* for each subsequent attribute */
putchar (' '); /* delimit with space */
(*f[k]) (i,fa[k]); /* do that attribute */
}
fputs (")\015\012",stdout); /* trailer */
}
/* clean up string arguments */
for (k = 0; f[k]; k++) if (fa[k]) mail_free_stringlist (&fa[k]);
}
/* Fetch message body structure (extensible)
* Accepts: message number
* extra argument
*/
void fetch_bodystructure (unsigned long i,STRINGLIST *a)
{
BODY *body;
mail_fetchstructure (stream,i,&body);
fputs ("BODYSTRUCTURE ",stdout);
pbodystructure (body); /* output body */
}
/* Fetch message body structure (non-extensible)
* Accepts: message number
* extra argument
*/
void fetch_body (unsigned long i,STRINGLIST *a)
{
BODY *body;
mail_fetchstructure (stream,i,&body);
fputs ("BODY ",stdout); /* output attribute */
pbody (body); /* output body */
}
/* Fetch message body part
* Accepts: message number
* extra argument
*/
void fetch_body_part (unsigned long i,STRINGLIST *a)
{
char *s = a->text;
unsigned long j;
long k = 0;
BODY *body;
int f = mail_elt (stream,i)->seen;
mail_fetchstructure (stream,i,&body);
printf ("BODY[%s] ",s); /* output attribute */
if (body && (s = mail_fetchbody_full (stream,i,s,&j,NIL))) {
printf ("{%lu}\015\012",j); /* and literal string */
while (j -= k) k = fwrite (s += k,1,j,stdout);
changed_flags (i,f); /* output changed flags */
}
else fputs ("NIL",stdout); /* can't output anything at all */
}
/* Fetch message body part, peeking
* Accepts: message number
* extra argument
*/
void fetch_body_part_peek (unsigned long i,STRINGLIST *a)
{
char *s = a->text;
unsigned long j;
long k = 0;
BODY *body;
mail_fetchstructure (stream,i,&body);
printf ("BODY[%s] ",s); /* output attribute */
if (body && (s = mail_fetchbody_full (stream,i,s,&j,FT_PEEK))) {
printf ("{%lu}\015\012",j); /* and literal string */
while (j -= k) k = fwrite (s += k,1,j,stdout);
}
else fputs ("NIL",stdout); /* can't output anything at all */
}
/* Fetch envelope
* Accepts: message number
* extra argument
*/
void fetch_envelope (unsigned long i,STRINGLIST *a)
{
ENVELOPE *env = mail_fetchstructure (stream,i,NIL);
fputs ("ENVELOPE ",stdout); /* output attribute */
penv (env); /* output envelope */
}
/* Fetch matching header lines
* Accepts: message number
* extra argument
*/
void fetch_rfc822_header_lines (unsigned long i,STRINGLIST *a)
{
char c;
unsigned long size;
char *t = mail_fetchheader_full (stream,i,a,&size,NIL);
printf ("RFC822.HEADER {%lu}\015\012",size);
while (size--) {
c = *t++; /* don't want to risk it */
putchar (c);
}
}
/* Fetch not-matching header lines
* Accepts: message number
* extra argument
*/
void fetch_rfc822_header_lines_not (unsigned long i,STRINGLIST *a)
{
char c;
unsigned long size;
char *t = mail_fetchheader_full (stream,i,a,&size,FT_NOT);
printf ("RFC822.HEADER {%lu}\015\012",size);
while (size--) {
c = *t++; /* don't want to risk it */
putchar (c);
}
}
/* Fetch flags
* Accepts: message number
* extra argument
*/
void fetch_flags (unsigned long i,STRINGLIST *a)
{
unsigned long u;
char *t,tmp[MAILTMPLEN];
int c = NIL;
MESSAGECACHE *elt = mail_elt (stream,i);
if (!elt->valid) { /* have valid flags yet? */
sprintf (tmp,"%lu",i);
mail_fetchflags (stream,tmp);
}
fputs ("FLAGS (",stdout); /* output attribute */
/* output system flags */
if (elt->recent) put_flag (&c,"\\Recent");
if (elt->seen) put_flag (&c,"\\Seen");
if (elt->deleted) put_flag (&c,"\\Deleted");
if (elt->flagged) put_flag (&c,"\\Flagged");
if (elt->answered) put_flag (&c,"\\Answered");
if (u = elt->user_flags) do /* any user flags? */
if (t = stream->user_flags[find_rightmost_bit (&u)]) put_flag (&c,t);
while (u); /* until no more user flags */
putchar (')'); /* end of flags */
elt->spare2 = NIL; /* we've sent the update */
}
/* Output a flag
* Accepts: pointer to current delimiter character
* flag to output
* Changes delimiter character to space
*/
void put_flag (int *c,char *s)
{
if (*c) putchar (*c); /* put delimiter */
fputs (s,stdout); /* dump flag */
*c = ' '; /* change delimiter if necessarnew */
}
/* Output flags if was unseen
* Accepts: message number
* prior value of Seen flag
*/
void changed_flags (unsigned long i,int f)
{
if (!f) { /* was unseen? */
putchar (' '); /* yes, delimit with space */
fetch_flags (i,NIL); /* output flags */
}
}
/* Fetch message internal date
* Accepts: message number
* extra argument
*/
void fetch_internaldate (unsigned long i,STRINGLIST *a)
{
char tmp[MAILTMPLEN];
MESSAGECACHE *elt = mail_elt (stream,i);
if (!elt->day) { /* have internal date yet? */
sprintf (tmp,"%lu",i);
mail_fetchfast (stream,tmp);
}
printf ("INTERNALDATE \"%s\"",mail_date (tmp,elt));
}
/* Fetch unique identifier
* Accepts: message number
* extra argument
*/
void fetch_uid (unsigned long i,STRINGLIST *a)
{
printf ("UID %lu",mail_uid (stream,i));
}
/* Yes, I know the double fetch is bletcherous. But few clients do this. */
/* Fetch complete RFC-822 format message
* Accepts: message number
* extra argument
*/
void fetch_rfc822 (unsigned long i,STRINGLIST *a)
{
char *s,c;
unsigned long size,fullsize;
int f = mail_elt (stream,i)->seen;
mail_fetchtext_full (stream,i,&fullsize,NIL);
s = mail_fetchheader_full (stream,i,NIL,&size,NIL);
fullsize += size; /* increment size */
printf ("RFC822 {%lu}\015\012",fullsize);
while (size--) {
c = *s++; /* don't want to risk it */
putchar (c);
}
s = mail_fetchtext_full (stream,i,&size,NIL);
while (size--) {
c = *s++; /* don't want to risk it */
putchar (c);
}
changed_flags (i,f); /* output changed flags */
}
/* Fetch complete RFC-822 format message, peeking
* Accepts: message number
* extra argument
*/
void fetch_rfc822_peek (unsigned long i,STRINGLIST *a)
{
char *s,c;
unsigned long size,fullsize;
mail_fetchtext_full (stream,i,&fullsize,FT_PEEK);
s = mail_fetchheader_full (stream,i,NIL,&size,NIL);
fullsize += size; /* increment size */
printf ("RFC822 {%lu}\015\012",fullsize);
while (size--) {
c = *s++; /* don't want to risk it */
putchar (c);
}
s = mail_fetchtext_full (stream,i,&size,FT_PEEK);
while (size--) {
c = *s++; /* don't want to risk it */
putchar (c);
}
}
/* Fetch RFC-822 header
* Accepts: message number
* extra argument
*/
void fetch_rfc822_header (unsigned long i,STRINGLIST *a)
{
char *s,c;
unsigned long size;
s = mail_fetchheader_full (stream,i,NIL,&size,NIL);
printf ("RFC822.HEADER {%lu}\015\012",size);
while (size--) {
c = *s++; /* don't want to risk it */
putchar (c);
}
}
/* Fetch RFC-822 message length
* Accepts: message number
* extra argument
*/
void fetch_rfc822_size (unsigned long i,STRINGLIST *a)
{
MESSAGECACHE *elt = mail_elt (stream,i);
if (!elt->rfc822_size) { /* have message size yet? */
char tmp[MAILTMPLEN];
sprintf (tmp,"%lu",i);
mail_fetchfast (stream,tmp);
}
printf ("RFC822.SIZE %lu",elt->rfc822_size);
}
/* Fetch RFC-822 text only
* Accepts: message number
* extra argument
*/
void fetch_rfc822_text (unsigned long i,STRINGLIST *a)
{
char c,*s;
unsigned long size;
int f = mail_elt (stream,i)->seen;
s = mail_fetchtext_full (stream,i,&size,NIL);
printf ("RFC822.TEXT {%lu}\015\012",size);
while (size--) {
c = *s++; /* don't want to risk it */
putchar (c);
}
changed_flags (i,f); /* output changed flags */
}
/* Fetch RFC-822 text only, peeking
* Accepts: message number
* extra argument
*/
void fetch_rfc822_text_peek (unsigned long i,STRINGLIST *a)
{
char c,*s;
unsigned long size;
s = mail_fetchtext_full (stream,i,&size,FT_PEEK);
printf ("RFC822.TEXT {%lu}\015\012",size);
while (size--) {
c = *s++; /* don't want to risk it */
putchar (c);
}
}
/* Print envelope
* Accepts: body
*/
void penv (ENVELOPE *env)
{
if (env) { /* only if there is an envelope */
/* disgusting MacMS kludge */
if (mackludge) printf ("%lu ",cstring (env->date) + cstring (env->subject)
+ caddr (env->from) + caddr (env->sender) +
caddr (env->reply_to) + caddr (env->to) +
caddr (env->cc) + caddr (env->bcc) +
cstring (env->in_reply_to) +
cstring (env->message_id));
putchar ('('); /* delimiter */
pstring (env->date); /* output envelope fields */
putchar (' ');
pstring (env->subject);
putchar (' ');
paddr (env->from);
putchar (' ');
paddr (env->sender);
putchar (' ');
paddr (env->reply_to);
putchar (' ');
paddr (env->to);
putchar (' ');
paddr (env->cc);
putchar (' ');
paddr (env->bcc);
putchar (' ');
pstring (env->in_reply_to);
putchar (' ');
pstring (env->message_id);
putchar (')'); /* end of envelope */
}
else fputs ("NIL",stdout); /* no envelope */
}
/* Print body structure (extensible)
* Accepts: body
*/
void pbodystructure (BODY *body)
{
if (body) { /* only if there is a body */
PARAMETER *param;
PART *part;
putchar ('('); /* delimiter */
/* multipart type? */
if (body->type == TYPEMULTIPART) {
/* print each part */
if (part = body->contents.part)
for (; part; part = part->next) pbodystructure (&(part->body));
else fputs ("(\"TEXT\" \"PLAIN\" NIL NIL NIL \"7BIT\" 0 0)",stdout);
putchar (' '); /* space delimiter */
pstring (body->subtype); /* subtype */
/* multipart body extension data */
if (param = body->parameter) {
fputs (" (",stdout);
do {
pstring (param->attribute);
putchar (' ');
pstring (param->value);
if (param = param->next) putchar (' ');
} while (param);
putchar (')'); /* end of parameters */
}
else fputs (" NIL",stdout);
}
else { /* non-multipart body type */
pstring ((char *) body_types[body->type]);
putchar (' ');
pstring (body->subtype);
if (param = body->parameter) {
fputs (" (",stdout);
do {
pstring (param->attribute);
putchar (' ');
pstring (param->value);
if (param = param->next) putchar (' ');
} while (param);
fputs (") ",stdout);
}
else fputs (" NIL ",stdout);
pstring (body->id);
putchar (' ');
pstring (body->description);
putchar (' ');
pstring ((char *) body_encodings[body->encoding]);
printf (" %lu",body->size.bytes);
switch (body->type) { /* extra stuff depends upon body type */
case TYPEMESSAGE:
/* can't do this if not RFC822 */
if (strcmp (body->subtype,"RFC822")) break;
putchar (' ');
penv (body->contents.msg.env);
putchar (' ');
pbodystructure (body->contents.msg.body);
case TYPETEXT:
printf (" %lu",body->size.lines);
break;
default:
break;
}
putchar (' ');
pstring (body->md5);
}
putchar (')'); /* end of body */
}
else fputs ("NIL",stdout); /* no body */
}
/* Print body (non-extensible)
* Accepts: body
*/
void pbody (BODY *body)
{
if (body) { /* only if there is a body */
PARAMETER *param;
PART *part;
putchar ('('); /* delimiter */
/* multipart type? */
if (body->type == TYPEMULTIPART) {
/* print each part */
if (part = body->contents.part)
for (; part; part = part->next) pbody (&(part->body));
else fputs ("(\"TEXT\" \"PLAIN\" NIL NIL NIL \"7BIT\" 0 0)",stdout);
putchar (' '); /* space delimiter */
pstring (body->subtype); /* and finally the subtype */
}
else { /* non-multipart body type */
pstring ((char *) body_types[body->type]);
putchar (' ');
pstring (body->subtype);
if (param = body->parameter) {
fputs (" (",stdout);
do {
pstring (param->attribute);
putchar (' ');
pstring (param->value);
if (param = param->next) putchar (' ');
} while (param);
fputs (") ",stdout);
}
else fputs (" NIL ",stdout);
pstring (body->id);
putchar (' ');
pstring (body->description);
putchar (' ');
pstring ((char *) body_encodings[body->encoding]);
printf (" %lu",body->size.bytes);
switch (body->type) { /* extra stuff depends upon body type */
case TYPEMESSAGE:
/* can't do this if not RFC822 */
if (strcmp (body->subtype,"RFC822")) break;
putchar (' ');
penv (body->contents.msg.env);
putchar (' ');
pbody (body->contents.msg.body);
case TYPETEXT:
printf (" %lu",body->size.lines);
break;
default:
break;
}
}
putchar (')'); /* end of body */
}
else fputs ("NIL",stdout); /* no body */
}
/* Print string
* Accepts: string
*/
void pstring (char *s)
{
if (s) { /* is there a string? */
/* must use literal string */
if (strpbrk (s,"\012\015\"\\")) {
printf ("{%lu}\015\012",(unsigned long) strlen (s));
fputs (s,stdout); /* don't merge this with the printf() */
}
else { /* may use quoted string */
putchar ('"'); /* don't even think of merging this into a */
fputs (s,stdout); /* printf(). Cretin VAXen can't do a */
putchar ('"'); /* printf() of godzilla strings! */
}
}
else fputs ("NIL",stdout); /* empty string */
}
/* Print address list
* Accepts: address list
*/
void paddr (ADDRESS *a)
{
if (a) { /* have anything in address? */
putchar ('('); /* open the address list */
do { /* for each address */
putchar ('('); /* open the address */
pstring (a->personal); /* personal name */
putchar (' ');
pstring (a->adl); /* at-domain-list */
putchar (' ');
pstring (a->mailbox); /* mailbox */
putchar (' ');
pstring (a->host); /* domain name of mailbox's host */
putchar (')'); /* terminate address */
} while (a = a->next); /* until end of address */
putchar (')'); /* close address list */
}
else fputs ("NIL",stdout); /* empty address */
}
/* Count string and space afterwards
* Accepts: string
* Returns: 1 plus length of string
*/
long cstring (char *s)
{
char tmp[MAILTMPLEN];
unsigned long i = s ? strlen (s) : 0;
if (s) { /* is there a string? */
/* must use literal string */
if (strpbrk (s,"\012\015\"%{\\")) {
sprintf (tmp,"{%lu}\015\012",i);
i += strlen (tmp);
}
else i += 2; /* quoted string */
}
else i += 3; /* NIL */
return i + 1; /* return string plus trailing space */
}
/* Count address list and space afterwards
* Accepts: address list
*/
long caddr (ADDRESS *a)
{
long i = 3; /* open, close, and space */
/* count strings in address list */
if (a) do i += 1 + cstring (a->personal) + cstring (a->adl) +
cstring (a->mailbox) + cstring (a->host);
while (a = a->next); /* until end of address */
else i = 4; /* NIL plus space */
return i; /* return the count */
}
/* Anonymous users may only use these mailboxes in these namespaces */
char *oktab[] = {"#news.", "#ftp/", 0};
/* Check if mailbox name is OK
* Accepts: reference name
* mailbox name
*/
long nameok (char *ref,char *name)
{
int i;
if (!name) return NIL; /* failure if missing name */
if (!anonymous) return T; /* otherwise OK if not anonymous */
/* validate reference */
if (ref) for (i = 0; oktab[i]; i++)
if (!strncmp (ref,oktab[i],strlen (oktab[i]))) {
if (*name == '#') break; /* reference OK, any override? */
else return T; /* no, we're done */
}
for (i = 0; oktab[i]; i++) /* validate mailbox */
if (!strncmp (name,oktab[i],strlen (oktab[i]))) return T;
response = "%s NO Anonymous may not %s this name\015\012";
return NIL;
}
/* IMAP4 Authentication responder
* Accepts: challenge
* Returns: response
*/
#define RESPBUFLEN 8*MAILTMPLEN
char *imap_responder (void *challenge,unsigned long clen,unsigned long *rlen)
{
unsigned long i;
unsigned char *t,resp[RESPBUFLEN];
printf ("+ %s",t = rfc822_binary (challenge,clen,&i));
fflush (stdout); /* dump output buffer */
/* slurp response buffer */
slurp ((char *) resp,RESPBUFLEN-1);
if (!(t = (unsigned char *) strchr ((char *) resp,'\012'))) return flush ();
if (t[-1] == '\015') --t; /* remove CR */
*t = '\0'; /* tie off buffer */
return (resp[0] != '*') ? rfc822_base64 (resp,t-resp,rlen ? rlen : &i) : NIL;
}
/* Co-routines from MAIL library */
/* Message matches a search
* Accepts: MAIL stream
* message number
*/
void mm_searched (MAILSTREAM *s,unsigned long msgno)
{
/* nothing to do here */
}
/* Message exists (i.e. there are that many messages in the mailbox)
* Accepts: MAIL stream
* message number
*/
void mm_exists (MAILSTREAM *s,unsigned long number)
{
/* note change in number of messages */
if ((s != tstream) && (nmsgs != number)) {
printf ("* %lu EXISTS\015\012",(nmsgs = number));
recent = 0xffffffff; /* make sure update recent too */
}
}
/* Message expunged
* Accepts: MAIL stream
* message number
*/
void mm_expunged (MAILSTREAM *s,unsigned long number)
{
if (s != tstream) printf ("* %lu EXPUNGE\015\012",number);
}
/* Message status changed
* Accepts: MAIL stream
* message number
*/
void mm_flags (MAILSTREAM *s,unsigned long number)
{
if (s != tstream) mail_elt (s,number)->spare2 = T;
}
/* Mailbox found
* Accepts: hierarchy delimiter
* mailbox name
* attributes
*/
void mm_list (MAILSTREAM *stream,char delimiter,char *name,long attributes)
{
mm_list_work ("LIST",delimiter,name,attributes);
}
/* Subscribed mailbox found
* Accepts: hierarchy delimiter
* mailbox name
* attributes
*/
void mm_lsub (MAILSTREAM *stream,char delimiter,char *name,long attributes)
{
mm_list_work ("LSUB",delimiter,name,attributes);
}
/* Mailbox status
* Accepts: MAIL stream
* mailbox name
* mailbox status
*/
void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status)
{
char tmp[MAILTMPLEN];
tmp[0] = tmp[1] = '\0';
if (status->flags & SA_MESSAGES)
sprintf (tmp + strlen (tmp)," MESSAGES %ld",status->messages);
if (status->flags & SA_RECENT)
sprintf (tmp + strlen (tmp)," RECENT %ld",status->recent);
if (status->flags & SA_UNSEEN)
sprintf (tmp + strlen (tmp)," UNSEEN %ld",status->unseen);
if (status->flags & SA_UIDNEXT)
sprintf (tmp + strlen (tmp)," UID-NEXT %ld",status->uidnext);
if (status->flags & SA_UIDVALIDITY)
sprintf (tmp+strlen(tmp)," UID-VALIDITY %ld",status->uidvalidity);
printf ("* STATUS %s (%s)\015\012",mailbox,tmp+1);
}
/* Worker routine for LIST and LSUB
* Accepts: name of response
* hierarchy delimiter
* mailbox name
* attributes
*/
void mm_list_work (char *what,char delimiter,char *name,long attributes)
{
char *s,tmp[MAILTMPLEN];
if (finding) printf ("* MAILBOX %s",name);
else { /* new form */
tmp[0] = tmp[1] = '\0';
if (attributes & LATT_NOINFERIORS) strcat (tmp," \\NoInferiors");
if (attributes & LATT_NOSELECT) strcat (tmp," \\NoSelect");
if (attributes & LATT_MARKED) strcat (tmp," \\Marked");
if (attributes & LATT_UNMARKED) strcat (tmp," \\UnMarked");
tmp[0] = '('; /* put in real delimiter */
switch (delimiter) {
case '\\': /* quoted delimiter */
case '"':
sprintf (tmp + strlen (tmp),") \"\\%c\"",delimiter);
break;
case '\0': /* no delimiter */
strcat (tmp,") NIL");
break;
default: /* unquoted delimiter */
sprintf (tmp + strlen (tmp),") \"%c\"",delimiter);
break;
}
printf ("* %s %s ",what,tmp);
/* must use literal string */
if (strpbrk (name,"\012\015\"\\")) {
printf ("{%lu}\015\012",(unsigned long) strlen (name));
fputs (name,stdout); /* don't merge this with the printf() */
}
else { /* either quoted string or atom */
for (s = name; /* see if anything that requires quoting */
(*s > ' ') && (*s != '(') && (*s != ')') && (*s != '{'); ++s);
if (*s) { /* must use quoted string */
putchar ('"'); /* don't even think of merging this into a */
fputs (name,stdout); /* printf(). Cretin VAXen can't do a */
putchar ('"'); /* printf() of godzilla strings! */
}
else fputs (name,stdout); /* else plop down as atomic */
}
}
fputs ("\015\012",stdout);
}
/* Notification event
* Accepts: MAIL stream
* string to log
* error flag
*/
void mm_notify (MAILSTREAM *s,char *string,long errflg)
{
char tmp[MAILTMPLEN];
if (!tstream || (s != tstream)) switch (errflg) {
case NIL: /* information message, set as OK response */
tmp[11] = '\0'; /* see if TRYCREATE for MacMS kludge */
if (!strcmp (ucase (strncpy (tmp,string,11)),"[TRYCREATE]")) trycreate = T;
case BYE: /* some other server signing off */
case PARSE: /* parse glitch, output unsolicited OK */
printf ("* OK %s\015\012",string);
break;
case WARN: /* warning, output unsolicited NO (kludge!) */
printf ("* NO %s\015\012",string);
break;
case ERROR: /* error that broke command */
default: /* default should never happen */
printf ("* BAD %s\015\012",string);
break;
}
}
/* Log an event for the user to see
* Accepts: string to log
* error flag
*/
void mm_log (char *string,long errflg)
{
switch (errflg) { /* action depends upon the error flag */
case NIL: /* information message, set as OK response */
if (response == win) { /* only if no other response yet */
response = altwin; /* switch to alternative win message */
fs_give ((void **) &lsterr);
lsterr = cpystr (string); /* copy string for later use */
}
break;
case PARSE: /* parse glitch, output unsolicited OK */
printf ("* OK [PARSE] %s\015\012",string);
break;
case WARN: /* warning, output unsolicited NO (kludge!) */
if (strcmp (string,"Mailbox is empty")) printf ("* NO %s\015\012",string);
break;
case ERROR: /* error that broke command */
default: /* default should never happen */
response = lose; /* set fatality */
fs_give ((void **) &lsterr);/* flush old error */
lsterr = cpystr (string); /* note last error */
break;
}
}
/* Log an event to debugging telemetry
* Accepts: string to log
*/
void mm_dlog (char *string)
{
mm_log (string,WARN); /* shouldn't happen normally */
}
/* Get user name and password for this host
* Accepts: parse of network user name
* where to return user name
* where to return password
* trial count
*/
void mm_login (NETMBX *mb,char *username,char *password,long trial)
{
/* set user name */
strcpy (username,*mb->user ? mb->user : user);
strcpy (password,pass); /* and password */
}
/* About to enter critical code
* Accepts: stream
*/
void mm_critical (MAILSTREAM *s)
{
/* Not doing anything here for now */
}
/* About to exit critical code
* Accepts: stream
*/
void mm_nocritical (MAILSTREAM *s)
{
/* Not doing anything here for now */
}
/* Disk error found
* Accepts: stream
* system error code
* flag indicating that mailbox may be clobbered
* Returns: abort flag
*/
long mm_diskerror (MAILSTREAM *s,long errcode,long serious)
{
char tmp[MAILTMPLEN];
if (serious) { /* try your damnest if clobberage likely */
mm_notify (s,"Retrying to fix probable mailbox damage!",ERROR);
fflush (stdout); /* dump output buffer */
syslog (LOG_ALERT,
"Retrying after disk error user=%.80s host=%.80s mbx=%.80s: %.80s",
user,tcp_clienthost (tmp),
(stream && stream->mailbox) ? stream->mailbox : "???",
strerror (errcode));
alarm (0); /* make damn sure timeout disabled */
sleep (60); /* give it some time to clear up */
return NIL;
}
/* otherwise die before more damage is done */
printf ("* BYE Aborting due to disk error %s\015\012",strerror (errcode));
syslog (LOG_ALERT,"Fatal disk error user=%.80s host=%.80s mbx=%.80s: %.80s",
user,tcp_clienthost (tmp),
(stream && stream->mailbox) ? stream->mailbox : "???",
strerror (errcode));
return T;
}
/* Log a fatal error event
* Accepts: string to log
*/
void mm_fatal (char *string)
{
char tmp[MAILTMPLEN];
printf ("* BYE [ALERT] IMAP4 server crashing: %s\015\012",string);
syslog (LOG_ALERT,"Fatal error user=%.80s host=%.80s mbx=%.80s: %.80s",
user ? user : "???",tcp_clienthost (tmp),
(stream && stream->mailbox) ? stream->mailbox : "???",string);
}
syntax highlighted by Code2HTML, v. 0.9.1