home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / imap-3.0 / non-ANSI / c-client / mmdf.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-06-30  |  65.5 KB  |  2,240 lines

  1. /*
  2.  * Program:    MMDF mail routines
  3.  *
  4.  * Author:    Bruce W. Knipe <bwk@cyberstore.ca> from bezerk.c by:
  5.  *        Mark Crispin
  6.  *        Networks and Distributed Computing
  7.  *        Computing & Communications
  8.  *        University of Washington
  9.  *        Administration Building, AG-44
  10.  *        Seattle, WA  98195
  11.  *        Internet: MRC@CAC.Washington.EDU
  12.  *
  13.  * Date:    15 May 1993
  14.  * Last Edited:    30 June 1993
  15.  *
  16.  * Copyright 1993 by the University of Washington
  17.  *
  18.  *  Permission to use, copy, modify, and distribute this software and its
  19.  * documentation for any purpose and without fee is hereby granted, provided
  20.  * that the above copyright notice appears in all copies and that both the
  21.  * above copyright notice and this permission notice appear in supporting
  22.  * documentation, and that the name of the University of Washington not be
  23.  * used in advertising or publicity pertaining to distribution of the software
  24.  * without specific, written prior permission.  This software is made
  25.  * available "as is", and
  26.  * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
  27.  * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
  28.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
  29.  * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
  30.  * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  31.  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
  32.  * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
  33.  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  34.  *
  35.  */
  36.  
  37. #include <stdio.h>
  38. #include <ctype.h>
  39. #include <netdb.h>
  40. #include <errno.h>
  41. extern int errno;        /* just in case */
  42. #include <signal.h>
  43. #include "mail.h"
  44. #include "osdep.h"
  45. #include <pwd.h>
  46. #include <sys/file.h>
  47. #include <sys/stat.h>
  48. #include <sys/time.h>
  49. #include "mmdf.h"
  50. #include "rfc822.h"
  51. #include "misc.h"
  52.  
  53. /* MMDF mail routines */
  54.  
  55.  
  56. /* Driver dispatch used by MAIL */
  57.  
  58. DRIVER mmdfdriver = {
  59.   "mmdf",            /* driver name */
  60.   (DRIVER *) NIL,        /* next driver */
  61.   mmdf_valid,            /* mailbox is valid for us */
  62.   mmdf_parameters,        /* manipulate parameters */
  63.   mmdf_find,            /* find mailboxes */
  64.   mmdf_find_bboards,        /* find bboards */
  65.   mmdf_find_all,        /* find all mailboxes */
  66.   mmdf_find_all_bboards,    /* find all bboards */
  67.   mmdf_subscribe,        /* subscribe to mailbox */
  68.   mmdf_unsubscribe,        /* unsubscribe from mailbox */
  69.   mmdf_subscribe_bboard,    /* subscribe to bboard */
  70.   mmdf_unsubscribe_bboard,    /* unsubscribe from bboard */
  71.   mmdf_create,            /* create mailbox */
  72.   mmdf_delete,            /* delete mailbox */
  73.   mmdf_rename,            /* rename mailbox */
  74.   mmdf_open,            /* open mailbox */
  75.   mmdf_close,            /* close mailbox */
  76.   mmdf_fetchfast,        /* fetch message "fast" attributes */
  77.   mmdf_fetchflags,        /* fetch message flags */
  78.   mmdf_fetchstructure,        /* fetch message envelopes */
  79.   mmdf_fetchheader,        /* fetch message header only */
  80.   mmdf_fetchtext,        /* fetch message body only */
  81.   mmdf_fetchbody,        /* fetch message body section */
  82.   mmdf_setflag,            /* set message flag */
  83.   mmdf_clearflag,        /* clear message flag */
  84.   mmdf_search,            /* search for message based on criteria */
  85.   mmdf_ping,            /* ping mailbox to see if still alive */
  86.   mmdf_check,            /* check for new messages */
  87.   mmdf_expunge,            /* expunge deleted messages */
  88.   mmdf_copy,            /* copy messages to another mailbox */
  89.   mmdf_move,            /* move messages to another mailbox */
  90.   mmdf_append,            /* append string message to mailbox */
  91.   mmdf_gc            /* garbage collect stream */
  92. };
  93.  
  94.                 /* prototype stream */
  95. MAILSTREAM mmdfproto = {&mmdfdriver};
  96.  
  97. /* MMDF mail validate mailbox
  98.  * Accepts: mailbox name
  99.  * Returns: our driver if name is valid, NIL otherwise
  100.  */
  101.  
  102. DRIVER *mmdf_valid (name)
  103.     char *name;
  104. {
  105.   char tmp[MAILTMPLEN];
  106.   return mmdf_isvalid (name,tmp) ? &mmdfdriver : NIL;
  107. }
  108.  
  109.  
  110. /* MMDF mail test for valid mailbox
  111.  * Accepts: mailbox name
  112.  * Returns: T if valid, NIL otherwise
  113.  */
  114.  
  115. int mmdf_isvalid (name,tmp)
  116.     char *name;
  117.     char *tmp;
  118. {
  119.   int i,fd,ti,zn;
  120.   int ret = NIL;
  121.   char *s = tmp,*t;
  122.   struct stat sbuf;
  123.   struct hostent *host_name;
  124.   if (!lhostn) {        /* have local host yet? */
  125.     gethostname(tmp,MAILTMPLEN);/* get local host name */
  126.     lhostn = cpystr ((host_name = gethostbyname (tmp)) ?
  127.              host_name->h_name : tmp);
  128.   }
  129.                 /* INBOX is always accepted */
  130.   if (!strcmp (ucase (strcpy (tmp,name)),"INBOX")) return T;
  131.                 /* if file, get its status */
  132.   if (*name != '{' && (stat (mmdf_file (tmp,name),&sbuf) == 0) &&
  133.       ((fd = open (tmp,O_RDONLY,NIL)) >= 0)) {
  134.     if (sbuf.st_size == 0) {     /* allow empty file if not .txt */
  135.       if ((i = strlen (tmp)) < 4 || strcmp (tmp + i - 4 ,".txt"))
  136.     return LONGT;
  137.     }
  138.     else if ((read (fd,tmp,MAILTMPLEN-1) >= 0) &&
  139.          (tmp[0] == (char)0x01) && VALID (s,t,ti,zn)) ret = T;
  140.     close (fd);            /* close the file */
  141.   }
  142.   return ret;            /* return what we should */
  143. }
  144.  
  145. /* MMDF manipulate driver parameters
  146.  * Accepts: function code
  147.  *        function-dependent value
  148.  * Returns: function-dependent return value
  149.  */
  150.  
  151. void *mmdf_parameters (function,value)
  152.     long function;
  153.     void *value;
  154. {
  155.   fatal ("Invalid mmdf_parameters function");
  156. }
  157.  
  158. /* MMDF mail find list of mailboxes
  159.  * Accepts: mail stream
  160.  *        pattern to search
  161.  */
  162.  
  163. void mmdf_find (stream,pat)
  164.     MAILSTREAM *stream;
  165.     char *pat;
  166. {
  167.   void *s = NIL;
  168.   char *t,tmp[MAILTMPLEN];
  169.   while (t = sm_read (&s))    /* read subscription database */
  170.     if ((*t != '{') && strcmp (t,"INBOX") && pmatch (t,pat) &&
  171.     mmdf_isvalid (t,tmp)) mm_mailbox (t);
  172. }
  173.  
  174.  
  175. /* MMDF mail find list of bboards
  176.  * Accepts: mail stream
  177.  *        pattern to search
  178.  */
  179.  
  180. void mmdf_find_bboards (stream,pat)
  181.     MAILSTREAM *stream;
  182.     char *pat;
  183. {
  184.   /* Always a no-op */
  185. }
  186.  
  187. /* MMDF mail find list of all mailboxes
  188.  * Accepts: mail stream
  189.  *        pattern to search
  190.  */
  191.  
  192. void mmdf_find_all (stream,pat)
  193.     MAILSTREAM *stream;
  194.     char *pat;
  195. {
  196.   DIR *dirp;
  197.   struct direct *d;
  198.   char tmp[MAILTMPLEN],file[MAILTMPLEN];
  199.   int i = 0;
  200.   char *s,*t;
  201.   if (s = strrchr (pat,'/')) {    /* directory specified in pattern? */
  202.     strncpy (file,pat,i = (++s) - pat);
  203.     file[i] = '\0';        /* tie off prefix */
  204.     t = mmdf_file (tmp,pat);    /* make fully-qualified file name */
  205.                 /* tie off directory name */
  206.     if (s = strrchr (t,'/')) *s = '\0';
  207.   }
  208.   else t = myhomedir ();    /* use home directory to search */
  209.   if (dirp = opendir (t)) {    /* now open that directory */
  210.     while (d = readdir (dirp)) {/* for each directory entry */
  211.       strcpy (file + i,d->d_name);
  212.       if (pmatch (file,pat) && (mmdf_isvalid (file,tmp))) mm_mailbox (file);
  213.     }
  214.     closedir (dirp);        /* flush directory */
  215.   }
  216. }
  217.  
  218.  
  219. /* MMDF mail find list of all bboards
  220.  * Accepts: mail stream
  221.  *        pattern to search
  222.  */
  223.  
  224. void mmdf_find_all_bboards (stream,pat)
  225.     MAILSTREAM *stream;
  226.     char *pat;
  227. {
  228.   /* Always a no-op */
  229. }
  230.  
  231. /* MMDF mail subscribe to mailbox
  232.  * Accepts: mail stream
  233.  *        mailbox to add to subscription list
  234.  * Returns: T on success, NIL on failure
  235.  */
  236.  
  237. long mmdf_subscribe (stream,mailbox)
  238.     MAILSTREAM *stream;
  239.     char *mailbox;
  240. {
  241.   char tmp[MAILTMPLEN];
  242.   return sm_subscribe (mmdf_file (tmp,mailbox));
  243. }
  244.  
  245.  
  246. /* MMDF mail unsubscribe to mailbox
  247.  * Accepts: mail stream
  248.  *        mailbox to delete from subscription list
  249.  * Returns: T on success, NIL on failure
  250.  */
  251.  
  252. long mmdf_unsubscribe (stream,mailbox)
  253.     MAILSTREAM *stream;
  254.     char *mailbox;
  255. {
  256.   char tmp[MAILTMPLEN];
  257.   return sm_unsubscribe (mmdf_file (tmp,mailbox));
  258. }
  259.  
  260.  
  261. /* MMDF mail subscribe to bboard
  262.  * Accepts: mail stream
  263.  *        bboard to add to subscription list
  264.  * Returns: T on success, NIL on failure
  265.  */
  266.  
  267. long mmdf_subscribe_bboard (stream,mailbox)
  268.     MAILSTREAM *stream;
  269.     char *mailbox;
  270. {
  271.   return NIL;            /* never valid for MMDF */
  272. }
  273.  
  274.  
  275. /* MMDF mail unsubscribe to bboard
  276.  * Accepts: mail stream
  277.  *        bboard to delete from subscription list
  278.  * Returns: T on success, NIL on failure
  279.  */
  280.  
  281. long mmdf_unsubscribe_bboard (stream,mailbox)
  282.     MAILSTREAM *stream;
  283.     char *mailbox;
  284. {
  285.   return NIL;            /* never valid for MMDF */
  286. }
  287.  
  288. /* MMDF mail create mailbox
  289.  * Accepts: MAIL stream
  290.  *        mailbox name to create
  291.  * Returns: T on success, NIL on failure
  292.  */
  293.  
  294. long mmdf_create (stream,mailbox)
  295.     MAILSTREAM *stream;
  296.     char *mailbox;
  297. {
  298.   char tmp[MAILTMPLEN];
  299.   int i,fd;
  300.                 /* must be a ".txt" file */
  301.   if ((i = strlen (mailbox)) > 4 && !strcmp (mailbox + i - 4 ,".txt")) {
  302.     mm_log ("Can't create mailbox: name must not end with .txt",ERROR);
  303.     return NIL;
  304.   }
  305.   if ((fd = open (bezerk_file (tmp,mailbox),O_WRONLY|O_CREAT|O_EXCL,0600))<0) {
  306.     sprintf (tmp,"Can't create mailbox %s: %s",mailbox,strerror (errno));
  307.     mm_log (tmp,ERROR);
  308.     return NIL;
  309.   }
  310.   close (fd);            /* close the file */
  311.   return T;            /* return success */
  312. }
  313.  
  314.  
  315. /* MMDF mail delete mailbox
  316.  * Accepts: MAIL stream
  317.  *        mailbox name to delete
  318.  * Returns: T on success, NIL on failure
  319.  */
  320.  
  321. long mmdf_delete (stream,mailbox)
  322.     MAILSTREAM *stream;
  323.     char *mailbox;
  324. {
  325.   return mmdf_rename (stream,mailbox,NIL);
  326. }
  327.  
  328. /* MMDF mail rename mailbox
  329.  * Accepts: MAIL stream
  330.  *        old mailbox name
  331.  *        new mailbox name (or NIL for delete)
  332.  * Returns: T on success, NIL on failure
  333.  */
  334.  
  335. long mmdf_rename (stream,old,new)
  336.     MAILSTREAM *stream;
  337.     char *old;
  338.     char *new;
  339. {
  340.   long ret = T;
  341.   char tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN],lockx[MAILTMPLEN];
  342.   int fd,ld;
  343.                 /* get the c-client lock */
  344.   if ((ld = open (lockname (lock,mmdf_file (file,old)),O_RDWR|O_CREAT,0666))
  345.       < 0) {
  346.     sprintf (tmp,"Can't get lock for mailbox %s: %s",old,strerror (errno));
  347.     mm_log (tmp,ERROR);
  348.     return NIL;
  349.   }
  350.                 /* lock out other c-clients */
  351.   if (flock (ld,LOCK_EX|LOCK_NB)) {
  352.     close (ld);            /* couldn't lock, give up on it then */
  353.     sprintf (tmp,"Mailbox %s is in use by another process",old);
  354.     mm_log (tmp,ERROR);
  355.     return NIL;
  356.   }
  357.                 /* lock out non c-client applications */
  358.   if ((fd = mmdf_lock (file,O_RDONLY,S_IREAD|S_IWRITE,lockx,LOCK_EX)) < 0) {
  359.     sprintf (tmp,"Can't lock mailbox %s: %s",old,strerror (errno));
  360.     mm_log (tmp,ERROR);
  361.     return NIL;
  362.   }
  363.                 /* do the rename or delete operation */
  364.   if (new ? rename (file,mmdf_file (tmp,new)) : unlink (file)) {
  365.     sprintf (tmp,"Can't %s mailbox %s: %s",new ? "rename" : "delete",old,
  366.          strerror (errno));
  367.     mm_log (tmp,ERROR);
  368.     ret = NIL;            /* set failure */
  369.   }
  370.   mmdf_unlock (fd,NIL,lockx);    /* unlock and close mailbox */
  371.   flock (ld,LOCK_UN);        /* release c-client lock lock */
  372.   close (ld);            /* close c-client lock */
  373.   unlink (lock);        /* and delete it */
  374.   return ret;            /* return success */
  375. }
  376.  
  377. /* MMDF mail open
  378.  * Accepts: Stream to open
  379.  * Returns: Stream on success, NIL on failure
  380.  */
  381.  
  382. MAILSTREAM *mmdf_open (stream)
  383.     MAILSTREAM *stream;
  384. {
  385.   long i;
  386.  
  387.   long retry = KODRETRY;    /* number of seconds to retry */
  388.   int fd;
  389.   char tmp[MAILTMPLEN];
  390.   struct stat sbuf;
  391.                 /* return prototype for OP_PROTOTYPE call */
  392.   if (!stream) return &mmdfproto;
  393.   if (LOCAL) {            /* close old file if stream being recycled */
  394.     mmdf_close (stream);    /* dump and save the changes */
  395.     stream->dtb = &mmdfdriver;/* reattach this driver */
  396.     mail_free_cache (stream);    /* clean up cache */
  397.   }
  398.   stream->local = fs_get (sizeof (MMDFLOCAL));
  399.                 /* canonicalize the stream mailbox name */
  400.   mmdf_file (tmp,stream->mailbox);
  401.   fs_give ((void **) &stream->mailbox);
  402.   stream->mailbox = cpystr (tmp);
  403.   /* You may wonder why LOCAL->name is needed.  It isn't at all obvious from
  404.    * the code.  The problem is that when a stream is recycled with another
  405.    * mailbox of the same type, the driver's close method isn't called because
  406.    * it could be IMAP and closing then would defeat the entire point of
  407.    * recycling.  Hence there is code in the file drivers to call the close
  408.    * method such as what appears above.  The problem is, by this point,
  409.    * mail_open() has already changed the stream->mailbox name to point to the
  410.    * new name, and mmdf_close() needs the old name.
  411.    */
  412.   LOCAL->name = cpystr (tmp);    /* local copy for recycle case */
  413.                 /* build name of our lock file */
  414.   LOCAL->lname = cpystr (lockname (tmp,stream->mailbox));
  415.   LOCAL->ld = NIL;        /* no state locking yet */
  416.   LOCAL->filesize = 0;        /* initialize file information */
  417.   LOCAL->filetime = 0;
  418.   LOCAL->msgs = NIL;        /* no cache yet */
  419.   LOCAL->cachesize = 0;
  420.   LOCAL->buf = (char *) fs_get ((LOCAL->buflen = CHUNK) + 1);
  421.   stream->sequence++;        /* bump sequence number */
  422.  
  423.   LOCAL->dirty = NIL;        /* no update yet */
  424.                 /* make lock for read/write access */
  425.   if (!stream->readonly) while (retry) {
  426.                 /* get a new file handle each time */
  427.     if ((fd = open (LOCAL->lname,O_RDWR|O_CREAT,0666)) < 0)
  428.       mm_log ("Can't open mailbox lock, access is readonly",WARN);
  429.                 /* can get the lock? */
  430.     if (flock (fd,LOCK_EX|LOCK_NB)) {
  431.       if (retry-- == KODRETRY) {/* no, first time through? */
  432.                 /* yes, get other process' PID */
  433.     if (!fstat (fd,&sbuf) && (i = min (sbuf.st_size,MAILTMPLEN)) &&
  434.         (read (fd,tmp,i) == i) && (i = atol (tmp))) {
  435.       kill (i,SIGUSR2);    /* send the Kiss Of Death */
  436.       sprintf (tmp,"Trying to get mailbox lock from process %ld",i);
  437.       mm_log (tmp,WARN);
  438.     }
  439.     else retry = 0;        /* give up */
  440.       }
  441.       close (fd);        /* get a new handle next time around */
  442.       if (retry) sleep (1);    /* wait a second before trying again */
  443.       else mm_log ("Mailbox is open by another process, access is readonly",
  444.            WARN);
  445.     }
  446.     else {            /* got the lock, nobody else can alter state */
  447.       LOCAL->ld = fd;        /* note lock's fd */
  448.       chmod (LOCAL->lname,0666);/* make sure mode OK (don't use fchmod()) */
  449.                 /* note our PID in the lock */
  450.       sprintf (tmp,"%d",getpid ());
  451.       write (fd,tmp,strlen (tmp));
  452.       fsync (fd);        /* make sure it's available */
  453.       retry = 0;        /* no more need to try */
  454.     }
  455.   }
  456.  
  457.                 /* parse mailbox */
  458.   stream->nmsgs = stream->recent = 0;
  459.                 /* will we be able to get write access? */
  460.   if (LOCAL->ld && access (LOCAL->name,W_OK) && (errno == EACCES)) {
  461.     mm_log ("Can't get write access to mailbox, access is readonly",WARN);
  462.     flock (LOCAL->ld,LOCK_UN);    /* release the lock */
  463.     close (LOCAL->ld);        /* close the lock file */
  464.     LOCAL->ld = NIL;        /* no more lock fd */
  465.     unlink (LOCAL->lname);    /* delete it */
  466.     fs_give ((void **) &LOCAL->lname);
  467.   }
  468.                 /* parse mailbox */
  469.   if ((fd = mmdf_parse (stream,tmp,LOCK_SH)) >= 0) {
  470.     mmdf_unlock (fd,stream,tmp);
  471.     mail_unlock (stream);
  472.   }
  473.   if (!LOCAL) return NIL;    /* failure if stream died */
  474.   stream->readonly = !LOCAL->ld;/* make sure upper level knows readonly */
  475.                 /* notify about empty mailbox */
  476.   if (!(stream->nmsgs || stream->silent)) mm_log ("Mailbox is empty",NIL);
  477.   return stream;        /* return stream alive to caller */
  478. }
  479.  
  480. /* MMDF mail close
  481.  * Accepts: MAIL stream
  482.  */
  483.  
  484. void mmdf_close (stream)
  485.     MAILSTREAM *stream;
  486. {
  487.   int fd;
  488.   int silent = stream->silent;
  489.   char lock[MAILTMPLEN];
  490.   if (LOCAL && LOCAL->ld) {    /* is stream alive? */
  491.     stream->silent = T;        /* note this stream is dying */
  492.                 /* lock mailbox and parse new messages */
  493.     if (LOCAL->dirty && (fd = mmdf_parse (stream,lock,LOCK_EX)) >= 0) {
  494.                 /* dump any changes not saved yet */
  495.       if (mmdf_extend    (stream,fd,"Close failed to update mailbox"))
  496.     mmdf_save (stream,fd);
  497.                 /* flush locks */
  498.       mmdf_unlock (fd,stream,lock);
  499.       mail_unlock (stream);
  500.     }
  501.     stream->silent = silent;    /* restore previous status */
  502.   }
  503.   mmdf_abort (stream);    /* now punt the file and local data */
  504. }
  505.  
  506.  
  507. /* MMDF mail fetch fast information
  508.  * Accepts: MAIL stream
  509.  *        sequence
  510.  */
  511.  
  512. void mmdf_fetchfast (stream,sequence)
  513.     MAILSTREAM *stream;
  514.     char *sequence;
  515. {
  516.   return;            /* no-op for local mail */
  517. }
  518.  
  519.  
  520. /* MMDF mail fetch flags
  521.  * Accepts: MAIL stream
  522.  *        sequence
  523.  */
  524.  
  525. void mmdf_fetchflags (stream,sequence)
  526.     MAILSTREAM *stream;
  527.     char *sequence;
  528. {
  529.   return;            /* no-op for local mail */
  530. }
  531.  
  532. /* MMDF mail fetch structure
  533.  * Accepts: MAIL stream
  534.  *        message # to fetch
  535.  *        pointer to return body
  536.  * Returns: envelope of this message, body returned in body value
  537.  *
  538.  * Fetches the "fast" information as well
  539.  */
  540.  
  541. ENVELOPE *mmdf_fetchstructure (stream,msgno,body)
  542.     MAILSTREAM *stream;
  543.     long msgno;
  544.     BODY **body;
  545. {
  546.   ENVELOPE **env;
  547.   BODY **b;
  548.   STRING bs;
  549.   LONGCACHE *lelt;
  550.   FILECACHE *m = LOCAL->msgs[msgno - 1];
  551.   long i = max (m->headersize,m->bodysize);
  552.   if (stream->scache) {        /* short cache */
  553.     if (msgno != stream->msgno){/* flush old poop if a different message */
  554.       mail_free_envelope (&stream->env);
  555.       mail_free_body (&stream->body);
  556.     }
  557.     stream->msgno = msgno;
  558.     env = &stream->env;        /* get pointers to envelope and body */
  559.     b = &stream->body;
  560.   }
  561.   else {            /* long cache */
  562.     lelt = mail_lelt (stream,msgno);
  563.     env = &lelt->env;        /* get pointers to envelope and body */
  564.     b = &lelt->body;
  565.   }
  566.   if ((body && !*b) || !*env) {    /* have the poop we need? */
  567.     mail_free_envelope (env);    /* flush old envelope and body */
  568.     mail_free_body (b);
  569.     if (i > LOCAL->buflen) {    /* make sure enough buffer space */
  570.       fs_give ((void **) &LOCAL->buf);
  571.       LOCAL->buf = (char *) fs_get ((LOCAL->buflen = i) + 1);
  572.     }
  573.     INIT (&bs,mail_string,(void *) m->body,m->bodysize);
  574.                 /* parse envelope and body */
  575.     rfc822_parse_msg (env,body ? b : NIL,m->header,m->headersize,&bs,lhostn,
  576.               LOCAL->buf);
  577.   }
  578.   if (body) *body = *b;        /* return the body */
  579.   return *env;            /* return the envelope */
  580. }
  581.  
  582. /* Bezerk mail snarf message, only for Tenex driver
  583.  * Accepts: MAIL stream
  584.  *        message # to snarf
  585.  *        pointer to size to return
  586.  * Returns: message text in RFC822 format
  587.  */
  588.  
  589. char *bezerk_snarf (stream,msgno,size)
  590.     MAILSTREAM *stream;
  591.     long msgno;
  592.     long *size;
  593. {
  594.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  595.   FILECACHE *m = LOCAL->msgs[msgno - 1];
  596.   if (((*size = m->headersize + m->bodysize) > LOCAL->buflen) ||
  597.       LOCAL->buflen > CHUNK) {    /* make sure stream can hold the text */
  598.                 /* fs_resize would do an unnecessary copy */
  599.     fs_give ((void **) &LOCAL->buf);
  600.     LOCAL->buf = (char *) fs_get((LOCAL->buflen = max (*size,(long) CHUNK))+1);
  601.   }
  602.                 /* copy the text */
  603.   if (m->headersize) memcpy (LOCAL->buf,m->header,m->headersize);
  604.   if (m->bodysize) memcpy (LOCAL->buf + m->headersize,m->body,m->bodysize);
  605.   LOCAL->buf[*size] = '\0';    /* tie off string */
  606.   return LOCAL->buf;
  607. }
  608.  
  609. /* MMDF mail fetch message header
  610.  * Accepts: MAIL stream
  611.  *        message # to fetch
  612.  * Returns: message header in RFC822 format
  613.  */
  614.  
  615. char *mmdf_fetchheader (stream,msgno)
  616.     MAILSTREAM *stream;
  617.     long msgno;
  618. {
  619.   FILECACHE *m = LOCAL->msgs[msgno - 1];
  620.                 /* copy the string */
  621.   return strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,m->header,m->headersize);
  622. }
  623.  
  624.  
  625. /* MMDF mail fetch message text (only)
  626.     body only;
  627.  * Accepts: MAIL stream
  628.  *        message # to fetch
  629.  * Returns: message text in RFC822 format
  630.  */
  631.  
  632. char *mmdf_fetchtext (stream,msgno)
  633.     MAILSTREAM *stream;
  634.     long msgno;
  635. {
  636.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  637.   FILECACHE *m = LOCAL->msgs[msgno - 1];
  638.   if (!elt->seen) {        /* if message not seen */
  639.     elt->seen = T;        /* mark message as seen */
  640.                 /* recalculate Status/X-Status lines */
  641.     mmdf_update_status (m->status,elt);
  642.     LOCAL->dirty = T;        /* note stream is now dirty */
  643.   }
  644.   return strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,m->body,m->bodysize);
  645. }
  646.  
  647. /* MMDF fetch message body as a structure
  648.  * Accepts: Mail stream
  649.  *        message # to fetch
  650.  *        section specifier
  651.  *        pointer to length
  652.  * Returns: pointer to section of message body
  653.  */
  654.  
  655. char *mmdf_fetchbody (stream,m,s,len)
  656.     MAILSTREAM *stream;
  657.     long m;
  658.     char *s;
  659.     unsigned long *len;
  660. {
  661.   BODY *b;
  662.   PART *pt;
  663.   unsigned long i;
  664.   char *base = LOCAL->msgs[m - 1]->body;
  665.   unsigned long offset = 0;
  666.   MESSAGECACHE *elt = mail_elt (stream,m);
  667.                 /* make sure have a body */
  668.   if (!(mmdf_fetchstructure (stream,m,&b) && b && s && *s &&
  669.     ((i = strtol (s,&s,10)) > 0))) return NIL;
  670.   do {                /* until find desired body part */
  671.                 /* multipart content? */
  672.     if (b->type == TYPEMULTIPART) {
  673.       pt = b->contents.part;    /* yes, find desired part */
  674.       while (--i && (pt = pt->next));
  675.       if (!pt) return NIL;    /* bad specifier */
  676.                 /* note new body, check valid nesting */
  677.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  678.       offset = pt->offset;    /* get new offset */
  679.     }
  680.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  681.                 /* need to go down further? */
  682.     if (i = *s) switch (b->type) {
  683.     case TYPEMESSAGE:        /* embedded message */
  684.       offset = b->contents.msg.offset;
  685.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  686.     case TYPEMULTIPART:        /* multipart, get next section */
  687.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  688.     default:            /* bogus subpart specification */
  689.       return NIL;
  690.     }
  691.   } while (i);
  692.                 /* lose if body bogus */
  693.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  694.   if (!elt->seen) {        /* if message not seen */
  695.     elt->seen = T;        /* mark message as seen */
  696.                 /* recalculate Status/X-Status lines */
  697.     mmdf_update_status (LOCAL->msgs[m - 1]->status,elt);
  698.     LOCAL->dirty = T;        /* note stream is now dirty */
  699.   }
  700.   return rfc822_contents (&LOCAL->buf,&LOCAL->buflen,len,base + offset,
  701.               b->size.ibytes,b->encoding);
  702. }
  703.  
  704. /* MMDF mail set flag
  705.  * Accepts: MAIL stream
  706.  *        sequence
  707.  *        flag(s)
  708.  */
  709.  
  710. void mmdf_setflag (stream,sequence,flag)
  711.     MAILSTREAM *stream;
  712.     char *sequence;
  713.     char *flag;
  714. {
  715.   MESSAGECACHE *elt;
  716.   long i;
  717.   short f = mmdf_getflags (stream,flag);
  718.   if (!f) return;        /* no-op if no flags to modify */
  719.                 /* get sequence and loop on it */
  720.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  721.     if ((elt = mail_elt (stream,i))->sequence) {
  722.                 /* set all requested flags */
  723.       if (f&fSEEN) elt->seen = T;
  724.       if (f&fDELETED) elt->deleted = T;
  725.       if (f&fFLAGGED) elt->flagged = T;
  726.       if (f&fANSWERED) elt->answered = T;
  727.                 /* recalculate Status/X-Status lines */
  728.       mmdf_update_status (LOCAL->msgs[i - 1]->status,elt);
  729.       LOCAL->dirty = T;        /* note stream is now dirty */
  730.     }
  731. }
  732.  
  733.  
  734. /* MMDF mail clear flag
  735.  * Accepts: MAIL stream
  736.  *        sequence
  737.  *        flag(s)
  738.  */
  739.  
  740. void mmdf_clearflag (stream,sequence,flag)
  741.     MAILSTREAM *stream;
  742.     char *sequence;
  743.     char *flag;
  744. {
  745.   MESSAGECACHE *elt;
  746.   long i;
  747.   short f = mmdf_getflags (stream,flag);
  748.   if (!f) return;        /* no-op if no flags to modify */
  749.                 /* get sequence and loop on it */
  750.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  751.     if ((elt = mail_elt (stream,i))->sequence) {
  752.                 /* clear all requested flags */
  753.       if (f&fSEEN) elt->seen = NIL;
  754.       if (f&fDELETED) elt->deleted = NIL;
  755.       if (f&fFLAGGED) elt->flagged = NIL;
  756.       if (f&fANSWERED) elt->answered = NIL;
  757.                 /* recalculate Status/X-Status lines */
  758.       mmdf_update_status (LOCAL->msgs[i - 1]->status,elt);
  759.       LOCAL->dirty = T;        /* note stream is now dirty */
  760.     }
  761. }
  762.  
  763. /* MMDF mail search for messages
  764.  * Accepts: MAIL stream
  765.  *        search criteria
  766.  */
  767.  
  768. void mmdf_search (stream,criteria)
  769.     MAILSTREAM *stream;
  770.     char *criteria;
  771. {
  772.   long i,n;
  773.   char *d;
  774.   search_t f;
  775.                 /* initially all searched */
  776.   for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = T;
  777.                 /* get first criterion */
  778.   if (criteria && (criteria = strtok (criteria," "))) {
  779.                 /* for each criterion */
  780.     for (; criteria; (criteria = strtok (NIL," "))) {
  781.       f = NIL; d = NIL; n = 0;    /* init then scan the criterion */
  782.       switch (*ucase (criteria)) {
  783.       case 'A':            /* possible ALL, ANSWERED */
  784.     if (!strcmp (criteria+1,"LL")) f = mmdf_search_all;
  785.     else if (!strcmp (criteria+1,"NSWERED")) f = mmdf_search_answered;
  786.     break;
  787.       case 'B':            /* possible BCC, BEFORE, BODY */
  788.     if (!strcmp (criteria+1,"CC"))
  789.       f = mmdf_search_string (mmdf_search_bcc,&d,&n);
  790.     else if (!strcmp (criteria+1,"EFORE"))
  791.       f = mmdf_search_date (mmdf_search_before,&n);
  792.     else if (!strcmp (criteria+1,"ODY"))
  793.       f = mmdf_search_string (mmdf_search_body,&d,&n);
  794.     break;
  795.       case 'C':            /* possible CC */
  796.     if (!strcmp (criteria+1,"C"))
  797.       f = mmdf_search_string (mmdf_search_cc,&d,&n);
  798.     break;
  799.       case 'D':            /* possible DELETED */
  800.     if (!strcmp (criteria+1,"ELETED")) f = mmdf_search_deleted;
  801.     break;
  802.       case 'F':            /* possible FLAGGED, FROM */
  803.     if (!strcmp (criteria+1,"LAGGED")) f = mmdf_search_flagged;
  804.     else if (!strcmp (criteria+1,"ROM"))
  805.       f = mmdf_search_string (mmdf_search_from,&d,&n);
  806.     break;
  807.       case 'K':            /* possible KEYWORD */
  808.     if (!strcmp (criteria+1,"EYWORD"))
  809.       f = mmdf_search_flag (mmdf_search_keyword,&d);
  810.     break;
  811.       case 'N':            /* possible NEW */
  812.     if (!strcmp (criteria+1,"EW")) f = mmdf_search_new;
  813.     break;
  814.  
  815.       case 'O':            /* possible OLD, ON */
  816.     if (!strcmp (criteria+1,"LD")) f = mmdf_search_old;
  817.     else if (!strcmp (criteria+1,"N"))
  818.       f = mmdf_search_date (mmdf_search_on,&n);
  819.     break;
  820.       case 'R':            /* possible RECENT */
  821.     if (!strcmp (criteria+1,"ECENT")) f = mmdf_search_recent;
  822.     break;
  823.       case 'S':            /* possible SEEN, SINCE, SUBJECT */
  824.     if (!strcmp (criteria+1,"EEN")) f = mmdf_search_seen;
  825.     else if (!strcmp (criteria+1,"INCE"))
  826.       f = mmdf_search_date (mmdf_search_since,&n);
  827.     else if (!strcmp (criteria+1,"UBJECT"))
  828.       f = mmdf_search_string (mmdf_search_subject,&d,&n);
  829.     break;
  830.       case 'T':            /* possible TEXT, TO */
  831.     if (!strcmp (criteria+1,"EXT"))
  832.       f = mmdf_search_string (mmdf_search_text,&d,&n);
  833.     else if (!strcmp (criteria+1,"O"))
  834.       f = mmdf_search_string (mmdf_search_to,&d,&n);
  835.     break;
  836.       case 'U':            /* possible UN* */
  837.     if (criteria[1] == 'N') {
  838.       if (!strcmp (criteria+2,"ANSWERED")) f = mmdf_search_unanswered;
  839.       else if (!strcmp (criteria+2,"DELETED")) f = mmdf_search_undeleted;
  840.       else if (!strcmp (criteria+2,"FLAGGED")) f = mmdf_search_unflagged;
  841.       else if (!strcmp (criteria+2,"KEYWORD"))
  842.         f = mmdf_search_flag (mmdf_search_unkeyword,&d);
  843.       else if (!strcmp (criteria+2,"SEEN")) f = mmdf_search_unseen;
  844.     }
  845.     break;
  846.       default:            /* we will barf below */
  847.     break;
  848.       }
  849.       if (!f) {            /* if can't determine any criteria */
  850.     sprintf (LOCAL->buf,"Unknown search criterion: %.30s",criteria);
  851.     mm_log (LOCAL->buf,ERROR);
  852.     return;
  853.       }
  854.                 /* run the search criterion */
  855.       for (i = 1; i <= stream->nmsgs; ++i)
  856.     if (mail_elt (stream,i)->searched && !(*f) (stream,i,d,n))
  857.       mail_elt (stream,i)->searched = NIL;
  858.     }
  859.                 /* report search results to main program */
  860.     for (i = 1; i <= stream->nmsgs; ++i)
  861.       if (mail_elt (stream,i)->searched) mail_searched (stream,i);
  862.   }
  863. }
  864.  
  865. /* MMDF mail ping mailbox
  866.  * Accepts: MAIL stream
  867.  * Returns: T if stream alive, else NIL
  868.  * No-op for readonly files, since read/writer can expunge it from under us!
  869.  */
  870.  
  871. long mmdf_ping (stream)
  872.     MAILSTREAM *stream;
  873. {
  874.   char lock[MAILTMPLEN];
  875.   struct stat sbuf;
  876.   int fd;
  877.                 /* does he want to give up readwrite? */
  878.   if (stream->readonly && LOCAL->ld) {
  879.     flock (LOCAL->ld,LOCK_UN);    /* yes, release the lock */
  880.     close (LOCAL->ld);        /* close the lock file */
  881.     LOCAL->ld = NIL;        /* no more lock fd */
  882.     unlink (LOCAL->lname);    /* delete it */
  883.     fs_give ((void **) &LOCAL->lname);
  884.   }
  885.                 /* make sure it is alright to do this at all */
  886.   if (LOCAL && LOCAL->ld && !stream->lock) {
  887.                 /* get current mailbox size */
  888.     stat (LOCAL->name,&sbuf);    /* parse if mailbox changed */
  889.     if ((sbuf.st_size != LOCAL->filesize) &&
  890.     ((fd = mmdf_parse (stream,lock,LOCK_SH)) >= 0)) {
  891.                 /* unlock mailbox */
  892.       mmdf_unlock (fd,stream,lock);
  893.       mail_unlock (stream);    /* and stream */
  894.     }
  895.   }
  896.   return LOCAL ? T : NIL;    /* return if still alive */
  897. }
  898.  
  899. /* MMDF mail check mailbox
  900.  * Accepts: MAIL stream
  901.  * No-op for readonly files, since read/writer can expunge it from under us!
  902.  */
  903.  
  904. void mmdf_check (stream)
  905.     MAILSTREAM *stream;
  906. {
  907.   char lock[MAILTMPLEN];
  908.   int fd;
  909.                 /* parse and lock mailbox */
  910.   if (LOCAL && LOCAL->ld && ((fd = mmdf_parse (stream,lock,LOCK_EX)) >= 0)) {
  911.     if (LOCAL->dirty && mmdf_extend (stream,fd,"Unable to update mailbox"))
  912.       mmdf_save (stream,fd);    /* dump checkpoint if needed */
  913.                 /* flush locks */
  914.     mmdf_unlock (fd,stream,lock);
  915.     mail_unlock (stream);
  916.   }
  917.   if (LOCAL && LOCAL->ld) mm_log ("Check completed",NIL);
  918. }
  919.  
  920. /* MMDF mail expunge mailbox
  921.  * Accepts: MAIL stream
  922.  */
  923.  
  924. void mmdf_expunge (stream)
  925.     MAILSTREAM *stream;
  926. {
  927.   int fd,j;
  928.   long i = 1;
  929.   long n = 0;
  930.   unsigned long recent;
  931.   MESSAGECACHE *elt;
  932.   char *r = "No messages deleted, so no update needed";
  933.   char lock[MAILTMPLEN];
  934.   if (LOCAL && LOCAL->ld) {    /* parse and lock mailbox */
  935.     if ((fd = mmdf_parse (stream,lock,LOCK_EX)) >= 0) {
  936.       recent = stream->recent;    /* get recent now that new ones parsed */
  937.       while ((j = (i<=stream->nmsgs)) && !(elt = mail_elt (stream,i))->deleted)
  938.     i++;            /* find first deleted message */
  939.       if (j) {            /* found one? */
  940.                 /* make sure we can do the worst case thing */
  941.     if (mmdf_extend (stream,fd,"Unable to expunge mailbox")) {
  942.       do {            /* flush deleted messages */
  943.         if ((elt = mail_elt (stream,i))->deleted) {
  944.                 /* if recent, note one less recent message */
  945.           if (elt->recent) --recent;
  946.                 /* flush local cache entry */
  947.           fs_give ((void **) &LOCAL->msgs[i - 1]);
  948.           for (j = i; j < stream->nmsgs; j++)
  949.         LOCAL->msgs[j - 1] = LOCAL->msgs[j];
  950.           LOCAL->msgs[stream->nmsgs - 1] = NIL;
  951.                 /* notify upper levels */
  952.           mail_expunged (stream,i);
  953.           n++;        /* count another expunged message */
  954.         }
  955.         else i++;        /* otherwise try next message */
  956.       } while (i <= stream->nmsgs);
  957.                 /* dump checkpoint of the results */
  958.       mmdf_save (stream,fd);
  959.       sprintf ((r = LOCAL->buf),"Expunged %d messages",n);
  960.     }
  961.       }
  962.                 /* notify upper level, free locks */
  963.       mail_exists (stream,stream->nmsgs);
  964.       mail_recent (stream,recent);
  965.       mmdf_unlock (fd,stream,lock);
  966.       mail_unlock (stream);
  967.     }
  968.   }
  969.   else r = "Expunge ignored on readonly mailbox";
  970.   if (LOCAL && !stream->silent) mm_log (r,NIL);
  971. }
  972.  
  973. /* MMDF mail copy message(s)
  974.     s;
  975.  * Accepts: MAIL stream
  976.  *        sequence
  977.  *        destination mailbox
  978.  * Returns: T if copy successful, else NIL
  979.  */
  980.  
  981. long mmdf_copy (stream,sequence,mailbox)
  982.     MAILSTREAM *stream;
  983.     char *sequence;
  984.     char *mailbox;
  985. {
  986.                 /* copy the messages */
  987.   return (mail_sequence (stream,sequence)) ?
  988.     mmdf_copy_messages (stream,mailbox) : NIL;
  989. }
  990.  
  991.  
  992. /* MMDF mail move message(s)
  993.     s;
  994.  * Accepts: MAIL stream
  995.  *        sequence
  996.  *        destination mailbox
  997.  * Returns: T if move successful, else NIL
  998.  */
  999.  
  1000. long mmdf_move (stream,sequence,mailbox)
  1001.     MAILSTREAM *stream;
  1002.     char *sequence;
  1003.     char *mailbox;
  1004. {
  1005.   long i;
  1006.   MESSAGECACHE *elt;
  1007.   if (!(mail_sequence (stream,sequence) &&
  1008.     mmdf_copy_messages (stream,mailbox))) return NIL;
  1009.                 /* delete all requested messages */
  1010.   for (i = 1; i <= stream->nmsgs; i++)
  1011.     if ((elt = mail_elt (stream,i))->sequence) {
  1012.       elt->deleted = T;        /* mark message deleted */
  1013.                 /* recalculate Status/X-Status lines */
  1014.       mmdf_update_status (LOCAL->msgs[i - 1]->status,elt);
  1015.       LOCAL->dirty = T;        /* note stream is now dirty */
  1016.     }
  1017.   return T;
  1018. }
  1019.  
  1020. /* MMDF mail append message from stringstruct
  1021.  * Accepts: MAIL stream
  1022.  *        destination mailbox
  1023.  *        stringstruct of messages to append
  1024.  * Returns: T if append successful, else NIL
  1025.  */
  1026.  
  1027. long mmdf_append (stream,mailbox,message)
  1028.     MAILSTREAM *stream;
  1029.     char *mailbox;
  1030.     STRING *message;
  1031. {
  1032.   struct stat sbuf;
  1033.   int fd;
  1034.   char c,tmp[MAILTMPLEN],lock[MAILTMPLEN];
  1035.   int i = 0;
  1036.   char *s = tmp;
  1037.   long t = time (0);
  1038.   long size = SIZE (message);
  1039.   if ((fd = mmdf_lock (mmdf_file (tmp,mailbox),O_WRONLY|O_APPEND|O_CREAT,
  1040.              S_IREAD|S_IWRITE,lock,LOCK_EX)) < 0) {
  1041.     sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
  1042.     mm_log (tmp,ERROR);
  1043.     return NIL;
  1044.   }
  1045.   mm_critical (stream);        /* go critical */
  1046.   fstat (fd,&sbuf);        /* get current file size */
  1047.   sprintf (tmp,"From %s@%s %s",myusername (),lhostn,ctime (&t));
  1048.                 /* write header */
  1049.   if (write (fd,tmp,strlen (tmp)) < 0) {
  1050.     sprintf (tmp,"Header write failed: %s",strerror (errno));
  1051.     mm_log (tmp,ERROR);
  1052.     ftruncate (fd,sbuf.st_size);
  1053.   }
  1054.   else while (size--) {        /* copy text, tossing out CR's */
  1055.     if ((c = SNX (message)) != '\015') s[i++] = c;
  1056.                 /* dump if filled buffer or no more data */
  1057.     if ((!size) || (i == MAILTMPLEN)) {
  1058.       if ((write (fd,tmp,i)) < 0) {
  1059.     sprintf (tmp,"Message append failed: %s",strerror (errno));
  1060.     mm_log (tmp,ERROR);
  1061.     ftruncate (fd,sbuf.st_size);
  1062.     break;
  1063.       }
  1064.       i = 0;            /* restart buffer */
  1065.                 /* write out final newline if at end */
  1066.       if (!size) write (fd,"\n",1);
  1067.     }
  1068.   }
  1069.   fsync (fd);            /* force out the update */
  1070.   mmdf_unlock (fd,NIL,lock);    /* unlock and close mailbox */
  1071.   mm_nocritical (stream);    /* release critical */
  1072.   return T;            /* return success */
  1073. }
  1074.  
  1075. /* MMDF garbage collect stream
  1076.  * Accepts: Mail stream
  1077.  *        garbage collection flags
  1078.  */
  1079.  
  1080. void mmdf_gc (stream,gcflags)
  1081.     MAILSTREAM *stream;
  1082.     long gcflags;
  1083. {
  1084.   /* nothing here for now */
  1085. }
  1086.  
  1087. /* Internal routines */
  1088.  
  1089.  
  1090. /* MMDF mail abort stream
  1091.  * Accepts: MAIL stream
  1092.  */
  1093.  
  1094. void mmdf_abort (stream)
  1095.     MAILSTREAM *stream;
  1096. {
  1097.   long i;
  1098.   if (LOCAL) {            /* only if a file is open */
  1099.     if (LOCAL->name) fs_give ((void **) &LOCAL->name);
  1100.     if (LOCAL->ld) {        /* have a mailbox lock? */
  1101.       flock (LOCAL->ld,LOCK_UN);/* yes, release the lock */
  1102.       close (LOCAL->ld);    /* close the lock file */
  1103.       unlink (LOCAL->lname);    /* and delete it */
  1104.     }
  1105.     fs_give ((void **) &LOCAL->lname);
  1106.     if (LOCAL->msgs) {        /* free local cache */
  1107.       for (i = 0; i < stream->nmsgs; ++i) fs_give ((void **) &LOCAL->msgs[i]);
  1108.       fs_give ((void **) &LOCAL->msgs);
  1109.     }
  1110.                 /* free local text buffers */
  1111.     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
  1112.                 /* nuke the local data */
  1113.     fs_give ((void **) &stream->local);
  1114.     stream->dtb = NIL;        /* log out the DTB */
  1115.   }
  1116. }
  1117.  
  1118.  
  1119. /* MMDF mail generate file string
  1120.  * Accepts: temporary buffer to write into
  1121.  *        mailbox name string
  1122.  * Returns: local file string
  1123.  */
  1124.  
  1125.  
  1126. char *mmdf_file (dst,name)
  1127.     char *dst;
  1128.     char *name;
  1129. {
  1130.   struct passwd *pw;
  1131.   char *s,*t,tmp[MAILTMPLEN];
  1132.   switch (*name) {
  1133.   case '/':            /* absolute file path */
  1134.     strcpy (dst,name);        /* copy the mailbox name */
  1135.     break;
  1136.   case '~':            /* home directory */
  1137.     if (name[1] == '/') t = myhomedir ();
  1138.     else {
  1139.       strcpy (tmp,name + 1);    /* copy user name */
  1140.       if (s = strchr (tmp,'/')) *s = '\0';
  1141.       t = ((pw = getpwnam (tmp)) && pw->pw_dir) ? pw->pw_dir : "/NOSUCHUSER";
  1142.     }
  1143.     sprintf (dst,"%s%s",t,(s = strchr (name,'/')) ? s : "");
  1144.     break;
  1145.   default:            /* other name */
  1146.     if (strcmp (ucase (strcpy (dst,name)),"INBOX"))
  1147.       sprintf (dst,"%s/%s",myhomedir (),name);
  1148.                 /* INBOX becomes mail spool directory file */
  1149.     else sprintf (dst,MAILFILE,myusername ());
  1150.   }
  1151.   return dst;
  1152. }
  1153.  
  1154. /* MMDF open and lock mailbox
  1155.  * Accepts: file name to open/lock
  1156.  *        file open mode
  1157.  *        destination buffer for lock file name
  1158.  *        type of locking operation (LOCK_SH or LOCK_EX)
  1159.  */
  1160.  
  1161. int mmdf_lock (file,flags,mode,lock,op)
  1162.     char *file;
  1163.     int flags;
  1164.     int mode;
  1165.     char *lock;
  1166.     int op;
  1167. {
  1168.   int fd,ld,j;
  1169.   int i = LOCKTIMEOUT * 60 - 1;
  1170.   char tmp[MAILTMPLEN];
  1171.   struct timeval tp;
  1172.   struct stat sb;
  1173.                 /* build lock filename */
  1174.   strcat (mmdf_file (lock,file),".lock");
  1175.   do {                /* until OK or out of tries */
  1176.     gettimeofday (&tp,NIL);    /* get the time now */
  1177. #ifdef NFSKLUDGE
  1178.   /* SUN-OS had an NFS, As kludgy as an albatross;
  1179.    * And everywhere that it was installed, It was a total loss.  -- MRC 9/25/91
  1180.    */
  1181.                 /* build hitching post file name */
  1182.     sprintf (tmp,"%s.%d.%d.",lock,time (0),getpid ());
  1183.     j = strlen (tmp);        /* append local host name */
  1184.     gethostname (tmp + j,(MAILTMPLEN - j) - 1);
  1185.                 /* try to get hitching-post file */
  1186.     if ((ld = open (tmp,O_WRONLY|O_CREAT|O_EXCL,0666)) < 0) {
  1187.                 /* prot fail & non-ex, don't use lock files */
  1188.       if ((errno == EACCES) && (stat (tmp,&sb))) *lock = '\0';
  1189.       else {            /* otherwise something strange is going on */
  1190.     sprintf (tmp,"Error creating %s: %s",lock,strerror (errno));
  1191.     mm_log (tmp,WARN);    /* this is probably not good */
  1192.                 /* don't use lock files if not one of these */
  1193.     if ((errno != EEXIST) && (errno != EACCES)) *lock = '\0';
  1194.       }
  1195.     }
  1196.     else {            /* got a hitching-post */
  1197.       chmod (tmp,0666);        /* make sure others can break the lock */
  1198.       close (ld);        /* close the hitching-post */
  1199.       link (tmp,lock);        /* tie hitching-post to lock, ignore failure */
  1200.       stat (tmp,&sb);        /* get its data */
  1201.       unlink (tmp);        /* flush hitching post */
  1202.       /* If link count .ne. 2, hitch failed.  Set ld to -1 as if open() failed
  1203.      so we try again.  If extant lock file and time now is .gt. file time
  1204.      plus timeout interval, flush the lock so can win next time around. */
  1205.       if ((ld = (sb.st_nlink != 2) ? -1 : 0) && (!stat (lock,&sb)) &&
  1206.       (tp.tv_sec > sb.st_ctime + LOCKTIMEOUT * 60)) unlink (lock);
  1207.     }
  1208.  
  1209. #else
  1210.   /* This works on modern Unix systems which are not afflicted with NFS mail.
  1211.    * "Modern" means that O_EXCL works.  I think that NFS mail is a terrible
  1212.    * idea -- that's what IMAP is for -- but some people insist upon losing...
  1213.    */
  1214.                 /* try to get the lock */
  1215.     if ((ld = open (lock,O_WRONLY|O_CREAT|O_EXCL,0666)) < 0) switch (errno) {
  1216.     case EEXIST:        /* if extant and old, grab it for ourselves */
  1217.       if ((!stat (lock,&sb)) && tp.tv_sec > sb.st_ctime + LOCKTIMEOUT * 60)
  1218.     ld = open (lock,O_WRONLY|O_CREAT,0666);
  1219.       break;
  1220.     case EACCES:        /* protection fail, ignore if non-ex or old */
  1221.       if (stat (lock,&sb) || (tp.tv_sec > sb.st_ctime + LOCKTIMEOUT * 60))
  1222.     *lock = '\0';        /* assume no world write mail spool dir */
  1223.       break;
  1224.     default:            /* some other failure */
  1225.       sprintf (tmp,"Error creating %s: %s",lock,strerror (errno));
  1226.       mm_log (tmp,WARN);    /* this is probably not good */
  1227.       *lock = '\0';        /* don't use lock files */
  1228.       break;
  1229.     }
  1230.     if (ld >= 0) {        /* if made a lock file */
  1231.       chmod (tmp,0666);        /* make sure others can break the lock */
  1232.       close (ld);        /* close the lock file */
  1233.     }
  1234. #endif
  1235.     if ((ld < 0) && *lock) {    /* if failed to make lock file and retry OK */
  1236.       if (!(i%15)) {
  1237.     sprintf (tmp,"Mailbox %s is locked, will override in %d seconds...",
  1238.          file,i);
  1239.     mm_log (tmp,WARN);
  1240.       }
  1241.       sleep (1);        /* wait 1 second before next try */
  1242.     }
  1243.   } while (i-- && ld < 0 && *lock);
  1244.                 /* open file */
  1245.   if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
  1246.   else {            /* open failed */
  1247.     j = errno;            /* preserve error code */
  1248.     if (*lock) unlink (lock);    /* flush the lock file if any */
  1249.     errno = j;            /* restore error code */
  1250.   }
  1251.   return fd;
  1252. }
  1253.  
  1254. /* MMDF unlock and close mailbox
  1255.  * Accepts: file descriptor
  1256.  *        (optional) mailbox stream to check atime/mtime
  1257.  *        (optional) lock file name
  1258.  */
  1259.  
  1260. void mmdf_unlock (fd,stream,lock)
  1261.     int fd;
  1262.     MAILSTREAM *stream;
  1263.     char *lock;
  1264. {
  1265.   struct stat sbuf;
  1266.   struct timeval tp[2];
  1267.   fstat (fd,&sbuf);        /* get file times */
  1268.                 /* if stream and csh would think new mail */
  1269.   if (stream && (sbuf.st_atime <= sbuf.st_mtime)) {
  1270.     gettimeofday (&tp[0],NIL);    /* set atime to now */
  1271.                 /* set mtime to (now - 1) if necessary */
  1272.     tp[1].tv_sec = tp[0].tv_sec > sbuf.st_mtime ? sbuf.st_mtime :
  1273.       tp[0].tv_sec - 1;
  1274.     tp[1].tv_usec = 0;        /* oh well */
  1275.                 /* set the times, note change */
  1276.     if (!utimes (LOCAL->name,tp)) LOCAL->filetime = tp[1].tv_sec;
  1277.   }
  1278.   flock (fd,LOCK_UN);        /* release flock'ers */
  1279.   close (fd);            /* close the file */
  1280.                 /* flush the lock file if any */
  1281.   if (lock && *lock) unlink (lock);
  1282. }
  1283.  
  1284. /* MMDF mail parse and lock mailbox
  1285.  * Accepts: MAIL stream
  1286.  *        space to write lock file name
  1287.  *        type of locking operation
  1288.  * Returns: file descriptor if parse OK, mailbox is locked shared
  1289.  *        -1 if failure, stream aborted
  1290.  */
  1291.  
  1292. int mmdf_parse (stream,lock,op)
  1293.     MAILSTREAM *stream;
  1294.     char *lock;
  1295.     int op;
  1296. {
  1297.   int fd;
  1298.   long delta,i,j,is,is1;
  1299.   char *s,*s1,*t,*e;
  1300.   int ti,zn;
  1301.   int first = T;
  1302.   long nmsgs = stream->nmsgs;
  1303.   long recent = stream->recent;
  1304.   struct stat sbuf;
  1305.   MESSAGECACHE *elt;
  1306.   FILECACHE *m = NIL,*n;
  1307.   mail_lock (stream);        /* guard against recursion or pingers */
  1308.                 /* open and lock mailbox (shared OK) */
  1309.   if ((fd = mmdf_lock (LOCAL->name,LOCAL->ld ? O_RDWR : O_RDONLY,NIL,
  1310.              lock,op)) < 0) {
  1311.                 /* failed, OK for non-ex file? */
  1312.     if ((errno != ENOENT) || LOCAL->filesize) {
  1313.       sprintf (LOCAL->buf,"Mailbox open failed, aborted: %s",strerror (errno));
  1314.       mm_log (LOCAL->buf,ERROR);
  1315.       mmdf_abort (stream);
  1316.     }
  1317.     else {            /* this is to allow for non-ex INBOX */
  1318.       mail_exists (stream,0);    /* make sure upper level sees this as empty */
  1319.       mail_recent (stream,0);
  1320.     }
  1321.     mail_unlock (stream);
  1322.     return -1;
  1323.   }
  1324.   fstat (fd,&sbuf);        /* get status */
  1325.                 /* calculate change in size */
  1326.   if ((delta = sbuf.st_size - LOCAL->filesize) < 0) {
  1327.     sprintf (LOCAL->buf,"Mailbox shrank from %d to %d bytes, aborted",
  1328.          LOCAL->filesize,sbuf.st_size);
  1329.     mm_log (LOCAL->buf,ERROR);    /* this is pretty bad */
  1330.     mmdf_unlock (fd,stream,lock);
  1331.     mmdf_abort (stream);
  1332.     mail_unlock (stream);
  1333.     return -1;
  1334.   }
  1335.  
  1336.   else if (delta) {        /* get to that position in the file */
  1337.     lseek (fd,LOCAL->filesize,L_SET);
  1338.     s = s1 = LOCAL->buf;    /* initial read-in location */
  1339.     i = 0;            /* initial unparsed read-in count */
  1340.     do {
  1341.       i = min (CHUNK,delta);    /* calculate read-in size */
  1342.                 /* increase the read-in buffer if necessary */
  1343.       if ((j = i + (s1 - s)) >= LOCAL->buflen) {
  1344.     is = s - LOCAL->buf;    /* note former start of message position */
  1345.     is1 = s1 - LOCAL->buf;    /* and start of new data position */
  1346.     if (s1 - s) fs_resize ((void **) &LOCAL->buf,(LOCAL->buflen = j) + 1);
  1347.     else {            /* fs_resize would do an unnecessary copy */
  1348.       fs_give ((void **) &LOCAL->buf);
  1349.       LOCAL->buf = (char *) fs_get ((LOCAL->buflen = j) + 1);
  1350.     }
  1351.     s = LOCAL->buf + is;    /* new start of message */
  1352.     s1 = LOCAL->buf + is1;    /* new start of new data */
  1353.       }
  1354.       s1[i] = '\0';        /* tie off chunk */
  1355.       if (read (fd,s1,i) < 0) {    /* read a chunk of new text */
  1356.     sprintf (LOCAL->buf,"Error reading mail file: %s",strerror (errno));
  1357.     mm_log (LOCAL->buf,ERROR);
  1358.     mmdf_unlock (fd,stream,lock);
  1359.     mmdf_abort (stream);
  1360.     mail_unlock (stream);
  1361.     return -1;
  1362.       }
  1363.       delta -= i;        /* account for data read in */
  1364.                 /* validate newly-appended data */
  1365.       if (first) {        /* only do this first time! */
  1366.     if (!((*s ==(char)0x01) && VALID (s,t,ti,zn))) {
  1367.       mm_log ("Mailbox format invalidated (consult an expert), aborted",
  1368.           ERROR);
  1369.       mmdf_unlock (fd,stream,lock);
  1370.       mmdf_abort (stream);
  1371.       mail_unlock (stream);
  1372.       return -1;
  1373.     }
  1374.     first = NIL;        /* don't do this again */
  1375.       }
  1376.  
  1377.                 /* found end of message or end of data? */
  1378.       while ((e = mmdf_eom (s,s1,i)) || !delta) {
  1379.     nmsgs++;        /* yes, have a new message */
  1380.     if (e) j = (e - s) - 1;    /* calculate message length */
  1381.     else j = strlen (s) - 1;/* otherwise is remainder of data */
  1382.     if (m) {        /* new cache needed, have previous data? */
  1383.       n->header = (char *) fs_get (sizeof (FILECACHE) + j + 1);
  1384.       n = (FILECACHE *) n->header;
  1385.     }
  1386.     else m = n = (FILECACHE *) fs_get (sizeof (FILECACHE) + j + 1);
  1387.                 /* copy message data */
  1388.     strncpy (n->internal,s,j);
  1389.     n->internal[j] = '\0';
  1390.     n->header = NIL;    /* initially no link */
  1391.     n->headersize = j;    /* stash away buffer length */
  1392.     if (e) {        /* saw end of message? */
  1393.       i -= e - s1;        /* new unparsed data count */
  1394.       s = s1 = e;        /* advance to new message */
  1395.     }
  1396.     else break;        /* else punt this loop */
  1397.       }
  1398.       if (delta) {        /* end of message not found? */
  1399.     s1 += i;        /* end of unparsed data */
  1400.     if (s != LOCAL->buf){    /* message doesn't begin at buffer? */
  1401.       i = s1 - s;        /* length of message so far */
  1402.       memmove (LOCAL->buf,s,i);
  1403.       s = LOCAL->buf;    /* message now starts at buffer origin */
  1404.       s1 = s + i;        /* calculate new end of unparsed data */
  1405.     }
  1406.       }
  1407.     } while (delta);        /* until nothing more new to read */
  1408.   }
  1409.   else {            /* no change, don't babble if never got time */
  1410.     if (LOCAL->filetime && LOCAL->filetime != sbuf.st_mtime)
  1411.       mm_log ("New mailbox modification time but apparently no changes",WARN);
  1412.   }
  1413.                 /* expand the primary cache */
  1414.   (*mailcache) (stream,nmsgs,CH_SIZE);
  1415.   if (LOCAL->msgs) {        /* have a cache yet? */
  1416.     if (nmsgs >= LOCAL->cachesize)
  1417.       fs_resize ((void **) &LOCAL->msgs,
  1418.          (LOCAL->cachesize += CACHEINCREMENT) * sizeof (FILECACHE *));
  1419.   }
  1420.   else LOCAL->msgs = (FILECACHE **)
  1421.     fs_get ((LOCAL->cachesize = nmsgs + CACHEINCREMENT) *sizeof (FILECACHE *));
  1422.  
  1423.   if (LOCAL->buflen > CHUNK) {    /* maybe move where the buffer is in memory*/
  1424.     fs_give ((void **) &LOCAL->buf);
  1425.     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = CHUNK) + 1);
  1426.   }
  1427.   for (i = stream->nmsgs, n = m; i < nmsgs; i++) {
  1428.     LOCAL->msgs[i] = m = n;    /* set cache, and next cache pointer */
  1429.     n = (FILECACHE *) n->header;
  1430.     /* This is a bugtrap for bogons in the new message cache, which may happen
  1431.      * if memory is corrupted.  Note that in the case of a totally empty
  1432.      * message, a newline is appended and counts adjusted.
  1433.      */
  1434.     if ((!((s = m->internal) && VALID (s,t,ti,zn))) &&
  1435.     !(s && !strchr (s,'\n') && strcat (s,"\n") && VALID (s,t,ti,zn) &&
  1436.       m->headersize++)) fatal ("Bogus entry in new cache list");
  1437.                 /* pointer to message header */
  1438.     m->header = s = strchr (t++,'\n') + 1;
  1439.     m->headersize -= m->header - m->internal;
  1440.     m->body = NIL;        /* assume no body as yet */
  1441.     m->bodysize = 0;
  1442.     recent++;            /* assume recent by default */
  1443.     (elt = mail_elt (stream,i+1))->recent = T;
  1444.                 /* calculate initial Status/X-Status lines */
  1445.     mmdf_update_status (m->status,elt);
  1446.                 /* generate plausable IMAPish date string */
  1447.     LOCAL->buf[2] = LOCAL->buf[6] = LOCAL->buf[20] = '-';
  1448.     LOCAL->buf[11] = ' ';
  1449.     LOCAL->buf[14] = LOCAL->buf[17] = ':';
  1450.                 /* dd */
  1451.     LOCAL->buf[0] = t[ti - 3]; LOCAL->buf[1] = t[ti - 2];
  1452.                 /* mmm */
  1453.     LOCAL->buf[3] = t[ti - 7]; LOCAL->buf[4] = t[ti - 6];
  1454.     LOCAL->buf[5] = t[ti - 5];
  1455.                 /* hh */
  1456.     LOCAL->buf[12] = t[ti]; LOCAL->buf[13] = t[ti + 1];
  1457.                 /* mm */
  1458.     LOCAL->buf[15] = t[ti + 3]; LOCAL->buf[16] = t[ti + 4];
  1459.     if (t[ti += 5] == ':') {    /* ss if present */
  1460.       LOCAL->buf[18] = t[++ti];
  1461.       LOCAL->buf[19] = t[++ti];
  1462.       ti++;            /* move to space */
  1463.     }
  1464.     else LOCAL->buf[18] = LOCAL->buf[19] = '0';
  1465.                 /* yy -- advance over timezone if necessary */
  1466.     if (zn == ++ti) ti += (((t[zn] == '+') || (t[zn] == '-')) ? 6 : 4);
  1467.     LOCAL->buf[7] = t[ti]; LOCAL->buf[8] = t[ti + 1];
  1468.     LOCAL->buf[9] = t[ti + 2]; LOCAL->buf[10] = t[ti + 3];
  1469.                 /* zzz */
  1470.     t = zn ? (t + zn) : "LCL";
  1471.     LOCAL->buf[21] = *t++; LOCAL->buf[22] = *t++; LOCAL->buf[23] = *t++;
  1472.     if ((LOCAL->buf[21] != '+') && (LOCAL->buf[21] != '-'))
  1473.       LOCAL->buf[24] = '\0';
  1474.     else {            /* numeric time zone */
  1475.       LOCAL->buf[24] = *t++; LOCAL->buf[25] = *t++;
  1476.       LOCAL->buf[26] = '\0'; LOCAL->buf[20] = ' ';
  1477.     }
  1478.                 /* set internal date */
  1479.     if (!mail_parse_date (elt,LOCAL->buf)) mm_log ("Unparsable date",WARN);
  1480.  
  1481.     is = 0;            /* initialize newline count */
  1482.     e = NIL;            /* no status stuff yet */
  1483.     do switch (*(t = s)) {    /* look at header lines */
  1484.     case 'X':            /* possible X-Status: line */
  1485.       if (s[1] == '-' && s[2] == 'S') s += 2;
  1486.       else {
  1487.     is++;            /* count another newline */
  1488.     break;            /* this is uninteresting after all */
  1489.       }
  1490.     case 'S':            /* possible Status: line */
  1491.       if (s[1] == 't' && s[2] == 'a' && s[3] == 't' && s[4] == 'u' &&
  1492.       s[5] == 's' && s[6] == ':') {
  1493.     if (!e) e = t;        /* note deletion point */
  1494.     s += 6;            /* advance to status flags */
  1495.     do switch (*s++) {    /* parse flags */
  1496.     case 'R':        /* message read */
  1497.       elt->seen = T;
  1498.       break;
  1499.     case 'O':        /* message old */
  1500.       if (elt->recent) {    /* don't do this more than once! */
  1501.         elt->recent = NIL;
  1502.         recent--;        /* not recent any longer... */
  1503.       }
  1504.       break;
  1505.     case 'D':        /* message deleted */
  1506.       elt->deleted = T;
  1507.       break;
  1508.     case 'F':        /* message flagged */
  1509.       elt ->flagged = T;
  1510.       break;
  1511.     case 'A':        /* message answered */
  1512.       elt ->answered = T;
  1513.       break;
  1514.     default:        /* some other crap */
  1515.       break;
  1516.     } while (*s && *s != '\n');
  1517.                 /* recalculate Status/X-Status lines */
  1518.     mmdf_update_status (m->status,elt);
  1519.       }
  1520.       else is++;        /* otherwise random line */
  1521.       break;
  1522.  
  1523.     case '\n':            /* end of header */
  1524.       m->body = ++s;        /* start of body is here */
  1525.       j = m->body - m->header;    /* new header size */
  1526.                 /* calculate body size */
  1527.       m->bodysize = m->headersize - j;
  1528.       if (e) {            /* saw status poop? */
  1529.     *e++ = '\n';        /* patch in trailing newline */
  1530.     m->headersize = e - m->header;
  1531.       }
  1532.       else m->headersize = j;    /* set header size */
  1533.       s = NIL;            /* don't scan any further */
  1534.       is++;            /* count a newline */
  1535.       break;
  1536.     case '\0':            /* end of message */
  1537.       if (e) {            /* saw status poop? */
  1538.     *e++ = '\n';        /* patch in trailing newline */
  1539.     m->headersize = e - m->header;
  1540.       }
  1541.       is++;            /* count an extra newline here */
  1542.       break;
  1543.     default:            /* anything else is uninteresting */
  1544.       if (e) {            /* have status stuff to worry about? */
  1545.     j = s - e;        /* yuck!!  calculate size of delete area */
  1546.                 /* blat remaining number of bytes down */
  1547.     memmove (e,s,m->header + m->headersize - s);
  1548.     m->headersize -= j;    /* update for new size */
  1549.     e = NIL;        /* no more delete area */
  1550.                 /* tie off old cruft */
  1551.     *(m->header + m->headersize) = '\0';
  1552.       }
  1553.       is++;
  1554.       break;
  1555.     } while (s && (s = strchr (s,'\n')) && s++);
  1556.                 /* count newlines in body */
  1557.     if (s = m->body) while (*s) if (*s++ == '\n') is++;
  1558.     elt->rfc822_size = m->headersize + m->bodysize + is;
  1559.   }
  1560.   if (n) fatal ("Cache link-list inconsistency");
  1561.   while (i < LOCAL->cachesize) LOCAL->msgs[i++] = NIL;
  1562.                 /* update parsed file size and time */
  1563.   LOCAL->filesize = sbuf.st_size;
  1564.   LOCAL->filetime = sbuf.st_mtime;
  1565.   mail_exists (stream,nmsgs);    /* notify upper level of new mailbox size */
  1566.   mail_recent (stream,recent);    /* and of change in recent messages */
  1567.   if (recent) LOCAL->dirty = T;    /* mark dirty so O flags are set */
  1568.   return fd;            /* return the winnage */
  1569. }
  1570.  
  1571. /* MMDF search for end of message
  1572.  * Accepts: start of message
  1573.  *        start of new data
  1574.  *        size of new data
  1575.  * Returns: pointer to start of new message if one found
  1576.  */
  1577.  
  1578. #define Word unsigned long
  1579.  
  1580. char *mmdf_eom (som,sod,i)
  1581.     char *som;
  1582.     char *sod;
  1583.     long i;
  1584. {
  1585.   char *s = sod;        /* find start of line in current message */
  1586.   char *t;
  1587.   int ti,zn;
  1588.   union {
  1589.     unsigned long wd;
  1590.     char ch[9];
  1591.   } wdtest;
  1592.   strcpy (wdtest.ch,"AAAA1234");/* constant for word testing */
  1593.   while ((s > som) && *s-- != '\n');
  1594.   if (wdtest.wd != 0x41414141) {/* not a 32-bit word machine? */
  1595.     while (s = strstr (s,"\nFrom ")) if (s++ && VALID (s,t,ti,zn)) return s;
  1596.   }
  1597.   else {            /* can do it faster this way */
  1598.     register Word m = 0x0a0a0a0a;
  1599.     while ((long) s & 3)    /* any characters before word boundary? */
  1600.       if ((*s++ == '\n') && s[0] == (char)0x01 && VALID (s,t,ti,zn)) return s;
  1601.     i = (sod + i) - s;        /* total number of tries */
  1602.     do {            /* fast search for newline */
  1603.       if (0x80808080 & (0x01010101 + (0x7f7f7f7f & ~(m ^ *(Word *) s)))) {
  1604.                 /* interesting word, check it closer */
  1605.     if (*s++ == '\n' && s[0] == (char)0x01 && VALID (s,t,ti,zn)) return s;
  1606.     else if (*s++ == '\n' && s[0] == (char)0x01 && VALID (s,t,ti,zn)) return s;
  1607.     else if (*s++ == '\n' && s[0] == (char)0x01 && VALID (s,t,ti,zn)) return s;
  1608.     else if (*s++ == '\n' && s[0] == (char)0x01 && VALID (s,t,ti,zn)) return s;
  1609.       }
  1610.       else s += 4;        /* try next word */
  1611.       i -= 4;            /* count a word checked */
  1612.     } while (i > 24);        /* continue until end of plausible string */
  1613.   }
  1614.   return NIL;
  1615. }
  1616.  
  1617. /* MMDF extend mailbox to reserve worst-case space for expansion
  1618.  * Accepts: MAIL stream
  1619.  *        file descriptor
  1620.  *        error string
  1621.  * Returns: T if extend OK and have gone critical, NIL if should abort
  1622.  */
  1623.  
  1624. int mmdf_extend (stream,fd,error)
  1625.     MAILSTREAM *stream;
  1626.     int fd;
  1627.     char *error;
  1628. {
  1629.   struct stat sbuf;
  1630.   FILECACHE *m;
  1631.   char tmp[MAILTMPLEN];
  1632.   int i,ok;
  1633.   long f;
  1634.   char *s;
  1635.   int retry;
  1636.                 /* calculate estimated size of mailbox */
  1637.   for (i = 0,f = 0; i < stream->nmsgs; i++) {
  1638.     m = LOCAL->msgs[i];        /* get cache pointer */
  1639.     f += (m->header - m->internal) + m->headersize + sizeof (STATUS) +
  1640.       m->bodysize + 1;        /* update guesstimate */
  1641.     }
  1642.   mm_critical (stream);        /* go critical */
  1643.                 /* return now if file large enough */
  1644.   if (f <= LOCAL->filesize) return T;
  1645.   s = (char *) fs_get (f -= LOCAL->filesize);
  1646.   memset (s,0,f);        /* get a block of nulls */
  1647.                 /* get to end of file */
  1648.   lseek (fd,LOCAL->filesize,L_SET);
  1649.   do {
  1650.     retry = NIL;        /* no retry yet */
  1651.     if (!(ok = (write (fd,s,f) >= 0))) {
  1652.       i = errno;        /* note error before doing ftruncate */
  1653.                 /* restore prior file size */
  1654.       ftruncate (fd,LOCAL->filesize);
  1655.       fsync (fd);        /* is this necessary? */
  1656.       fstat (fd,&sbuf);        /* now get updated file time */
  1657.       LOCAL->filetime = sbuf.st_mtime;
  1658.                 /* punt if that's what main program wants */
  1659.       if (mm_diskerror (stream,i,NIL)) {
  1660.     mm_nocritical (stream);    /* exit critical */
  1661.     sprintf (tmp,"%s: %s",error,strerror (i));
  1662.     mm_notify (stream,tmp,WARN);
  1663.       }
  1664.       else retry = T;        /* set to retry */
  1665.     }
  1666.   } while (retry);        /* repeat if need to try again */
  1667.   fs_give ((void **) &s);    /* flush buffer of nulls */
  1668.   return ok;            /* return status */
  1669. }
  1670.  
  1671. /* MMDF save mailbox
  1672.  * Accepts: MAIL stream
  1673.  *        mailbox file descriptor
  1674.  *
  1675.  * Mailbox must be readwrite and locked for exclusive access.
  1676.  */
  1677.  
  1678. void mmdf_save (stream,fd)
  1679.     MAILSTREAM *stream;
  1680.     int fd;
  1681. {
  1682.   struct stat sbuf;
  1683.   struct iovec iov[16];
  1684.   int iovc;
  1685.   long i;
  1686.   int e;
  1687.   int retry;
  1688.   do {                /* restart point if failure */
  1689.     retry = NIL;        /* no need to retry yet */
  1690.                 /* start at beginning of file */
  1691.     lseek (fd,LOCAL->filesize = 0,L_SET);
  1692.                 /* loop through all messages */
  1693.     for (i = 1,iovc = 0; i <= stream->nmsgs; i++) {
  1694.                 /* set up iov's for this message */
  1695.       mmdf_write_message (iov,&iovc,LOCAL->msgs[i-1]);
  1696.                 /* filled up iovec or end of messages? */
  1697.       if ((iovc == 16) || (i == stream->nmsgs)) {
  1698.                 /* write messages */
  1699.     if ((e = writev (fd,iov,iovc)) < 0) {
  1700.       sprintf (LOCAL->buf,"Unable to rewrite mailbox: %s",
  1701.            strerror (e = errno));
  1702.       mm_log (LOCAL->buf,WARN);
  1703.       mm_diskerror (stream,e,T);
  1704.       retry = T;        /* must retry */
  1705.       break;        /* abort this particular try */
  1706.     }
  1707.     else {            /* won */
  1708.       iovc = 0;        /* restart iovec */
  1709.       LOCAL->filesize += e;    /* count these bytes in data */
  1710.     }
  1711.       }
  1712.     }
  1713.   } while (retry);        /* repeat if need to try again */
  1714.   fsync (fd);            /* make sure the disk has the update */
  1715.                 /* nuke any cruft after that */
  1716.   ftruncate (fd,LOCAL->filesize);
  1717.   fsync (fd);            /* is this necessary? */
  1718.   fstat (fd,&sbuf);        /* now get updated file time */
  1719.   LOCAL->filetime = sbuf.st_mtime;
  1720.   LOCAL->dirty = NIL;        /* stream no longer dirty */
  1721.   mm_nocritical (stream);    /* exit critical */
  1722. }
  1723.  
  1724. /* MMDF copy messages
  1725.  * Accepts: MAIL stream
  1726.  *        mailbox name
  1727.  * Returns: T if copy successful else NIL
  1728.  */
  1729.  
  1730. int mmdf_copy_messages (stream,mailbox)
  1731.     MAILSTREAM *stream;
  1732.     char *mailbox;
  1733. {
  1734.   char file[MAILTMPLEN];
  1735.   char lock[MAILTMPLEN];
  1736.   struct iovec iov[16];
  1737.   int fd,iovc;
  1738.   struct stat sbuf;
  1739.   long i;
  1740.   int ok = T;
  1741.                 /* make sure valid mailbox */
  1742.   if (!mmdf_isvalid (mailbox,file)) {
  1743.     sprintf (LOCAL->buf,"Not a MMDF-format mailbox: %s",mailbox);
  1744.     mm_log (LOCAL->buf,ERROR);
  1745.     return NIL;
  1746.   }
  1747.   if ((fd = mmdf_lock (mmdf_file (file,mailbox),O_WRONLY|O_APPEND|O_CREAT,
  1748.              S_IREAD|S_IWRITE,lock,LOCK_EX)) < 0) {
  1749.     sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
  1750.     mm_log (LOCAL->buf,ERROR);
  1751.     return NIL;
  1752.   }
  1753.   mm_critical (stream);        /* go critical */
  1754.   fstat (fd,&sbuf);        /* get current file size */
  1755.                 /* write all requested messages to mailbox */
  1756.   for (i = 1,iovc = 0; ok && i <= stream->nmsgs; i++) {
  1757.                 /* set up iov's if message selected */
  1758.     if (mail_elt (stream,i)->sequence)
  1759.       mmdf_write_message (iov,&iovc,LOCAL->msgs[i - 1]);
  1760.                 /* filled up iovec or end of messages? */
  1761.     if (iovc && ((iovc == 16) || (i == stream->nmsgs))) {
  1762.       if (ok = (writev (fd,iov,iovc) >= 0)) iovc = 0;
  1763.       else {
  1764.     sprintf (LOCAL->buf,"Message copy failed: %s",strerror (errno));
  1765.     mm_log (LOCAL->buf,ERROR);
  1766.     ftruncate (fd,sbuf.st_size);
  1767.     break;
  1768.       }
  1769.     }
  1770.   }
  1771.   fsync (fd);            /* force out the update */
  1772.   mmdf_unlock (fd,NIL,lock);    /* unlock and close mailbox */
  1773.   mm_nocritical (stream);    /* release critical */
  1774.   return ok;            /* return whether or not succeeded */
  1775. }
  1776.  
  1777. /* MMDF write message to mailbox
  1778.  * Accepts: I/O vector
  1779.  *        I/O vector index
  1780.  *        local cache for this message
  1781.  *
  1782.  * This routine is the reason why the local cache has a copy of the status.
  1783.  * We can be called to dump out the mailbox as part of a stream recycle, since
  1784.  * we don't write out the mailbox when flags change and hence an update may be
  1785.  * needed.  However, at this point the elt has already become history, so we
  1786.  * can't use any information other than what is local to us.
  1787.  */
  1788.  
  1789. void mmdf_write_message (iov,i,m)
  1790.     struct iovec iov[];
  1791.     int *i;
  1792.     FILECACHE *m;
  1793. {
  1794.   iov[*i].iov_base =m->internal;/* pointer/counter to headers */
  1795.                 /* length of internal + message headers */
  1796.   iov[*i].iov_len = (m->header + m->headersize) - m->internal;
  1797.                 /* suppress extra newline if present */
  1798.   if ((iov[*i].iov_base)[iov[*i].iov_len - 2] == '\n') iov[(*i)++].iov_len--;
  1799.   else (*i)++;            /* unlikely but... */
  1800.   iov[*i].iov_base = m->status;    /* pointer/counter to status */
  1801.   iov[(*i)++].iov_len = strlen (m->status);
  1802.   iov[*i].iov_base = m->body;    /* pointer/counter to text body */
  1803.   iov[(*i)++].iov_len = m->bodysize;
  1804.   iov[*i].iov_base = "\n";    /* pointer/counter to extra newline */
  1805.   iov[(*i)++].iov_len = 1;
  1806. }
  1807.  
  1808. /* MMDF update status string
  1809.  * Accepts: destination string to write
  1810.  *        message cache entry
  1811.  */
  1812.  
  1813. void mmdf_update_status (status,elt)
  1814.     char *status;
  1815.     MESSAGECACHE *elt;
  1816. {
  1817.   /* This used to be an sprintf(), but thanks to certain cretinous C libraries
  1818.      with horribly slow implementations of sprintf() I had to change it to this
  1819.      mess.  At least it should be fast. */
  1820.   char *t = status + 8;
  1821.   status[0] = 'S'; status[1] = 't'; status[2] = 'a'; status[3] = 't';
  1822.   status[4] = 'u'; status[5] = 's'; status[6] = ':';  status[7] = ' ';
  1823.   if (elt->seen) *t++ = 'R'; *t++ = 'O'; *t++ = '\n';
  1824.   *t++ = 'X'; *t++ = '-'; *t++ = 'S'; *t++ = 't'; *t++ = 'a'; *t++ = 't';
  1825.   *t++ = 'u'; *t++ = 's'; *t++ = ':'; *t++ = ' ';
  1826.   if (elt->deleted) *t++ = 'D'; if (elt->flagged) *t++ = 'F';
  1827.   if (elt->answered) *t++ = 'A';
  1828.   *t++ = '\n'; *t++ = '\n'; *t++ = '\0';
  1829. }
  1830.  
  1831. /* Parse flag list
  1832.  * Accepts: MAIL stream
  1833.  *        flag list as a character string
  1834.  * Returns: flag command list
  1835.  */
  1836.  
  1837.  
  1838. short mmdf_getflags (stream,flag)
  1839.     MAILSTREAM *stream;
  1840.     char *flag;
  1841. {
  1842.   char *t;
  1843.   short f = 0;
  1844.   short i,j;
  1845.   if (flag && *flag) {        /* no-op if no flag string */
  1846.                 /* check if a list and make sure valid */
  1847.     if ((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) {
  1848.       mm_log ("Bad flag list",ERROR);
  1849.       return NIL;
  1850.     }
  1851.                 /* copy the flag string w/o list construct */
  1852.     strncpy (LOCAL->buf,flag+i,(j = strlen (flag) - (2*i)));
  1853.     LOCAL->buf[j] = '\0';
  1854.     t = ucase (LOCAL->buf);    /* uppercase only from now on */
  1855.  
  1856.     while (*t) {        /* parse the flags */
  1857.       if (*t == '\\') {        /* system flag? */
  1858.     switch (*++t) {        /* dispatch based on first character */
  1859.     case 'S':        /* possible \Seen flag */
  1860.       if (t[1] == 'E' && t[2] == 'E' && t[3] == 'N') i = fSEEN;
  1861.       t += 4;        /* skip past flag name */
  1862.       break;
  1863.     case 'D':        /* possible \Deleted flag */
  1864.       if (t[1] == 'E' && t[2] == 'L' && t[3] == 'E' && t[4] == 'T' &&
  1865.           t[5] == 'E' && t[6] == 'D') i = fDELETED;
  1866.       t += 7;        /* skip past flag name */
  1867.       break;
  1868.     case 'F':        /* possible \Flagged flag */
  1869.       if (t[1] == 'L' && t[2] == 'A' && t[3] == 'G' && t[4] == 'G' &&
  1870.           t[5] == 'E' && t[6] == 'D') i = fFLAGGED;
  1871.       t += 7;        /* skip past flag name */
  1872.       break;
  1873.     case 'A':        /* possible \Answered flag */
  1874.       if (t[1] == 'N' && t[2] == 'S' && t[3] == 'W' && t[4] == 'E' &&
  1875.           t[5] == 'R' && t[6] == 'E' && t[7] == 'D') i = fANSWERED;
  1876.       t += 8;        /* skip past flag name */
  1877.       break;
  1878.     default:        /* unknown */
  1879.       i = 0;
  1880.       break;
  1881.     }
  1882.                 /* add flag to flags list */
  1883.     if (i && ((*t == '\0') || (*t++ == ' '))) f |= i;
  1884.     else {            /* bitch about bogus flag */
  1885.       mm_log ("Unknown system flag",ERROR);
  1886.       return NIL;
  1887.     }
  1888.       }
  1889.       else {            /* no user flags yet */
  1890.     mm_log ("Unknown flag",ERROR);
  1891.     return NIL;
  1892.       }
  1893.     }
  1894.   }
  1895.   return f;
  1896. }
  1897.  
  1898. /* Search support routines
  1899.  * Accepts: MAIL stream
  1900.  *        message number
  1901.  *        pointer to additional data
  1902.  * Returns: T if search matches, else NIL
  1903.  */
  1904.  
  1905.  
  1906. char mmdf_search_all (stream,msgno,d,n)
  1907.     MAILSTREAM *stream;
  1908.     long msgno;
  1909.     char *d;
  1910.     long n;
  1911. {
  1912.   return T;            /* ALL always succeeds */
  1913. }
  1914.  
  1915.  
  1916. char mmdf_search_answered (stream,msgno,d,n)
  1917.     MAILSTREAM *stream;
  1918.     long msgno;
  1919.     char *d;
  1920.     long n;
  1921. {
  1922.   return mail_elt (stream,msgno)->answered ? T : NIL;
  1923. }
  1924.  
  1925.  
  1926. char mmdf_search_deleted (stream,msgno,d,n)
  1927.     MAILSTREAM *stream;
  1928.     long msgno;
  1929.     char *d;
  1930.     long n;
  1931. {
  1932.   return mail_elt (stream,msgno)->deleted ? T : NIL;
  1933. }
  1934.  
  1935.  
  1936. char mmdf_search_flagged (stream,msgno,d,n)
  1937.     MAILSTREAM *stream;
  1938.     long msgno;
  1939.     char *d;
  1940.     long n;
  1941. {
  1942.   return mail_elt (stream,msgno)->flagged ? T : NIL;
  1943. }
  1944.  
  1945.  
  1946. char mmdf_search_keyword (stream,msgno,d,n)
  1947.     MAILSTREAM *stream;
  1948.     long msgno;
  1949.     char *d;
  1950.     long n;
  1951. {
  1952.   return NIL;            /* keywords not supported yet */
  1953. }
  1954.  
  1955.  
  1956. char mmdf_search_new (stream,msgno,d,n)
  1957.     MAILSTREAM *stream;
  1958.     long msgno;
  1959.     char *d;
  1960.     long n;
  1961. {
  1962.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1963.   return (elt->recent && !elt->seen) ? T : NIL;
  1964. }
  1965.  
  1966. char mmdf_search_old (stream,msgno,d,n)
  1967.     MAILSTREAM *stream;
  1968.     long msgno;
  1969.     char *d;
  1970.     long n;
  1971. {
  1972.   return mail_elt (stream,msgno)->recent ? NIL : T;
  1973. }
  1974.  
  1975.  
  1976. char mmdf_search_recent (stream,msgno,d,n)
  1977.     MAILSTREAM *stream;
  1978.     long msgno;
  1979.     char *d;
  1980.     long n;
  1981. {
  1982.   return mail_elt (stream,msgno)->recent ? T : NIL;
  1983. }
  1984.  
  1985.  
  1986. char mmdf_search_seen (stream,msgno,d,n)
  1987.     MAILSTREAM *stream;
  1988.     long msgno;
  1989.     char *d;
  1990.     long n;
  1991. {
  1992.   return mail_elt (stream,msgno)->seen ? T : NIL;
  1993. }
  1994.  
  1995.  
  1996. char mmdf_search_unanswered (stream,msgno,d,n)
  1997.     MAILSTREAM *stream;
  1998.     long msgno;
  1999.     char *d;
  2000.     long n;
  2001. {
  2002.   return mail_elt (stream,msgno)->answered ? NIL : T;
  2003. }
  2004.  
  2005.  
  2006. char mmdf_search_undeleted (stream,msgno,d,n)
  2007.     MAILSTREAM *stream;
  2008.     long msgno;
  2009.     char *d;
  2010.     long n;
  2011. {
  2012.   return mail_elt (stream,msgno)->deleted ? NIL : T;
  2013. }
  2014.  
  2015.  
  2016. char mmdf_search_unflagged (stream,msgno,d,n)
  2017.     MAILSTREAM *stream;
  2018.     long msgno;
  2019.     char *d;
  2020.     long n;
  2021. {
  2022.   return mail_elt (stream,msgno)->flagged ? NIL : T;
  2023. }
  2024.  
  2025.  
  2026. char mmdf_search_unkeyword (stream,msgno,d,n)
  2027.     MAILSTREAM *stream;
  2028.     long msgno;
  2029.     char *d;
  2030.     long n;
  2031. {
  2032.   return T;            /* keywords not supported yet */
  2033. }
  2034.  
  2035.  
  2036. char mmdf_search_unseen (stream,msgno,d,n)
  2037.     MAILSTREAM *stream;
  2038.     long msgno;
  2039.     char *d;
  2040.     long n;
  2041. {
  2042.   return mail_elt (stream,msgno)->seen ? NIL : T;
  2043. }
  2044.  
  2045. char mmdf_search_before (stream,msgno,d,n)
  2046.     MAILSTREAM *stream;
  2047.     long msgno;
  2048.     char *d;
  2049.     long n;
  2050. {
  2051.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  2052.   return (char) ((long)((elt->year << 9) + (elt->month << 5) + elt->day) < n);
  2053. }
  2054.  
  2055.  
  2056. char mmdf_search_on (stream,msgno,d,n)
  2057.     MAILSTREAM *stream;
  2058.     long msgno;
  2059.     char *d;
  2060.     long n;
  2061. {
  2062.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  2063.   return (char) (((elt->year << 9) + (elt->month << 5) + elt->day) == n);
  2064. }
  2065.  
  2066.  
  2067. char mmdf_search_since (stream,msgno,d,n)
  2068.     MAILSTREAM *stream;
  2069.     long msgno;
  2070.     char *d;
  2071.     long n;
  2072. {
  2073.                 /* everybody interprets "since" as .GE. */
  2074.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  2075.   return (char) ((long)((elt->year << 9) + (elt->month << 5) + elt->day) >= n);
  2076. }
  2077.  
  2078.  
  2079. char mmdf_search_body (stream,msgno,d,n)
  2080.     MAILSTREAM *stream;
  2081.     long msgno;
  2082.     char *d;
  2083.     long n;
  2084. {
  2085.   FILECACHE *m = LOCAL->msgs[msgno - 1];
  2086.   return search (m->body,m->bodysize,d,n);
  2087. }
  2088.  
  2089.  
  2090. char mmdf_search_subject (stream,msgno,d,n)
  2091.     MAILSTREAM *stream;
  2092.     long msgno;
  2093.     char *d;
  2094.     long n;
  2095. {
  2096.   char *s = mmdf_fetchstructure (stream,msgno,NIL)->subject;
  2097.   return s ? search (s,strlen (s),d,n) : NIL;
  2098. }
  2099.  
  2100.  
  2101. char mmdf_search_text (stream,msgno,d,n)
  2102.     MAILSTREAM *stream;
  2103.     long msgno;
  2104.     char *d;
  2105.     long n;
  2106. {
  2107.   FILECACHE *m = LOCAL->msgs[msgno - 1];
  2108.   return search (m->header,m->headersize,d,n) ||
  2109.     mmdf_search_body (stream,msgno,d,n);
  2110. }
  2111.  
  2112. char mmdf_search_bcc (stream,msgno,d,n)
  2113.     MAILSTREAM *stream;
  2114.     long msgno;
  2115.     char *d;
  2116.     long n;
  2117. {
  2118.   LOCAL->buf[0] = '\0';        /* initially empty string */
  2119.                 /* get text for address */
  2120.   rfc822_write_address (LOCAL->buf,
  2121.             mmdf_fetchstructure (stream,msgno,NIL)->bcc);
  2122.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  2123. }
  2124.  
  2125.  
  2126. char mmdf_search_cc (stream,msgno,d,n)
  2127.     MAILSTREAM *stream;
  2128.     long msgno;
  2129.     char *d;
  2130.     long n;
  2131. {
  2132.   LOCAL->buf[0] = '\0';        /* initially empty string */
  2133.                 /* get text for address */
  2134.   rfc822_write_address (LOCAL->buf,
  2135.             mmdf_fetchstructure (stream,msgno,NIL)->cc);
  2136.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  2137. }
  2138.  
  2139.  
  2140. char mmdf_search_from (stream,m,d,n)
  2141.     MAILSTREAM *stream;
  2142.     long m;
  2143.     char *d;
  2144.     long n;
  2145. {
  2146.   LOCAL->buf[0] = '\0';        /* initially empty string */
  2147.                 /* get text for address */
  2148.   rfc822_write_address (LOCAL->buf,mmdf_fetchstructure (stream,m,NIL)->from);
  2149.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  2150. }
  2151.  
  2152.  
  2153. char mmdf_search_to (stream,msgno,d,n)
  2154.     MAILSTREAM *stream;
  2155.     long msgno;
  2156.     char *d;
  2157.     long n;
  2158. {
  2159.   LOCAL->buf[0] = '\0';        /* initially empty string */
  2160.                 /* get text for address */
  2161.   rfc822_write_address (LOCAL->buf,
  2162.             mmdf_fetchstructure (stream,msgno,NIL)->to);
  2163.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  2164. }
  2165.  
  2166. /* Search parsers */
  2167.  
  2168.  
  2169. /* Parse a date
  2170.  * Accepts: function to return
  2171.  *        pointer to date integer to return
  2172.  * Returns: function to return
  2173.  */
  2174.  
  2175. search_t mmdf_search_date (f,n)
  2176.     search_t f;
  2177.     long *n;
  2178. {
  2179.   long i;
  2180.   char *s;
  2181.   MESSAGECACHE elt;
  2182.                 /* parse the date and return fn if OK */
  2183.   return (mmdf_search_string (f,&s,&i) && mail_parse_date (&elt,s) &&
  2184.       (*n = (elt.year << 9) + (elt.month << 5) + elt.day)) ? f : NIL;
  2185. }
  2186.  
  2187. /* Parse a flag
  2188.  * Accepts: function to return
  2189.  *        pointer to string to return
  2190.  * Returns: function to return
  2191.  */
  2192.  
  2193. search_t mmdf_search_flag (f,d)
  2194.     search_t f;
  2195.     char **d;
  2196. {
  2197.                 /* get a keyword, return if OK */
  2198.   return (*d = strtok (NIL," ")) ? f : NIL;
  2199. }
  2200.  
  2201.  
  2202. /* Parse a string
  2203.  * Accepts: function to return
  2204.  *        pointer to string to return
  2205.  *        pointer to string length to return
  2206.  * Returns: function to return
  2207.  */
  2208.  
  2209.  
  2210. search_t mmdf_search_string (f,d,n)
  2211.     search_t f;
  2212.     char **d;
  2213.     long *n;
  2214. {
  2215.   char *c = strtok (NIL,"");    /* remainder of criteria */
  2216.   if (c) {            /* better be an argument */
  2217.     switch (*c) {        /* see what the argument is */
  2218.     case '\0':            /* catch bogons */
  2219.     case ' ':
  2220.       return NIL;
  2221.     case '"':            /* quoted string */
  2222.       if (!(strchr (c+1,'"') && (*d = strtok (c,"\"")) && (*n = strlen (*d))))
  2223.     return NIL;
  2224.       break;
  2225.     case '{':            /* literal string */
  2226.       *n = strtol (c+1,&c,10);    /* get its length */
  2227.       if (*c++ != '}' || *c++ != '\015' || *c++ != '\012' ||
  2228.       *n > strlen (*d = c)) return NIL;
  2229.       c[*n] = '\255';        /* write new delimiter */
  2230.       strtok (c,"\255");    /* reset the strtok mechanism */
  2231.       break;
  2232.     default:            /* atomic string */
  2233.       *n = strlen (*d = strtok (c," "));
  2234.       break;
  2235.     }
  2236.     return f;
  2237.   }
  2238.   else return NIL;
  2239. }
  2240.