home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / pine3.07 / c-client / tenex.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-06-01  |  47.0 KB  |  1,635 lines

  1. /*
  2.  * Program:    Tenex mail routines
  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:    22 May 1990
  13.  * Last Edited:    1 June 1992
  14.  *
  15.  * Copyright 1992 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.  
  37. #include <stdio.h>
  38. #include <ctype.h>
  39. #include <pwd.h>
  40. #include <netdb.h>
  41. #include <errno.h>
  42. extern int errno;        /* just in case */
  43. #include <sys/types.h>
  44. #include <sys/file.h>
  45. #include <sys/stat.h>
  46. #include <sys/time.h>
  47. #include <sys/uio.h>
  48. #include "osdep.h"
  49. #include "mail.h"
  50. #include "tenex.h"
  51. #include "rfc822.h"
  52. #include "misc.h"
  53.  
  54. /* Tenex mail routines */
  55.  
  56.  
  57. /* Driver dispatch used by MAIL */
  58.  
  59. DRIVER tenexdriver = {
  60.   (DRIVER *) NIL,        /* next driver */
  61.   tenex_valid,            /* mailbox is valid for us */
  62.   tenex_find,            /* find mailboxes */
  63.   tenex_find_bboards,        /* find bboards */
  64.   tenex_open,            /* open mailbox */
  65.   tenex_close,            /* close mailbox */
  66.   tenex_fetchfast,        /* fetch message "fast" attributes */
  67.   tenex_fetchflags,        /* fetch message flags */
  68.   tenex_fetchenvelope,        /* fetch message envelopes */
  69.   tenex_fetchheader,        /* fetch message header only */
  70.   tenex_fetchtext,        /* fetch message body only */
  71.   tenex_fetchbody,        /* fetch message body section */
  72.   tenex_setflag,        /* set message flag */
  73.   tenex_clearflag,        /* clear message flag */
  74.   tenex_search,            /* search for message based on criteria */
  75.   tenex_ping,            /* ping mailbox to see if still alive */
  76.   tenex_check,            /* check for new messages */
  77.   tenex_expunge,        /* expunge deleted messages */
  78.   tenex_copy,            /* copy messages to another mailbox */
  79.   tenex_move,            /* move messages to another mailbox */
  80.   tenex_gc            /* garbage collect stream */
  81. };
  82.  
  83. /* Tenex mail validate mailbox
  84.  * Accepts: mailbox name
  85.  * Returns: our driver if name is valid, otherwise calls valid in next driver
  86.  */
  87.  
  88. DRIVER *tenex_valid (name)
  89.     char *name;
  90. {
  91.   return tenex_isvalid (name) ? &tenexdriver :
  92.     (tenexdriver.next ? (*tenexdriver.next->valid) (name) : NIL);
  93. }
  94.  
  95.  
  96. /* Tenex mail test for valid mailbox
  97.  * Accepts: mailbox name
  98.  * Returns: T if valid, NIL otherwise
  99.  */
  100.  
  101. int tenex_isvalid (name)
  102.     char *name;
  103. {
  104.   int fd;
  105.   char tmp[MAILTMPLEN];
  106.   struct stat sbuf;
  107.                 /* if file, get its status */
  108.   if (*name != '{' && (stat (tenex_file (tmp,name),&sbuf) == 0)) {
  109.     if (sbuf.st_size != 0) {    /* if non-empty file */
  110.       if ((fd = open (tmp,O_RDONLY,NIL)) >= 0 && read (fd,tmp,23) >= 0) {
  111.     close (fd);        /* close the file */
  112.                 /* must begin with dd-mmm-yy hh:mm:ss" */
  113.     if (tmp[2] == '-' && tmp[6] == '-' && tmp[9] == ' ' &&
  114.         tmp[12] == ':' && tmp[15] == ':') return T;
  115.       }
  116.     }
  117.                 /* allow empty if a ".txt" file */
  118.     else if (strstr (tmp,".txt")) return T;
  119.   }
  120.   return NIL;            /* failed miserably */
  121. }
  122.  
  123. /* Tenex mail find list of mailboxes
  124.  * Accepts: mail stream
  125.  *        pattern to search
  126.  */
  127.  
  128. void tenex_find (stream,pat)
  129.     MAILSTREAM *stream;
  130.     char *pat;
  131. {
  132.   int fd;
  133.   char tmp[MAILTMPLEN];
  134.   char *s,*t;
  135.   struct stat sbuf;
  136.                 /* make file name */
  137.   sprintf (tmp,"%s/.mailboxlist",getpwuid (geteuid ())->pw_dir);
  138.   if ((fd = open (tmp,O_RDONLY,NIL)) >= 0) {
  139.     fstat (fd,&sbuf);        /* get file size and read data */
  140.     read (fd,s = (char *) fs_get (sbuf.st_size + 1),sbuf.st_size);
  141.     close (fd);            /* close file */
  142.     s[sbuf.st_size] = '\0';    /* tie off string */
  143.     if (t = strtok (s,"\n"))    /* get first mailbox name */
  144.       do if ((*t != '{') && strcmp (t,"INBOX") && pmatch (t,pat) &&
  145.          tenex_isvalid (t)) mm_mailbox (t);
  146.                 /* for each mailbox */
  147.     while (t = strtok (NIL,"\n"));
  148.   }
  149. }
  150.  
  151.  
  152. /* Tenex mail find list of bboards
  153.  * Accepts: mail stream
  154.  *        pattern to search
  155.  */
  156.  
  157. void tenex_find_bboards (stream,pat)
  158.     MAILSTREAM *stream;
  159.     char *pat;
  160. {
  161. }
  162.  
  163. /* Tenex mail open
  164.  * Accepts: stream to open
  165.  * Returns: stream on success, NIL on failure
  166.  */
  167.  
  168. MAILSTREAM *tenex_open (stream)
  169.     MAILSTREAM *stream;
  170. {
  171.   long i;
  172.   int fd;
  173.   char *s,*t,*k;
  174.   char tmp[MAILTMPLEN];
  175.   struct stat sbuf;
  176.   struct hostent *host_name;
  177.   if (LOCAL) {            /* close old file if stream being recycled */
  178.     tenex_close (stream);    /* dump and save the changes */
  179.     stream->dtb = &tenexdriver;    /* reattach this driver */
  180.                 /* clean up cache (again) */
  181.     mail_free_cache (&stream->cache,&stream->cachesize);
  182.   }
  183.   else {            /* flush old flagstring and flags if any */
  184.     if (stream->flagstring) fs_give ((void **) &stream->flagstring);
  185.     for (i = 0; i < NUSERFLAGS; ++i) stream->user_flags[i] = NIL;
  186.                 /* open .imapinit or .mminit file */
  187.     if ((fd = open (strcat (strcpy (tmp,getpwuid (geteuid ())->pw_dir),
  188.                 "/.imapinit"),O_RDONLY,NIL)) < 0)
  189.       fd = open (strcat (strcpy (tmp,getpwuid (geteuid ())->pw_dir),
  190.              "/.mminit"),O_RDONLY,NIL);
  191.     if (fd >= 0) {        /* got an init file? */
  192.       fstat (fd,&sbuf);        /* yes, get size */
  193.       read (fd,(s = stream->flagstring = (char *) fs_get (sbuf.st_size + 1)),
  194.         sbuf.st_size);    /* make readin area and read the file */
  195.       close (fd);        /* don't need the file any more */
  196.       s[sbuf.st_size] = '\0';    /* tie it off */
  197.                 /* parse init file */
  198.       while (*s && (t = strchr (s,'\n'))) {
  199.     *t = '\0';        /* tie off line, find second space */
  200.     if ((k = strchr (s,' ')) && (k = strchr (++k,' '))) {
  201.       *k = '\0';        /* tie off two words, is it "set keywords"? */
  202.       if (!strcmp (lcase (s),"set keywords")) {
  203.                 /* yes, get first keyword */
  204.         k = strtok (++k,", ");
  205.                 /* until parsed all keywords or empty */
  206.         for (i = 0; k && i < NUSERFLAGS; ++i) {
  207.                 /* set keyword, get next one */
  208.           stream->user_flags[i] = k;
  209.           k = strtok (NIL,", ");
  210.         }
  211.         break;        /* don't need to look at rest of file */
  212.       }
  213.     }
  214.     s = ++t;        /* try next line */
  215.       }
  216.     }
  217.   }
  218.  
  219.   if ((fd = open (tenex_file (tmp,stream->mailbox),O_RDWR,NIL)) < 0) {
  220.     if ((fd = open (tenex_file (tmp,stream->mailbox),O_RDONLY,NIL)) < 0) {
  221.       sprintf (tmp,"Can't open mailbox: %s",strerror (errno));
  222.       mm_log (tmp,ERROR);
  223.       return NIL;
  224.     }
  225.     else {            /* got it, but readonly */
  226.       mm_log ("Can't get write access to mailbox, access is readonly",WARN);
  227.       stream->readonly = T;
  228.     }
  229.   }
  230.   stream->local = fs_get (sizeof (TENEXLOCAL));
  231.                 /* note if an INBOX or not */
  232.   LOCAL->inbox = !strcmp (ucase (stream->mailbox),"INBOX");
  233.                 /* canonicalize the stream mailbox name */
  234.   fs_give ((void **) &stream->mailbox);
  235.   stream->mailbox = cpystr (tmp);
  236.   gethostname (tmp,MAILTMPLEN);    /* get local host name */
  237.   LOCAL->host = cpystr ((host_name = gethostbyname (tmp)) ?
  238.             host_name->h_name : tmp);
  239.                 /* bind and lock the file */
  240.   flock (LOCAL->fd = fd,LOCK_SH);
  241.   LOCAL->filesize = 0;        /* initialize parsed file size */
  242.   LOCAL->msgs = NIL;        /* no cache yet */
  243.   LOCAL->cachesize = 0;
  244.   LOCAL->text = NIL;        /* no text yet */
  245.   LOCAL->textsize = LOCAL->textend = 0;
  246.   LOCAL->buf = (char *) fs_get (MAXMESSAGESIZE + 1);
  247.   LOCAL->buflen = MAXMESSAGESIZE;
  248.   stream->sequence++;        /* bump sequence number */
  249.                 /* parse mailbox */
  250.   stream->nmsgs = stream->recent = 0;
  251.   if (tenex_ping (stream) && !stream->nmsgs) mm_log ("Mailbox is empty",NIL);
  252.   return stream;        /* return stream to caller */
  253. }
  254.  
  255. /* Tenex mail close
  256.  * Accepts: MAIL stream
  257.  */
  258.  
  259. void tenex_close (stream)
  260.     MAILSTREAM *stream;
  261. {
  262.   long i;
  263.   if (stream && LOCAL) {    /* only if a file is open */
  264.     if (LOCAL->host) fs_give ((void **) &LOCAL->host);
  265.     flock (LOCAL->fd,LOCK_UN);    /* unlock local file */
  266.     close (LOCAL->fd);        /* close the local file */
  267.                 /* free local cache */
  268.     for (i = 0; i < stream->nmsgs; ++i)
  269.       if (LOCAL->msgs[i]) fs_give ((void **) &LOCAL->msgs[i]);
  270.     fs_give ((void **) &LOCAL->msgs);
  271.                 /* free local text buffer */
  272.     if (LOCAL->text) fs_give ((void **) &LOCAL->text);
  273.     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
  274.                 /* nuke the local data */
  275.     fs_give ((void **) &stream->local);
  276.     stream->dtb = NIL;        /* log out the DTB */
  277.   }
  278. }
  279.  
  280.  
  281. /* Tenex mail fetch fast information
  282.  * Accepts: MAIL stream
  283.  *        sequence
  284.  */
  285.  
  286. void tenex_fetchfast (stream,sequence)
  287.     MAILSTREAM *stream;
  288.     char *sequence;
  289. {
  290.   return;            /* no-op for local mail */
  291. }
  292.  
  293.  
  294. /* Tenex mail fetch flags
  295.  * Accepts: MAIL stream
  296.  *        sequence
  297.  */
  298.  
  299. void tenex_fetchflags (stream,sequence)
  300.     MAILSTREAM *stream;
  301.     char *sequence;
  302. {
  303.   return;            /* no-op for local mail */
  304. }
  305.  
  306. /* Tenex mail fetch envelope
  307.  * Accepts: MAIL stream
  308.  *        message # to fetch
  309.  * Returns: envelope of this message
  310.  *
  311.  * Fetches the "fast" information as well
  312.  */
  313.  
  314. ENVELOPE *tenex_fetchenvelope (stream,msgno)
  315.     MAILSTREAM *stream;
  316.     long msgno;
  317. {
  318.   FILECACHE *m = LOCAL->msgs[msgno - 1];
  319.   long i = max (m->headersize,m->bodysize);
  320.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  321.   if (i > LOCAL->buflen) {    /* make sure buffer can hold the text */
  322.                 /* fs_resize does an unnecesary copy */
  323.     fs_give ((void **) &LOCAL->buf);
  324.     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = i) + 1);
  325.   }
  326.   if (!elt->env)        /* make envelope now if don't have one */
  327.     rfc822_parse_msg (&elt->env,&elt->body,
  328.               LOCAL->text + m->header,m->headersize,
  329.               LOCAL->text + m->body,m->bodysize,
  330.               LOCAL->host,LOCAL->buf);
  331.   return elt->env;        /* return the envelope */
  332. }
  333.  
  334. /* Tenex mail fetch message header
  335.  * Accepts: MAIL stream
  336.  *        message # to fetch
  337.  * Returns: message header in RFC822 format
  338.  */
  339.  
  340. char *tenex_fetchheader (stream,msgno)
  341.     MAILSTREAM *stream;
  342.     long msgno;
  343. {
  344.   FILECACHE *m = LOCAL->msgs[msgno-1];
  345.                 /* copy the string */
  346.   return strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,
  347.              LOCAL->text + m->header,m->headersize);
  348. }
  349.  
  350.  
  351. /* Tenex mail fetch message text (only)
  352.     body only;
  353.  * Accepts: MAIL stream
  354.  *        message # to fetch
  355.  * Returns: message text in RFC822 format
  356.  */
  357.  
  358. char *tenex_fetchtext (stream,msgno)
  359.     MAILSTREAM *stream;
  360.     long msgno;
  361. {
  362.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  363.   FILECACHE *m = LOCAL->msgs[msgno-1];
  364.   elt->seen = T;        /* mark message as seen */
  365.                 /* recalculate status */
  366.   tenex_update_status (stream,msgno,T);
  367.   return strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,
  368.              LOCAL->text + m->body,m->bodysize);
  369. }
  370.  
  371. /* Tenex fetch message body as a structure
  372.  * Accepts: Mail stream
  373.  *        message # to fetch
  374.  *        section specifier
  375.  *        pointer to length
  376.  * Returns: pointer to section of message body
  377.  */
  378.  
  379. char *tenex_fetchbody (stream,m,s,len)
  380.     MAILSTREAM *stream;
  381.     long m;
  382.     char *s;
  383.     unsigned long *len;
  384. {
  385.   BODY *b;
  386.   PART *pt;
  387.   unsigned long i;
  388.   char *base = LOCAL->text + LOCAL->msgs[m-1]->body;
  389.   unsigned long offset = 0;
  390.   MESSAGECACHE *elt = mail_elt (stream,m);
  391.                 /* make sure have a body */
  392.   if (!(tenex_fetchenvelope (stream,m) && (b = mail_elt (stream,m)->body) &&
  393.     s && *s && ((i = strtol (s,&s,10)) > 0))) return NIL;
  394.   do {                /* until find desired body part */
  395.                 /* multipart content? */
  396.     if (b->type == TYPEMULTIPART) {
  397.       pt = b->contents.part;    /* yes, find desired part */
  398.       while (--i && (pt = pt->next));
  399.       if (!pt) return NIL;    /* bad specifier */
  400.                 /* note new body, check valid nesting */
  401.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  402.       base += offset;        /* calculate new base */
  403.       offset = pt->offset;    /* and its offset */
  404.     }
  405.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  406.                 /* need to go down further? */
  407.     if (i = *s) switch (b->type) {
  408.     case TYPEMESSAGE:        /* embedded message, calculate new base */
  409.       base += offset + b->contents.msg.offset;
  410.       offset = 0;        /* no offset any more */
  411.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  412.     case TYPEMULTIPART:        /* multipart, get next section */
  413.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  414.     default:            /* bogus subpart specification */
  415.       return NIL;
  416.     }
  417.   } while (i);
  418.                 /* lose if body bogus */
  419.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  420.   elt->seen = T;        /* mark message as seen */
  421.                 /* recalculate status */
  422.   tenex_update_status (stream,m,T);
  423.   return rfc822_contents (&LOCAL->buf,&LOCAL->buflen,len,base + offset,
  424.               b->size.ibytes,b->encoding);
  425. }
  426.  
  427. /* Tenex mail set flag
  428.  * Accepts: MAIL stream
  429.  *        sequence
  430.  *        flag(s)
  431.  */
  432.  
  433. void tenex_setflag (stream,sequence,flag)
  434.     MAILSTREAM *stream;
  435.     char *sequence;
  436.     char *flag;
  437. {
  438.   MESSAGECACHE *elt;
  439.   long i;
  440.   long uf;
  441.   short f;
  442.                 /* no-op if no flags to modify */
  443.   if (!((f = tenex_getflags (stream,flag,&uf)) || uf)) return;
  444.                 /* get sequence and loop on it */
  445.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  446.     if ((elt = tenex_elt (stream,i))->sequence) {
  447.                 /* set all requested flags */
  448.       if (f&fSEEN) elt->seen = T;
  449.       if (f&fDELETED) elt->deleted = T;
  450.       if (f&fFLAGGED) elt->flagged = T;
  451.       if (f&fANSWERED) elt->answered = T;
  452.       elt->user_flags |= uf;
  453.                 /* recalculate status */
  454.       tenex_update_status (stream,i,NIL);
  455.     }
  456.                 /* make sure the update takes */
  457.   if (!stream->readonly) fsync (LOCAL->fd);
  458. }
  459.  
  460. /* Tenex mail clear flag
  461.  * Accepts: MAIL stream
  462.  *        sequence
  463.  *        flag(s)
  464.  */
  465.  
  466. void tenex_clearflag (stream,sequence,flag)
  467.     MAILSTREAM *stream;
  468.     char *sequence;
  469.     char *flag;
  470. {
  471.   MESSAGECACHE *elt;
  472.   long i;
  473.   long uf;
  474.   short f;
  475.                 /* no-op if no flags to modify */
  476.   if (!((f = tenex_getflags (stream,flag,&uf)) || uf)) return;
  477.                 /* get sequence and loop on it */
  478.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  479.     if ((elt = tenex_elt (stream,i))->sequence) {
  480.                 /* clear all requested flags */
  481.       if (f&fSEEN) elt->seen = NIL;
  482.       if (f&fDELETED) elt->deleted = NIL;
  483.       if (f&fFLAGGED) elt->flagged = NIL;
  484.       if (f&fANSWERED) elt->answered = NIL;
  485.       elt->user_flags &= ~uf;
  486.                 /* recalculate status */
  487.       tenex_update_status (stream,i,NIL);
  488.     }
  489.                 /* make sure the update takes */
  490.   if (!stream->readonly) fsync (LOCAL->fd);
  491. }
  492.  
  493. /* Tenex mail search for messages
  494.  * Accepts: MAIL stream
  495.  *        search criteria
  496.  */
  497.  
  498. void tenex_search (stream,criteria)
  499.     MAILSTREAM *stream;
  500.     char *criteria;
  501. {
  502.   long i,n;
  503.   char *d;
  504.   search_t f;
  505.                 /* initially all searched */
  506.   for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = T;
  507.                 /* get first criterion */
  508.   if (criteria && (criteria = strtok (criteria," "))) {
  509.                 /* for each criterion */
  510.     for (; criteria; (criteria = strtok (NIL," "))) {
  511.       f = NIL; d = NIL; n = 0;    /* init then scan the criterion */
  512.       switch (*ucase (criteria)) {
  513.       case 'A':            /* possible ALL, ANSWERED */
  514.     if (!strcmp (criteria+1,"LL")) f = tenex_search_all;
  515.     else if (!strcmp (criteria+1,"NSWERED")) f = tenex_search_answered;
  516.     break;
  517.       case 'B':            /* possible BCC, BEFORE, BODY */
  518.     if (!strcmp (criteria+1,"CC"))
  519.       f = tenex_search_string (tenex_search_bcc,&d,&n);
  520.     else if (!strcmp (criteria+1,"EFORE"))
  521.       f = tenex_search_date (tenex_search_before,&n);
  522.     else if (!strcmp (criteria+1,"ODY"))
  523.       f = tenex_search_string (tenex_search_body,&d,&n);
  524.     break;
  525.       case 'C':            /* possible CC */
  526.     if (!strcmp (criteria+1,"C"))
  527.       f = tenex_search_string (tenex_search_cc,&d,&n);
  528.     break;
  529.       case 'D':            /* possible DELETED */
  530.     if (!strcmp (criteria+1,"ELETED")) f = tenex_search_deleted;
  531.     break;
  532.       case 'F':            /* possible FLAGGED, FROM */
  533.     if (!strcmp (criteria+1,"LAGGED")) f = tenex_search_flagged;
  534.     else if (!strcmp (criteria+1,"ROM"))
  535.       f = tenex_search_string (tenex_search_from,&d,&n);
  536.     break;
  537.       case 'K':            /* possible KEYWORD */
  538.     if (!strcmp (criteria+1,"EYWORD"))
  539.       f = tenex_search_flag (tenex_search_keyword,&n,stream);
  540.     break;
  541.       case 'N':            /* possible NEW */
  542.     if (!strcmp (criteria+1,"EW")) f = tenex_search_new;
  543.     break;
  544.  
  545.       case 'O':            /* possible OLD, ON */
  546.     if (!strcmp (criteria+1,"LD")) f = tenex_search_old;
  547.     else if (!strcmp (criteria+1,"N"))
  548.       f = tenex_search_date (tenex_search_on,&n);
  549.     break;
  550.       case 'R':            /* possible RECENT */
  551.     if (!strcmp (criteria+1,"ECENT")) f = tenex_search_recent;
  552.     break;
  553.       case 'S':            /* possible SEEN, SINCE, SUBJECT */
  554.     if (!strcmp (criteria+1,"EEN")) f = tenex_search_seen;
  555.     else if (!strcmp (criteria+1,"INCE"))
  556.       f = tenex_search_date (tenex_search_since,&n);
  557.     else if (!strcmp (criteria+1,"UBJECT"))
  558.       f = tenex_search_string (tenex_search_subject,&d,&n);
  559.     break;
  560.       case 'T':            /* possible TEXT, TO */
  561.     if (!strcmp (criteria+1,"EXT"))
  562.       f = tenex_search_string (tenex_search_text,&d,&n);
  563.     else if (!strcmp (criteria+1,"O"))
  564.       f = tenex_search_string (tenex_search_to,&d,&n);
  565.     break;
  566.       case 'U':            /* possible UN* */
  567.     if (criteria[1] == 'N') {
  568.       if (!strcmp (criteria+2,"ANSWERED")) f = tenex_search_unanswered;
  569.       else if (!strcmp (criteria+2,"DELETED")) f = tenex_search_undeleted;
  570.       else if (!strcmp (criteria+2,"FLAGGED")) f = tenex_search_unflagged;
  571.       else if (!strcmp (criteria+2,"KEYWORD"))
  572.         f = tenex_search_flag (tenex_search_unkeyword,&n,stream);
  573.       else if (!strcmp (criteria+2,"SEEN")) f = tenex_search_unseen;
  574.     }
  575.     break;
  576.       default:            /* we will barf below */
  577.     break;
  578.       }
  579.       if (!f) {            /* if can't determine any criteria */
  580.     sprintf (LOCAL->buf,"Unknown search criterion: %.80s",criteria);
  581.     mm_log (LOCAL->buf,ERROR);
  582.     return;
  583.       }
  584.                 /* run the search criterion */
  585.       for (i = 1; i <= stream->nmsgs; ++i)
  586.     if (mail_elt (stream,i)->searched && !(*f) (stream,i,d,n))
  587.       mail_elt (stream,i)->searched = NIL;
  588.     }
  589.                 /* report search results to main program */
  590.     for (i = 1; i <= stream->nmsgs; ++i)
  591.       if (mail_elt (stream,i)->searched) mail_searched (stream,i);
  592.   }
  593. }
  594.  
  595. /* Tenex mail ping mailbox
  596.  * Accepts: MAIL stream
  597.  * Returns: T if stream still alive, NIL if not
  598.  */
  599.  
  600. long tenex_ping (stream)
  601.     MAILSTREAM *stream;
  602. {
  603.   long i = 0;
  604.   long r,j;
  605.   struct stat sbuf;
  606.   struct iovec iov[2];
  607.   MAILSTREAM *bezerk = NIL;
  608.                 /* punt if stream no longer alive */
  609.   if (!(stream && LOCAL)) return NIL;
  610. #if unix
  611.                 /* only if this is a read-write inbox */
  612.   if (LOCAL->inbox && !stream->readonly) {
  613.     stream->silent = T;        /* avoid blabber in initial parse */
  614.                 /* do initial parse if necessary */
  615.     r = (!LOCAL->filesize) && (!tenex_parse (stream));
  616.     stream->silent = NIL;    /* allow things to continue */
  617.     if (r) return NIL;        /* oops */
  618.  
  619.     mm_critical (stream);    /* go critical */
  620.                 /* calculate name of bezerk file */
  621.     sprintf (LOCAL->buf,MAILFILE,getpwuid (geteuid ())->pw_name);
  622.     stat (LOCAL->buf,&sbuf);    /* see if anything there */
  623.                 /* non-empty and we can lock our file? */
  624.     if (sbuf.st_size && !flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
  625.       fstat (LOCAL->fd,&sbuf);    /* yes, get current file size */
  626.                 /* sizes match and can get bezerk mailbox? */
  627.       if ((sbuf.st_size == LOCAL->filesize) &&
  628.       (bezerk = mail_open (bezerk,LOCAL->buf,36)) &&
  629.       (r = bezerk->nmsgs)) {
  630.                 /* yes, go to end of file in our mailbox */
  631.     lseek (LOCAL->fd,sbuf.st_size,L_SET);
  632.                 /* for each message in bezerk mailbox */
  633.     while (r && (++i <= bezerk->nmsgs)) {
  634.                 /* snarf message from Berkeley mailbox */
  635.       iov[1].iov_base = bezerk_snarf (bezerk,i,&j);
  636.                 /* calculate header line */
  637.       sprintf ((iov[0].iov_base = LOCAL->buf),"%s,%d;000000000000\n",
  638.            mail_elt (bezerk,i)->internal_date,iov[1].iov_len = j);
  639.       iov[0].iov_len = strlen (iov[0].iov_base);
  640.                 /* copy message to new mailbox */
  641.       if (writev (LOCAL->fd,iov,2) < 0) {
  642.         sprintf (LOCAL->buf,"Can't copy new mail: %s",strerror (errno));
  643.         mm_log (LOCAL->buf,ERROR);
  644.         ftruncate (LOCAL->fd,sbuf.st_size);
  645.         r = 0;        /* flag that we have lost big */
  646.       }
  647.     }
  648.     if (r) {        /* delete all the messages we copied */
  649.       for (i = 1; i <= r; i++) mail_elt (bezerk,i)->deleted = T;
  650.       mail_expunge (bezerk);/* now expunge all those messages */
  651.     }
  652.       }
  653.       if (bezerk) mail_close (bezerk);
  654.     }
  655.     flock (LOCAL->fd,LOCK_SH);    /* back to shared access */
  656.     mm_nocritical (stream);    /* release critical */
  657.   }
  658. #endif
  659.                 /* parse mailbox, punt if parse dies */
  660.   return (tenex_parse (stream)) ? T : NIL;
  661. }
  662.  
  663. /* Tenex mail check mailbox (too)
  664.     reparses status too;
  665.  * Accepts: MAIL stream
  666.  */
  667.  
  668. void tenex_check (stream)
  669.     MAILSTREAM *stream;
  670. {
  671.   long i = 1;
  672.   if (tenex_ping (stream)) {    /* ping mailbox */
  673.                 /* get new message status */
  674.     while (i <= stream->nmsgs) tenex_elt (stream,i++);
  675.     mm_log ("Check completed",NIL);
  676.   }
  677. }
  678.  
  679.  
  680. /* Tenex mail expunge mailbox
  681.  * Accepts: MAIL stream
  682.  */
  683.  
  684. void tenex_expunge (stream)
  685.     MAILSTREAM *stream;
  686. {
  687.   long j;
  688.   long i = 1;
  689.   long n = 0;
  690.   long delta = 0;
  691.   unsigned long recent;
  692.   MESSAGECACHE *elt;
  693.   FILECACHE *m;
  694.                 /* do nothing if stream dead */
  695.   if (!tenex_ping (stream)) return;
  696.   if (stream->readonly) {    /* won't do on readonly files! */
  697.     mm_log ("Expunge ignored on readonly mailbox",WARN);
  698.     return;
  699.   }
  700.                 /* get exclusive access */
  701.   if (flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
  702.     flock (LOCAL->fd,LOCK_SH);    /* recover previous lock */
  703.     mm_log ("Expunge rejected: mailbox locked",ERROR);
  704.     return;
  705.   }
  706.  
  707.   recent = stream->recent;    /* get recent now that pinged and locked */
  708.   while (i <= stream->nmsgs) {    /* for each message */
  709.     m = LOCAL->msgs[i-1];    /* get file cache */
  710.                 /* if deleted */
  711.     if ((elt = tenex_elt (stream,i))->deleted) {
  712.       if (elt->recent) --recent;/* if recent, note one less recent message */
  713.                 /* one more message to flush */
  714.       delta += m->headersize + m->bodysize + m->header - m->internal;
  715.       fs_give ((void **) &m);    /* flush local cache entry */
  716.       for (j = i; j < stream->nmsgs; j++) LOCAL->msgs[j-1] = LOCAL->msgs[j];
  717.       LOCAL->msgs[stream->nmsgs - 1] = NIL;
  718.       mail_expunged (stream,i);    /* notify upper levels */
  719.       n++;            /* count up one more expunged message */
  720.     }
  721.     else if (i++ && delta) {    /* if undeleted message needs moving */
  722.                 /* blat it down to new position */
  723.       memmove (LOCAL->text + m->internal - delta,LOCAL->text + m->internal,
  724.            m->headersize + m->bodysize + m->header - m->internal);
  725.       m->internal -= delta;    /* relocate pointers */
  726.       m->header -= delta;
  727.       m->body -= delta;
  728.     }
  729.   }
  730.   if (n) {            /* expunged anything? */
  731.                 /* yes, calculate new file/text size */
  732.     if ((LOCAL->textend -= delta) != (LOCAL->filesize -= delta) ||
  733.     (LOCAL->filesize < 0)) fatal ("File/text pointers screwed up!");
  734.     while (T) {            /* modal to ensure it won */
  735.       lseek (LOCAL->fd,0,L_SET);/* seek to start of file */
  736.       if (write (LOCAL->fd,LOCAL->text,LOCAL->textend) >= 0) break;
  737.       sprintf (LOCAL->buf,"Unable to rewrite mailbox: %s",strerror (i =errno));
  738.       mm_log (LOCAL->buf,WARN);
  739.       mm_diskerror (stream,i,T);/* report to top level */
  740.     }
  741.                 /* nuke any remaining cruft */
  742.     ftruncate (LOCAL->fd,LOCAL->filesize);
  743.     sprintf (LOCAL->buf,"Expunged %d messages",n);
  744.     mm_log (LOCAL->buf,NIL);    /* output the news */
  745.   }
  746.   else mm_log ("No messages deleted, so no update needed",NIL);
  747.                 /* notify upper level of new mailbox size */
  748.   mail_exists (stream,stream->nmsgs);
  749.   mail_recent (stream,recent);
  750.   flock (LOCAL->fd,LOCK_SH);    /* allow sharers again */
  751. }
  752.  
  753. /* Tenex mail copy message(s)
  754.     s;
  755.  * Accepts: MAIL stream
  756.  *        sequence
  757.  *        destination mailbox
  758.  * Returns: T if success, NIL if failed
  759.  */
  760.  
  761. long tenex_copy (stream,sequence,mailbox)
  762.     MAILSTREAM *stream;
  763.     char *sequence;
  764.     char *mailbox;
  765. {
  766.                 /* copy the messages */
  767.   return (mail_sequence (stream,sequence)) ?
  768.     tenex_copy_messages (stream,mailbox) : NIL;
  769. }
  770.  
  771.  
  772. /* Tenex mail move message(s)
  773.     s;
  774.  * Accepts: MAIL stream
  775.  *        sequence
  776.  *        destination mailbox
  777.  * Returns: T if success, NIL if failed
  778.  */
  779.  
  780. long tenex_move (stream,sequence,mailbox)
  781.     MAILSTREAM *stream;
  782.     char *sequence;
  783.     char *mailbox;
  784. {
  785.   long i;
  786.   MESSAGECACHE *elt;
  787.   if (!(mail_sequence (stream,sequence) &&
  788.     tenex_copy_messages (stream,mailbox))) return NIL;
  789.                 /* delete all requested messages */
  790.   for (i = 1; i <= stream->nmsgs; i++)
  791.     if ((elt = tenex_elt (stream,i))->sequence) {
  792.       elt->deleted = T;        /* mark message deleted */
  793.                 /* recalculate status */
  794.       tenex_update_status (stream,i,NIL);
  795.     }
  796.                 /* make sure the update takes */
  797.   if (!stream->readonly) fsync (LOCAL->fd);
  798.   return T;
  799. }
  800.  
  801.  
  802. /* Tenex garbage collect stream
  803.  * Accepts: Mail stream
  804.  *        garbage collection flags
  805.  */
  806.  
  807. void tenex_gc (stream,gcflags)
  808.     MAILSTREAM *stream;
  809.     long gcflags;
  810. {
  811.   /* nothing here for now */
  812. }
  813.  
  814. /* Internal routines */
  815.  
  816.  
  817. /* Tenex mail generate file string
  818.  * Accepts: temporary buffer to write into
  819.  *        mailbox name string
  820.  * Returns: local file string
  821.  */
  822.  
  823. char *tenex_file (dst,name)
  824.     char *dst;
  825.     char *name;
  826. {
  827.   char *home = getpwuid (geteuid ())->pw_dir;
  828.   strcpy (dst,name);        /* copy the mailbox name */
  829.   if (*dst != '/') {        /* pass absolute file paths */
  830.     if (strcmp (ucase (dst),"INBOX")) sprintf (dst,"%s/%s",home,name);
  831.                 /* INBOX becomes ~USER/mail.txt file */
  832.     else sprintf (dst,"%s/mail.txt",home);
  833.   }
  834.   return dst;
  835. }
  836.  
  837.  
  838. /* Parse flag list
  839.  * Accepts: MAIL stream
  840.  *        flag list as a character string
  841.  *        pointer to user flags to return
  842.  * Returns: system flags
  843.  */
  844.  
  845. int tenex_getflags (stream,flag,uf)
  846.     MAILSTREAM *stream;
  847.     char *flag;
  848.     long *uf;
  849. {
  850.   char key[MAILTMPLEN];
  851.   char *t,*s;
  852.   short f = 0;
  853.   long i;
  854.   short j;
  855.   *uf = 0;            /* initially no user flags */
  856.   if (flag && *flag) {        /* no-op if no flag string */
  857.                 /* check if a list and make sure valid */
  858.     if ((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) {
  859.       mm_log ("Bad flag list",ERROR);
  860.       return NIL;
  861.     }
  862.                 /* copy the flag string w/o list construct */
  863.     strncpy (LOCAL->buf,flag+i,(j = strlen (flag) - (2*i)));
  864.     LOCAL->buf[j] = '\0';    /* tie off tail */
  865.  
  866.                 /* make uppercase, find first, parse */
  867.     if (t = strtok (ucase (LOCAL->buf)," ")) do {
  868.       i = 0;            /* no flag yet */
  869.                 /* system flag, dispatch on first character */
  870.       if (*t == '\\') switch (*++t) {
  871.       case 'S':            /* possible \Seen flag */
  872.     if (t[1] == 'E' && t[2] == 'E' && t[3] == 'N' && t[4] == '\0')
  873.       f |= i = fSEEN;
  874.     break;
  875.       case 'D':            /* possible \Deleted flag */
  876.     if (t[1] == 'E' && t[2] == 'L' && t[3] == 'E' && t[4] == 'T' &&
  877.         t[5] == 'E' && t[6] == 'D' && t[7] == '\0') f |= i = fDELETED;
  878.     break;
  879.       case 'F':            /* possible \Flagged flag */
  880.     if (t[1] == 'L' && t[2] == 'A' && t[3] == 'G' && t[4] == 'G' &&
  881.         t[5] == 'E' && t[6] == 'D' && t[7] == '\0') f |= i = fFLAGGED;
  882.     break;
  883.       case 'A':            /* possible \Answered flag */
  884.     if (t[1] == 'N' && t[2] == 'S' && t[3] == 'W' && t[4] == 'E' &&
  885.         t[5] == 'R' && t[6] == 'E' && t[7] == 'D' && t[8] == '\0')
  886.       f |= i = fANSWERED;
  887.     break;
  888.       default:            /* unknown */
  889.     break;
  890.       }
  891.       else {            /* user flag, search through table */
  892.     for (j = 0; !i && j < NUSERFLAGS && (s = stream->user_flags[j]); ++j)
  893.       if (!strcmp (t,ucase (strcpy (key,s)))) *uf |= i = 1 << j;
  894.       }
  895.       if (!i) {            /* didn't find a matching flag? */
  896.     sprintf (key,"Unknown flag: %.80s",t);
  897.     mm_log (key,ERROR);
  898.     *uf = NIL;        /* be sure no user flags returned */
  899.     return NIL;        /* return no system flags */
  900.       }
  901.                 /* parse next flag */
  902.     } while (t = strtok (NIL," "));
  903.   }
  904.   return f;
  905. }
  906.  
  907. /* Tenex mail parse mailbox
  908.  * Accepts: MAIL stream
  909.  * Returns: T if parse OK
  910.  *        NIL if failure, stream aborted
  911.  */
  912.  
  913. #define PAD(s) LOCAL->textsize = (s | 0x7fff) + 0x8001
  914. #define Word unsigned long
  915.  
  916. int tenex_parse (stream)
  917.     MAILSTREAM *stream;
  918. {
  919.   struct stat sbuf;
  920.   MESSAGECACHE *elt = NIL;
  921.   FILECACHE *m = NIL;
  922.   char *end;
  923.   char c,*s,*t,*v;
  924.   long i,j,k,delta;
  925.   long nmsgs = stream->nmsgs;
  926.   long recent = stream->recent;
  927.   union {
  928.     unsigned long wd;
  929.     char ch[9];
  930.   } wdtest;
  931.   strcpy (wdtest.ch,"AAAA1234");/* constant for word testing */
  932.   fstat (LOCAL->fd,&sbuf);    /* get status */
  933.                 /* calculate change in size */
  934.   if ((delta = sbuf.st_size - LOCAL->filesize) < 0) {
  935.     sprintf (LOCAL->buf,"Mailbox shrank from %d to %d bytes, aborted",
  936.          LOCAL->filesize,sbuf.st_size);
  937.     mm_log (LOCAL->buf,ERROR);    /* this is pretty bad */
  938.     tenex_close (stream);
  939.     return NIL;
  940.   }
  941.   if (delta > 0) {        /* apparent change in the file */
  942.     if (!LOCAL->text)        /* create buffer if first time through */
  943.       LOCAL->text = (char *) fs_get (PAD (sbuf.st_size));
  944.                 /* else resize if need more space */
  945.     else if (LOCAL->textsize < (i = delta + LOCAL->textend))
  946.       fs_resize ((void **) &LOCAL->text,PAD (i));
  947.                 /* get to that position in the file */
  948.     lseek (LOCAL->fd,LOCAL->filesize,L_SET);
  949.                 /* read the new text */
  950.     if (read (LOCAL->fd,(s = LOCAL->text+LOCAL->textend),delta) < 0) {
  951.       sprintf (LOCAL->buf,"I/O error reading mail file: %s",strerror (errno));
  952.       mm_log (LOCAL->buf,ERROR);
  953.       tenex_close (stream);
  954.       return NIL;
  955.     }
  956.  
  957.                 /* must begin with dd-mmm-yy hh:mm:ss" */
  958.     if (s[2] != '-' || s[6] != '-' || s[9] != ' ' || s[12] != ':' ||
  959.     s[15] != ':') {
  960.       mm_log ("Mailbox invalidated by some other process, aborted",ERROR);
  961.       tenex_close (stream);
  962.       return NIL;
  963.     }
  964.                 /* update parsed file size */
  965.     LOCAL->filesize = sbuf.st_size;
  966.                 /* calculate and tie off end of text */
  967.     *(end = (LOCAL->text + (LOCAL->textend += delta))) = '\0';
  968.     if (!LOCAL->msgs) {        /* if no cache yet */
  969.                 /* calculate initial cache size */
  970.       for (t = s,i = 0; (t = strchr (t,',')) && (j = strtol (++t,&t,10)) &&
  971.        (*t == ';') && (t = strchr (t,'\n')) && ((t += j) < end); i++);
  972.       mail_cache (stream,i);    /* instantiate caches */
  973.       LOCAL->msgs = (FILECACHE **)
  974.     fs_get ((LOCAL->cachesize = i + CACHEINCREMENT) * sizeof(FILECACHE *));
  975.       for (i = 0; i < LOCAL->cachesize; i++) LOCAL->msgs[i] = NIL;
  976.     }
  977.     while (s < end) {        /* mailbox parse loop */
  978.       v = s;            /* save pointer to internal header */
  979.                 /* parse header */
  980.       if ((t = strchr (s,'\n')) && ((i = t - s) < LOCAL->buflen) &&
  981.       strncpy (LOCAL->buf,s,i) && (!(LOCAL->buf[i] = '\0')) &&
  982.       (s = t + 1) && strtok (LOCAL->buf,",") && (t = strtok (NIL,";")) &&
  983.       (i = strtol (t,&t,10)) && (!(t && *t)) && (t = strtok (NIL," \r")) &&
  984.       (strlen (t) == 12)) {    /* create new cache entries if valid */
  985.                 /* validate mailbox not too big */
  986.                 /* increase cache if necessary */
  987.     if (nmsgs >= LOCAL->cachesize) {
  988.       fs_resize ((void **) &LOCAL->msgs,
  989.              (LOCAL->cachesize += CACHEINCREMENT) * sizeof(FILECACHE));
  990.       for (j = nmsgs; j < LOCAL->cachesize; j++) LOCAL->msgs[j] = NIL;
  991.     }
  992.     m = LOCAL->msgs[nmsgs] = (FILECACHE *) fs_get (sizeof (FILECACHE));
  993.     elt = mail_elt (stream,++nmsgs);
  994.                 /* note internal and message headers */
  995.     m->internal = v - LOCAL->text;
  996.     m->header = s - LOCAL->text;
  997.     c = s[i];        /* save start of next message */
  998.     s[i] = '\0';        /* tie off message temporarily */
  999.  
  1000.                 /* fast scan for body text on 32-bit machine */
  1001.     if (wdtest.wd == 0x41414141) {
  1002.       m->body = NIL;    /* not found yet */
  1003.       k = 0;        /* not CRLF */
  1004.                 /* any characters before word boundary? */
  1005.       if (j = (long) (v = s) & 3) {
  1006.         j = 4 - j;        /* calculate how many */
  1007.         v += j;        /* start at new word */
  1008.         j = i - (5 + j);
  1009.       }
  1010.       else j = i - 5;    /* j is total # of tries */
  1011.       do {            /* fast search for string */
  1012.         if (0x80808080&(0x01010101+(0x7f7f7f7f&~(0x0a0a0a0a^*(Word *)v)))){
  1013.           if (v[0] == '\n' && ((v[1] == '\n') ||
  1014.                    (k = (v[-1] == '\r') && (v[1] == '\r') &&
  1015.                     (v[2] == '\n')))) {
  1016.         m->body = (v + 2 + k) - LOCAL->text;
  1017.         break;
  1018.           }
  1019.           if (v[1] == '\n' && ((v[2] == '\n') ||
  1020.                    (k = (v[0] == '\r') && (v[2] == '\r') &&
  1021.                     (v[3] == '\n')))) {
  1022.         m->body = (v + 3 + k) - LOCAL->text;
  1023.         break;
  1024.           }
  1025.           if (v[2] == '\n' && ((v[3] == '\n') ||
  1026.                    (k = (v[1] == '\r') && (v[3] == '\r') &&
  1027.                     (v[4] == '\n')))) {
  1028.         m->body = (v + 4 + k) - LOCAL->text;
  1029.         break;
  1030.           }
  1031.           if (v[3] == '\n' && ((v[4] == '\n') ||
  1032.                    (k = (v[2] == '\r') && (v[4] == '\r') &&
  1033.                     (v[5] == '\n')))) {
  1034.         m->body = (v + 5 + k) - LOCAL->text;
  1035.         break;
  1036.           }
  1037.         }
  1038.         v += 4,j -= 4;    /* try next word */
  1039.       } while (j > 0);    /* continue until end of string */
  1040.     }
  1041.     else {            /* do it the (possibly slower) way */
  1042.       m->body = (v = strstr (s,"\n\n")) ? (v + 2) - LOCAL->text :
  1043.         ((v = strstr (s,"\r\n\r\n")) ? (v + 4) - LOCAL->text : NIL);
  1044.     }
  1045.  
  1046.     s[i] = c;        /* restore next message character */
  1047.                 /* compute header and body sizes */
  1048.     m->headersize = m->body ? m->body - m->header : i;
  1049.     m->bodysize = i - m->headersize;
  1050.     if (stream->nmsgs) {    /* if not first time through */
  1051.       elt->recent = T;    /* this is a recent message */
  1052.       recent++;        /* count up a new recent message */
  1053.     }
  1054.     elt->rfc822_size = i;    /* note message size */
  1055.     for (j = 0; j < i; j++) if (s[j] == '\n') elt->rfc822_size++;
  1056.                 /* set internal date */
  1057.     elt->internal_date = cpystr (LOCAL->buf);
  1058.                 /* as well as parsed value */
  1059.     m->date = tenex_date (LOCAL->buf);
  1060.     s += i;            /* advance to next message */
  1061.                 /* calculate system flags */
  1062.     if ((i = ((t[10]-'0') * 8) + t[11]-'0') & fSEEN) elt->seen = T;
  1063.     if (i & fDELETED) elt->deleted = T;
  1064.     if (i & fFLAGGED) elt->flagged = T;
  1065.     if (i & fANSWERED) elt->answered = T;
  1066.     t[10] = '\0';        /* tie off flags */
  1067.     j = strtol (t,NIL,8);    /* get user flags value */
  1068.                 /* set up all valid user flags (reversed!) */
  1069.     while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
  1070.               stream->user_flags[i]) elt->user_flags |= 1 << i;
  1071.       }
  1072.       else {            /* header parse failed! */
  1073.     sprintf (LOCAL->buf,"Bad format in message %d",nmsgs);
  1074.     mm_log (LOCAL->buf,ERROR);
  1075.     tenex_close (stream);
  1076.     return NIL;
  1077.       }
  1078.     }
  1079.   }
  1080.   mail_exists (stream,nmsgs);    /* notify upper level of new mailbox size */
  1081.   mail_recent (stream,recent);    /* and of change in recent messages */
  1082.   return T;            /* return the winnage */
  1083. }
  1084.  
  1085. /* Tenex copy messages
  1086.  * Accepts: MAIL stream
  1087.  *        mailbox copy vector
  1088.  *        mailbox name
  1089.  * Returns: T if success, NIL if failed
  1090.  */
  1091.  
  1092. int tenex_copy_messages (stream,mailbox)
  1093.     MAILSTREAM *stream;
  1094.     char *mailbox;
  1095. {
  1096.   struct iovec iov[2];
  1097.   struct stat sbuf;
  1098.   MESSAGECACHE *elt;
  1099.   FILECACHE *m;
  1100.   long j = NIL;
  1101.   long i;
  1102.   int fd = open (tenex_file (LOCAL->buf,mailbox),O_WRONLY|O_APPEND|O_CREAT,
  1103.          S_IREAD|S_IWRITE);
  1104.   if (fd < 0) {            /* got file? */
  1105.     sprintf (LOCAL->buf,"Unable to open copy mailbox: %s",strerror (errno));
  1106.     mm_log (LOCAL->buf,ERROR);
  1107.     return NIL;
  1108.   }
  1109.   mm_critical (stream);        /* go critical */
  1110.   /* This used to be LOCK_EX, but this would cause a hang if the destination
  1111.    * mailbox was open.  This could put MailManager, etc. into a deadlock.
  1112.    * Something will need to be done about interlocking multiple appends.
  1113.    */
  1114.   flock (fd,LOCK_SH);        /* lock out expunges */
  1115.   fstat (fd,&sbuf);        /* get current file size */
  1116.                 /* for each requested message */
  1117.   for (i = 1; i <= stream->nmsgs; i++)
  1118.     if ((elt =mail_elt (stream,i))->sequence) {
  1119.       m = LOCAL->msgs[i - 1];    /* internal message pointers */
  1120.                 /* pointer to text of message */
  1121.       iov[1].iov_base = LOCAL->text + m->header;
  1122.       iov[1].iov_len = m->headersize + m->bodysize;
  1123.       sprintf ((iov[0].iov_base = LOCAL->buf),"%s,%d;000000000000\n",
  1124.            elt->internal_date,iov[1].iov_len);
  1125.                 /* new internal header */
  1126.       iov[0].iov_len = strlen (iov[0].iov_base);
  1127.                 /* write the message */
  1128.       if (j = (writev (fd,iov,2) < 0)) {
  1129.     sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
  1130.     mm_log (LOCAL->buf,ERROR);
  1131.     ftruncate (fd,sbuf.st_size);
  1132.     break;
  1133.       }
  1134.     }
  1135.   fsync (fd);            /* force out the update */
  1136.   flock (fd,LOCK_UN);        /* unlock mailbox */
  1137.   close (fd);            /* close the file */
  1138.   mm_nocritical (stream);    /* release critical */
  1139.   return !j;
  1140. }
  1141.  
  1142. /* Tenex get cache element with status updating from file
  1143.  * Accepts: MAIL stream
  1144.  *        message number
  1145.  * Returns: cache element
  1146.  */
  1147.  
  1148. MESSAGECACHE *tenex_elt (stream,msgno)
  1149.     MAILSTREAM *stream;
  1150.     long msgno;
  1151. {
  1152.   long i = 0,j;
  1153.   char c;
  1154.   char *s;
  1155.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1156.   FILECACHE *m = LOCAL->msgs[msgno-1];
  1157.                 /* locate flags in header */
  1158.   if (!(s = strchr (LOCAL->text + m->internal,';')) ||
  1159.       ((m->header - 1 - (i = ++s - LOCAL->text)) < 12))
  1160.     fatal ("Bad mailbox flag syntax!");
  1161.                 /* set the seek pointer */
  1162.   lseek (LOCAL->fd,(off_t) i,L_SET);
  1163.                 /* read the new flags */
  1164.   if (read (LOCAL->fd,s,12) < 0) {
  1165.     sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
  1166.     fatal (LOCAL->buf);
  1167.   }
  1168.                 /* calculate system flags */
  1169.   elt->seen = (i = ((s[10]-'0') * 8) + s[11]-'0') & fSEEN ? T : NIL;
  1170.   elt->deleted = i & fDELETED ? T : NIL;
  1171.   elt->flagged = i & fFLAGGED ? T : NIL;
  1172.   elt->answered = i & fANSWERED ? T : NIL;
  1173.   c = s[10];            /* remember first system flags byte */
  1174.   s[10] = '\0';            /* tie off flags */
  1175.   j = strtol (s,NIL,8);        /* get user flags value */
  1176.   s[10] = c;            /* restore first system flags byte */
  1177.                 /* set up all valid user flags (reversed!) */
  1178.   while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
  1179.         stream->user_flags[i]) elt->user_flags |= 1 << i;
  1180.   return elt;
  1181. }
  1182.  
  1183. /* Tenex update status string
  1184.  * Accepts: MAIL stream
  1185.  *        message number
  1186.  *        flag saying whether or not to sync
  1187.  */
  1188.  
  1189. void tenex_update_status (stream,msgno,syncflag)
  1190.     MAILSTREAM *stream;
  1191.     long msgno;
  1192.     int syncflag;
  1193. {
  1194.   long i = 0,j;
  1195.   long k = 0;
  1196.   char *s;
  1197.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1198.   FILECACHE *m = LOCAL->msgs[msgno-1];
  1199.   if (!stream->readonly) {    /* not if readonly you don't */
  1200.                 /* locate flags in header */
  1201.     if (!(s = strchr (LOCAL->text + m->internal,';')) ||
  1202.     ((m->header - 1 - (i = ++s - LOCAL->text)) < 12))
  1203.       fatal ("Bad mailbox flag syntax!");
  1204.     j = elt->user_flags;    /* get user flags */
  1205.                 /* reverse bits (dontcha wish we had CIRC?) */
  1206.     while (j) k |= 1 << 29 - find_rightmost_bit (&j);
  1207.                 /* print new flag string */
  1208.     sprintf (LOCAL->buf,"%010lo%02o",k,
  1209.          (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
  1210.          (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered));
  1211.     memcpy (s,LOCAL->buf,12);    /* set the new flags w/o a NUL */
  1212.                 /* set the seek pointer */
  1213.     while (T) {            /* write the new flags */
  1214.       lseek (LOCAL->fd,(off_t) i,L_SET);
  1215.       if (write (LOCAL->fd,s,12) == 12) break;
  1216.       sprintf (LOCAL->buf,"Unable to write status: %s",strerror (j = errno));
  1217.       mm_log (LOCAL->buf,WARN);
  1218.       mm_diskerror (stream,j,T);
  1219.     }
  1220.                 /* sync if requested */
  1221.     if (syncflag) fsync (LOCAL->fd);
  1222.   }
  1223. }
  1224.  
  1225. /* Search support routines
  1226.  * Accepts: MAIL stream
  1227.  *        message number
  1228.  *        pointer to additional data
  1229.  * Returns: T if search matches, else NIL
  1230.  */
  1231.  
  1232.  
  1233. char tenex_search_all (stream,msgno,d,n)
  1234.     MAILSTREAM *stream;
  1235.     long msgno;
  1236.     char *d;
  1237.     long n;
  1238. {
  1239.   return T;            /* ALL always succeeds */
  1240. }
  1241.  
  1242.  
  1243. char tenex_search_answered (stream,msgno,d,n)
  1244.     MAILSTREAM *stream;
  1245.     long msgno;
  1246.     char *d;
  1247.     long n;
  1248. {
  1249.   return mail_elt (stream,msgno)->answered ? T : NIL;
  1250. }
  1251.  
  1252.  
  1253. char tenex_search_deleted (stream,msgno,d,n)
  1254.     MAILSTREAM *stream;
  1255.     long msgno;
  1256.     char *d;
  1257.     long n;
  1258. {
  1259.   return mail_elt (stream,msgno)->deleted ? T : NIL;
  1260. }
  1261.  
  1262.  
  1263. char tenex_search_flagged (stream,msgno,d,n)
  1264.     MAILSTREAM *stream;
  1265.     long msgno;
  1266.     char *d;
  1267.     long n;
  1268. {
  1269.   return mail_elt (stream,msgno)->flagged ? T : NIL;
  1270. }
  1271.  
  1272.  
  1273. char tenex_search_keyword (stream,msgno,d,n)
  1274.     MAILSTREAM *stream;
  1275.     long msgno;
  1276.     char *d;
  1277.     long n;
  1278. {
  1279.   return mail_elt (stream,msgno)->user_flags & n ? T : NIL;
  1280. }
  1281.  
  1282.  
  1283. char tenex_search_new (stream,msgno,d,n)
  1284.     MAILSTREAM *stream;
  1285.     long msgno;
  1286.     char *d;
  1287.     long n;
  1288. {
  1289.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1290.   return (elt->recent && !elt->seen) ? T : NIL;
  1291. }
  1292.  
  1293. char tenex_search_old (stream,msgno,d,n)
  1294.     MAILSTREAM *stream;
  1295.     long msgno;
  1296.     char *d;
  1297.     long n;
  1298. {
  1299.   return mail_elt (stream,msgno)->recent ? NIL : T;
  1300. }
  1301.  
  1302.  
  1303. char tenex_search_recent (stream,msgno,d,n)
  1304.     MAILSTREAM *stream;
  1305.     long msgno;
  1306.     char *d;
  1307.     long n;
  1308. {
  1309.   return mail_elt (stream,msgno)->recent ? T : NIL;
  1310. }
  1311.  
  1312.  
  1313. char tenex_search_seen (stream,msgno,d,n)
  1314.     MAILSTREAM *stream;
  1315.     long msgno;
  1316.     char *d;
  1317.     long n;
  1318. {
  1319.   return mail_elt (stream,msgno)->seen ? T : NIL;
  1320. }
  1321.  
  1322.  
  1323. char tenex_search_unanswered (stream,msgno,d,n)
  1324.     MAILSTREAM *stream;
  1325.     long msgno;
  1326.     char *d;
  1327.     long n;
  1328. {
  1329.   return mail_elt (stream,msgno)->answered ? NIL : T;
  1330. }
  1331.  
  1332.  
  1333. char tenex_search_undeleted (stream,msgno,d,n)
  1334.     MAILSTREAM *stream;
  1335.     long msgno;
  1336.     char *d;
  1337.     long n;
  1338. {
  1339.   return mail_elt (stream,msgno)->deleted ? NIL : T;
  1340. }
  1341.  
  1342.  
  1343. char tenex_search_unflagged (stream,msgno,d,n)
  1344.     MAILSTREAM *stream;
  1345.     long msgno;
  1346.     char *d;
  1347.     long n;
  1348. {
  1349.   return mail_elt (stream,msgno)->flagged ? NIL : T;
  1350. }
  1351.  
  1352.  
  1353. char tenex_search_unkeyword (stream,msgno,d,n)
  1354.     MAILSTREAM *stream;
  1355.     long msgno;
  1356.     char *d;
  1357.     long n;
  1358. {
  1359.   return mail_elt (stream,msgno)->user_flags & n ? NIL : T;
  1360. }
  1361.  
  1362.  
  1363. char tenex_search_unseen (stream,msgno,d,n)
  1364.     MAILSTREAM *stream;
  1365.     long msgno;
  1366.     char *d;
  1367.     long n;
  1368. {
  1369.   return mail_elt (stream,msgno)->seen ? NIL : T;
  1370. }
  1371.  
  1372. char tenex_search_before (stream,msgno,d,n)
  1373.     MAILSTREAM *stream;
  1374.     long msgno;
  1375.     char *d;
  1376.     long n;
  1377. {
  1378.   return (char) (LOCAL->msgs[msgno-1]->date < n);
  1379. }
  1380.  
  1381.  
  1382. char tenex_search_on (stream,msgno,d,n)
  1383.     MAILSTREAM *stream;
  1384.     long msgno;
  1385.     char *d;
  1386.     long n;
  1387. {
  1388.   return (char) (LOCAL->msgs[msgno-1]->date == n);
  1389. }
  1390.  
  1391.  
  1392. char tenex_search_since (stream,msgno,d,n)
  1393.     MAILSTREAM *stream;
  1394.     long msgno;
  1395.     char *d;
  1396.     long n;
  1397. {
  1398.                 /* everybody interprets "since" as .GE. */
  1399.   return (char) (LOCAL->msgs[msgno-1]->date >= n);
  1400. }
  1401.  
  1402.  
  1403. char tenex_search_body (stream,msgno,d,n)
  1404.     MAILSTREAM *stream;
  1405.     long msgno;
  1406.     char *d;
  1407.     long n;
  1408. {
  1409.   FILECACHE *m = LOCAL->msgs[msgno-1];
  1410.   return search (LOCAL->text + m->body,m->bodysize,d,n);
  1411. }
  1412.  
  1413.  
  1414. char tenex_search_subject (stream,msgno,d,n)
  1415.     MAILSTREAM *stream;
  1416.     long msgno;
  1417.     char *d;
  1418.     long n;
  1419. {
  1420.   char *s = tenex_fetchenvelope (stream,msgno)->subject;
  1421.   return s ? search (s,strlen (s),d,n) : NIL;
  1422. }
  1423.  
  1424.  
  1425. char tenex_search_text (stream,msgno,d,n)
  1426.     MAILSTREAM *stream;
  1427.     long msgno;
  1428.     char *d;
  1429.     long n;
  1430. {
  1431.   FILECACHE *m = LOCAL->msgs[msgno-1];
  1432.   return search (LOCAL->text + m->header,m->headersize,d,n) ||
  1433.     tenex_search_body (stream,msgno,d,n);
  1434. }
  1435.  
  1436. char tenex_search_bcc (stream,msgno,d,n)
  1437.     MAILSTREAM *stream;
  1438.     long msgno;
  1439.     char *d;
  1440.     long n;
  1441. {
  1442.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1443.                 /* get text for address */
  1444.   rfc822_write_address (LOCAL->buf,tenex_fetchenvelope (stream,msgno)->bcc);
  1445.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1446. }
  1447.  
  1448.  
  1449. char tenex_search_cc (stream,msgno,d,n)
  1450.     MAILSTREAM *stream;
  1451.     long msgno;
  1452.     char *d;
  1453.     long n;
  1454. {
  1455.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1456.                 /* get text for address */
  1457.   rfc822_write_address (LOCAL->buf,tenex_fetchenvelope (stream,msgno)->cc);
  1458.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1459. }
  1460.  
  1461.  
  1462. char tenex_search_from (stream,msgno,d,n)
  1463.     MAILSTREAM *stream;
  1464.     long msgno;
  1465.     char *d;
  1466.     long n;
  1467. {
  1468.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1469.                 /* get text for address */
  1470.   rfc822_write_address (LOCAL->buf,tenex_fetchenvelope (stream,msgno)->from);
  1471.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1472. }
  1473.  
  1474.  
  1475. char tenex_search_to (stream,msgno,d,n)
  1476.     MAILSTREAM *stream;
  1477.     long msgno;
  1478.     char *d;
  1479.     long n;
  1480. {
  1481.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1482.                 /* get text for address */
  1483.   rfc822_write_address (LOCAL->buf,tenex_fetchenvelope (stream,msgno)->to);
  1484.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1485. }
  1486.  
  1487. /* Search parsers */
  1488.  
  1489.  
  1490. /* Parse a date
  1491.  * Accepts: function to return
  1492.  *        pointer to date integer to return
  1493.  * Returns: function to return
  1494.  */
  1495.  
  1496. search_t tenex_search_date (f,n)
  1497.     search_t f;
  1498.     long *n;
  1499. {
  1500.   long i;
  1501.   char *s;
  1502.                 /* parse the date and return fn if OK */
  1503.   return (tenex_search_string (f,&s,&i) && (*n = tenex_date (s))) ? f : NIL;
  1504. }
  1505.  
  1506.  
  1507. /* Actual date parser (routine)
  1508.     minimal and stupid routine;
  1509.  * Accepts: date to parse
  1510.  * Returns: date integer
  1511.  */
  1512.  
  1513. long tenex_date (s)
  1514.     char *s;
  1515. {
  1516.   long ms;
  1517.   long d,m,y;
  1518.                 /* parse first number (probable month) */
  1519.   if (!(s && (m = strtol ((const char *) s,&s,10)))) return NIL;
  1520.   switch (*s) {            /* different parse based on delimiter */
  1521.   case '/':            /* mm/dd/yy format */
  1522.                 /* parse remainder of date */
  1523.     if (!((d = strtol ((const char *) ++s,&s,10)) && *s == '/' &&
  1524.       (y = strtol ((const char *) ++s,&s,10)) && *s == '\0'))
  1525.       return NIL;
  1526.     break;
  1527.  
  1528.   case '-':            /* dd-mmm-yy format */
  1529.     d = m;            /* so the number we got is a day */
  1530.                 /* make sure string is UC and long enough! */
  1531.     if (strlen (ucase (s)) < 5) return NIL;
  1532.                 /* slurp up the month string */
  1533.     ms = (((long) s[1]) << 16) + (((long) s[2]) << 8) + s[3];
  1534.     switch (ms) {        /* determine the month */
  1535.     case ('J' << 16) + ('A' << 8) + 'N':
  1536.       m = 1; break;
  1537.     case ('F' << 16) + ('E' << 8) + 'B':
  1538.       m = 2; break;
  1539.     case ('M' << 16) + ('A' << 8) + 'R':
  1540.       m = 3; break;
  1541.     case ('A' << 16) + ('P' << 8) + 'R':
  1542.       m = 4; break;
  1543.     case ('M' << 16) + ('A' << 8) + 'Y':
  1544.       m = 5; break;
  1545.     case ('J' << 16) + ('U' << 8) + 'N':
  1546.       m = 6; break;
  1547.     case ('J' << 16) + ('U' << 8) + 'L':
  1548.       m = 7; break;
  1549.     case ('A' << 16) + ('U' << 8) + 'G':
  1550.       m = 8; break;
  1551.     case ('S' << 16) + ('E' << 8) + 'P':
  1552.       m = 9; break;
  1553.     case ('O' << 16) + ('C' << 8) + 'T':
  1554.       m = 10; break;
  1555.     case ('N' << 16) + ('O' << 8) + 'V':
  1556.       m = 11; break;
  1557.     case ('D' << 16) + ('E' << 8) + 'C':
  1558.       m = 12; break;
  1559.     default:
  1560.       return NIL;
  1561.     }
  1562.                 /* parse the year */
  1563.     if (s[4] == '-' && (y = (int) strtol ((const char *) s+5,&s,10)) &&
  1564.     (*s == '\0' || *s == ' ')) break;
  1565.   default:            /* unknown format */
  1566.     return NIL;
  1567.   }
  1568.   y += (y < 64) ? 2000 : 0;    /* the PDP-10 world began in 1964 */
  1569.   y -= (y >= 1964) ? 1964 : 64;
  1570.                 /* minimal validity check of date */
  1571.   if (d < 1 || d > 31 || m < 1 || m > 12 || y < 0 || y >= 100) return NIL;
  1572.   return (y*12+m-1)*31+d-1;    /* calculate and return date value */
  1573. }
  1574.  
  1575. /* Parse a flag
  1576.  * Accepts: function to return
  1577.  *        pointer to keyword integer to return
  1578.  *        MAIL stream
  1579.  * Returns: function to return
  1580.  */
  1581.  
  1582. search_t tenex_search_flag (f,n,stream)
  1583.     search_t f;
  1584.     long *n;
  1585.     MAILSTREAM *stream;
  1586. {
  1587.   short i;
  1588.   char *s,*t;
  1589.   if (t = strtok (NIL," ")) {    /* get a keyword */
  1590.     ucase (t);            /* get uppercase form of flag */
  1591.     for (i = 0; i < NUSERFLAGS && (s = stream->user_flags[i]); ++i)
  1592.       if (!strcmp (t,ucase (strcpy (LOCAL->buf,s))) && (*n = 1 << i)) return f;
  1593.   }
  1594.   return NIL;            /* couldn't find keyword */
  1595. }
  1596.  
  1597. /* Parse a string
  1598.  * Accepts: function to return
  1599.  *        pointer to string to return
  1600.  *        pointer to string length to return
  1601.  * Returns: function to return
  1602.  */
  1603.  
  1604.  
  1605. search_t tenex_search_string (f,d,n)
  1606.     search_t f;
  1607.     char **d;
  1608.     long *n;
  1609. {
  1610.   char *c = strtok (NIL,"");    /* remainder of criteria */
  1611.   if (c) {            /* better be an argument */
  1612.     switch (*c) {        /* see what the argument is */
  1613.     case '\0':            /* catch bogons */
  1614.     case ' ':
  1615.       return NIL;
  1616.     case '"':            /* quoted string */
  1617.       if (!(strchr (c+1,'"') && (*d = strtok (c,"\"")) && (*n = strlen (*d))))
  1618.     return NIL;
  1619.       break;
  1620.     case '{':            /* literal string */
  1621.       *n = strtol (c+1,&c,10);    /* get its length */
  1622.       if (*c++ != '}' || *c++ != '\015' || *c++ != '\012' ||
  1623.       *n > strlen (*d = c)) return NIL;
  1624.       c[*n] = '\255';        /* write new delimiter */
  1625.       strtok (c,"\255");    /* reset the strtok mechanism */
  1626.       break;
  1627.     default:            /* atomic string */
  1628.       *n = strlen (*d = strtok (c," "));
  1629.       break;
  1630.     }
  1631.     return f;
  1632.   }
  1633.   else return NIL;
  1634. }
  1635.