home *** CD-ROM | disk | FTP | other *** search
/ ftp.uv.es / 2014.11.ftp.uv.es.tar / ftp.uv.es / pub / unix / pine4.10.tar.gz / pine4.10.tar / pine4.10 / imap / src / imapd / imapd.c < prev    next >
C/C++ Source or Header  |  1999-01-28  |  107KB  |  3,470 lines

  1. /*
  2.  * Program:    IMAP4rev1 server
  3.  *
  4.  * Author:    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  * Date:    5 November 1990
  13.  * Last Edited:    27 January 1999
  14.  *
  15.  * Copyright 1999 by the University of Washington
  16.  *
  17.  *  Permission to use, copy, modify, and distribute this software and its
  18.  * documentation for any purpose and without fee is hereby granted, provided
  19.  * that the above copyright notice appears in all copies and that both the
  20.  * above copyright notice and this permission notice appear in supporting
  21.  * documentation, and that the name of the University of Washington not be
  22.  * used in advertising or publicity pertaining to distribution of the software
  23.  * without specific, written prior permission.  This software is made
  24.  * available "as is", and
  25.  * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
  26.  * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
  27.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
  28.  * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
  29.  * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  30.  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
  31.  * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
  32.  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  33.  *
  34.  */
  35.  
  36. /* Primary I/O calls */
  37.  
  38. #define PBIN getchar        /* primary byte input */
  39.                 /* primary string input */
  40. #define PSIN(s,n) fgets (s,n,stdin)
  41. #define PBOUT(c) putchar (c)    /* primary byte output */
  42.                 /* primary string output */
  43. #define PSOUT(s) fputs (s,stdout)
  44. #define PFLUSH fflush (stdout)    /* flush primary output */
  45. #define CRLF PSOUT ("\015\012")    /* primary output terpri */
  46.  
  47.  
  48. /* Parameter files */
  49.  
  50. #include "mail.h"
  51. #include "osdep.h"
  52. #include "rfc822.h"
  53. #include <stdio.h>
  54. #include <ctype.h>
  55. #include <errno.h>
  56. extern int errno;        /* just in case */
  57. #include <sys/stat.h>
  58. #include <signal.h>
  59. #include "misc.h"
  60.  
  61.  
  62.             
  63. #define LOGINTIMEOUT 60*3    /* not logged in autologout timer */
  64. #define TIMEOUT 60*30        /* logged in autologout timer */
  65. #define ALERTTIMEOUT 60        /* alert check timer */
  66. #define IDLETIMEOUT 60        /* IDLE command poll timer */
  67. #define CHECKTIMEOUT 15*60    /* IDLE command last checkpoint timer */
  68.  
  69.  
  70. #define LITSTKLEN 20        /* length of literal stack */
  71. #define MAXCLIENTLIT 10000    /* maximum non-APPEND client literal size */
  72. #define TMPLEN 8192        /* size of temporary buffers */
  73.  
  74.  
  75. /* Server states */
  76.  
  77. #define LOGIN 0
  78. #define SELECT 1
  79. #define OPEN 2
  80. #define LOGOUT 3
  81.  
  82. /* Body text fetching */
  83.  
  84. typedef struct text_args {
  85.   char *section;        /* body section */
  86.   STRINGLIST *lines;        /* header lines */
  87.   unsigned long first;        /* first octet to fetch */
  88.   unsigned long last;        /* number of octets to fetch */
  89.   long flags;            /* flags */
  90. } TEXTARGS;
  91.  
  92.  
  93. /* Message pointer */
  94.  
  95. typedef struct msg_data {
  96.   MAILSTREAM *stream;        /* stream */
  97.   unsigned long msgno;        /* message number */
  98. } MSGDATA;
  99.  
  100. /* Function prototypes */
  101.  
  102. int main (int argc,char *argv[]);
  103. void ping_mailbox (unsigned long uid);
  104. time_t palert (char *file,time_t oldtime);
  105. void msg_string_init (STRING *s,void *data,unsigned long size);
  106. char msg_string_next (STRING *s);
  107. void msg_string_setpos (STRING *s,unsigned long i);
  108. void new_flags (MAILSTREAM *stream);
  109. void clkint (void);
  110. void kodint (void);
  111. void hupint (void);
  112. void trmint (void);
  113. void slurp (char *s,int n);
  114. char inchar (void);
  115. char *flush (void);
  116. char *parse_astring (char **arg,unsigned long *i,char *del);
  117. char *snarf (char **arg);
  118. char *snarf_list (char **arg);
  119. STRINGLIST *parse_stringlist (char **s,int *list);
  120. long parse_criteria (SEARCHPGM *pgm,char **arg,unsigned long maxmsg,
  121.              unsigned long maxuid);
  122. long parse_criterion (SEARCHPGM *pgm,char **arg,unsigned long msgmsg,
  123.               unsigned long maxuid);
  124. long crit_date (unsigned short *date,char **arg);
  125. long crit_date_work (unsigned short *date,char **arg);
  126. long crit_set (SEARCHSET **set,char **arg,unsigned long maxima);
  127. long crit_number (unsigned long *number,char **arg);
  128. long crit_string (STRINGLIST **string,char **arg);
  129.  
  130. void fetch (char *t,unsigned long uid);
  131. typedef void (*fetchfn_t) (unsigned long i,void *args);
  132. void fetch_work (char *t,unsigned long uid,fetchfn_t f[],void *fa[]);
  133. void fetch_bodystructure (unsigned long i,void *args);
  134. void fetch_body (unsigned long i,void *args);
  135. void fetch_body_part_mime (unsigned long i,void *args);
  136. void fetch_body_part_contents (unsigned long i,void *args);
  137. void fetch_body_part_header (unsigned long i,void *args);
  138. void fetch_body_part_text (unsigned long i,void *args);
  139. void remember (unsigned long uid,char *id,SIZEDTEXT *st);
  140. void fetch_envelope (unsigned long i,void *args);
  141. void fetch_encoding (unsigned long i,void *args);
  142. void changed_flags (unsigned long i,int f);
  143. void fetch_rfc822_header_lines (unsigned long i,void *args);
  144. void fetch_rfc822_header_lines_not (unsigned long i,void *args);
  145. void fetch_flags (unsigned long i,void *args);
  146. void put_flag (int *c,char *s);
  147. void fetch_internaldate (unsigned long i,void *args);
  148. void fetch_uid (unsigned long i,void *args);
  149. void fetch_rfc822 (unsigned long i,void *args);
  150. void fetch_rfc822_header (unsigned long i,void *args);
  151. void fetch_rfc822_size (unsigned long i,void *args);
  152. void fetch_rfc822_text (unsigned long i,void *args);
  153. void penv (ENVELOPE *env);
  154. void pbodystructure (BODY *body);
  155. void pbody (BODY *body);
  156. void pparam (PARAMETER *param);
  157. void paddr (ADDRESS *a);
  158. void pnum (unsigned long i);
  159. void pstring (char *s);
  160. void pnstring (char *label,SIZEDTEXT *st);
  161. void pastring (char *s);
  162. void pbodypartstring (unsigned long msgno,char *id,SIZEDTEXT *st,TEXTARGS *ta);
  163. void pstringorlist (STRINGLIST *s);
  164. void pstringlist (STRINGLIST *s);
  165. void psizedtext (SIZEDTEXT *s);
  166. void ptext (SIZEDTEXT *s);
  167. void pthread (THREADNODE *thr);
  168. long nameok (char *ref,char *name);
  169. char *bboardname (char *cmd,char *name);
  170. char *imap_responder (void *challenge,unsigned long clen,unsigned long *rlen);
  171. long proxycopy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
  172. void mm_list_work (char *what,int delimiter,char *name,long attributes);
  173. char *lasterror (void);
  174.  
  175. /* Global storage */
  176.  
  177. char *version = "12.250";    /* version number of this server */
  178. time_t alerttime = 0;        /* time of last alert */
  179. time_t sysalerttime = 0;    /* time of last system alert */
  180. time_t useralerttime = 0;    /* time of last user alert */
  181. time_t lastcheck = 0;        /* time of last checkpoint */
  182. int state = LOGIN;        /* server state */
  183. int trycreate = 0;        /* saw a trycreate */
  184. int finding = NIL;        /* doing old FIND command */
  185. int anonymous = 0;        /* non-zero if anonymous */
  186. int critical = NIL;        /* non-zero if in critical code */
  187. int quell_events = NIL;        /* non-zero if in FETCH response */
  188. int existsquelled = NIL;    /* non-zero if an EXISTS was quelled */
  189. MAILSTREAM *stream = NIL;    /* mailbox stream */
  190. DRIVER *curdriver = NIL;    /* note current driver */
  191. MAILSTREAM *tstream = NIL;    /* temporary mailbox stream */
  192. unsigned long nmsgs =0xffffffff;/* last reported # of messages and recent */
  193. unsigned long recent = 0xffffffff;
  194. char *user = NIL;        /* user name */
  195. char *pass = NIL;        /* password */
  196. char cmdbuf[TMPLEN];        /* command buffer */
  197. char *tag;            /* tag portion of command */
  198. char *cmd;            /* command portion of command */
  199. char *arg;            /* pointer to current argument of command */
  200. char *lstwrn = NIL;        /* last warning message from c-client */
  201. char *lsterr = NIL;        /* last error message from c-client */
  202. char *response = NIL;        /* command response */
  203. int litsp = 0;            /* literal stack pointer */
  204. char *litstk[LITSTKLEN];    /* stack to hold literals */
  205. unsigned long lastuid = -1;    /* last fetched uid */
  206. char *lastid = NIL;        /* last fetched body id for this message */
  207. SIZEDTEXT lastst = {NIL,0};    /* last sizedtext */
  208.  
  209.  
  210. /* Response texts which appear in multiple places */
  211.  
  212. char *win = "%.80s OK %.80s completed\015\012";
  213. char *altwin = "%.80s OK %.900s\015\012";
  214. char *lose = "%.80s NO %.80s failed: %.900s\015\012";
  215. char *losetry = "%.80s NO [TRYCREATE] %.80s failed: %.900s\015\012";
  216. char *misarg = "%.80s BAD Missing required argument to %.80s\015\012";
  217. char *badarg = "%.80s BAD Argument given to %.80s when none expected\015\012";
  218. char *badseq = "%.80s BAD Bogus sequence in %.80s\015\012";
  219. char *badatt = "%.80s BAD Bogus attribute list in %.80s\015\012";
  220. char *argrdy = "+ Ready for argument\015\012";
  221.  
  222.  
  223. /* Message string driver for message stringstructs */
  224.  
  225. STRINGDRIVER msg_string = {
  226.   msg_string_init,        /* initialize string structure */
  227.   msg_string_next,        /* get next byte in string structure */
  228.   msg_string_setpos        /* set position in string structure */
  229. };
  230.  
  231. /* Main program */
  232.  
  233. int main (int argc,char *argv[])
  234. {
  235.   unsigned long i,j,k,m,uid;
  236.   long f;
  237.   char *s,*t,*u,*v,tmp[MAILTMPLEN];
  238.   struct stat sbuf;
  239.                 /* initialize server */
  240.   server_init (argv[0],"imap","imaps","imap",clkint,kodint,hupint,trmint);
  241. #include "linkage.c"
  242.                 /* forbid automatic untagged expunge */
  243.   mail_parameters (NIL,SET_EXPUNGEATPING,NIL);
  244.   s = myusername_full (&i);    /* get user name and flags */
  245.   switch (i) {
  246.   case MU_NOTLOGGEDIN:
  247.     t = "OK";            /* not logged in, ordinary startup */
  248.     break;
  249.   case MU_ANONYMOUS:
  250.     anonymous = T;        /* anonymous user, fall into default */
  251.     s = "ANONYMOUS";
  252.   case MU_LOGGEDIN:
  253.     t = "PREAUTH";        /* already logged in, pre-authorized */
  254.     user = cpystr (s);        /* copy user name */
  255.     pass = cpystr ("*");    /* set fake password */
  256.     state = SELECT;        /* enter select state */
  257.     break;
  258.   default:
  259.     fatal ("Unknown state from myusername_full()");
  260.   }
  261.   PSOUT ("* ");
  262.   PSOUT (t);
  263.   PBOUT (' ');
  264.   PSOUT (tcp_serverhost ());
  265.   PSOUT (" IMAP4rev1 v");
  266.   PSOUT (version);
  267.   PSOUT (" server ready\015\012");
  268.   PFLUSH;            /* dump output buffer */
  269.   if (state == SELECT)        /* do this after the banner */
  270.     syslog (LOG_INFO,"Preauthenticated user=%.80s host=%.80s",
  271.         user,tcp_clienthost ());
  272.   mail_parameters (NIL,SET_MAILPROXYCOPY,(void *) proxycopy);
  273.  
  274.   do {                /* command processing loop */
  275.     slurp (cmdbuf,TMPLEN);    /* slurp command */
  276.                 /* no more last error or literal */
  277.     if (lstwrn) fs_give ((void **) &lstwrn);
  278.     if (lsterr) fs_give ((void **) &lsterr);
  279.     while (litsp) fs_give ((void **) &litstk[--litsp]);
  280.                 /* find end of line */
  281.     if (!strchr (cmdbuf,'\012')) {
  282.       if (t = strchr (cmdbuf,' ')) *t = '\0';
  283.       if ((t - cmdbuf) > 100) t = NIL;
  284.       flush ();            /* flush excess */
  285.       if (state == LOGIN)    /* error if NLI */
  286.     syslog (LOG_INFO,"Line too long before authentication host=%.80s",
  287.         tcp_clienthost ());
  288.       sprintf (tmp,response,t ? cmdbuf : "*");
  289.       PSOUT (tmp);
  290.     }
  291.     else if (!(tag = strtok (cmdbuf," \015\012"))) {
  292.       if (state == LOGIN)    /* error if NLI */
  293.     syslog (LOG_INFO,"Null command before authentication host=%.80s",
  294.         tcp_clienthost ());
  295.       PSOUT ("* BAD Null command\015\012");
  296.     }
  297.     else if (strlen (tag) > 50) PSOUT ("* BAD Excessively long tag\015\012");
  298.     else if (!(cmd = strtok (NIL," \015\012"))) {
  299.       if (state == LOGIN)    /* error if NLI */
  300.     syslog (LOG_INFO,"Missing command before authentication host=%.80s",
  301.         tcp_clienthost ());
  302.       PSOUT (tag);
  303.       PSOUT (" BAD Missing command\015\012");
  304.     }
  305.     else {            /* parse command */
  306.       response = win;        /* set default response */
  307.       finding = NIL;        /* no longer FINDing */
  308.       ucase (cmd);        /* canonicalize command case */
  309.                 /* UID command? */
  310.       if (!strcmp (cmd,"UID") && strtok (NIL," \015\012")) {
  311.     uid = T;        /* a UID command */
  312.     cmd[3] = ' ';        /* restore the space delimiter */
  313.     ucase (cmd);        /* make sure command all uppercase */
  314.       }
  315.       else uid = NIL;        /* not a UID command */
  316.                 /* snarf argument */
  317.       arg = strtok (NIL,"\015\012");
  318.  
  319.                 /* these commands always valid */
  320.       if (!strcmp (cmd,"NOOP")) {
  321.     if (arg) response = badarg;
  322.     else if (stream)    /* allow untagged EXPUNGE */
  323.       mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
  324.       }
  325.       else if (!strcmp (cmd,"LOGOUT")) {
  326.     if (arg) response = badarg;
  327.     else {            /* time to say farewell */
  328.       server_init (NIL,NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
  329.       if (state == OPEN) mail_close (stream);
  330.       state = LOGOUT;
  331.       stream = NIL;
  332.       PSOUT ("* BYE ");
  333.       PSOUT (mylocalhost ());
  334.       PSOUT (" IMAP4rev1 server terminating connection\015\012");
  335.     }
  336.       }
  337.       else if (!strcmp (cmd,"CAPABILITY")) {
  338.     if (arg) response = badarg;
  339.     else {
  340.       AUTHENTICATOR *auth = mail_lookup_auth (1);
  341.       THREADER *thr = (THREADER *) mail_parameters (NIL,GET_THREADERS,NIL);
  342.       PSOUT ("* CAPABILITY IMAP4 IMAP4REV1 NAMESPACE IDLE SCAN SORT MAILBOX-REFERRALS LOGIN-REFERRALS");
  343. #ifdef NETSCAPE_BRAIN_DAMAGE
  344.       PSOUT (" X-NETSCAPE");
  345. #endif
  346.       while (auth) {
  347.         if (auth->server) {
  348.           PSOUT (" AUTH=");
  349.           PSOUT (auth->name);
  350.         }
  351.         auth = auth->next;
  352.       }
  353.       if (!stat (ANOFILE,&sbuf)) PSOUT (" AUTH=ANONYMOUS");
  354.       while (thr) {
  355.         PSOUT (" THREAD=");
  356.         PSOUT (thr->name);
  357.         thr = thr->next;
  358.       }
  359.       CRLF;
  360.     }
  361.     if (stream)        /* allow untagged EXPUNGE */
  362.       mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
  363.       }
  364. #ifdef NETSCAPE_BRAIN_DAMAGE
  365.       else if (!strcmp (cmd,"NETSCAPE")) {
  366.     PSOUT ("* OK [NETSCAPE]\015\012* VERSION 1.0 UNIX\015\012* ACCOUNT-URL \"");
  367.     PSOUT (NETSCAPE_BRAIN_DAMAGE);
  368.     PBOUT ('"');
  369.     CRLF;
  370.       }
  371. #endif
  372.  
  373.       else switch (state) {    /* dispatch depending upon state */
  374.       case LOGIN:        /* waiting to get logged in */
  375.                 /* new style authentication */
  376.     if (!strcmp (cmd,"AUTHENTICATE")) {
  377.       if (user) fs_give ((void **) &user);
  378.       if (pass) fs_give ((void **) &pass);
  379.                 /* single argument */
  380.       if (!(s = snarf (&arg))) response = misarg;
  381.       else if (arg) response = badarg;
  382.       else if (!strcmp (ucase (s),"ANONYMOUS") && !stat (ANOFILE,&sbuf)) {
  383.         if ((s = imap_responder ("",0,NIL)) &&
  384.         anonymous_login (argc,argv)) {
  385.           anonymous = T;    /* note we are anonymous */
  386.           user = cpystr ("ANONYMOUS");
  387.           state = SELECT;    /* make select */
  388.           syslog (LOG_INFO,"Authenticated anonymous=%.80s host=%.80s",s,
  389.               tcp_clienthost ());
  390.           fs_give ((void **) &s);
  391.         }
  392.         else response ="%.80s NO AUTHENTICATE ANONYMOUS cancelled\015\012";
  393.       }
  394.       else if (user = cpystr (mail_auth (s,imap_responder,argc,argv))) {
  395.         state = SELECT;
  396.         syslog (LOG_INFO,"Authenticated user=%.80s host=%.80s",
  397.             user,tcp_clienthost ());
  398.       }
  399.       else {
  400.         lsterr = cpystr (s);
  401.         response = "%.80s NO %.80s %.80s failed\015\012";
  402.         syslog (LOG_INFO,"AUTHENTICATE %.80s failure host=%.80s",s,
  403.             tcp_clienthost ());
  404.       }
  405.     }
  406.  
  407.                 /* plaintext login with password */
  408.     else if (!strcmp (cmd,"LOGIN")) {
  409.       if (user) fs_give ((void **) &user);
  410.       if (pass) fs_give ((void **) &pass);
  411.                 /* two arguments */
  412.       if (!((user = cpystr (snarf (&arg))) &&
  413.         (pass = cpystr (snarf (&arg))))) response = misarg;
  414.       else if (arg) response = badarg;
  415.                 /* see if we allow anonymous */
  416.       else if (((user[0] == 'a') || (user[0] == 'A')) &&
  417.            ((user[1] == 'n') || (user[1] == 'N')) &&
  418.            ((user[2] == 'o') || (user[2] == 'O')) &&
  419.            ((user[3] == 'n') || (user[3] == 'N')) &&
  420.            ((user[4] == 'y') || (user[4] == 'Y')) &&
  421.            ((user[5] == 'm') || (user[5] == 'M')) &&
  422.            ((user[6] == 'o') || (user[6] == 'O')) &&
  423.            ((user[7] == 'u') || (user[7] == 'U')) &&
  424.            ((user[8] == 's') || (user[8] == 'S')) && !user[9] &&
  425.            !stat (ANOFILE,&sbuf) && anonymous_login (argc,argv)) {
  426.         anonymous = T;    /* note we are anonymous */
  427.         ucase (user);    /* make all uppercase for consistency */
  428.         state = SELECT;    /* make select */
  429.         syslog (LOG_INFO,"Login anonymous=%.80s host=%.80s",pass,
  430.             tcp_clienthost ());
  431.       }
  432.                 /* see if username and password are OK */
  433.       else if (server_login (user,pass,argc,argv)) {
  434.         state = SELECT;
  435.         syslog (LOG_INFO,"Login user=%.80s host=%.80s",user,
  436.             tcp_clienthost ());
  437.       }
  438.       else response = "%.80s NO %.80s failed\015\012";
  439.     }
  440.     else response =
  441.       "%.80s BAD Command unrecognized/login please: %.80s\015\012";
  442.     break;
  443.  
  444.       case OPEN:        /* valid only when mailbox open */
  445.                 /* fetch mailbox attributes */
  446.     if (!strcmp (cmd,"FETCH") || !strcmp (cmd,"UID FETCH")) {
  447.       if (!(arg && (s = strtok (arg," ")) && (t = strtok(NIL,"\015\012"))))
  448.         response = misarg;
  449.       else if (uid ? mail_uid_sequence (stream,s) :
  450.            mail_sequence (stream,s)) fetch (t,uid);
  451.       else response = badseq;
  452.     }
  453.                 /* store mailbox attributes */
  454.     else if (!strcmp (cmd,"STORE") || !strcmp (cmd,"UID STORE")) {
  455.                 /* must have three arguments */
  456.       if (arg && (s = strtok (arg," ")) && (v = strtok (NIL," ")) &&
  457.           (t = strtok (NIL,"\015\012"))) {
  458.         f = ST_SET | (uid ? ST_UID : NIL)|((v[5]&&v[6]) ? ST_SILENT : NIL);
  459.         if (!strcmp (ucase (v),"FLAGS") || !strcmp (v,"FLAGS.SILENT")) {
  460.           strcpy (tmp,"\\Answered \\Flagged \\Deleted \\Draft \\Seen");
  461.           for (i = 0, u = tmp;
  462.            (i < NUSERFLAGS) && (v = stream->user_flags[i]); i++)
  463.             if (strlen (v) <
  464.             ((size_t) (MAILTMPLEN - ((u += strlen (u)) + 2 - tmp)))) {
  465.           *u++ = ' ';    /* write next flag */
  466.           strcpy (u,v);
  467.         }
  468.           mail_flag (stream,s,tmp,f & ~ST_SET);
  469.         }
  470.         else if (!strcmp (v,"-FLAGS") || !strcmp (v,"-FLAGS.SILENT"))
  471.           f &= ~ST_SET;    /* clear flags */
  472.         else if (strcmp (v,"+FLAGS") && strcmp (v,"+FLAGS.SILENT")) {
  473.           response = badatt;
  474.           break;
  475.         }
  476.                 /* find last keyword */
  477.         for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i]; i++);
  478.         mail_flag (stream,s,t,f);
  479.                 /* any new keywords appeared? */
  480.         if (i < NUSERFLAGS && stream->user_flags[i]) new_flags (stream);
  481.                 /* return flags if silence not wanted */
  482.         if (!(f & ST_SILENT) &&
  483.         (uid ? mail_uid_sequence (stream,s) : mail_sequence(stream,s)))
  484.           for (i = 1; i <= nmsgs; i++) if (mail_elt(stream,i)->sequence)
  485.         mail_elt (stream,i)->spare2 = T;
  486.       }
  487.       else response = misarg;
  488.     }
  489.  
  490.                 /* fetch partial mailbox attributes */
  491.     else if (!strcmp (cmd,"PARTIAL")) {
  492.       SIZEDTEXT st;
  493.       if (!(arg && (m = strtoul (arg,&s,10)) && (t = strtok (s," ")) &&
  494.         (s = strtok (NIL,"\015\012")) && (j = strtoul (s,&s,10)) &&
  495.         (k = strtoul (s,&s,10)))) response = misarg;
  496.       else if (s && *s) response = badarg;
  497.       else if (m > nmsgs) response = badseq;
  498.       else {        /* looks good */
  499.         int sf = mail_elt (stream,m)->seen;
  500.         if (!strcmp (ucase (t),"RFC822"))
  501.           st.data = (unsigned char *)
  502.         mail_fetch_message (stream,m,&st.size,NIL);
  503.         else if (!strcmp (t,"RFC822.PEEK"))
  504.           st.data = (unsigned char *)
  505.         mail_fetch_message (stream,m,&st.size,FT_PEEK);
  506.         else if (!strcmp (t,"RFC822.HEADER"))
  507.           st.data = (unsigned char *)
  508.         mail_fetch_header (stream,m,NIL,NIL,&st.size,FT_PEEK);
  509.         else if (!strcmp (t,"RFC822.TEXT"))
  510.           st.data = (unsigned char *)
  511.         mail_fetch_text (stream,m,NIL,&st.size,NIL);
  512.         else if (!strcmp (t,"RFC822.TEXT.PEEK"))
  513.           st.data = (unsigned char *)
  514.         mail_fetch_text (stream,m,NIL,&st.size,FT_PEEK);
  515.         else if (!strncmp (t,"BODY[",5) && (v = strchr(t+5,']')) && !v[1]){
  516.           strncpy (tmp,t+5,i = v - (t+5));
  517.           tmp[i] = '\0';    /* tie off body part */
  518.           st.data = (unsigned char *)
  519.         mail_fetch_body (stream,m,tmp,&st.size,NIL);
  520.         }
  521.         else if (!strncmp (t,"BODY.PEEK[",10) &&
  522.              (v = strchr (t+10,']')) && !v[1]) {
  523.           strncpy (tmp,t+10,i = v - (t+10));
  524.           tmp[i] = '\0';    /* tie off body part */
  525.           st.data = (unsigned char *)
  526.         mail_fetch_body (stream,m,tmp,&st.size,FT_PEEK);
  527.         }
  528.         else response = badatt, st.data = NIL;
  529.         if (st.data) {    /* got a string? */
  530.           PSOUT ("* ");
  531.           pnum (m);
  532.           PSOUT (" FETCH (");
  533.           PSOUT (t);
  534.           PBOUT (' ');
  535.                 /* start position larger than text size */
  536.           if (st.size <= --j) PSOUT ("NIL");
  537.           else {        /* output as sized text */
  538.         st.data += j;
  539.         if ((st.size -= j) > k) st.size = k;
  540.         psizedtext (&st);
  541.           }
  542.           changed_flags (m,sf);
  543.           PSOUT (")\015\012");
  544.         }
  545.       }
  546.     }
  547.  
  548.                 /* check for new mail */
  549.     else if (!strcmp (cmd,"CHECK")) {
  550.                 /* no arguments */
  551.       if (arg) response = badarg;
  552.       else if (!anonymous) {
  553.         mail_check (stream);
  554.                 /* remember last check time */
  555.         lastcheck = time (0);
  556.       }
  557.     }
  558.                 /* expunge deleted messages */
  559.     else if (!(anonymous || strcmp (cmd,"EXPUNGE"))) {
  560.                 /* no arguments */
  561.       if (arg) response = badarg;
  562.       else {
  563.         mail_expunge (stream);
  564.                 /* remember last checkpoint */
  565.         lastcheck = time (0);
  566.       }
  567.     }
  568.                 /* close mailbox */
  569.     else if (!strcmp (cmd,"CLOSE")) {
  570.                 /* no arguments */
  571.       if (arg) response = badarg;
  572.       else {
  573.         lastuid = -1;    /* no last uid */
  574.         if (lastid) fs_give ((void **) &lastid);
  575.         if (lastst.data) fs_give ((void **) &lastst.data);
  576.         stream = mail_close_full (stream,anonymous ? NIL : CL_EXPUNGE);
  577.         state = SELECT;    /* no longer opened */
  578.         lastcheck = 0;    /* no last checkpoint */
  579.       }
  580.     }
  581.     else if (!anonymous &&    /* copy message(s) */
  582.          (!strcmp (cmd,"COPY") || !strcmp (cmd,"UID COPY"))) {
  583.       trycreate = NIL;    /* no trycreate status */
  584.       if (!(arg && (s = strtok (arg," ")) && (arg = strtok(NIL,"\015\012"))
  585.         && (t = snarf (&arg)))) response = misarg;
  586.       else if (arg) response = badarg;
  587.       else if (!nmsgs) response = "%.80s NO Mailbox is empty\015\012";
  588.                 /* try copy */
  589.       if (!(stream->dtb->copy) (stream,s,t,uid ? CP_UID : NIL)) {
  590.         response = trycreate ? losetry : lose;
  591.         if (!lsterr) lsterr = cpystr ("No such destination mailbox");
  592.       }
  593.     }
  594.  
  595.                 /* sort mailbox */
  596.     else if (!strcmp (cmd,"SORT") || !strcmp (cmd,"UID SORT")) {
  597.                 /* must have four arguments */
  598.       if (!(arg && (*arg == '(') && (t = strchr (s = arg + 1,')')) &&
  599.         (t[1] == ' ') && (*(arg = t + 2)))) response = misarg;
  600.       else {        /* read criteria */
  601.         SEARCHPGM *spg = NIL;
  602.         char *cs = NIL;
  603.         SORTPGM *pgm = NIL,*pg = NIL;
  604.         unsigned long *slst,*sl;
  605.         *t = NIL;        /* tie off criteria list */
  606.         s = strtok (ucase (s)," ");
  607.         do {        /* parse sort attributes */
  608.           if (pg) pg = pg->next = mail_newsortpgm ();
  609.           else pgm = pg = mail_newsortpgm ();
  610.           if (!strcmp (s,"REVERSE")) {
  611.         pg->reverse = T;
  612.         if (!(s = strtok (NIL," "))) {
  613.           s = "";    /* end of attributes */
  614.           break;
  615.         }
  616.           }
  617.           if (!strcmp (s,"DATE")) pg->function = SORTDATE;
  618.           else if (!strcmp (s,"ARRIVAL")) pg->function = SORTARRIVAL;
  619.           else if (!strcmp (s,"FROM")) pg->function = SORTFROM;
  620.           else if (!strcmp (s,"SUBJECT")) pg->function = SORTSUBJECT;
  621.           else if (!strcmp (s,"TO")) pg->function = SORTTO;
  622.           else if (!strcmp (s,"CC")) pg->function = SORTCC;
  623.           else if (!strcmp (s,"SIZE")) pg->function = SORTSIZE;
  624.           else break;
  625.         } while (s = strtok (NIL," "));
  626.                 /* bad SORT attribute */
  627.         if (s) response = badatt;
  628.         else if (!((t = snarf (&arg)) && (cs = cpystr (t)) && arg && *arg))
  629.           response = misarg;/* missing search attributes */
  630.         else if (!parse_criteria (spg = mail_newsearchpgm (),&arg,nmsgs,
  631.                       nmsgs ? mail_uid (stream,nmsgs) : 0))
  632.           response = badatt;/* bad search attribute */
  633.         else if (arg && *arg) response = badarg;
  634.         else if (slst = mail_sort (stream,cs,spg,pgm,uid ? SE_UID : NIL)) {
  635.           PSOUT ("* SORT");
  636.           for (sl = slst; *sl; sl++) {
  637.         PBOUT (' ');
  638.         pnum (*sl);
  639.           }
  640.           CRLF;
  641.           fs_give ((void **) &slst);
  642.         }
  643.         if (pgm) mail_free_sortpgm (&pgm);
  644.         if (spg) mail_free_searchpgm (&spg);
  645.         if (cs) fs_give ((void **) &cs);
  646.       }
  647.     }
  648.  
  649.                 /* thread mailbox */
  650.     else if (!strcmp (cmd,"THREAD") || !strcmp (cmd,"UID THREAD")) {
  651.       THREADNODE *thr;
  652.       SEARCHPGM *spg;
  653.       char *cs = NIL;
  654.                 /* must have four arguments */
  655.       if (!(arg && (s = strtok (arg," ")) && (cs = strtok (NIL," ")) &&
  656.         (cs = cpystr (cs)) && (arg = strtok (NIL,"\015\012"))))
  657.         response = misarg;
  658.       else if (!parse_criteria (spg = mail_newsearchpgm (),&arg,nmsgs,
  659.                     nmsgs ? mail_uid (stream,nmsgs) : 0))
  660.           response = badatt;/* bad thread attribute */
  661.       else if (arg && *arg) response = badarg;
  662.       else if (thr = mail_thread (stream,ucase (s),cs,spg,
  663.                       uid ? SE_UID : NIL)) {
  664.         PSOUT ("* THREAD ");
  665.         pthread (thr);
  666.         CRLF;
  667.         mail_free_threadnode (&thr);
  668.       }
  669.       if (spg) mail_free_searchpgm (&spg);
  670.       if (cs) fs_give ((void **) &cs);
  671.     }
  672.  
  673.                 /* search mailbox */
  674.         else if (!strcmp (cmd,"SEARCH") || !strcmp (cmd,"UID SEARCH")) {
  675.       char *charset = NIL;
  676.       SEARCHPGM *pgm;
  677.                 /* one or more arguments required */
  678.       if (!arg) response = misarg;
  679.                 /* character set specified? */
  680.       else if ((arg[0] == 'C' || arg[0] == 'c') &&
  681.            (arg[1] == 'H' || arg[1] == 'h') &&
  682.            (arg[2] == 'A' || arg[2] == 'a') &&
  683.            (arg[3] == 'R' || arg[3] == 'r') &&
  684.            (arg[4] == 'S' || arg[4] == 's') &&
  685.            (arg[5] == 'E' || arg[5] == 'e') &&
  686.            (arg[6] == 'T' || arg[6] == 't') &&
  687.            (arg[7] == ' ' || arg[7] == ' ')) {
  688.         arg += 8;        /* yes, skip over CHARSET token */
  689.         if (s = snarf (&arg)) charset = cpystr (s);
  690.         else {        /* missing character set */
  691.           response = misarg;
  692.           break;
  693.         }
  694.       }
  695.                 /* must have arguments here */
  696.       if (!(arg && *arg)) response = misarg;
  697.       else if (parse_criteria (pgm = mail_newsearchpgm (),&arg,nmsgs,
  698.                    nmsgs ? mail_uid (stream,nmsgs):0)&&!*arg) {
  699.         mail_search_full (stream,charset,pgm,SE_FREE);
  700.         if (response == win || response == altwin) {
  701.                 /* output search results */
  702.           PSOUT ("* SEARCH");
  703.           for (i = 1; i <= nmsgs; ++i) if (mail_elt (stream,i)->searched) {
  704.         PBOUT (' ');
  705.         pnum (uid ? mail_uid (stream,i) : i);
  706.           }
  707.           CRLF;
  708.         }
  709.       }
  710.       else {
  711.         response = "%.80s BAD Bogus criteria list in %.80s\015\012";
  712.         mail_free_searchpgm (&pgm);
  713.       }
  714.       if (charset) fs_give ((void **) &charset);
  715.     }
  716.  
  717.     else            /* fall into select case */
  718.       case SELECT:        /* valid whenever logged in */
  719.                 /* select new mailbox */
  720.       if (!(strcmp (cmd,"SELECT") && strcmp (cmd,"EXAMINE") &&
  721.         strcmp (cmd,"BBOARD"))) {
  722.                 /* single argument */
  723.       if (!(s = snarf (&arg))) response = misarg;
  724.       else if (arg) response = badarg;
  725.       else if (nameok (NIL,s = bboardname (cmd,s))) {
  726.         DRIVER *factory = mail_valid (NIL,s,NIL);
  727.         f = (anonymous ? OP_ANONYMOUS + OP_READONLY : NIL) |
  728.           ((*cmd == 'S') ? NIL : OP_READONLY);
  729.         curdriver = NIL;    /* no drivers known */
  730.         lastuid = -1;    /* no last uid */
  731.         if (lastid) fs_give ((void **) &lastid);
  732.         if (lastst.data) fs_give ((void **) &lastst.data);
  733.                 /* force update */
  734.         nmsgs = recent = 0xffffffff;
  735.         if (factory && !strcmp (factory->name,"phile") &&
  736.         (stream = mail_open (stream,s,f | OP_SILENT)) &&
  737.         ((response == win) || (response == altwin))) {
  738.           BODY *b;
  739.                 /* see if proxy open */
  740.           if ((mail_elt (stream,1)->rfc822_size < 400) &&
  741.           mail_fetchstructure (stream,1,&b) && (b->type == TYPETEXT) &&
  742.           (s = mail_fetch_text (stream,1,NIL,&i,NIL)) &&
  743.           (i < MAILTMPLEN) && (s[0] == '{')) {
  744.                 /* copy and tie off */
  745.         strncpy (tmp,s,i)[i] = '\0';
  746.                 /* nuke any trailing newline */
  747.         if (s = strpbrk (tmp,"\r\n")) *s = '\0';
  748.                 /* try to open proxy */
  749.         if ((tstream = mail_open (NIL,tmp,f | OP_SILENT)) &&
  750.             ((response == win) || (response == altwin)) &&
  751.             tstream->nmsgs) {
  752.                 /* got it, close the link */
  753.           mail_close (stream);
  754.           stream = tstream;
  755.           tstream = NIL;
  756.         }
  757.           }
  758.                 /* now give the exists event */
  759.           stream->silent = NIL;
  760.           mm_exists (stream,stream->nmsgs);
  761.         }
  762.                 /* open stream normally then */
  763.         else stream = mail_open (stream,s,f);
  764.         if (stream && ((response == win) || (response == altwin))) {
  765.           state = OPEN;    /* note state open */
  766.                 /* note readonly/readwrite */
  767.           response = stream->rdonly ?
  768.         "%.80s OK [READ-ONLY] %.80s completed\015\012" :
  769.           "%.80s OK [READ-WRITE] %.80s completed\015\012";
  770.           if (anonymous)
  771.         syslog (LOG_INFO,"Anonymous select of %.80s host=%.80s",
  772.             stream->mailbox,tcp_clienthost ());
  773.           lastcheck = 0;    /* no last check */
  774.         }
  775.         else {        /* failed */
  776.           state = SELECT;    /* no mailbox open now */
  777.           response = lose;    /* open failed */
  778.         }
  779.       }
  780.     }
  781.  
  782.                 /* APPEND message to mailbox */
  783.     else if (!(anonymous || strcmp (cmd,"APPEND"))) {
  784.       u = v = NIL;        /* init flags/date */
  785.                 /* parse mailbox name */
  786.       if ((s = snarf (&arg)) && arg) {
  787.         if (*arg == '(') {    /* parse optional flag list */
  788.           u = ++arg;    /* pointer to flag list contents */
  789.           while (*arg && (*arg != ')')) arg++;
  790.           if (*arg) *arg++ = '\0';
  791.           if (*arg == ' ') arg++;
  792.         }
  793.                 /* parse optional date */
  794.         if (*arg == '"') v = snarf (&arg);
  795.                 /* parse message */
  796.         if (!arg || (*arg != '{'))
  797.           response = "%.80s BAD Missing literal in %.80s\015\012";
  798.         else if (!isdigit (arg[1]))
  799.           response = "%.80s BAD Missing message to %.80s\015\012";
  800.         else if (!(i = strtoul (arg+1,&t,10)))
  801.           response = "%.80s No Empty message to %.80s\015\012";
  802.         else if ((*t != '}') || t[1]) response = badarg;
  803.         else {        /* append the data */
  804.           STRING st;
  805.           PSOUT (argrdy);    /* tell client ready for argument */
  806.           PFLUSH;        /* dump output buffer */
  807.                 /* get a literal buffer */
  808.           t = (char *) fs_get (i+1);
  809.           alarm (TIMEOUT);    /* get data under timeout */
  810.           for (j = 0; j < i; j++) t[j] = inchar ();
  811.           alarm (0);    /* stop timeout */
  812.           t[i] = '\0';    /* make sure tied off */
  813.                     /* get new command tail */
  814.           if ((j = inchar ()) == '\015') j = inchar ();
  815.           if (j == '\012') {/* must be end of command */
  816.         INIT (&st,mail_string,(void *) t,i);
  817.         trycreate = NIL;    /* no trycreate status */
  818.         if (!mail_append_full (NIL,s,u,v,&st)) {
  819.           response = trycreate ? losetry : lose;
  820.           if (!lsterr) lsterr = cpystr ("Unexpected APPEND failure");
  821.         }
  822.           }
  823.           else {        /* junk after literal */
  824.         while (inchar () != '\012');
  825.         response = badarg;
  826.           }
  827.           fs_give ((void **) &t);
  828.         }
  829.       }
  830.       else response = misarg;
  831.       if (stream)        /* allow untagged EXPUNGE */
  832.         mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
  833.     }
  834.  
  835.                 /* list mailboxes */
  836.     else if (!strcmp (cmd,"LIST") || !strcmp (cmd,"RLIST")) {
  837.                 /* get reference and mailbox argument */
  838.       if (!((s = snarf (&arg)) && (t = snarf_list (&arg))))
  839.         response = misarg;
  840.       else if (arg) response = badarg;
  841.                 /* make sure anonymous can't do bad things */
  842.       else if (nameok (s,t)) mail_list (NIL,s,t);
  843.       if (stream)        /* allow untagged EXPUNGE */
  844.         mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
  845.     }
  846.                 /* scan mailboxes */
  847.     else if (!strcmp (cmd,"SCAN")) {
  848.                 /* get arguments */
  849.       if (!((s = snarf (&arg)) && (t = snarf_list (&arg)) &&
  850.         (u = snarf (&arg)))) response = misarg;
  851.       else if (arg) response = badarg;
  852.                 /* make sure anonymous can't do bad things */
  853.       else if (nameok (s,t)) mail_scan (NIL,s,t,u);
  854.       if (stream)        /* allow untagged EXPUNGE */
  855.         mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
  856.     }
  857.                 /* list subscribed mailboxes */
  858.     else if (!(anonymous || (strcmp(cmd,"LSUB") && strcmp(cmd,"RLSUB")))) {
  859.                 /* get reference and mailbox argument */
  860.       if (!((s = snarf (&arg)) && (t = snarf_list (&arg))))
  861.         response = misarg;
  862.       else if (arg) response = badarg;
  863.                 /* make sure anonymous can't do bad things */
  864.       else if (nameok (s,t)) mail_lsub (NIL,s,t);
  865.       if (stream)        /* allow untagged EXPUNGE */
  866.         mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
  867.     }
  868.  
  869.                 /* find mailboxes */
  870.     else if (!strcmp (cmd,"FIND")) {
  871.       response = "%.80s OK FIND %.80s completed\015\012";
  872.                 /* get subcommand and true argument */
  873.       if (!(arg && (s = strtok (arg," \015\012")) && (cmd = ucase (s)) &&
  874.         (arg = strtok (NIL,"\015\012")) && (s = snarf_list (&arg))))
  875.         response = misarg;    /* missing required argument */
  876.       else if (arg) response = badarg;
  877.                 /* punt on single-char wildcards */
  878.       else if (strpbrk (s,"%?")) response =
  879.         "%.80s NO FIND %.80s ? or %% wildcard not supported\015\012";
  880.       else if (nameok (NIL,s)) {
  881.         finding = T;    /* note that we are FINDing */
  882.                 /* dispatch based on type */
  883.         if (!strcmp (cmd,"MAILBOXES") && !anonymous) mail_lsub (NIL,NIL,s);
  884.         else if (!strcmp (cmd,"ALL.MAILBOXES")) {
  885.                 /* convert * to % for compatible behavior */
  886.           for (t = s; *t; t++) if (*t == '*') *t = '%';
  887.           mail_list (NIL,NIL,s);
  888.         }
  889.         else response="%.80s BAD Command unrecognized: FIND %.80s\015\012";
  890.       }
  891.       if (stream)        /* allow untagged EXPUNGE */
  892.         mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
  893.     }
  894.  
  895.                 /* status of mailbox */
  896.     else if (!strcmp (cmd,"STATUS")) {
  897.       if (!((s = snarf (&arg)) && arg && (*arg++ == '(') &&
  898.         (t = strchr (arg,')')) && (t - arg) && !t[1]))
  899.         response = misarg;
  900.       else {
  901.         f = NIL;        /* initially no flags */
  902.         *t = '\0';        /* tie off flag string */
  903.                 /* read flags */
  904.         t = strtok (ucase (arg)," ");
  905.         do {        /* parse each one; unknown generate warning */
  906.           if (!strcmp (t,"MESSAGES")) f |= SA_MESSAGES;
  907.           else if (!strcmp (t,"RECENT")) f |= SA_RECENT;
  908.           else if (!strcmp (t,"UNSEEN")) f |= SA_UNSEEN;
  909.           else if (!strcmp (t,"UIDNEXT")) f |= SA_UIDNEXT;
  910.           else if (!strcmp (t,"UIDVALIDITY")) f |= SA_UIDVALIDITY;
  911.           else {
  912.         PSOUT ("* NO Unknown status flag ");
  913.         PSOUT (t);
  914.         CRLF;
  915.           }
  916.         } while (t = strtok (NIL," "));
  917.         ping_mailbox (uid);    /* in case the fool did STATUS on open mbx */
  918.                 /* get mailbox status */
  919.         if ((state == LOGOUT) || !mail_status (NIL,s,f)) response = lose;
  920.       }
  921.       if (stream)        /* allow untagged EXPUNGE */
  922.         mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
  923.     }
  924.                 /* subscribe to mailbox */
  925.     else if (!(anonymous || strcmp (cmd,"SUBSCRIBE"))) {
  926.                 /* get <mailbox> or MAILBOX <mailbox> */
  927.       if (!(s = snarf (&arg))) response = misarg;
  928.       else if (arg) {
  929.         sprintf (tmp,"%.30s",s);
  930.         if (strcmp (ucase (tmp),"MAILBOX")) response = badarg;
  931.         else if (!(s = snarf (&arg))) response = misarg;
  932.         else if (arg) response = badarg;
  933.         else mail_subscribe (NIL,s);
  934.       }
  935.       else mail_subscribe (NIL,s);
  936.       if (stream)        /* allow untagged EXPUNGE */
  937.         mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
  938.     }
  939.                 /* unsubscribe to mailbox */
  940.     else if (!(anonymous || strcmp (cmd,"UNSUBSCRIBE"))) {
  941.                 /* get <mailbox> or MAILBOX <mailbox> */
  942.       if (!(s = snarf (&arg))) response = misarg;
  943.       else if (arg) {
  944.         sprintf (tmp,"%.30s",s);
  945.         if (strcmp (ucase (tmp),"MAILBOX")) response = badarg;
  946.         else if (!(s = snarf (&arg))) response = misarg;
  947.         else if (arg) response = badarg;
  948.         else mail_unsubscribe (NIL,s);
  949.       }
  950.       else mail_unsubscribe (NIL,s);
  951.       if (stream)        /* allow untagged EXPUNGE */
  952.         mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
  953.     }
  954.  
  955.     else if (!strcmp (cmd,"NAMESPACE")) {
  956.       if (arg) response = badarg;
  957.       else {
  958.         NAMESPACE **ns = (NAMESPACE **) mail_parameters(NIL,GET_NAMESPACE,
  959.                                  NIL);
  960.         NAMESPACE *n;
  961.         PARAMETER *p;
  962.         PSOUT ("* NAMESPACE");
  963.         if (ns) for (i = 0; i < 3; i++) {
  964.           if (n = ns[i]) {
  965.         PSOUT (" (");
  966.         do {
  967.           PBOUT ('(');
  968.           pstring (n->name);
  969.           switch (n->delimiter) {
  970.           case '\\':    /* quoted delimiter */
  971.           case '"':
  972.             PSOUT (" \"\\\\\"");
  973.             break;
  974.           case '\0':    /* no delimiter */
  975.             PSOUT (" NIL");
  976.             break;
  977.           default:    /* unquoted delimiter */
  978.             PSOUT (" \"");
  979.             PBOUT (n->delimiter);
  980.             PBOUT ('"');
  981.             break;
  982.           }
  983.           if (p = n->param) do {
  984.             PBOUT (' ');
  985.             pstring (p->attribute);
  986.             PBOUT (' ');
  987.             pstring (p->value);
  988.           } while (p = p->next);
  989.           PBOUT (')');
  990.         } while (n = n->next);
  991.         PBOUT (')');
  992.           }
  993.           else PSOUT (" NIL");
  994.         }
  995.         else PSOUT (" NIL NIL NIL");
  996.         CRLF;
  997.       }
  998.       if (stream)        /* allow untagged EXPUNGE */
  999.         mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
  1000.     }
  1001.  
  1002.                 /* create mailbox */
  1003.     else if (!(anonymous || strcmp (cmd,"CREATE"))) {
  1004.       if (!(s = snarf (&arg))) response = misarg;
  1005.       else if (arg) response = badarg;
  1006.       else mail_create (NIL,s);
  1007.       if (stream)        /* allow untagged EXPUNGE */
  1008.         mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
  1009.     }
  1010.                 /* delete mailbox */
  1011.     else if (!(anonymous || strcmp (cmd,"DELETE"))) {
  1012.       if (!(s = snarf (&arg))) response = misarg;
  1013.       else if (arg) response = badarg;
  1014.       else mail_delete (NIL,s);
  1015.       if (stream)        /* allow untagged EXPUNGE */
  1016.         mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
  1017.     }
  1018.                 /* rename mailbox */
  1019.     else if (!(anonymous || strcmp (cmd,"RENAME"))) {
  1020.       if (!((s = snarf (&arg)) && (t = snarf (&arg)))) response = misarg;
  1021.       else if (arg) response = badarg;
  1022.       else mail_rename (NIL,s,t);
  1023.       if (stream)        /* allow untagged EXPUNGE */
  1024.         mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
  1025.     }
  1026.  
  1027.                 /* idle mode */
  1028.     else if (!strcmp (cmd,"IDLE")) {
  1029.                 /* no arguments */
  1030.       if (arg) response = badarg;
  1031.       else {        /* tell client ready for argument */
  1032.         PSOUT (argrdy);
  1033.         PFLUSH;        /* dump output buffer */
  1034.                 /* maybe do a checkpoint if not anonymous */
  1035.         if (!anonymous && stream && (time (0) > lastcheck + CHECKTIMEOUT)){
  1036.           mail_check (stream);
  1037.                 /* cancel likely altwin from mail_check() */
  1038.           if (response == altwin) response = win;
  1039.                 /* remember last checkpoint */
  1040.           lastcheck = time (0);
  1041.         }
  1042.         do {        /* main idle loop */
  1043.           mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *)stream);
  1044.           ping_mailbox (uid);
  1045.           if (lstwrn) {    /* have a warning? */
  1046.         PSOUT ("* NO ");
  1047.         PSOUT (lstwrn);
  1048.         CRLF;
  1049.         fs_give ((void **) &lstwrn);
  1050.           }
  1051.           PFLUSH;        /* dump output buffer */
  1052.         } while ((state != LOGOUT) && !server_input_wait (IDLETIMEOUT));
  1053.         if (state != LOGOUT) {
  1054.           slurp (tmp,MAILTMPLEN);
  1055.           if (((tmp[0] != 'D') && (tmp[0] != 'd')) ||
  1056.           ((tmp[1] != 'O') && (tmp[1] != 'o')) ||
  1057.           ((tmp[2] != 'N') && (tmp[2] != 'n')) ||
  1058.           ((tmp[3] != 'E') && (tmp[3] != 'e')) ||
  1059.           (((tmp[4] != '\015') || (tmp[5] != '\012')) &&
  1060.            (tmp[4] != '\012')))
  1061.         response = "%.80s BAD Bogus IDLE continuation\015\012";
  1062.         }
  1063.       }
  1064.     }
  1065.     else response = "%.80s BAD Command unrecognized: %.80s\015\012";
  1066.     break;
  1067.  
  1068.       default:
  1069.         response = "%.80s BAD Server in unknown state for %.80s command\015\012";
  1070.     break;
  1071.       }
  1072.       if (lstwrn) {        /* output most recent warning */
  1073.     PSOUT ("* NO ");
  1074.     PSOUT (lstwrn);
  1075.     CRLF;
  1076.       }
  1077.                 /* get text for alternative win message now */
  1078.       if (response == altwin) cmd = lsterr;
  1079.                 /* build response */
  1080.       sprintf (tmp,response,tag,cmd,lasterror ());
  1081.       ping_mailbox (uid);    /* update mailbox status before response */
  1082.       PSOUT (tmp);        /* output response */
  1083.     }
  1084.     PFLUSH;            /* make sure output blatted */
  1085.   } while (state != LOGOUT);    /* until logged out */
  1086.   syslog (LOG_INFO,"Logout user=%.80s host=%.80s",user ? user : "???",
  1087.       tcp_clienthost ());
  1088.   return 0;            /* all done */
  1089. }
  1090.  
  1091. /* Ping mailbox during each cycle.  Also check alerts
  1092.  * Accepts: last command was UID flag
  1093.  */
  1094.  
  1095. void ping_mailbox (unsigned long uid)
  1096. {
  1097.   unsigned long i;
  1098.   char tmp[MAILTMPLEN];
  1099.   if (state == OPEN) {
  1100.     if (!mail_ping (stream)) {    /* make sure stream still alive */
  1101.       PSOUT ("* BYE ");
  1102.       PSOUT (mylocalhost ());
  1103.       PSOUT (" Fatal mailbox error: ");
  1104.       PSOUT (lasterror ());
  1105.       CRLF;
  1106.       state = LOGOUT;        /* go away */
  1107.       syslog (LOG_INFO,
  1108.           "Fatal mailbox error user=%.80s host=%.80s mbx=%.80s: %.80s",
  1109.           user ? user : "???",tcp_clienthost (),
  1110.           (stream && stream->mailbox) ? stream->mailbox : "???",
  1111.           lasterror ());
  1112.       return;
  1113.     }
  1114.                 /* change in number of messages? */
  1115.     if (existsquelled || (nmsgs != stream->nmsgs)) {
  1116.       PSOUT ("* ");
  1117.       pnum (nmsgs = stream->nmsgs);
  1118.       PSOUT (" EXISTS\015\012");
  1119.     }
  1120.                 /* change in recent messages? */
  1121.     if (existsquelled || (recent != stream->recent)) {
  1122.       PSOUT ("* ");
  1123.       pnum (recent = stream->recent);
  1124.       PSOUT (" RECENT\015\012");
  1125.     }
  1126.     existsquelled = NIL;    /* don't do this until asked again */
  1127.                 /* don't bother if driver changed */
  1128.     if (curdriver == stream->dtb) {
  1129.       for (i = 1; i <= nmsgs; i++) if (mail_elt (stream,i)->spare2) {
  1130.     PSOUT ("* ");
  1131.     pnum (i);
  1132.     PSOUT (" FETCH (");
  1133.     fetch_flags (i,NIL);    /* output changed flags */
  1134.     if (uid) {        /* need to include UIDs in response? */
  1135.       PBOUT (' ');
  1136.       fetch_uid (i,NIL);
  1137.     }
  1138.     PSOUT (")\015\012");
  1139.       }
  1140.     }
  1141.  
  1142.     else {            /* driver changed */
  1143.       PSOUT ("* OK [UIDVALIDITY ");
  1144.       pnum (stream->uid_validity);
  1145.       PSOUT ("] UID validity status\015\012* OK [UIDNEXT ");
  1146.       pnum (stream->uid_last + 1);
  1147.       PSOUT ("] Predicted next UID\015\012");
  1148.       if (stream->uid_nosticky) {
  1149.     PSOUT ("* NO [UIDNOTSTICKY] Non-permanent unique identifiers: ");
  1150.     PSOUT (stream->mailbox);
  1151.     CRLF;
  1152.       }
  1153.       new_flags (stream);    /* send mailbox flags */
  1154.       if (curdriver) {        /* note readonly/write if possible change */
  1155.     PSOUT ("* OK [READ-");
  1156.     PSOUT (stream->rdonly ? "ONLY" : "WRITE");
  1157.     PSOUT ("] Mailbox status\015\012");
  1158.       }
  1159.       curdriver = stream->dtb;
  1160.       if (nmsgs) {        /* get flags for all messages */
  1161.     sprintf (tmp,"1:%lu",nmsgs);
  1162.     mail_fetch_flags (stream,tmp,NIL);
  1163.                 /* don't do this if newsrc already did */
  1164.     if (!(curdriver->flags & DR_NEWS)) {
  1165.                 /* find first unseen message */
  1166.       for (i = 1; i <= nmsgs && mail_elt (stream,i)->seen; i++);
  1167.       if (i <= nmsgs) {
  1168.         PSOUT ("* OK [UNSEEN ");
  1169.         pnum (i);
  1170.         PSOUT ("] first unseen message in ");
  1171.         PSOUT (stream->mailbox);
  1172.         CRLF;
  1173.       }
  1174.     }
  1175.       }
  1176.     }
  1177.   }
  1178.                 /* don't do these stat()s every cycle */
  1179.   if (time (0) > alerttime + ALERTTIMEOUT) {
  1180.                 /* output any new alerts */
  1181.     sysalerttime = palert (ALERTFILE,sysalerttime);
  1182.     useralerttime = palert (mailboxfile (tmp,USERALERTFILE),useralerttime);
  1183.   }
  1184. }
  1185.  
  1186. /* Print an alert file
  1187.  * Accepts: path of alert file
  1188.  *        time of last printed alert file
  1189.  * Returns: updated time of last printed alert file
  1190.  */
  1191.  
  1192. time_t palert (char *file,time_t oldtime)
  1193. {
  1194.   FILE *alf;
  1195.   struct stat sbuf;
  1196.   int c,lc = '\012';
  1197.                 /* have a new alert file? */
  1198.   if (stat (file,&sbuf) || (sbuf.st_mtime <= oldtime) ||
  1199.       !(alf = fopen (file,"r"))) return oldtime;
  1200.                 /* yes, display it */
  1201.   while ((c = getc (alf)) != EOF) {
  1202.     if (lc == '\012') PSOUT ("* OK [ALERT] ");
  1203.     switch (c) {        /* output character */
  1204.     case '\012':        /* newline means do CRLF */
  1205.       CRLF;
  1206.     case '\015':        /* flush CRs */
  1207.     case '\0':            /* flush nulls */
  1208.       break;
  1209.     default:
  1210.       PBOUT (c);        /* output all other characters */
  1211.       break;
  1212.     }
  1213.     lc = c;            /* note previous character */
  1214.   }
  1215.   fclose (alf);
  1216.   if (lc != '\012') CRLF;    /* final terminating CRLF */
  1217.   return sbuf.st_mtime;        /* return updated last alert time */
  1218. }
  1219.  
  1220. /* Initialize file string structure for file stringstruct
  1221.  * Accepts: string structure
  1222.  *        pointer to message data structure
  1223.  *        size of string
  1224.  */
  1225.  
  1226. void msg_string_init (STRING *s,void *data,unsigned long size)
  1227. {
  1228.   MSGDATA *md = (MSGDATA *) data;
  1229.   s->data = data;        /* note stream/msgno and header length */
  1230.   mail_fetchheader_full (md->stream,md->msgno,NIL,&s->data1,FT_PREFETCHTEXT);
  1231. #if 0
  1232.   s->size = size;        /* message size */
  1233. #else    /* This kludge is necessary because of broken IMAP servers (sigh!) */
  1234.   mail_fetchtext_full (md->stream,md->msgno,&s->size,NIL);
  1235.   s->size += s->data1;        /* header + body size */
  1236. #endif
  1237.   SETPOS (s,0);
  1238. }
  1239.  
  1240.  
  1241. /* Get next character from file stringstruct
  1242.  * Accepts: string structure
  1243.  * Returns: character, string structure chunk refreshed
  1244.  */
  1245.  
  1246. char msg_string_next (STRING *s)
  1247. {
  1248.   char c = *s->curpos++;    /* get next byte */
  1249.   SETPOS (s,GETPOS (s));    /* move to next chunk */
  1250.   return c;            /* return the byte */
  1251. }
  1252.  
  1253.  
  1254. /* Set string pointer position for file stringstruct
  1255.  * Accepts: string structure
  1256.  *        new position
  1257.  */
  1258.  
  1259. void msg_string_setpos (STRING *s,unsigned long i)
  1260. {
  1261.   MSGDATA *md = (MSGDATA *) s->data;
  1262.   if (i < s->data1) {        /* want header? */
  1263.     s->chunk = mail_fetchheader (md->stream,md->msgno);
  1264.     s->chunksize = s->data1;    /* header length */
  1265.     s->offset = 0;        /* offset is start of message */
  1266.   }
  1267.   else if (i < s->size) {    /* want body */
  1268.     s->chunk = mail_fetchtext (md->stream,md->msgno);
  1269.     s->chunksize = s->size - s->data1;
  1270.     s->offset = s->data1;    /* offset is end of header */
  1271.   }
  1272.   else {            /* off end of message */
  1273.     s->chunk = NIL;        /* make sure that we crack on this then */
  1274.     s->chunksize = 1;        /* make sure SNX cracks the right way... */
  1275.     s->offset = i;
  1276.   }
  1277.                 /* initial position and size */
  1278.   s->curpos = s->chunk + (i -= s->offset);
  1279.   s->cursize = s->chunksize - i;
  1280. }
  1281.  
  1282. /* Send flags for stream
  1283.  * Accepts: MAIL stream
  1284.  *        scratch buffer
  1285.  */
  1286.  
  1287. void new_flags (MAILSTREAM *stream)
  1288. {
  1289.   int i,c;
  1290.   PSOUT ("* FLAGS (");
  1291.   for (i = 0; i < NUSERFLAGS; i++) if (stream->user_flags[i]) {
  1292.     PSOUT (stream->user_flags[i]);
  1293.     PBOUT (' ');
  1294.   }
  1295.   PSOUT ("\\Answered \\Flagged \\Deleted \\Draft \\Seen)\015\012* OK [PERMANENTFLAGS (");
  1296.   for (i = c = 0; i < NUSERFLAGS; i++)
  1297.     if ((stream->perm_user_flags & (1 << i)) && stream->user_flags[i])
  1298.       put_flag (&c,stream->user_flags[i]);
  1299.   if (stream->kwd_create) put_flag (&c,"\\*");
  1300.   if (stream->perm_answered) put_flag (&c,"\\Answered");
  1301.   if (stream->perm_flagged) put_flag (&c,"\\Flagged");
  1302.   if (stream->perm_deleted) put_flag (&c,"\\Deleted");
  1303.   if (stream->perm_draft) put_flag (&c,"\\Draft");
  1304.   if (stream->perm_seen) put_flag (&c,"\\Seen");
  1305.   PSOUT (")] Permanent flags\015\012");
  1306. }
  1307.  
  1308. /* Clock interrupt
  1309.  */
  1310.  
  1311. void clkint (void)
  1312. {
  1313.   alarm (0);            /* disable all interrupts */
  1314.   server_init (NIL,NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
  1315.   if (!quell_events)
  1316.     PSOUT ("* BYE Autologout; idle for too long\015\012");
  1317.   syslog (LOG_INFO,"Autologout user=%.80s host=%.80s",user ? user : "???",
  1318.       tcp_clienthost ());
  1319.   PFLUSH;            /* make sure output blatted */
  1320.   if (critical) state = LOGOUT;    /* badly hosed if in critical code */
  1321.   else {            /* try to gracefully close the stream */
  1322.     if ((state == OPEN) && !stream->lock) mail_close (stream);
  1323.     state = LOGOUT;
  1324.     stream = NIL;
  1325.     _exit (1);            /* die die die */
  1326.   }
  1327. }
  1328.  
  1329.  
  1330. /* Kiss Of Death interrupt
  1331.  */
  1332.  
  1333. void kodint (void)
  1334. {
  1335.   alarm (0);            /* disable all interrupts */
  1336.   server_init (NIL,NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
  1337.   if (!quell_events) PSOUT ("* BYE Lost mailbox lock\015\012");
  1338.   PFLUSH;            /* make sure output blatted */
  1339.   syslog (LOG_INFO,"Killed (lost mailbox lock) user=%.80s host=%.80s",
  1340.       user ? user : "???",tcp_clienthost ());
  1341.   if (critical) state = LOGOUT;    /* must defer if in critical code */
  1342.   else {            /* try to gracefully close the stream */
  1343.     if ((state == OPEN) && !stream->lock) mail_close (stream);
  1344.     state = LOGOUT;
  1345.     stream = NIL;
  1346.     _exit (1);            /* die die die */
  1347.   }
  1348. }
  1349.  
  1350. /* Hangup interrupt
  1351.  */
  1352.  
  1353. void hupint (void)
  1354. {
  1355.   alarm (0);            /* disable all interrupts */
  1356.   server_init (NIL,NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
  1357.   syslog (LOG_INFO,"Hangup user=%.80s host=%.80s",user ? user : "???",
  1358.       tcp_clienthost ());
  1359.   if (critical) state = LOGOUT;    /* must defer if in critical code */
  1360.   else {            /* try to gracefully close the stream */
  1361.     if ((state == OPEN) && !stream->lock) mail_close (stream);
  1362.     state = LOGOUT;
  1363.     stream = NIL;
  1364.     _exit (1);            /* die die die */
  1365.   }
  1366. }
  1367.  
  1368.  
  1369. /* Termination interrupt
  1370.  */
  1371.  
  1372. void trmint (void)
  1373. {
  1374.   alarm (0);            /* disable all interrupts */
  1375.   server_init (NIL,NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
  1376.   if (!quell_events) PSOUT ("* BYE Killed\015\012");
  1377.   syslog (LOG_INFO,"Killed user=%.80s host=%.80s",user ? user : "???",
  1378.       tcp_clienthost ());
  1379.   if (critical) state = LOGOUT;    /* must defer if in critical code */
  1380.   else {            /* try to gracefully close the stream */
  1381.     if ((state == OPEN) && !stream->lock) mail_close (stream);
  1382.     state = LOGOUT;
  1383.     stream = NIL;
  1384.     _exit (1);            /* die die die */
  1385.   }
  1386. }
  1387.  
  1388. /* Slurp a command line
  1389.  * Accepts: buffer pointer
  1390.  *        buffer size
  1391.  */
  1392.  
  1393. void slurp (char *s,int n)
  1394. {
  1395.   s[--n] = '\0';        /* last buffer character is guaranteed NUL */
  1396.                 /* get a command under timeout */
  1397.   alarm ((state != LOGIN) ? TIMEOUT : LOGINTIMEOUT);
  1398.   errno = 0;            /* clear error */
  1399.   while (!PSIN (s,n)) {        /* read buffer */
  1400.     if (errno==EINTR) errno = 0;/* ignore if some interrupt */
  1401.     else {
  1402.       char *e = errno ? strerror (errno) : "command stream end of file";
  1403.       alarm (0);        /* disable all interrupts */
  1404.       server_init (NIL,NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
  1405.       syslog (LOG_INFO,"%.80s, while reading line user=%.80s host=%.80s",
  1406.           e,user ? user : "???",tcp_clienthost ());
  1407.                 /* try to gracefully close the stream */
  1408.       if (state == OPEN) mail_close (stream);
  1409.       state = LOGOUT;
  1410.       stream = NIL;
  1411.       _exit (1);
  1412.     }
  1413.   }
  1414.   alarm (0);            /* make sure timeout disabled */
  1415. }
  1416.  
  1417.  
  1418. /* Read a character
  1419.  * Returns: character
  1420.  */
  1421.  
  1422. char inchar (void)
  1423. {
  1424.   int c;
  1425.   while ((c = PBIN ()) == EOF) {
  1426.     if (errno==EINTR) errno = 0;/* ignore if some interrupt */
  1427.     else {
  1428.       char *e = errno ? strerror (errno) : "command stream end of file";
  1429.       alarm (0);        /* disable all interrupts */
  1430.       server_init (NIL,NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
  1431.       syslog (LOG_INFO,"%.80s, while reading char user=%.80s host=%.80s",
  1432.           e,user ? user : "???",tcp_clienthost ());
  1433.                 /* try to gracefully close the stream */
  1434.       if (state == OPEN) mail_close (stream);
  1435.       state = LOGOUT;
  1436.       stream = NIL;
  1437.       _exit (1);
  1438.     }
  1439.   }
  1440.   return c;
  1441. }
  1442.  
  1443.  
  1444. /* Flush until newline seen
  1445.  * Returns: NIL, always
  1446.  */
  1447.  
  1448. char *flush (void)
  1449. {
  1450.   while (inchar () != '\012');    /* slurp until we find newline */
  1451.   response = "%.80s BAD Command line too long\015\012";
  1452.   return NIL;
  1453. }
  1454.  
  1455. /* Parse an IMAP astring
  1456.  * Accepts: pointer to argument text pointer
  1457.  *        pointer to returned size
  1458.  *        pointer to returned delimiter
  1459.  * Returns: argument
  1460.  */
  1461.  
  1462. char *parse_astring (char **arg,unsigned long *size,char *del)
  1463. {
  1464.   unsigned long i;
  1465.   char c,*s,*t,*v;
  1466.   if (!*arg) return NIL;    /* better be an argument */
  1467.   switch (**arg) {        /* see what the argument is */
  1468.   default:            /* atom */
  1469.     for (s = t = *arg, i = 0;
  1470.      (*t > ' ') && (*t < 0x7f) && (*t != '(') && (*t != ')') &&
  1471.      (*t != '{') && (*t != '%') && (*t != '*') && (*t != '"') &&
  1472.      (*t != '\\'); ++t,++i);
  1473.     if (*size = i) break;    /* got atom if non-empty */
  1474.   case ')': case '%': case '*': case '\\': case '\0': case ' ':
  1475.    return NIL;            /* empty atom is a bogon */
  1476.   case '"':            /* hunt for trailing quote */
  1477.     for (s = t = v = *arg + 1; (c = *t++) != '"'; *v++ = c) {
  1478.       if (c == '\\') c = *t++;    /* quote next character */
  1479.                 /* else must be a CHAR */
  1480.       if (!c || (c & 0x80)) return NIL;
  1481.     }
  1482.     *v = '\0';            /* tie off string */
  1483.     *size = v - s;        /* return size */
  1484.     break;
  1485.  
  1486.   case '{':            /* literal string */
  1487.     s = *arg + 1;        /* get size */
  1488.     if (!isdigit (*s)) return NIL;
  1489.     if ((*size = i = strtoul (s,&t,10)) > MAXCLIENTLIT) {
  1490.       mm_notify (NIL,"Absurdly long client literal",ERROR);
  1491.       syslog (LOG_INFO,"Absurdly long client literal user=%.80s host=%.80s",
  1492.           user ? user : "???",tcp_clienthost ());
  1493.       return NIL;
  1494.     }
  1495.                 /* validate end of literal */
  1496.     if (!t || (*t != '}') || t[1]) return NIL;
  1497.     if (litsp >= LITSTKLEN) {    /* make sure don't overflow stack */
  1498.       mm_notify (NIL,"Too many literals in command",ERROR);
  1499.       return NIL;
  1500.     }
  1501.     PSOUT (argrdy);        /* tell client ready for argument */
  1502.     PFLUSH;            /* dump output buffer */
  1503.                 /* get a literal buffer */
  1504.     s = v = litstk[litsp++] = (char *) fs_get (i+1);
  1505.                 /* get literal under timeout */
  1506.     alarm ((state != LOGIN) ? TIMEOUT : LOGINTIMEOUT);
  1507.     while (i--) *v++ = inchar ();
  1508.     alarm (0);            /* stop timeout */
  1509.     *v++ = NIL;            /* make sure string tied off */
  1510.                     /* get new command tail */
  1511.     slurp ((*arg = v = t),TMPLEN - (t - cmdbuf));
  1512.     if (!strchr (t,'\012')) return flush ();
  1513.                 /* reset strtok mechanism, tie off if done */
  1514.     if (!strtok (t,"\015\012")) *t = '\0';
  1515.     break;
  1516.   }
  1517.   if (*del = *t) {        /* have a delimiter? */
  1518.     *t++ = '\0';        /* yes, stomp on it */
  1519.     *arg = t;            /* update argument pointer */
  1520.   }
  1521.   else *arg = NIL;        /* no more arguments */
  1522.   return s;
  1523. }
  1524.  
  1525. /* Snarf a command argument (simple jacket into parse_astring())
  1526.  * Accepts: pointer to argument text pointer
  1527.  * Returns: argument
  1528.  */
  1529.  
  1530. char *snarf (char **arg)
  1531. {
  1532.   unsigned long i;
  1533.   char c;
  1534.   char *s = parse_astring (arg,&i,&c);
  1535.   return ((c == ' ') || !c) ? s : NIL;
  1536. }
  1537.  
  1538.  
  1539. /* Snarf a list command argument (simple jacket into parse_astring())
  1540.  * Accepts: pointer to argument text pointer
  1541.  * Returns: argument
  1542.  */
  1543.  
  1544. char *snarf_list (char **arg)
  1545. {
  1546.   unsigned long i;
  1547.   char c;
  1548.   char *s,*t;
  1549.   if (!*arg) return NIL;    /* better be an argument */
  1550.   switch (**arg) {
  1551.   default:            /* atom and/or wildcard chars */
  1552.     for (s = t = *arg, i = 0;
  1553.      (*t > ' ') && (*t != '(') && (*t != ')') && (*t != '{') &&
  1554.      (*t != '"') && (*t != '\\'); ++t,++i);
  1555.     if (c = *t) {        /* have a delimiter? */
  1556.       *t++ = '\0';        /* stomp on it */
  1557.       *arg = t;            /* update argument pointer */
  1558.     }
  1559.     else *arg = NIL;
  1560.     break;
  1561.   case ')': case '\\': case '\0': case ' ':
  1562.     return NIL;            /* empty name is bogus */
  1563.   case '"':            /* quoted string? */
  1564.   case '{':            /* or literal? */
  1565.     s = parse_astring (arg,&i,&c);
  1566.     break;
  1567.   }
  1568.   return ((c == ' ') || !c) ? s : NIL;
  1569. }
  1570.  
  1571. /* Get a list of header lines
  1572.  * Accepts: pointer to string pointer
  1573.  *        pointer to list flag
  1574.  * Returns: string list
  1575.  */
  1576.  
  1577. STRINGLIST *parse_stringlist (char **s,int *list)
  1578. {
  1579.   char c = ' ',*t;
  1580.   unsigned long i;
  1581.   STRINGLIST *ret = NIL,*cur = NIL;
  1582.   if (*s && **s == '(') {    /* proper list? */
  1583.     ++*s;            /* for each item in list */
  1584.     while ((c == ' ') && (t = parse_astring (s,&i,&c))) {
  1585.                 /* get new block */
  1586.       if (cur) cur = cur->next = mail_newstringlist ();
  1587.       else cur = ret = mail_newstringlist ();
  1588.                 /* note text */
  1589.       cur->text.data = (unsigned char *) fs_get (i + 1);
  1590.       memcpy (cur->text.data,t,i);
  1591.       cur->text.size = i;        /* and size */
  1592.     }
  1593.                 /* must be end of list */
  1594.     if (c != ')') mail_free_stringlist (&ret);
  1595.   }
  1596.   if (t = *s) {            /* need to reload strtok() state? */
  1597.                 /* end of a list? */
  1598.     if (*list && (*t == ')') && !t[1]) *list = NIL;
  1599.     else {
  1600.       *--t = ' ';        /* patch a space back in */
  1601.       *--t = 'x';        /* and a hokey character before that */
  1602.       t = strtok (t," ");    /* reset to *s */
  1603.     }
  1604.   }
  1605.   return ret;
  1606. }
  1607.  
  1608. /* Parse search criteria
  1609.  * Accepts: search program to write criteria into
  1610.  *        pointer to argument text pointer
  1611.  *        maximum message number
  1612.  *        maximum UID
  1613.  * Returns: T if success, NIL if error
  1614.  */
  1615.  
  1616. long parse_criteria (SEARCHPGM *pgm,char **arg,unsigned long maxmsg,
  1617.              unsigned long maxuid)
  1618. {
  1619.   if (arg && *arg) {        /* must be an argument */
  1620.                 /* parse criteria */
  1621.     do if (!parse_criterion (pgm,arg,maxmsg,maxuid)) return NIL;
  1622.                 /* as long as a space delimiter */
  1623.     while (**arg == ' ' && (*arg)++);
  1624.                 /* failed if not end of criteria */
  1625.     if (**arg && **arg != ')') return NIL;
  1626.   }
  1627.   return T;            /* success */
  1628. }
  1629.  
  1630. /* Parse a search criterion
  1631.  * Accepts: search program to write criterion into
  1632.  *        pointer to argument text pointer
  1633.  *        maximum message number
  1634.  *        maximum UID
  1635.  * Returns: T if success, NIL if error
  1636.  */
  1637.  
  1638. long parse_criterion (SEARCHPGM *pgm,char **arg,unsigned long maxmsg,
  1639.               unsigned long maxuid)
  1640. {
  1641.   unsigned long i;
  1642.   char c = NIL,*s,*t,*v,*tail,*del;
  1643.   SEARCHSET **set;
  1644.   SEARCHPGMLIST **not;
  1645.   SEARCHOR **or;
  1646.   SEARCHHEADER **hdr;
  1647.   long ret = NIL;
  1648.   if (!(arg && *arg));        /* better be an argument */
  1649.   else if (**arg == '(') {    /* list of criteria? */
  1650.     (*arg)++;            /* yes, parse the criteria */
  1651.     if (parse_criteria (pgm,arg,maxmsg,maxuid) && **arg == ')') {
  1652.       (*arg)++;            /* skip closing paren */
  1653.       ret = T;            /* successful parse of list */
  1654.     }
  1655.   }
  1656.   else {            /* find end of criterion */
  1657.     if (!(tail = strpbrk ((s = *arg)," )"))) tail = *arg + strlen (*arg);
  1658.     c = *(del = tail);        /* remember the delimiter */
  1659.     *del = '\0';        /* tie off criterion */
  1660.     switch (*ucase (s)) {    /* dispatch based on character */
  1661.     case '*':            /* sequence */
  1662.     case '0': case '1': case '2': case '3': case '4':
  1663.     case '5': case '6': case '7': case '8': case '9':
  1664.       if (*(set = &pgm->msgno)){/* already a sequence? */
  1665.                 /* silly, but not as silly as the client! */
  1666.     for (not = &pgm->not; *not; not = &(*not)->next);
  1667.     *not = mail_newsearchpgmlist ();
  1668.     set = &((*not)->pgm->not = mail_newsearchpgmlist ())->pgm->msgno;
  1669.       }
  1670.       ret = crit_set (set,&s,maxmsg) && (tail == s);
  1671.       break;
  1672.     case 'A':            /* possible ALL, ANSWERED */
  1673.       if (!strcmp (s+1,"LL")) ret = T;
  1674.       else if (!strcmp (s+1,"NSWERED")) ret = pgm->answered = T;
  1675.       break;
  1676.  
  1677.     case 'B':            /* possible BCC, BEFORE, BODY */
  1678.       if (!strcmp (s+1,"CC") && c == ' ' && *++tail)
  1679.     ret = crit_string (&pgm->bcc,&tail);
  1680.       else if (!strcmp (s+1,"EFORE") && c == ' ' && *++tail)
  1681.     ret = crit_date (&pgm->before,&tail);
  1682.       else if (!strcmp (s+1,"ODY") && c == ' ' && *++tail)
  1683.     ret = crit_string (&pgm->body,&tail);
  1684.       break;
  1685.     case 'C':            /* possible CC */
  1686.       if (!strcmp (s+1,"C") && c == ' ' && *++tail)
  1687.     ret = crit_string (&pgm->cc,&tail);
  1688.       break;
  1689.     case 'D':            /* possible DELETED */
  1690.       if (!strcmp (s+1,"ELETED")) ret = pgm->deleted = T;
  1691.       if (!strcmp (s+1,"RAFT")) ret = pgm->draft = T;
  1692.       break;
  1693.     case 'F':            /* possible FLAGGED, FROM */
  1694.       if (!strcmp (s+1,"LAGGED")) ret = pgm->flagged = T;
  1695.       else if (!strcmp (s+1,"ROM") && c == ' ' && *++tail)
  1696.     ret = crit_string (&pgm->from,&tail);
  1697.       break;
  1698.     case 'H':            /* possible HEADER */
  1699.       if (!strcmp (s+1,"EADER") && c == ' ' && *(v = tail + 1) &&
  1700.       (s = parse_astring (&v,&i,&c)) && i && c == ' ' &&
  1701.       (t = parse_astring (&v,&i,&c))) {
  1702.     for (hdr = &pgm->header; *hdr; hdr = &(*hdr)->next);
  1703.     *hdr = mail_newsearchheader (s,t);
  1704.                 /* update tail, restore delimiter */
  1705.     *(tail = v ? v - 1 : t + i) = c;
  1706.     ret = T;        /* success */
  1707.       }
  1708.       break;
  1709.     case 'K':            /* possible KEYWORD */
  1710.       if (!strcmp (s+1,"EYWORD") && c == ' ' && *++tail)
  1711.     ret = crit_string (&pgm->keyword,&tail);
  1712.       break;
  1713.     case 'L':
  1714.       if (!strcmp (s+1,"ARGER") && c == ' ' && *++tail)
  1715.     ret = crit_number (&pgm->larger,&tail);
  1716.       break;
  1717.     case 'N':            /* possible NEW, NOT */
  1718.       if (!strcmp (s+1,"EW")) ret = pgm->recent = pgm->unseen = T;
  1719.       else if (!strcmp (s+1,"OT") && c == ' ' && *++tail) {
  1720.     for (not = &pgm->not; *not; not = &(*not)->next);
  1721.     *not = mail_newsearchpgmlist ();
  1722.     ret = parse_criterion ((*not)->pgm,&tail,maxmsg,maxuid);
  1723.       }
  1724.       break;
  1725.  
  1726.     case 'O':            /* possible OLD, ON */
  1727.       if (!strcmp (s+1,"LD")) ret = pgm->old = T;
  1728.       else if (!strcmp (s+1,"N") && c == ' ' && *++tail)
  1729.     ret = crit_date (&pgm->on,&tail);
  1730.       else if (!strcmp (s+1,"R") && c == ' ') {
  1731.     for (or = &pgm->or; *or; or = &(*or)->next);
  1732.     *or = mail_newsearchor ();
  1733.     ret = *++tail && parse_criterion ((*or)->first,&tail,maxmsg,maxuid) &&
  1734.       *tail == ' ' && *++tail &&
  1735.         parse_criterion ((*or)->second,&tail,maxmsg,maxuid);
  1736.       }
  1737.       break;
  1738.     case 'R':            /* possible RECENT */
  1739.       if (!strcmp (s+1,"ECENT")) ret = pgm->recent = T;
  1740.       break;
  1741.     case 'S':            /* possible SEEN, SINCE, SUBJECT */
  1742.       if (!strcmp (s+1,"EEN")) ret = pgm->seen = T;
  1743.       else if (!strcmp (s+1,"ENTBEFORE") && c == ' ' && *++tail)
  1744.     ret = crit_date (&pgm->sentbefore,&tail);
  1745.       else if (!strcmp (s+1,"ENTON") && c == ' ' && *++tail)
  1746.     ret = crit_date (&pgm->senton,&tail);
  1747.       else if (!strcmp (s+1,"ENTSINCE") && c == ' ' && *++tail)
  1748.     ret = crit_date (&pgm->sentsince,&tail);
  1749.       else if (!strcmp (s+1,"INCE") && c == ' ' && *++tail)
  1750.     ret = crit_date (&pgm->since,&tail);
  1751.       else if (!strcmp (s+1,"MALLER") && c == ' ' && *++tail)
  1752.     ret = crit_number (&pgm->smaller,&tail);
  1753.       else if (!strcmp (s+1,"UBJECT") && c == ' ' && *++tail)
  1754.     ret = crit_string (&pgm->subject,&tail);
  1755.       break;
  1756.     case 'T':            /* possible TEXT, TO */
  1757.       if (!strcmp (s+1,"EXT") && c == ' ' && *++tail)
  1758.     ret = crit_string (&pgm->text,&tail);
  1759.       else if (!strcmp (s+1,"O") && c == ' ' && *++tail)
  1760.     ret = crit_string (&pgm->to,&tail);
  1761.       break;
  1762.  
  1763.     case 'U':            /* possible UID, UN* */
  1764.       if (!strcmp (s+1,"ID") && c== ' ' && *++tail) {
  1765.     if (*(set = &pgm->uid)){/* already a sequence? */
  1766.                 /* silly, but not as silly as the client! */
  1767.       for (not = &pgm->not; *not; not = &(*not)->next);
  1768.       *not = mail_newsearchpgmlist ();
  1769.       set = &((*not)->pgm->not = mail_newsearchpgmlist ())->pgm->uid;
  1770.     }
  1771.     ret = crit_set (set,&tail,maxuid);
  1772.       }
  1773.       else if (!strcmp (s+1,"NANSWERED")) ret = pgm->unanswered = T;
  1774.       else if (!strcmp (s+1,"NDELETED")) ret = pgm->undeleted = T;
  1775.       else if (!strcmp (s+1,"NDRAFT")) ret = pgm->undraft = T;
  1776.       else if (!strcmp (s+1,"NFLAGGED")) ret = pgm->unflagged = T;
  1777.       else if (!strcmp (s+1,"NKEYWORD") && c == ' ' && *++tail)
  1778.     ret = crit_string (&pgm->unkeyword,&tail);
  1779.       else if (!strcmp (s+1,"NSEEN")) ret = pgm->unseen = T;
  1780.       break;
  1781.     default:            /* oh dear */
  1782.       break;
  1783.     }
  1784.     if (ret) {            /* only bother if success */
  1785.       *del = c;            /* restore delimiter */
  1786.       *arg = tail;        /* update argument pointer */
  1787.     }
  1788.   }
  1789.   return ret;            /* return more to come */
  1790. }
  1791.  
  1792. /* Parse a search date criterion
  1793.  * Accepts: date to write into
  1794.  *        pointer to argument text pointer
  1795.  * Returns: T if success, NIL if error
  1796.  */
  1797.  
  1798. long crit_date (unsigned short *date,char **arg)
  1799. {
  1800.                 /* handle quoted form */
  1801.   if (**arg != '"') return crit_date_work (date,arg);
  1802.   (*arg)++;            /* skip past opening quote */
  1803.   if (!(crit_date_work (date,arg) && (**arg == '"'))) return NIL;
  1804.   (*arg)++;            /* skip closing quote */
  1805.   return T;
  1806. }
  1807.  
  1808. /* Worker routine to parse a search date criterion
  1809.  * Accepts: date to write into
  1810.  *        pointer to argument text pointer
  1811.  * Returns: T if success, NIL if error
  1812.  */
  1813.  
  1814. long crit_date_work (unsigned short *date,char **arg)
  1815. {
  1816.   int d,m,y;
  1817.                 /* day */
  1818.   if (isdigit (d = *(*arg)++) || ((d == ' ') && isdigit (**arg))) {
  1819.     if (d == ' ') d = 0;    /* leading space */
  1820.     else d -= '0';        /* first digit */
  1821.     if (isdigit (**arg)) {    /* if a second digit */
  1822.       d *= 10;            /* slide over first digit */
  1823.       d += *(*arg)++ - '0';    /* second digit */
  1824.     }
  1825.     if ((**arg == '-') && (y = *++(*arg))) {
  1826.       m = (y >= 'a' ? y - 'a' : y - 'A') * 1024;
  1827.       if ((y = *++(*arg))) {
  1828.     m += (y >= 'a' ? y - 'a' : y - 'A') * 32;
  1829.     if ((y = *++(*arg))) {
  1830.       m += (y >= 'a' ? y - 'a' : y - 'A');
  1831.       switch (m) {        /* determine the month */
  1832.       case (('J'-'A') * 1024) + (('A'-'A') * 32) + ('N'-'A'): m = 1; break;
  1833.       case (('F'-'A') * 1024) + (('E'-'A') * 32) + ('B'-'A'): m = 2; break;
  1834.       case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('R'-'A'): m = 3; break;
  1835.       case (('A'-'A') * 1024) + (('P'-'A') * 32) + ('R'-'A'): m = 4; break;
  1836.       case (('M'-'A') * 1024) + (('A'-'A') * 32) + ('Y'-'A'): m = 5; break;
  1837.       case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('N'-'A'): m = 6; break;
  1838.       case (('J'-'A') * 1024) + (('U'-'A') * 32) + ('L'-'A'): m = 7; break;
  1839.       case (('A'-'A') * 1024) + (('U'-'A') * 32) + ('G'-'A'): m = 8; break;
  1840.       case (('S'-'A') * 1024) + (('E'-'A') * 32) + ('P'-'A'): m = 9; break;
  1841.       case (('O'-'A') * 1024) + (('C'-'A') * 32) + ('T'-'A'): m = 10;break;
  1842.       case (('N'-'A') * 1024) + (('O'-'A') * 32) + ('V'-'A'): m = 11;break;
  1843.       case (('D'-'A') * 1024) + (('E'-'A') * 32) + ('C'-'A'): m = 12;break;
  1844.       default: return NIL;
  1845.       }
  1846.       if ((*++(*arg) == '-') && isdigit (*++(*arg))) {
  1847.         y = 0;        /* init year */
  1848.         do {
  1849.           y *= 10;        /* add this number */
  1850.           y += *(*arg)++ - '0';
  1851.         }
  1852.         while (isdigit (**arg));
  1853.                 /* minimal validity check of date */
  1854.         if (d < 1 || d > 31 || m < 1 || m > 12 || y < 0) return NIL; 
  1855.                 /* Tenex/ARPAnet began in 1969 */
  1856.         if (y < 100) y += (y >= (BASEYEAR - 1900)) ? 1900 : 2000;
  1857.                 /* return value */
  1858.         *date = ((y - BASEYEAR) << 9) + (m << 5) + d;
  1859.         return T;        /* success */
  1860.       }
  1861.     }
  1862.       }
  1863.     }
  1864.   }
  1865.   return NIL;            /* else error */
  1866. }
  1867.  
  1868. /* Parse a search set criterion
  1869.  * Accepts: set to write into
  1870.  *        pointer to argument text pointer
  1871.  *        maximum value permitted
  1872.  * Returns: T if success, NIL if error
  1873.  */
  1874.  
  1875. long crit_set (SEARCHSET **set,char **arg,unsigned long maxima)
  1876. {
  1877.   unsigned long i;
  1878.   *set = mail_newsearchset ();    /* instantiate a new search set */
  1879.   if (**arg == '*') {        /* maxnum? */
  1880.     (*arg)++;            /* skip past that number */
  1881.     (*set)->first = maxima;
  1882.   }
  1883.   else if (crit_number (&i,arg) && i) (*set)->first = i;
  1884.   else return NIL;        /* bogon */
  1885.   switch (**arg) {        /* decide based on delimiter */
  1886.   case ':':            /* sequence range */
  1887.     if (*++(*arg) == '*') {    /* maxnum? */
  1888.       (*arg)++;            /* skip past that number */
  1889.       (*set)->last -= maxima;
  1890.     }
  1891.     else if (crit_number (&i,arg) && i) {
  1892.       if (i < (*set)->first) {    /* backwards range */
  1893.     (*set)->last = (*set)->first;
  1894.     (*set)->first = i;
  1895.       }
  1896.       else (*set)->last = i;    /* set last number */
  1897.     }
  1898.     else return NIL;        /* bogon */
  1899.     if (**arg != ',') break;    /* drop into comma case if comma seen */
  1900.   case ',':
  1901.     (*arg)++;            /* skip past delimiter */
  1902.     return crit_set (&(*set)->next,arg,maxima);
  1903.   default:
  1904.     break;
  1905.   }
  1906.   return T;            /* return success */
  1907. }
  1908.  
  1909. /* Parse a search number criterion
  1910.  * Accepts: number to write into
  1911.  *        pointer to argument text pointer
  1912.  * Returns: T if success, NIL if error
  1913.  */
  1914.  
  1915. long crit_number (unsigned long *number,char **arg)
  1916. {
  1917.   if (!isdigit (**arg)) return NIL;
  1918.   *number = 0;
  1919.   while (isdigit (**arg)) {    /* found a digit? */
  1920.     *number *= 10;        /* add a decade */
  1921.     *number += *(*arg)++ - '0';    /* add number */
  1922.   }
  1923.   return T;
  1924. }
  1925.  
  1926.  
  1927. /* Parse a search string criterion
  1928.  * Accepts: date to write into
  1929.  *        pointer to argument text pointer
  1930.  * Returns: T if success, NIL if error
  1931.  */
  1932.  
  1933. long crit_string (STRINGLIST **string,char **arg)
  1934. {
  1935.   unsigned long i;
  1936.   char c;
  1937.   char *s = parse_astring (arg,&i,&c);
  1938.   if (!s) return NIL;
  1939.                 /* find tail of list */
  1940.   while (*string) string = &(*string)->next;
  1941.   *string = mail_newstringlist ();
  1942.   (*string)->text.data = (unsigned char *) fs_get (i + 1);
  1943.   memcpy ((*string)->text.data,s,i);
  1944.   (*string)->text.data[i] = '\0';
  1945.   (*string)->text.size = i;
  1946.                 /* if end of arguments, wrap it up here */
  1947.   if (!*arg) *arg = (char *) (*string)->text.data + i;
  1948.   else (*--(*arg) = c);        /* back up pointer, restore delimiter */
  1949.   return T;
  1950. }
  1951.  
  1952. /* Fetch message data
  1953.  * Accepts: string of data items to be fetched (must be writeable)
  1954.  *        UID fetch flag
  1955.  */
  1956.  
  1957. #define MAXFETCH 100
  1958.  
  1959. void fetch (char *t,unsigned long uid)
  1960. {
  1961.   fetchfn_t f[MAXFETCH +2];
  1962.   void *fa[MAXFETCH + 2];
  1963.   int k;
  1964.   memset ((void *) f,NIL,sizeof (f));
  1965.   memset ((void *) fa,NIL,sizeof (fa));
  1966.   fetch_work (t,uid,f,fa);    /* do the work */
  1967.                 /* clean up arguments */
  1968.   for (k = 1; f[k]; k++) if (fa[k]) (*f[k]) (0,fa[k]);
  1969. }
  1970.  
  1971.  
  1972. /* Fetch message data worker routine
  1973.  * Accepts: string of data items to be fetched (must be writeable)
  1974.  *        UID fetch flag
  1975.  *        function dispatch vector
  1976.  *        function argument vector
  1977.  */
  1978.  
  1979. void fetch_work (char *t,unsigned long uid,fetchfn_t f[],void *fa[])
  1980. {
  1981.   char *s,*v;
  1982.   unsigned long i;
  1983.   unsigned long k = 0;
  1984.   BODY *b;
  1985.   int list = NIL;
  1986.   int parse_envs = NIL;
  1987.   int parse_bodies = NIL;
  1988.   if (uid) {            /* need to fetch UIDs? */
  1989.     fa[k] = NIL;        /* no argument */
  1990.     f[k++] = fetch_uid;        /* push a UID fetch on the stack */
  1991.   }
  1992.  
  1993.                 /* process macros */
  1994.   if (!strcmp (ucase (t),"ALL"))
  1995.     strcpy (t,"(FLAGS INTERNALDATE RFC822.SIZE ENVELOPE)");
  1996.   else if (!strcmp (t,"FULL"))
  1997.     strcpy (t,"(FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY)");
  1998.   else if (!strcmp (t,"FAST")) strcpy (t,"(FLAGS INTERNALDATE RFC822.SIZE)");
  1999.   if (list = (*t == '(')) t++;    /* skip open paren */
  2000.   if (s = strtok (t," ")) do {    /* parse attribute list */
  2001.     if (list && (i = strlen (s)) && (s[i-1] == ')')) {
  2002.       list = NIL;        /* done with list */
  2003.       s[i-1] = '\0';        /* tie off last item */
  2004.     }
  2005.     fa[k] = NIL;        /* default to no argument */
  2006.     if (!strcmp (s,"UID")) {    /* no-op if implicit */
  2007.       if (!uid) f[k++] = fetch_uid;
  2008.     }
  2009.     else if (!strcmp (s,"FLAGS")) f[k++] = fetch_flags;
  2010.     else if (!strcmp (s,"INTERNALDATE")) f[k++] = fetch_internaldate;
  2011.     else if (!strcmp (s,"RFC822.SIZE")) f[k++] = fetch_rfc822_size;
  2012.     else if (!strcmp (s,"ENVELOPE")) {
  2013.       parse_envs = T;        /* we will need to parse envelopes */
  2014.       f[k++] = fetch_envelope;
  2015.     }
  2016.     else if (!strcmp (s,"BODY")) {
  2017.       parse_envs = parse_bodies = T;
  2018.       f[k++] = fetch_body;
  2019.     }
  2020.     else if (!strcmp (s,"BODYSTRUCTURE")) {
  2021.       parse_envs = parse_bodies = T;
  2022.       f[k++] = fetch_bodystructure;
  2023.     }
  2024.  
  2025.     else if (!strcmp (s,"RFC822") || !strcmp (s,"RFC822.PEEK")) {
  2026.       fa[k] = s[6] ? (void *) FT_PEEK : NIL;
  2027.       f[k++] = fetch_rfc822;
  2028.     }
  2029.     else if (!strcmp (s,"RFC822.HEADER")) f[k++] = fetch_rfc822_header;
  2030.     else if (!strcmp (s,"RFC822.TEXT") || !strcmp (s,"RFC822.TEXT.PEEK")) {
  2031.       fa[k] = s[11] ? (void *) FT_PEEK : NIL;
  2032.       f[k++] = fetch_rfc822_text;
  2033.     }
  2034.     else if (!strcmp (s,"RFC822.HEADER.LINES") &&
  2035.          (v = strtok (NIL,"\015\012")) &&
  2036.          (fa[k] = (void *) parse_stringlist (&v,&list)))
  2037.       f[k++] = fetch_rfc822_header_lines;
  2038.     else if (!strcmp (s,"RFC822.HEADER.LINES.NOT") &&
  2039.          (v = strtok (NIL,"\015\012")) &&
  2040.          (fa[k] = (void *) parse_stringlist (&v,&list)))
  2041.       f[k++] = fetch_rfc822_header_lines_not;
  2042.     else if (!strncmp (s,"BODY[",5) || !strncmp (s,"BODY.PEEK[",10)) {
  2043.       TEXTARGS *ta = (TEXTARGS *)
  2044.     memset (fs_get (sizeof (TEXTARGS)),0,sizeof (TEXTARGS));
  2045.       if (s[4] == '.') {    /* wanted peek? */
  2046.     ta->flags = FT_PEEK;
  2047.     s += 10;        /* skip to section specifier */
  2048.       }
  2049.       else s += 5;        /* skip to section specifier */
  2050.       f[k] = fetch_body_part_contents;
  2051.       if (*(v = s) != ']') {    /* non-empty section specifier? */
  2052.     if (isdigit (*v)) {    /* have section specifier? */
  2053.                 /* need envelopes and bodies */
  2054.       parse_envs = parse_bodies = T;
  2055.       while (isdigit (*v))    /* scan to end of section specifier */
  2056.         if ((*++v == '.') && isdigit (v[1])) v++;
  2057.                 /* any IMAP4rev1 stuff following? */
  2058.       if ((*v == '.') && isalpha (v[1])) {
  2059.         *v++ = '\0';    /* yes, tie off section specifier */
  2060.         if (!strncmp (v,"MIME",4)) {
  2061.           v += 4;        /* found <section>.MIME */
  2062.           f[k] = fetch_body_part_mime;
  2063.         }
  2064.       }
  2065.       else if (*v != ']') {    /* better be the end if no IMAP4rev1 stuff */
  2066.         fs_give ((void **) &ta);/* clean up */
  2067.         response = "%.80s BAD Syntax error in section specifier\015\012";
  2068.         return;
  2069.       }
  2070.     }
  2071.  
  2072.     if (*v != ']') {    /* IMAP4rev1 stuff here? */
  2073.       if (!strncmp (v,"HEADER",6)) {
  2074.         *v = '\0';        /* tie off in case top level */
  2075.         v += 6;        /* found [<section>.]HEADER */
  2076.         f[k] = fetch_body_part_header;
  2077.                 /* partial headers wanted? */
  2078.         if (!strncmp (v,".FIELDS",7)) {
  2079.           v += 7;        /* yes */
  2080.           if (!strncmp (v,".NOT",4)) {
  2081.         v += 4;        /* want to exclude named headers */
  2082.         ta->flags |= FT_NOT;
  2083.           }
  2084.           if (*v || !(v = strtok (NIL,"\015\012")) ||
  2085.           !(ta->lines = parse_stringlist (&v,&list))) {
  2086.         fs_give ((void **) &ta);/* clean up */
  2087.         response = "%.80s BAD Syntax error in header fields\015\012";
  2088.         return;
  2089.           }
  2090.         }
  2091.       }
  2092.       else if (!strncmp (v,"TEXT",4)) {
  2093.         *v = '\0';        /* tie off in case top level */
  2094.         v += 4;        /* found [<section>.]TEXT */
  2095.         f[k] = fetch_body_part_text;
  2096.       }
  2097.       else {
  2098.         fs_give ((void **) &ta);/* clean up */
  2099.         response = "%.80s BAD Unknown section text specifier\015\012";
  2100.         return;
  2101.       }
  2102.     }
  2103.       }
  2104.                 /* tie off section */
  2105.       if (*v == ']') *v++ = '\0';
  2106.       else {            /* bogon */
  2107.     if (ta->lines) mail_free_stringlist (&ta->lines);
  2108.     fs_give ((void **) &ta);/* clean up */
  2109.     response = "%.80s BAD Section specifier not terminated\015\012";
  2110.     return;
  2111.       }
  2112.  
  2113.       if (*v == '<') {        /* partial specifier? */
  2114.     ta->first = strtoul (v+1,&v,10);
  2115.     if ((*v++ != '.') || !(ta->last = strtoul (v,&v,10)) || (*v++ != '>')){
  2116.       if (ta->lines) mail_free_stringlist (&ta->lines);
  2117.       fs_give ((void **) &ta);
  2118.       response ="%.80s BAD Syntax error in partial text specifier\015\012";
  2119.       return;
  2120.     }
  2121.       }
  2122.       switch (*v) {        /* what's there now? */
  2123.       case ' ':            /* more follows */
  2124.     *--v = ' ';        /* patch a space back in */
  2125.     *--v = 'x';        /* and a hokey character before that */
  2126.     strtok (v," ");        /* reset strtok mechanism */
  2127.     break;
  2128.       case '\0':        /* none */
  2129.     break;
  2130.       case ')':            /* end of list */
  2131.     if (list && !v[1]) {    /* make sure of that */
  2132.       list = NIL;
  2133.       strtok (v," ");    /* reset strtok mechanism */
  2134.       break;        /* all done */
  2135.     }
  2136.                 /* otherwise it's a bogon, drop in */
  2137.       default:            /* bogon */
  2138.     if (ta->lines) mail_free_stringlist (&ta->lines);
  2139.     fs_give ((void **) &ta);
  2140.     response = "%.80s BAD Syntax error after section specifier\015\012";
  2141.     return;
  2142.       }
  2143.                 /* make copy of section specifier */
  2144.       if (s && *s) ta->section = cpystr (s);
  2145.       fa[k++] = (void *) ta;    /* set argument */
  2146.     }
  2147.     else {            /* unknown attribute */
  2148.       response = badatt;
  2149.       return;
  2150.     }
  2151.   } while ((s = strtok (NIL," ")) && (k < MAXFETCH) && list);
  2152.   else {
  2153.     response = misarg;        /* missing attribute list */
  2154.     return;
  2155.   }
  2156.  
  2157.   if (s) {            /* too many attributes? */
  2158.     response = "%.80s BAD Excessively complex FETCH attribute list\015\012";
  2159.     return;
  2160.   }
  2161.   if (list) {            /* too many attributes? */
  2162.     response = "%.80s BAD Unterminated FETCH attribute list\015\012";
  2163.     return;
  2164.   }
  2165.   f[k] = NIL;            /* tie off attribute list */
  2166.                 /* c-client clobbers sequence, use spare */
  2167.   for (i = 1; i <= nmsgs; i++)
  2168.     mail_elt (stream,i)->spare = mail_elt (stream,i)->sequence;
  2169.                 /* for each requested message */
  2170.   for (i = 1; i <= nmsgs; i++) if (mail_elt (stream,i)->spare) {
  2171.                 /* parse envelope, set body, do warnings */
  2172.     if (parse_envs) mail_fetchstructure (stream,i,parse_bodies ? &b : NIL);
  2173.     quell_events = T;        /* can't do any events now */
  2174.     PSOUT ("* ");        /* leader */
  2175.     pnum (i);
  2176.     PSOUT (" FETCH (");
  2177.     (*f[0]) (i,fa[0]);        /* do first attribute */
  2178.     for (k = 1; f[k]; k++) {    /* for each subsequent attribute */
  2179.       PBOUT (' ');        /* delimit with space */
  2180.       (*f[k]) (i,fa[k]);    /* do that attribute */
  2181.     }
  2182.     PSOUT (")\015\012");    /* trailer */
  2183.     quell_events = NIL;        /* events alright now */
  2184.   }
  2185. }
  2186.  
  2187. /* Fetch message body structure (extensible)
  2188.  * Accepts: message number
  2189.  *        extra argument
  2190.  */
  2191.  
  2192. void fetch_bodystructure (unsigned long i,void *args)
  2193. {
  2194.   BODY *body;
  2195.   mail_fetchstructure (stream,i,&body);
  2196.   PSOUT ("BODYSTRUCTURE ");
  2197.   pbodystructure (body);    /* output body */
  2198. }
  2199.  
  2200.  
  2201. /* Fetch message body structure (non-extensible)
  2202.  * Accepts: message number
  2203.  *        extra argument
  2204.  */
  2205.  
  2206.  
  2207. void fetch_body (unsigned long i,void *args)
  2208. {
  2209.   BODY *body;
  2210.   mail_fetchstructure (stream,i,&body);
  2211.   PSOUT ("BODY ");        /* output attribute */
  2212.   pbody (body);            /* output body */
  2213. }
  2214.  
  2215. /* Fetch body part MIME header
  2216.  * Accepts: message number
  2217.  *        extra argument
  2218.  */
  2219.  
  2220. void fetch_body_part_mime (unsigned long i,void *args)
  2221. {
  2222.   TEXTARGS *ta = (TEXTARGS *) args;
  2223.   if (i) {            /* do work? */
  2224.     SIZEDTEXT st;
  2225.     unsigned long uid = mail_uid (stream,i);
  2226.     char *tmp = (char *) fs_get (100 + strlen (ta->section));
  2227.     sprintf (tmp,"BODY[%s.MIME]",ta->section);
  2228.                 /* try to use remembered text */
  2229.     if ((uid == lastuid) && !strcmp (tmp,lastid)) st = lastst;
  2230.     else {            /* get data */
  2231.       st.data = (unsigned char *)
  2232.     mail_fetch_mime (stream,i,ta->section,&st.size,ta->flags);
  2233.       if (ta->first || ta->last) remember (uid,tmp,&st);
  2234.     }
  2235.     pbodypartstring (i,tmp,&st,ta);
  2236.     fs_give ((void **) &tmp);
  2237.   }
  2238.   else {            /* clean up the arguments */
  2239.     fs_give ((void **) &ta->section);
  2240.     fs_give ((void **) &args);
  2241.   }
  2242. }
  2243.  
  2244.  
  2245. /* Fetch body part contents
  2246.  * Accepts: message number
  2247.  *        extra argument
  2248.  */
  2249.  
  2250. void fetch_body_part_contents (unsigned long i,void *args)
  2251. {
  2252.   TEXTARGS *ta = (TEXTARGS *) args;
  2253.   if (i) {            /* do work? */
  2254.     SIZEDTEXT st;
  2255.     char *tmp = (char *) fs_get (100+(ta->section ? strlen (ta->section) : 0));
  2256.     unsigned long uid = mail_uid (stream,i);
  2257.     sprintf (tmp,"BODY[%s]",ta->section ? ta->section : "");
  2258.                 /* try to use remembered text */
  2259.     if ((uid == lastuid) && !strcmp (tmp,lastid)) st = lastst;
  2260.     else {            /* get data */
  2261.       st.data = (unsigned char *)
  2262.     mail_fetch_body (stream,i,ta->section,&st.size,ta->flags);
  2263.       if (ta->first || ta->last) remember (uid,tmp,&st);
  2264.     }
  2265.     pbodypartstring (i,tmp,&st,ta);
  2266.     fs_give ((void **) &tmp);
  2267.   }
  2268.   else {            /* clean up the arguments */
  2269.     fs_give ((void **) &ta->section);
  2270.     fs_give ((void **) &args);
  2271.   }
  2272. }
  2273.  
  2274. /* Fetch MESSAGE/RFC822 body part header
  2275.  * Accepts: message number
  2276.  *        extra argument
  2277.  */
  2278.  
  2279. void fetch_body_part_header (unsigned long i,void *args)
  2280. {
  2281.   TEXTARGS *ta = (TEXTARGS *) args;
  2282.   unsigned long len = 100 + (ta->section ? strlen (ta->section) : 0);
  2283.   STRINGLIST *s;
  2284.   for (s = ta->lines; s; s = s->next) len += s->text.size + 1;
  2285.   if (i) {            /* do work? */
  2286.     SIZEDTEXT st;
  2287.     char *tmp = (char *) fs_get (len);
  2288.     PSOUT ("BODY[");
  2289.                 /* output attribute */
  2290.     if (ta->section && *ta->section) {
  2291.       PSOUT (ta->section);
  2292.       PBOUT ('.');
  2293.     }
  2294.     PSOUT ("HEADER");
  2295.     if (ta->lines) {
  2296.       PSOUT ((ta->flags & FT_NOT) ? ".FIELDS.NOT " : ".FIELDS ");
  2297.       pstringlist (ta->lines);
  2298.     }
  2299.     strcpy (tmp,"]");        /* close section specifier */
  2300.     st.data = (unsigned char *)    /* get data (no hope in using remember here) */
  2301.       mail_fetch_header (stream,i,ta->section,ta->lines,&st.size,ta->flags);
  2302.     pbodypartstring (i,tmp,&st,ta);
  2303.     fs_give ((void **) &tmp);
  2304.   }
  2305.   else {            /* clean up the arguments */
  2306.     if (ta->lines) mail_free_stringlist (&ta->lines);
  2307.     if (ta->section) fs_give ((void **) &ta->section);
  2308.     fs_give ((void **) &args);
  2309.   }
  2310. }
  2311.  
  2312. /* Fetch MESSAGE/RFC822 body part text
  2313.  * Accepts: message number
  2314.  *        extra argument
  2315.  */
  2316.  
  2317. void fetch_body_part_text (unsigned long i,void *args)
  2318. {
  2319.   TEXTARGS *ta = (TEXTARGS *) args;
  2320.   if (i) {            /* do work? */
  2321.     SIZEDTEXT st;
  2322.     char *tmp = (char *) fs_get (100+(ta->section ? strlen (ta->section) : 0));
  2323.     unsigned long uid = mail_uid (stream,i);
  2324.                 /* output attribute */
  2325.     if (ta->section && *ta->section) sprintf (tmp,"BODY[%s.TEXT]",ta->section);
  2326.     else strcpy (tmp,"BODY[TEXT]");
  2327.                 /* try to use remembered text */
  2328.     if ((uid == lastuid) && !strcmp (tmp,lastid)) st = lastst;
  2329.     else {            /* get data */
  2330.       st.data = (unsigned char *)
  2331.     mail_fetch_text (stream,i,ta->section,&st.size,ta->flags);
  2332.       if (ta->first || ta->last) remember (uid,tmp,&st);
  2333.     }
  2334.     pbodypartstring (i,tmp,&st,ta);
  2335.     fs_give ((void **) &tmp);
  2336.   }
  2337.   else {            /* clean up the arguments */
  2338.     if (ta->section) fs_give ((void **) &ta->section);
  2339.     fs_give ((void **) &args);
  2340.   }
  2341. }
  2342.  
  2343.  
  2344. /* Remember body part text for subsequent partial fetching
  2345.  * Accepts: message UID
  2346.  *        body part id
  2347.  *        text
  2348.  */
  2349.  
  2350. void remember (unsigned long uid,char *id,SIZEDTEXT *st)
  2351. {
  2352.   lastuid = uid;        /* remember UID */
  2353.   if (lastid) fs_give ((void **) &lastid);
  2354.   lastid = cpystr (id);        /* remember body part id */
  2355.   if (lastst.data) fs_give ((void **) &lastst.data);
  2356.                 /* remember text */
  2357.   lastst.data = (unsigned char *) cpystr ((char *) st->data);
  2358.   lastst.size = st->size;
  2359. }
  2360.  
  2361. /* Fetch envelope
  2362.  * Accepts: message number
  2363.  *        extra argument
  2364.  */
  2365.  
  2366. void fetch_envelope (unsigned long i,void *args)
  2367. {
  2368.   ENVELOPE *env = mail_fetchenvelope (stream,i);
  2369.   PSOUT ("ENVELOPE ");        /* output attribute */
  2370.   penv (env);            /* output envelope */
  2371. }
  2372.  
  2373. /* Fetch matching header lines
  2374.  * Accepts: message number
  2375.  *        extra argument
  2376.  */
  2377.  
  2378. void fetch_rfc822_header_lines (unsigned long i,void *args)
  2379. {
  2380.   STRINGLIST *sa = (STRINGLIST *) args;
  2381.   if (i) {            /* do work? */
  2382.     SIZEDTEXT st;
  2383.     st.data = (unsigned char *)
  2384.       mail_fetch_header (stream,i,NIL,sa,&st.size,FT_PEEK);
  2385.                 /* output literal */
  2386.     pnstring ("RFC822.HEADER",&st);
  2387.   }
  2388.   else mail_free_stringlist (&sa);
  2389. }
  2390.  
  2391.  
  2392. /* Fetch not-matching header lines
  2393.  * Accepts: message number
  2394.  *        extra argument
  2395.  */
  2396.  
  2397. void fetch_rfc822_header_lines_not (unsigned long i,void *args)
  2398. {
  2399.   STRINGLIST *sa = (STRINGLIST *) args;
  2400.   if (i) {            /* do work? */
  2401.     SIZEDTEXT st;
  2402.     st.data = (unsigned char *)
  2403.       mail_fetch_header (stream,i,NIL,sa,&st.size,FT_NOT | FT_PEEK);
  2404.                 /* output literal */
  2405.     pnstring ("RFC822.HEADER",&st);
  2406.   }
  2407.   else mail_free_stringlist (&sa);
  2408. }
  2409.  
  2410. /* Fetch flags
  2411.  * Accepts: message number
  2412.  *        extra argument
  2413.  */
  2414.  
  2415. void fetch_flags (unsigned long i,void *args)
  2416. {
  2417.   unsigned long u;
  2418.   char *t,tmp[MAILTMPLEN];
  2419.   int c = NIL;
  2420.   MESSAGECACHE *elt = mail_elt (stream,i);
  2421.   if (!elt->valid) {        /* have valid flags yet? */
  2422.     sprintf (tmp,"%lu",i);
  2423.     mail_fetch_flags (stream,tmp,NIL);
  2424.   }
  2425.   PSOUT ("FLAGS (");        /* output attribute */
  2426.                 /* output system flags */
  2427.   if (elt->recent) put_flag (&c,"\\Recent");
  2428.   if (elt->seen) put_flag (&c,"\\Seen");
  2429.   if (elt->deleted) put_flag (&c,"\\Deleted");
  2430.   if (elt->flagged) put_flag (&c,"\\Flagged");
  2431.   if (elt->answered) put_flag (&c,"\\Answered");
  2432.   if (elt->draft) put_flag (&c,"\\Draft");
  2433.   if (u = elt->user_flags) do    /* any user flags? */
  2434.     if (t = stream->user_flags[find_rightmost_bit (&u)]) put_flag (&c,t);
  2435.   while (u);            /* until no more user flags */
  2436.   PBOUT (')');            /* end of flags */
  2437.   elt->spare2 = NIL;        /* we've sent the update */
  2438. }
  2439.  
  2440.  
  2441. /* Output a flag
  2442.  * Accepts: pointer to current delimiter character
  2443.  *        flag to output
  2444.  * Changes delimiter character to space
  2445.  */
  2446.  
  2447. void put_flag (int *c,char *s)
  2448. {
  2449.   if (*c) PBOUT (*c);        /* put delimiter */
  2450.   PSOUT (s);            /* dump flag */
  2451.   *c = ' ';            /* change delimiter if necessary */
  2452. }
  2453.  
  2454.  
  2455. /* Output flags if was unseen
  2456.  * Accepts: message number
  2457.  *        prior value of Seen flag
  2458.  */
  2459.  
  2460. void changed_flags (unsigned long i,int f)
  2461. {
  2462.                 /* was unseen, now seen? */
  2463.   if (!f && mail_elt (stream,i)->seen) {
  2464.     PBOUT (' ');        /* yes, delimit with space */
  2465.     fetch_flags (i,NIL);    /* output flags */
  2466.   }
  2467. }
  2468.  
  2469. /* Fetch message internal date
  2470.  * Accepts: message number
  2471.  *        extra argument
  2472.  */
  2473.  
  2474. void fetch_internaldate (unsigned long i,void *args)
  2475. {
  2476.   char tmp[MAILTMPLEN];
  2477.   MESSAGECACHE *elt = mail_elt (stream,i);
  2478.   if (!elt->day) {        /* have internal date yet? */
  2479.     sprintf (tmp,"%lu",i);
  2480.     mail_fetch_fast (stream,tmp,NIL);
  2481.   }
  2482.   PSOUT ("INTERNALDATE \"");
  2483.   PSOUT (mail_date (tmp,elt));
  2484.   PBOUT ('"');
  2485. }
  2486.  
  2487.  
  2488. /* Fetch unique identifier
  2489.  * Accepts: message number
  2490.  *        extra argument
  2491.  */
  2492.  
  2493. void fetch_uid (unsigned long i,void *args)
  2494. {
  2495.   PSOUT ("UID ");
  2496.   pnum (mail_uid (stream,i));
  2497. }
  2498.  
  2499. /* Fetch complete RFC-822 format message
  2500.  * Accepts: message number
  2501.  *        extra argument
  2502.  */
  2503.  
  2504. void fetch_rfc822 (unsigned long i,void *args)
  2505. {
  2506.   if (i) {            /* do work? */
  2507.     int f = mail_elt (stream,i)->seen;
  2508. #if 0
  2509.     SIZEDTEXT st;
  2510.     st.data = (unsigned char *)
  2511.       mail_fetch_message (stream,i,&st.size,(long) args);
  2512.     pnstring ("RFC822",st);
  2513. #else
  2514.     /* Yes, this version is bletcherous, but mail_fetch_message() requires
  2515.        too much memory */
  2516.     SIZEDTEXT txt,hdr;
  2517.     char *s = mail_fetch_header (stream,i,NIL,NIL,&hdr.size,FT_PEEK);
  2518.     hdr.data = (unsigned char *) memcpy (fs_get (hdr.size),s,hdr.size);
  2519.     txt.data = (unsigned char *)
  2520.       mail_fetch_text (stream,i,NIL,&txt.size,(long) args);
  2521.     PSOUT ("RFC822 {");
  2522.     pnum (hdr.size + txt.size);
  2523.     PSOUT ("}\015\012");
  2524.     ptext (&hdr);
  2525.     ptext (&txt);
  2526.     fs_give ((void **) &hdr.data);
  2527. #endif
  2528.     changed_flags (i,f);    /* output changed flags */
  2529.   }
  2530. }
  2531.  
  2532.  
  2533. /* Fetch RFC-822 header
  2534.  * Accepts: message number
  2535.  *        extra argument
  2536.  */
  2537.  
  2538. void fetch_rfc822_header (unsigned long i,void *args)
  2539. {
  2540.   SIZEDTEXT st;
  2541.   st.data = (unsigned char *)
  2542.     mail_fetch_header (stream,i,NIL,NIL,&st.size,FT_PEEK);
  2543.   pnstring ("RFC822.HEADER",&st);
  2544. }
  2545.  
  2546.  
  2547. /* Fetch RFC-822 message length
  2548.  * Accepts: message number
  2549.  *        extra argument
  2550.  */
  2551.  
  2552. void fetch_rfc822_size (unsigned long i,void *args)
  2553. {
  2554.   char tmp[MAILTMPLEN];
  2555.   MESSAGECACHE *elt = mail_elt (stream,i);
  2556.   if (!elt->rfc822_size) {    /* have message size yet? */
  2557.     sprintf (tmp,"%lu",i);
  2558.     mail_fetch_fast (stream,tmp,NIL);
  2559.   }
  2560.   PSOUT ("RFC822.SIZE ");
  2561.   pnum (elt->rfc822_size);
  2562. }
  2563.  
  2564. /* Fetch RFC-822 text only
  2565.  * Accepts: message number
  2566.  *        extra argument
  2567.  */
  2568.  
  2569. void fetch_rfc822_text (unsigned long i,void *args)
  2570. {
  2571.   if (i) {            /* do work? */
  2572.     int f = mail_elt (stream,i)->seen;
  2573.     SIZEDTEXT st;
  2574.     st.data = (unsigned char *)
  2575.       mail_fetch_text (stream,i,NIL,&st.size,(long) args);
  2576.     pnstring ("RFC822.TEXT",&st);
  2577.     changed_flags (i,f);    /* output changed flags */
  2578.   }
  2579. }
  2580.  
  2581. /* Print envelope
  2582.  * Accepts: body
  2583.  */
  2584.  
  2585. void penv (ENVELOPE *env)
  2586. {
  2587.   PBOUT ('(');            /* delimiter */
  2588.   if (env) {            /* only if there is an envelope */
  2589.     pstring (env->date);    /* output envelope fields */
  2590.     PBOUT (' ');
  2591.     pstring (env->subject);
  2592.     PBOUT (' ');
  2593.     paddr (env->from);
  2594.     PBOUT (' ');
  2595.     paddr (env->sender);
  2596.     PBOUT (' ');
  2597.     paddr (env->reply_to);
  2598.     PBOUT (' ');
  2599.     paddr (env->to);
  2600.     PBOUT (' ');
  2601.     paddr (env->cc);
  2602.     PBOUT (' ');
  2603.     paddr (env->bcc);
  2604.     PBOUT (' ');
  2605.     pstring (env->in_reply_to);
  2606.     PBOUT (' ');
  2607.     pstring (env->message_id);
  2608.   }
  2609.                 /* no envelope */
  2610.   else PSOUT ("NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL");
  2611.   PBOUT (')');            /* end of envelope */
  2612. }
  2613.  
  2614. /* Print body structure (extensible)
  2615.  * Accepts: body
  2616.  */
  2617.  
  2618. void pbodystructure (BODY *body)
  2619. {
  2620.   PBOUT ('(');            /* delimiter */
  2621.   if (body) {            /* only if there is a body */
  2622.     PART *part;
  2623.                 /* multipart type? */
  2624.     if (body->type == TYPEMULTIPART) {
  2625.                 /* print each part */
  2626.       if (part = body->nested.part)
  2627.     for (; part; part = part->next) pbodystructure (&(part->body));
  2628.       else pbodystructure (NIL);
  2629.       PBOUT (' ');        /* space delimiter */
  2630.       pstring (body->subtype);    /* subtype */
  2631.       PBOUT (' ');
  2632.       pparam (body->parameter);    /* multipart body extension data */
  2633.       PBOUT (' ');
  2634.       if (body->disposition.type) {
  2635.     PBOUT ('(');
  2636.     pstring (body->disposition.type);
  2637.     PBOUT (' ');
  2638.     pparam (body->disposition.parameter);
  2639.     PBOUT (')');
  2640.       }
  2641.       else PSOUT ("NIL");
  2642.       PBOUT (' ');
  2643.       pstringorlist (body->language);
  2644.     }
  2645.  
  2646.     else {            /* non-multipart body type */
  2647.       pstring ((char *) body_types[body->type]);
  2648.       PBOUT (' ');
  2649.       pstring (body->subtype);
  2650.       PBOUT (' ');
  2651.       pparam (body->parameter);
  2652.       PBOUT (' ');
  2653.       pstring (body->id);
  2654.       PBOUT (' ');
  2655.       pstring (body->description);
  2656.       PBOUT (' ');
  2657.       pstring ((char *) body_encodings[body->encoding]);
  2658.       PBOUT (' ');
  2659.       pnum (body->size.bytes);
  2660.       switch (body->type) {    /* extra stuff depends upon body type */
  2661.       case TYPEMESSAGE:
  2662.                 /* can't do this if not RFC822 */
  2663.     if (strcmp (body->subtype,"RFC822")) break;
  2664.     PBOUT (' ');
  2665.     penv (body->nested.msg->env);
  2666.     PBOUT (' ');
  2667.     pbodystructure (body->nested.msg->body);
  2668.       case TYPETEXT:
  2669.     PBOUT (' ');
  2670.     pnum (body->size.lines);
  2671.     break;
  2672.       default:
  2673.     break;
  2674.       }
  2675.       PBOUT (' ');
  2676.       pstring (body->md5);
  2677.       PBOUT (' ');
  2678.       if (body->disposition.type) {
  2679.     PBOUT ('(');
  2680.     pstring (body->disposition.type);
  2681.     PBOUT (' ');
  2682.     pparam (body->disposition.parameter);
  2683.     PBOUT (')');
  2684.       }
  2685.       else PSOUT ("NIL");
  2686.       PBOUT (' ');
  2687.       pstringorlist (body->language);
  2688.     }
  2689.   }
  2690.                 /* no body */
  2691.   else PSOUT ("\"TEXT\" \"PLAIN\" (\"CHARSET\" \"US-ASCII\") NIL NIL \"7BIT\" 0 0 NIL NIL NIL");
  2692.   PBOUT (')');            /* end of body */
  2693. }
  2694.  
  2695. /* Print body (non-extensible)
  2696.  * Accepts: body
  2697.  */
  2698.  
  2699. void pbody (BODY *body)
  2700. {
  2701.   PBOUT ('(');            /* delimiter */
  2702.   if (body) {            /* only if there is a body */
  2703.     PART *part;
  2704.                 /* multipart type? */
  2705.     if (body->type == TYPEMULTIPART) {
  2706.                 /* print each part */
  2707.       if (part = body->nested.part)
  2708.     for (; part; part = part->next) pbody (&(part->body));
  2709.       else pbody (NIL);
  2710.       PBOUT (' ');        /* space delimiter */
  2711.       pstring (body->subtype);    /* and finally the subtype */
  2712.     }
  2713.     else {            /* non-multipart body type */
  2714.       pstring ((char *) body_types[body->type]);
  2715.       PBOUT (' ');
  2716.       pstring (body->subtype);
  2717.       PBOUT (' ');
  2718.       pparam (body->parameter);
  2719.       PBOUT (' ');
  2720.       pstring (body->id);
  2721.       PBOUT (' ');
  2722.       pstring (body->description);
  2723.       PBOUT (' ');
  2724.       pstring ((char *) body_encodings[body->encoding]);
  2725.       PBOUT (' ');
  2726.       pnum (body->size.bytes);
  2727.       switch (body->type) {    /* extra stuff depends upon body type */
  2728.       case TYPEMESSAGE:
  2729.                 /* can't do this if not RFC822 */
  2730.     if (strcmp (body->subtype,"RFC822")) break;
  2731.     PBOUT (' ');
  2732.     penv (body->nested.msg->env);
  2733.     PBOUT (' ');
  2734.     pbody (body->nested.msg->body);
  2735.       case TYPETEXT:
  2736.     PBOUT (' ');
  2737.     pnum (body->size.lines);
  2738.     break;
  2739.       default:
  2740.     break;
  2741.       }
  2742.     }
  2743.   }
  2744.                 /* no body */
  2745.   else PSOUT ("\"TEXT\" \"PLAIN\" (\"CHARSET\" \"US-ASCII\") NIL NIL \"7BIT\" 0 0");
  2746.   PBOUT (')');            /* end of body */
  2747. }
  2748.  
  2749. /* Print parameter list
  2750.  * Accepts: paramter
  2751.  */
  2752.  
  2753. void pparam (PARAMETER *param)
  2754. {
  2755.   if (param) {            /* one specified? */
  2756.     PBOUT ('(');
  2757.     do {
  2758.       pstring (param->attribute);
  2759.       PBOUT (' ');
  2760.       pstring (param->value);
  2761.       if (param = param->next) PBOUT (' ');
  2762.     } while (param);
  2763.     PBOUT (')');        /* end of parameters */
  2764.   }
  2765.   else PSOUT ("NIL");
  2766. }
  2767.  
  2768.  
  2769. /* Print address list
  2770.  * Accepts: address list
  2771.  */
  2772.  
  2773. void paddr (ADDRESS *a)
  2774. {
  2775.   if (a) {            /* have anything in address? */
  2776.     PBOUT ('(');        /* open the address list */
  2777.     do {            /* for each address */
  2778.       PBOUT ('(');        /* open the address */
  2779.       pstring (a->personal);    /* personal name */
  2780.       PBOUT (' ');
  2781.       pstring (a->adl);        /* at-domain-list */
  2782.       PBOUT (' ');
  2783.       pstring (a->mailbox);    /* mailbox */
  2784.       PBOUT (' ');
  2785.       pstring (a->host);    /* domain name of mailbox's host */
  2786.       PBOUT (')');        /* terminate address */
  2787.     } while (a = a->next);    /* until end of address */
  2788.     PBOUT (')');        /* close address list */
  2789.   }
  2790.   else PSOUT ("NIL");        /* empty address */
  2791. }
  2792.  
  2793. /* Print number
  2794.  * Accepts: number
  2795.  */
  2796.  
  2797. void pnum (unsigned long i)
  2798. {
  2799.   char tmp[MAILTMPLEN];
  2800.   sprintf (tmp,"%lu",i);
  2801.   PSOUT (tmp);
  2802. }
  2803.  
  2804.  
  2805. /* Print null-terminated string
  2806.  * Accepts: string
  2807.  */
  2808.  
  2809. void pstring (char *s)
  2810. {
  2811.   SIZEDTEXT st;
  2812.   st.data = (unsigned char *) s;/* set up sized text */
  2813.   st.size = s ? strlen (s) : 0;    /* NIL text is zero size */
  2814.   pnstring (NIL,&st);        /* print nstring */
  2815. }
  2816.  
  2817.  
  2818. /* Print NIL or string
  2819.  * Accepts: label to be output before nstring
  2820.  *        pointer to sized text or NIL
  2821.  */
  2822.  
  2823. void pnstring (char *label,SIZEDTEXT *st)
  2824. {
  2825.   if (label) {
  2826.     PSOUT (label);
  2827.     PBOUT (' ');
  2828.   }
  2829.   if (st && st->data) psizedtext (st);
  2830.   else PSOUT ("NIL");
  2831. }
  2832.  
  2833.  
  2834. /* Print atom or string
  2835.  * Accepts: astring
  2836.  */
  2837.  
  2838. void pastring (char *s)
  2839. {
  2840.   char *t;
  2841.   if (!*s) PSOUT ("\"\"");    /* empty string */
  2842.   else {            /* see if atom */
  2843.     for (t = s; (*t > ' ') && !(*t & 0x80) &&
  2844.      (*t != '"') && (*t != '\\') && (*t != '(') && (*t != ')') &&
  2845.      (*t != '{') && (*t != '%') && (*t != '*'); t++);
  2846.     if (*t) pstring (s);    /* not an atom */
  2847.     else PSOUT (s);        /* else plop down as atomic */
  2848.   }
  2849. }
  2850.  
  2851. /* Print body part string
  2852.  * Accepts: message number
  2853.  *        body part id (note: must have space at end to append stuff)
  2854.  *        sized text of string
  2855.  *        text printing arguments
  2856.  */
  2857.  
  2858. void pbodypartstring (unsigned long msgno,char *id,SIZEDTEXT *st,TEXTARGS *ta)
  2859. {
  2860.   int f = mail_elt (stream,msgno)->seen;
  2861.   if (st->data) {        /* only if have useful data */
  2862.                 /* partial specifier */
  2863.     if (ta->first || ta->last) sprintf (id + strlen (id),"<%lu>",ta->first);
  2864.                   /* in case first byte beyond end of text */
  2865.     if (st->size <= ta->first) st->size = ta->first = 0;
  2866.     else {            /* offset and truncate */
  2867.       st->data += ta->first;    /* move to desired position */
  2868.       st->size -= ta->first;    /* reduced size */
  2869.       if (ta->last && (st->size > ta->last)) st->size = ta->last;
  2870.     }
  2871.   }
  2872.   pnstring (id,st);        /* output nstring */
  2873.   changed_flags (msgno,f);    /* and changed flags */
  2874. }
  2875.  
  2876.  
  2877. /* Print string or string listlist
  2878.  * Accepts: string / string list list
  2879.  */
  2880.  
  2881. void pstringorlist (STRINGLIST *s)
  2882. {
  2883.   if (!s) PSOUT ("NIL");    /* no argument given */
  2884.                 /* output list as list */
  2885.   else if (s->next) pstringlist (s);
  2886.   else psizedtext (&s->text);    /* and single-element list as atom */
  2887. }
  2888.  
  2889.  
  2890. /* Print string list
  2891.  * Accepts: string list
  2892.  */
  2893.  
  2894. void pstringlist (STRINGLIST *s)
  2895. {
  2896.   PBOUT ('(');            /* start list */
  2897.   do {
  2898.     psizedtext (&s->text);    /* output list member */
  2899.     if (s->next) PBOUT (' ');
  2900.   } while (s = s->next);
  2901.   PBOUT (')');            /* terminate list */
  2902. }
  2903.  
  2904. /* Print sized text as literal or quoted string
  2905.  * Accepts: sized text
  2906.  */
  2907.  
  2908. void psizedtext (SIZEDTEXT *s)
  2909. {
  2910.   unsigned long i;
  2911.   for (i = 0; i < s->size; i++)    /* check if must use literal */
  2912.     if (!(s->data[i] & 0xe0) || (s->data[i] & 0x80) ||
  2913.     (s->data[i] == '"') || (s->data[i] == '\\')) {
  2914.       PBOUT ('{');
  2915.       pnum (s->size);
  2916.       PSOUT ("}\015\012");
  2917.       ptext (s);
  2918.       return;
  2919.     }
  2920.   PBOUT ('"');            /* use quoted string */
  2921.   ptext (s);
  2922.   PBOUT ('"');
  2923. }
  2924.  
  2925.  
  2926. /* Print text
  2927.  * Accepts: pointer to text
  2928.  *        pointer to size of text
  2929.  */
  2930.  
  2931. void ptext (SIZEDTEXT *txt)
  2932. {
  2933.   unsigned long i;
  2934.   for (i = 0; ((i < txt->size) && ((PBOUT (txt->data[i])) != EOF)); i++);
  2935.   if (i < txt->size) {        /* failed to complete? */
  2936.     alarm (0);            /* disable all interrupts */
  2937.     server_init (NIL,NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
  2938.     syslog (LOG_INFO,"%.80s, while writing text user=%.80s host=%.80s",
  2939.         strerror (errno),user ? user : "???",tcp_clienthost ());
  2940.     if (state == OPEN) mail_close (stream);
  2941.     state = LOGOUT;
  2942.     stream = NIL;
  2943.     _exit (1);
  2944.   }
  2945. }
  2946.  
  2947. /* Print thread
  2948.  * Accepts: thread
  2949.  */
  2950.  
  2951. void pthread (THREADNODE *thr)
  2952. {
  2953.   THREADNODE *t;
  2954.   while (thr) {            /* for each branch */
  2955.     PBOUT ('(');        /* open branch */
  2956.     pnum (thr->num);        /* first first node message number */
  2957.     for (t = thr->next; t;) {    /* any subsequent nodes? */
  2958.       PBOUT (' ');        /* yes, delimit */
  2959.       if (t->branch) {        /* branches? */
  2960.     pthread (t);        /* yes, recurse to do branch */
  2961.     t = NIL;        /* done */
  2962.       }
  2963.       else {            /* just output this number */
  2964.     pnum (t->num);
  2965.     t = t->next;        /* and do next message */
  2966.       }
  2967.     }
  2968.     PBOUT (')');        /* done with this branch */
  2969.     thr = thr->branch;        /* do next branch */
  2970.   }
  2971. }
  2972.  
  2973. /* Anonymous users may only use these mailboxes in these namespaces */
  2974.  
  2975. char *oktab[] = {"#news.", "#ftp/", "#public/", 0};
  2976.  
  2977.  
  2978. /* Check if mailbox name is OK
  2979.  * Accepts: reference name
  2980.  *        mailbox name
  2981.  */
  2982.  
  2983. long nameok (char *ref,char *name)
  2984. {
  2985.   int i;
  2986.   char *s,*t;
  2987.   if (!name) return NIL;    /* failure if missing name */
  2988.   if (!anonymous) return T;    /* otherwise OK if not anonymous */
  2989.                 /* validate reference */
  2990.   if (ref && ((*ref == '#') || (*ref == '{')))
  2991.     for (i = 0; oktab[i]; i++) {
  2992.       for (s = ref, t = oktab[i];
  2993.        *t && (*s + (isupper (*s) ? 'a'-'A' : 0)) == *t; *s++, *t++);
  2994.       if (!*t) {        /* reference OK */
  2995.     if (*name == '#') break;/* check name if override */
  2996.     else return T;        /* otherwise done */
  2997.       }
  2998.     }
  2999.                 /* ordinary names are OK */
  3000.   if ((*name != '#') && (*name != '{')) return T;
  3001.   for (i = 0; oktab[i]; i++) {    /* validate mailbox */
  3002.     for (s = name, t = oktab[i];
  3003.      *t && (*s + (isupper (*s) ? 'a'-'A' : 0)) == *t; *s++, *t++);
  3004.     if (!*t) return T;        /* name is OK */
  3005.   }
  3006.   response = "%.80s NO Anonymous may not %.80s this name\015\012";
  3007.   return NIL;
  3008. }
  3009.  
  3010.  
  3011. /* Convert possible BBoard name to actual name
  3012.  * Accepts: command
  3013.  *        mailbox name
  3014.  * Returns: maibox name
  3015.  */
  3016.  
  3017. char *bboardname (char *cmd,char *name)
  3018. {
  3019.   if (cmd[0] == 'B') {        /* want bboard? */
  3020.     char *s = litstk[litsp++] = (char *) fs_get (strlen (name) + 9);
  3021.     sprintf (s,"#public/%s",(*name == '/') ? name+1 : name);
  3022.     name = s;
  3023.   }
  3024.   return name;
  3025. }
  3026.  
  3027. /* IMAP4rev1 Authentication responder
  3028.  * Accepts: challenge
  3029.  *        length of challenge
  3030.  *        pointer to response length return location if non-NIL
  3031.  * Returns: response
  3032.  */
  3033.  
  3034. #define RESPBUFLEN 8*MAILTMPLEN
  3035.  
  3036. char *imap_responder (void *challenge,unsigned long clen,unsigned long *rlen)
  3037. {
  3038.   unsigned long i,j;
  3039.   unsigned char *t,resp[RESPBUFLEN];
  3040.   PSOUT ("+ ");
  3041.   for (t = rfc822_binary ((void *) challenge,clen,&i),j = 0; j < i; j++)
  3042.     if (t[j] > ' ') PBOUT (t[j]);
  3043.   fs_give ((void **) &t);
  3044.   CRLF;
  3045.   PFLUSH;            /* dump output buffer */
  3046.                 /* slurp response buffer */
  3047.   slurp ((char *) resp,RESPBUFLEN);
  3048.   if (!(t = (unsigned char *) strchr ((char *) resp,'\012'))) return flush ();
  3049.   if (t[-1] == '\015') --t;    /* remove CR */
  3050.   *t = '\0';            /* tie off buffer */
  3051.   return (resp[0] != '*') ?
  3052.     (char *) rfc822_base64 (resp,t-resp,rlen ? rlen : &i) : NIL;
  3053. }
  3054.  
  3055. /* Proxy copy across mailbox formats
  3056.  * Accepts: mail stream
  3057.  *        sequence to copy on this stream
  3058.  *        destination mailbox
  3059.  *        option flags
  3060.  * Returns: T if success, else NIL
  3061.  */
  3062.  
  3063. long proxycopy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
  3064. {
  3065.   MAILSTREAM *ts;
  3066.   MESSAGECACHE *elt;
  3067.   STRING st;
  3068.   MSGDATA md;
  3069.   unsigned long i,j;
  3070.   char *s,*t,tmp[MAILTMPLEN],date[MAILTMPLEN];
  3071.   md.stream = stream;
  3072.   if (!((options & CP_UID) ?    /* validate sequence */
  3073.     mail_uid_sequence (stream,sequence) : mail_sequence (stream,sequence)))
  3074.     return NIL;
  3075.   response = win;        /* cancel previous errors */
  3076.   if (lsterr) fs_give ((void **) &lsterr);
  3077.                 /* c-client clobbers sequence, use spare */
  3078.   for (i = 1; i <= nmsgs; i++)
  3079.     mail_elt (stream,i)->spare = mail_elt (stream,i)->sequence;
  3080.   for (j = 0,md.msgno = 1; md.msgno <= nmsgs; j++,md.msgno++)
  3081.     if ((elt = mail_elt (stream,md.msgno))->spare) {
  3082.       if (!(elt->valid && elt->day)) {
  3083.     sprintf (tmp,"%lu",md.msgno);
  3084.     mail_fetch_fast (stream,tmp,NIL);
  3085.       }
  3086.       memset (s = tmp,0,MAILTMPLEN);
  3087.                 /* copy flags */
  3088.       if (elt->seen) strcat (s," \\Seen");
  3089.       if (elt->deleted) strcat (s," \\Deleted");
  3090.       if (elt->flagged) strcat (s," \\Flagged");
  3091.       if (elt->answered) strcat (s," \\Answered");
  3092.       if (elt->draft) strcat (s," \\Draft");
  3093.       if (i = elt->user_flags) do 
  3094.     if ((t = stream->user_flags[find_rightmost_bit (&i)]) && *t &&
  3095.         (strlen (t) < ((size_t) (MAILTMPLEN-((s += strlen (s))+2-tmp))))) {
  3096.     *s++ = ' ';        /* space delimiter */
  3097.     strcpy (s,t);
  3098.       } while (i);        /* until no more user flags */
  3099.       INIT (&st,msg_string,(void *) &md,elt->rfc822_size);
  3100.       if (!mail_append_full (NIL,mailbox,tmp+1,mail_date (date,elt),&st)) {
  3101.     s = lsterr ? lsterr : "Unexpected APPEND failure";
  3102.     if (j) {
  3103.       sprintf (tmp,"%.80s after %lu messages",s,j);
  3104.       mm_log (tmp,ERROR);
  3105.     }
  3106.     else mm_log (s,ERROR);
  3107.     return NIL;
  3108.       }
  3109.     }
  3110.   response = win;        /* stomp any previous babble */
  3111.                 /* get new driver name if was dummy */
  3112.   sprintf (tmp,"Cross-format (%.80s -> %.80s) COPY completed",
  3113.        stream->dtb->name,(ts = mail_open (NIL,mailbox,OP_PROTOTYPE)) ?
  3114.        ts->dtb->name : "unknown");
  3115.   mm_log (tmp,NIL);
  3116.   return T;
  3117. }
  3118.  
  3119. /* Co-routines from MAIL library */
  3120.  
  3121.  
  3122. /* Message matches a search
  3123.  * Accepts: MAIL stream
  3124.  *        message number
  3125.  */
  3126.  
  3127. void mm_searched (MAILSTREAM *s,unsigned long msgno)
  3128. {
  3129.                 /* nothing to do here */
  3130. }
  3131.  
  3132.  
  3133. /* Message exists (i.e. there are that many messages in the mailbox)
  3134.  * Accepts: MAIL stream
  3135.  *        message number
  3136.  */
  3137.  
  3138. void mm_exists (MAILSTREAM *s,unsigned long number)
  3139. {
  3140.                 /* note change in number of messages */
  3141.   if ((s != tstream) && (nmsgs != number)) {
  3142.     nmsgs = number;        /* always update number of messages */
  3143.     if (quell_events) existsquelled = T;
  3144.     else {
  3145.       PSOUT ("* ");
  3146.       pnum (nmsgs);
  3147.       PSOUT (" EXISTS\015\012");
  3148.     }
  3149.     recent = 0xffffffff;    /* make sure update recent too */
  3150.   }
  3151. }
  3152.  
  3153.  
  3154. /* Message expunged
  3155.  * Accepts: MAIL stream
  3156.  *        message number
  3157.  */
  3158.  
  3159. void mm_expunged (MAILSTREAM *s,unsigned long number)
  3160. {
  3161.   if (quell_events) fatal ("Impossible EXPUNGE event");
  3162.   if (s != tstream) {
  3163.     PSOUT ("* ");
  3164.     pnum (number);
  3165.     PSOUT (" EXPUNGE\015\012");
  3166.   }
  3167.   nmsgs--;
  3168.   existsquelled = T;        /* do EXISTS when command done */
  3169. }
  3170.  
  3171.  
  3172. /* Message status changed
  3173.  * Accepts: MAIL stream
  3174.  *        message number
  3175.  */
  3176.  
  3177. void mm_flags (MAILSTREAM *s,unsigned long number)
  3178. {
  3179.   if (s != tstream) mail_elt (s,number)->spare2 = T;
  3180. }
  3181.  
  3182. /* Mailbox found
  3183.  * Accepts: hierarchy delimiter
  3184.  *        mailbox name
  3185.  *        attributes
  3186.  */
  3187.  
  3188. void mm_list (MAILSTREAM *stream,int delimiter,char *name,long attributes)
  3189. {
  3190.   mm_list_work ("LIST",delimiter,name,attributes);
  3191. }
  3192.  
  3193.  
  3194. /* Subscribed mailbox found
  3195.  * Accepts: hierarchy delimiter
  3196.  *        mailbox name
  3197.  *        attributes
  3198.  */
  3199.  
  3200. void mm_lsub (MAILSTREAM *stream,int delimiter,char *name,long attributes)
  3201. {
  3202.   mm_list_work ("LSUB",delimiter,name,attributes);
  3203. }
  3204.  
  3205.  
  3206. /* Mailbox status
  3207.  * Accepts: MAIL stream
  3208.  *        mailbox name
  3209.  *        mailbox status
  3210.  */
  3211.  
  3212. void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status)
  3213. {
  3214.   if (!quell_events) {
  3215.     char tmp[MAILTMPLEN];
  3216.     tmp[0] = tmp[1] = '\0';
  3217.     if (status->flags & SA_MESSAGES)
  3218.       sprintf (tmp + strlen (tmp)," MESSAGES %lu",status->messages);
  3219.     if (status->flags & SA_RECENT)
  3220.       sprintf (tmp + strlen (tmp)," RECENT %lu",status->recent);
  3221.     if (status->flags & SA_UNSEEN)
  3222.       sprintf (tmp + strlen (tmp)," UNSEEN %lu",status->unseen);
  3223.     if (status->flags & SA_UIDNEXT)
  3224.       sprintf (tmp + strlen (tmp)," UIDNEXT %lu",status->uidnext);
  3225.     if (status->flags & SA_UIDVALIDITY)
  3226.       sprintf (tmp + strlen(tmp)," UIDVALIDITY %lu",status->uidvalidity);
  3227.     PSOUT ("* STATUS ");
  3228.     pastring (mailbox);
  3229.     PSOUT (" (");
  3230.     PSOUT (tmp+1);
  3231.     PBOUT (')');
  3232.     CRLF;
  3233.   }
  3234. }
  3235.  
  3236. /* Worker routine for LIST and LSUB
  3237.  * Accepts: name of response
  3238.  *        hierarchy delimiter
  3239.  *        mailbox name
  3240.  *        attributes
  3241.  */
  3242.  
  3243. void mm_list_work (char *what,int delimiter,char *name,long attributes)
  3244. {
  3245.   if (!quell_events) {
  3246.     char tmp[MAILTMPLEN];
  3247.     if (finding) {
  3248.       PSOUT ("* MAILBOX ");
  3249.       PSOUT (name);
  3250.     }
  3251.                 /* new form */
  3252.     else if ((cmd[0] == 'R') || !(attributes & LATT_REFERRAL)) {
  3253.       PSOUT ("* ");
  3254.       PSOUT (what);
  3255.       PSOUT (" (");
  3256.       tmp[0] = tmp[1] = '\0';
  3257.       if (attributes & LATT_NOINFERIORS) strcat (tmp," \\NoInferiors");
  3258.       if (attributes & LATT_NOSELECT) strcat (tmp," \\NoSelect");
  3259.       if (attributes & LATT_MARKED) strcat (tmp," \\Marked");
  3260.       if (attributes & LATT_UNMARKED) strcat (tmp," \\UnMarked");
  3261.       PSOUT (tmp+1);
  3262.       switch (delimiter) {
  3263.       case '\\':        /* quoted delimiter */
  3264.       case '"':
  3265.     PSOUT (") \"\\");
  3266.     PBOUT (delimiter);
  3267.     PBOUT ('"');
  3268.     break;
  3269.       case '\0':        /* no delimiter */
  3270.     PSOUT (") NIL");
  3271.     break;
  3272.       default:            /* unquoted delimiter */
  3273.     PSOUT (") \"");
  3274.     PBOUT (delimiter);
  3275.     PBOUT ('"');
  3276.     break;
  3277.       }
  3278.       PBOUT (' ');
  3279.       pastring (name);        /* output mailbox name */
  3280.     }
  3281.     CRLF;
  3282.   }
  3283. }
  3284.  
  3285. /* Notification event
  3286.  * Accepts: MAIL stream
  3287.  *        string to log
  3288.  *        error flag
  3289.  */
  3290.  
  3291. void mm_notify (MAILSTREAM *s,char *string,long errflg)
  3292. {
  3293.   char *code;
  3294.   if (!quell_events && (!tstream || (s != tstream))) {
  3295.     switch (errflg) {
  3296.     case NIL:            /* information message, set as OK response */
  3297.       if ((string[0] == '[') &&
  3298.       ((string[1] == 'T') || (string[1] == 't')) &&
  3299.       ((string[2] == 'R') || (string[2] == 'r')) &&
  3300.       ((string[3] == 'Y') || (string[3] == 'y')) &&
  3301.       ((string[4] == 'C') || (string[4] == 'c')) &&
  3302.       ((string[5] == 'R') || (string[5] == 'r')) &&
  3303.       ((string[6] == 'E') || (string[6] == 'e')) &&
  3304.       ((string[7] == 'A') || (string[7] == 'a')) &&
  3305.       ((string[8] == 'T') || (string[8] == 't')) &&
  3306.       ((string[9] == 'E') || (string[9] == 'e')) && (string[10] == ']'))
  3307.     trycreate = T;
  3308.     case BYE:            /* some other server signing off */
  3309.     case PARSE:            /* parse glitch, output unsolicited OK */
  3310.       code = "* OK ";
  3311.       break;
  3312.     case WARN:            /* warning, output unsolicited NO (kludge!) */
  3313.       code = "* NO ";
  3314.       break;
  3315.     case ERROR:            /* error that broke command */
  3316.     default:            /* default should never happen */
  3317.       code = "* BAD ";
  3318.       break;
  3319.     }
  3320.     PSOUT (code);
  3321.     PSOUT (string);
  3322.     CRLF;
  3323.   }
  3324. }
  3325.  
  3326. /* Log an event for the user to see
  3327.  * Accepts: string to log
  3328.  *        error flag
  3329.  */
  3330.  
  3331. void mm_log (char *string,long errflg)
  3332. {
  3333.   if (!quell_events) switch (errflg) {
  3334.   case NIL:            /* information message, set as OK response */
  3335.     if (response == win) {    /* only if no other response yet */
  3336.       response = altwin;    /* switch to alternative win message */
  3337.       if (lsterr) fs_give ((void **) &lsterr);
  3338.       lsterr = cpystr (string);    /* copy string for later use */
  3339.     }
  3340.     break;
  3341.   case PARSE:            /* parse glitch, output unsolicited OK */
  3342.     PSOUT ("* OK [PARSE] ");
  3343.     PSOUT (string);
  3344.     CRLF;
  3345.     break;
  3346.   case WARN:            /* warning, output unsolicited NO */
  3347.                 /* ignore "Mailbox is empty" (KLUDGE!) */
  3348.     if (strcmp (string,"Mailbox is empty")) {
  3349.       if (lstwrn) {        /* have previous warning? */
  3350.     PSOUT ("* NO ");
  3351.     PSOUT (lstwrn);
  3352.     CRLF;
  3353.     fs_give ((void **) &lstwrn);
  3354.       }
  3355.       lstwrn = cpystr (string);    /* note last warning */
  3356.     }
  3357.     break;
  3358.   case ERROR:            /* error that broke command */
  3359.   default:            /* default should never happen */
  3360.     response = lose;        /* set fatality */
  3361.     if (lsterr) fs_give ((void **) &lsterr);
  3362.     lsterr = cpystr (string);    /* note last error */
  3363.     break;
  3364.   }
  3365. }
  3366.  
  3367. /* Return last error
  3368.  */
  3369.  
  3370. char *lasterror (void)
  3371. {
  3372.   if (lsterr) return lsterr;
  3373.   if (lstwrn) return lstwrn;
  3374.   return "<unknown>";
  3375. }
  3376.  
  3377.  
  3378. /* Log an event to debugging telemetry
  3379.  * Accepts: string to log
  3380.  */
  3381.  
  3382. void mm_dlog (char *string)
  3383. {
  3384.   mm_log (string,WARN);        /* shouldn't happen normally */
  3385. }
  3386.  
  3387. /* Get user name and password for this host
  3388.  * Accepts: parse of network user name
  3389.  *        where to return user name
  3390.  *        where to return password
  3391.  *        trial count
  3392.  */
  3393.  
  3394. void mm_login (NETMBX *mb,char *username,char *password,long trial)
  3395. {
  3396.                 /* set user name */
  3397.   strncpy (username,*mb->user ? mb->user : user,NETMAXUSER);
  3398.   strncpy (password,pass,256);    /* and password */
  3399. }
  3400.  
  3401.  
  3402. /* About to enter critical code
  3403.  * Accepts: stream
  3404.  */
  3405.  
  3406. void mm_critical (MAILSTREAM *s)
  3407. {
  3408.   critical = T;
  3409. }
  3410.  
  3411.  
  3412. /* About to exit critical code
  3413.  * Accepts: stream
  3414.  */
  3415.  
  3416. void mm_nocritical (MAILSTREAM *s)
  3417. {
  3418.   critical = NIL;
  3419. }
  3420.  
  3421. /* Disk error found
  3422.  * Accepts: stream
  3423.  *        system error code
  3424.  *        flag indicating that mailbox may be clobbered
  3425.  * Returns: abort flag
  3426.  */
  3427.  
  3428. long mm_diskerror (MAILSTREAM *s,long errcode,long serious)
  3429. {
  3430.   if (serious) {        /* try your damnest if clobberage likely */
  3431.     mm_notify (s,"Retrying to fix probable mailbox damage!",ERROR);
  3432.     PFLUSH;            /* dump output buffer */
  3433.     syslog (LOG_ALERT,
  3434.         "Retrying after disk error user=%.80s host=%.80s mbx=%.80s: %.80s",
  3435.         user ? user : "???",tcp_clienthost (),
  3436.         (stream && stream->mailbox) ? stream->mailbox : "???",
  3437.         strerror (errcode));
  3438.     alarm (0);            /* make damn sure timeout disabled */
  3439.     sleep (60);            /* give it some time to clear up */
  3440.     return NIL;
  3441.   }
  3442.   if (!quell_events) {        /* otherwise die before more damage is done */
  3443.     PSOUT ("* BYE Aborting due to disk error: ");
  3444.     PSOUT (strerror (errcode));
  3445.     CRLF;
  3446.   }
  3447.   syslog (LOG_ALERT,"Fatal disk error user=%.80s host=%.80s mbx=%.80s: %.80s",
  3448.       user ? user : "???",tcp_clienthost (),
  3449.       (stream && stream->mailbox) ? stream->mailbox : "???",
  3450.       strerror (errcode));
  3451.   return T;
  3452. }
  3453.  
  3454.  
  3455. /* Log a fatal error event
  3456.  * Accepts: string to log
  3457.  */
  3458.  
  3459. void mm_fatal (char *string)
  3460. {
  3461.   if (!quell_events) {
  3462.     PSOUT ("* BYE [ALERT] IMAP4rev1 server crashing: ");
  3463.     PSOUT (string);
  3464.     CRLF;
  3465.   }
  3466.   syslog (LOG_ALERT,"Fatal error user=%.80s host=%.80s mbx=%.80s: %.80s",
  3467.       user ? user : "???",tcp_clienthost (),
  3468.       (stream && stream->mailbox) ? stream->mailbox : "???",string);
  3469. }
  3470.