home *** CD-ROM | disk | FTP | other *** search
/ ftp.uv.es / 2014.11.ftp.uv.es.tar / ftp.uv.es / pub / unix / pine4.10.tar.gz / pine4.10.tar / pine4.10 / imap / src / osdep / amiga / mmdf.c < prev    next >
C/C++ Source or Header  |  1999-02-02  |  61KB  |  1,780 lines

  1. /*
  2.  * Program:    MMDF 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:    20 December 1989
  13.  * Last Edited:    1 February 1999
  14.  *
  15.  * Copyright 1999 by the University of Washington
  16.  *
  17.  *  Permission to use, copy, modify, and distribute this software and its
  18.  * documentation for any purpose and without fee is hereby granted, provided
  19.  * that the above copyright notice appears in all copies and that both the
  20.  * above copyright notice and this permission notice appear in supporting
  21.  * documentation, and that the name of the University of Washington not be
  22.  * used in advertising or publicity pertaining to distribution of the software
  23.  * without specific, written prior permission.  This software is made
  24.  * available "as is", and
  25.  * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
  26.  * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
  27.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
  28.  * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
  29.  * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  30.  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
  31.  * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
  32.  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  33.  *
  34.  */
  35.  
  36. #include <stdio.h>
  37. #include <ctype.h>
  38. #include <errno.h>
  39. extern int errno;        /* just in case */
  40. #include <signal.h>
  41. #include "mail.h"
  42. #include "osdep.h"
  43. #include <time.h>
  44. #include <sys/stat.h>
  45. #include "mmdf.h"
  46. #include "pseudo.h"
  47. #include "fdstring.h"
  48. #include "misc.h"
  49. #include "dummy.h"
  50.  
  51. /* MMDF mail routines */
  52.  
  53.  
  54. /* Driver dispatch used by MAIL */
  55.  
  56. DRIVER mmdfdriver = {
  57.   "mmdf",            /* driver name */
  58.   DR_LOCAL|DR_MAIL,        /* driver flags */
  59.   (DRIVER *) NIL,        /* next driver */
  60.   mmdf_valid,            /* mailbox is valid for us */
  61.   mmdf_parameters,        /* manipulate parameters */
  62.   mmdf_scan,            /* scan mailboxes */
  63.   mmdf_list,            /* list mailboxes */
  64.   mmdf_lsub,            /* list subscribed mailboxes */
  65.   NIL,                /* subscribe to mailbox */
  66.   NIL,                /* unsubscribe from mailbox */
  67.   mmdf_create,            /* create mailbox */
  68.   mmdf_delete,            /* delete mailbox */
  69.   mmdf_rename,            /* rename mailbox */
  70.   NIL,                /* status of mailbox */
  71.   mmdf_open,            /* open mailbox */
  72.   mmdf_close,            /* close mailbox */
  73.   NIL,                /* fetch message "fast" attributes */
  74.   NIL,                /* fetch message flags */
  75.   NIL,                /* fetch overview */
  76.   NIL,                /* fetch message envelopes */
  77.   mmdf_header,            /* fetch message header */
  78.   mmdf_text,            /* fetch message text */
  79.   NIL,                /* fetch partial message text */
  80.   NIL,                /* unique identifier */
  81.   NIL,                /* message number */
  82.   NIL,                /* modify flags */
  83.   mmdf_flagmsg,            /* per-message modify flags */
  84.   NIL,                /* search for message based on criteria */
  85.   NIL,                /* sort messages */
  86.   NIL,                /* thread messages */
  87.   mmdf_ping,            /* ping mailbox to see if still alive */
  88.   mmdf_check,            /* check for new messages */
  89.   mmdf_expunge,            /* expunge deleted messages */
  90.   mmdf_copy,            /* copy messages to another mailbox */
  91.   mmdf_append,            /* append string message to mailbox */
  92.   NIL                /* garbage collect stream */
  93. };
  94.  
  95.                 /* prototype stream */
  96. MAILSTREAM mmdfproto = {&mmdfdriver};
  97.  
  98. char *mmdfhdr = MMDFHDRTXT;    /* MMDF header */
  99.  
  100. /* MMDF mail validate mailbox
  101.  * Accepts: mailbox name
  102.  * Returns: our driver if name is valid, NIL otherwise
  103.  */
  104.  
  105. DRIVER *mmdf_valid (char *name)
  106. {
  107.   char tmp[MAILTMPLEN];
  108.   return mmdf_isvalid (name,tmp) ? &mmdfdriver : NIL;
  109. }
  110.  
  111.  
  112. /* MMDF mail test for valid mailbox name
  113.  * Accepts: mailbox name
  114.  *        scratch buffer
  115.  * Returns: T if valid, NIL otherwise
  116.  */
  117.  
  118. long mmdf_isvalid (char *name,char *tmp)
  119. {
  120.   int fd;
  121.   int ret = NIL;
  122.   char *t,file[MAILTMPLEN];
  123.   struct stat sbuf;
  124.   time_t tp[2];
  125.   errno = EINVAL;        /* assume invalid argument */
  126.                 /* must be non-empty file */
  127.   if ((t = dummy_file (file,name)) && !stat (t,&sbuf)) {
  128.     if (!sbuf.st_size)errno = 0;/* empty file */
  129.     else if ((fd = open (file,O_RDONLY,NIL)) >= 0) {
  130.                 /* error -1 for invalid format */
  131.       if (!(ret = mmdf_isvalid_fd (fd,tmp))) errno = -1;
  132.       close (fd);        /* close the file */
  133.       tp[0] = sbuf.st_atime;    /* preserve atime and mtime */
  134.       tp[1] = sbuf.st_mtime;
  135.       utime (file,tp);        /* set the times */
  136.     }
  137.   }
  138.                 /* in case INBOX but not MMDF format */
  139.   else if ((errno == ENOENT) && ((name[0] == 'I') || (name[0] == 'i')) &&
  140.        ((name[1] == 'N') || (name[1] == 'n')) &&
  141.        ((name[2] == 'B') || (name[2] == 'b')) &&
  142.        ((name[3] == 'O') || (name[3] == 'o')) &&
  143.        ((name[4] == 'X') || (name[4] == 'x')) && !name[5]) errno = -1;
  144.   return ret;            /* return what we should */
  145. }
  146.  
  147. /* MMDF mail test for valid mailbox
  148.  * Accepts: file descriptor
  149.  *        scratch buffer
  150.  * Returns: T if valid, NIL otherwise
  151.  */
  152.  
  153. long mmdf_isvalid_fd (int fd,char *tmp)
  154. {
  155.   int zn;
  156.   int ret = NIL;
  157.   char *s,*t,c = '\n';
  158.   memset (tmp,'\0',MAILTMPLEN);
  159.   if (read (fd,tmp,MAILTMPLEN-1) >= 0) ret = ISMMDF (tmp) ? T : NIL;
  160.   return ret;            /* return what we should */
  161. }
  162.  
  163.  
  164. /* MMDF manipulate driver parameters
  165.  * Accepts: function code
  166.  *        function-dependent value
  167.  * Returns: function-dependent return value
  168.  */
  169.  
  170. void *mmdf_parameters (long function,void *value)
  171. {
  172.   return NIL;
  173. }
  174.  
  175. /* MMDF mail scan mailboxes
  176.  * Accepts: mail stream
  177.  *        reference
  178.  *        pattern to search
  179.  *        string to scan
  180.  */
  181.  
  182. void mmdf_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
  183. {
  184.   if (stream) dummy_scan (NIL,ref,pat,contents);
  185. }
  186.  
  187.  
  188. /* MMDF mail list mailboxes
  189.  * Accepts: mail stream
  190.  *        reference
  191.  *        pattern to search
  192.  */
  193.  
  194. void mmdf_list (MAILSTREAM *stream,char *ref,char *pat)
  195. {
  196.   if (stream) dummy_list (NIL,ref,pat);
  197. }
  198.  
  199.  
  200. /* MMDF mail list subscribed mailboxes
  201.  * Accepts: mail stream
  202.  *        reference
  203.  *        pattern to search
  204.  */
  205.  
  206. void mmdf_lsub (MAILSTREAM *stream,char *ref,char *pat)
  207. {
  208.   if (stream) dummy_lsub (NIL,ref,pat);
  209. }
  210.  
  211. /* MMDF mail create mailbox
  212.  * Accepts: MAIL stream
  213.  *        mailbox name to create
  214.  * Returns: T on success, NIL on failure
  215.  */
  216.  
  217. long mmdf_create (MAILSTREAM *stream,char *mailbox)
  218. {
  219.   char *s,mbx[MAILTMPLEN],tmp[MAILTMPLEN];
  220.   long ret = NIL;
  221.   int i,fd;
  222.   time_t ti = time (0);
  223.   if (!(s = dummy_file (mbx,mailbox))) {
  224.     sprintf (tmp,"Can't create %.80s: invalid name",mailbox);
  225.     mm_log (tmp,ERROR);
  226.   }
  227.                 /* create underlying file */
  228.   else if (dummy_create_path (stream,s)) {
  229.                 /* done if made directory */
  230.     if ((s = strrchr (s,'/')) && !s[1]) ret = T;
  231.     else if ((fd = open (mbx,O_WRONLY,
  232.              (int) mail_parameters(NIL,GET_MBXPROTECTION,NIL)))<0){
  233.       sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno));
  234.       mm_log (tmp,ERROR);
  235.       unlink (mbx);        /* delete the file */
  236.     }
  237.                 /* in case a whiner with no life */
  238.     else if (mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) ret = T; 
  239.     else {            /* initialize header */
  240.       memset (tmp,'\0',MAILTMPLEN);
  241.       sprintf (tmp,"%sFrom %s %sDate: ",mmdfhdr,pseudo_from,ctime (&ti));
  242.       rfc822_date (s = tmp + strlen (tmp));
  243.       sprintf (s += strlen (s),    /* write the pseudo-header */
  244.            "\nFrom: %s <%s@%s>\nSubject: %s\nX-IMAP: %010lu 0000000000",
  245.            pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,ti);
  246.       for (i = 0; i < NUSERFLAGS; ++i) if (default_user_flag (i))
  247.     sprintf (s += strlen (s)," %s",default_user_flag (i));
  248.       sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n%s",pseudo_msg,mmdfhdr);
  249.       if ((write (fd,tmp,strlen (tmp)) < 0) || close (fd)) {
  250.     sprintf (tmp,"Can't initialize mailbox node %.80s: %s",mbx,
  251.          strerror (errno));
  252.     mm_log (tmp,ERROR);
  253.     unlink (mbx);        /* delete the file */
  254.       }
  255.       else ret = T;        /* success */
  256.     }
  257.   }
  258.   return ret ? set_mbx_protections (mailbox,mbx) : NIL;
  259. }
  260.  
  261.  
  262. /* MMDF mail delete mailbox
  263.  * Accepts: MAIL stream
  264.  *        mailbox name to delete
  265.  * Returns: T on success, NIL on failure
  266.  */
  267.  
  268. long mmdf_delete (MAILSTREAM *stream,char *mailbox)
  269. {
  270.   return mmdf_rename (stream,mailbox,NIL);
  271. }
  272.  
  273. /* MMDF mail rename mailbox
  274.  * Accepts: MAIL stream
  275.  *        old mailbox name
  276.  *        new mailbox name (or NIL for delete)
  277.  * Returns: T on success, NIL on failure
  278.  */
  279.  
  280. long mmdf_rename (MAILSTREAM *stream,char *old,char *newname)
  281. {
  282.   long ret = NIL;
  283.   char c,*s;
  284.   char tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN],lockx[MAILTMPLEN];
  285.   int fd,ld;
  286.   struct stat sbuf;
  287.   mm_critical (stream);        /* get the c-client lock */
  288.   if (newname && !((s = dummy_file (tmp,newname)) && *s))
  289.     sprintf (tmp,"Can't rename mailbox %.80s to %.80s: invalid name",
  290.          old,newname);
  291.   else if ((ld = lockname (lock,dummy_file (file,old),LOCK_EX|LOCK_NB)) < 0)
  292.     sprintf (tmp,"Mailbox %.80s is in use by another process",old);
  293.   else {
  294.     if ((fd = mmdf_lock (file,O_RDWR,S_IREAD|S_IWRITE,lockx,LOCK_EX)) < 0)
  295.       sprintf (tmp,"Can't lock mailbox %.80s: %s",old,strerror (errno));
  296.     else {
  297.       if (newname) {        /* want rename? */
  298.                 /* found superior to destination name? */
  299.     if (s = strrchr (s,'/')) {
  300.       c = *++s;        /* remember first character of inferior */
  301.       *s = '\0';        /* tie off to get just superior */
  302.                 /* name doesn't exist, create it */
  303.       if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
  304.           !dummy_create (stream,tmp)) return NIL;
  305.       *s = c;        /* restore full name */
  306.     }
  307.     if (rename (file,tmp))
  308.       sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
  309.            strerror (errno));
  310.     else ret = T;        /* set success */
  311.       }
  312.       else if (unlink (file))
  313.     sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
  314.       else ret = T;        /* set success */
  315.       mmdf_unlock (fd,NIL,lockx);
  316.     }
  317.     flock (ld,LOCK_UN);        /* release c-client lock */
  318.     close (ld);            /* close c-client lock */
  319.     unlink (lock);        /* and delete it */
  320.   }
  321.   mm_nocritical (stream);    /* no longer critical */
  322.   if (!ret) mm_log (tmp,ERROR);    /* log error */
  323.   return ret;            /* return success or failure */
  324. }
  325.  
  326. /* MMDF mail open
  327.  * Accepts: Stream to open
  328.  * Returns: Stream on success, NIL on failure
  329.  */
  330.  
  331. MAILSTREAM *mmdf_open (MAILSTREAM *stream)
  332. {
  333.   long i;
  334.   int fd;
  335.   char tmp[MAILTMPLEN];
  336.   struct stat sbuf;
  337.   long retry;
  338.                 /* return prototype for OP_PROTOTYPE call */
  339.   if (!stream) return user_flags (&mmdfproto);
  340.   retry = stream->silent ? 1 : KODRETRY;
  341.   if (stream->local) fatal ("mmdf recycle stream");
  342.   stream->local = memset (fs_get (sizeof (MMDFLOCAL)),0,sizeof (MMDFLOCAL));
  343.                 /* canonicalize the stream mailbox name */
  344.   dummy_file (tmp,stream->mailbox);
  345.                 /* flush old name */
  346.   fs_give ((void **) &stream->mailbox);
  347.   /* You may wonder why LOCAL->name is needed.  It isn't at all obvious from
  348.    * the code.  The problem is that when a stream is recycled with another
  349.    * mailbox of the same type, the driver's close method isn't called because
  350.    * it could be IMAP and closing then would defeat the entire point of
  351.    * recycling.  Hence there is code in the file drivers to call the close
  352.    * method such as what appears above.  The problem is, by this point,
  353.    * mail_open() has already changed the stream->mailbox name to point to the
  354.    * new name, and mmdf_close() needs the old name.
  355.    */
  356.                 /* save canonical name */
  357.   stream->mailbox = cpystr (LOCAL->name = cpystr (tmp));
  358.   LOCAL->fd = LOCAL->ld = -1;    /* no file or state locking yet */
  359.   LOCAL->buf = (char *) fs_get ((LOCAL->buflen = CHUNK) + 1);
  360.   stream->sequence++;        /* bump sequence number */
  361.  
  362.                 /* make lock for read/write access */
  363.   if (!stream->rdonly) while (retry) {
  364.                 /* get a new file handle each time */
  365.     if ((fd = lockname (tmp,LOCAL->name,LOCK_EX|LOCK_NB)) < 0) {
  366.       if (retry-- == KODRETRY) {/* no, first time through? */
  367.                 /* yes, get other process' PID */
  368.     if (!fstat (fd,&sbuf) && (i = min (sbuf.st_size,MAILTMPLEN)) &&
  369.         (read (fd,tmp,i) == i) && !(tmp[i] = 0) && (i = atol (tmp))) {
  370.       kill ((int) i,SIGUSR2);
  371.       sprintf (tmp,"Trying to get mailbox lock from process %lu",i);
  372.       mm_log (tmp,WARN);
  373.     }
  374.     else retry = 0;        /* give up */
  375.       }
  376.       if (!stream->silent) {    /* nothing if silent stream */
  377.     if (retry) sleep (1);    /* wait a second before trying again */
  378.     else mm_log ("Mailbox is open by another process, access is readonly",
  379.              WARN);
  380.       }
  381.     }
  382.     else {            /* got the lock, nobody else can alter state */
  383.       LOCAL->ld = fd;        /* note lock's fd and name */
  384.       LOCAL->lname = cpystr (tmp);
  385.                 /* make sure mode OK (don't use fchmod()) */
  386.       chmod (LOCAL->lname,(int) mail_parameters (NIL,GET_LOCKPROTECTION,NIL));
  387.       if (stream->silent) i = 0;/* silent streams won't accept KOD */
  388.       else {            /* note our PID in the lock */
  389.     sprintf (tmp,"%d",getpid ());
  390.     write (fd,tmp,(i = strlen (tmp))+1);
  391.       }
  392.       ftruncate (fd,i);        /* make sure tied off */
  393.       fsync (fd);        /* make sure it's available */
  394.       retry = 0;        /* no more need to try */
  395.     }
  396.   }
  397.  
  398.                 /* parse mailbox */
  399.   stream->nmsgs = stream->recent = 0;
  400.                 /* will we be able to get write access? */
  401.   if ((LOCAL->ld >= 0) && access (LOCAL->name,W_OK) && (errno == EACCES)) {
  402.     mm_log ("Can't get write access to mailbox, access is readonly",WARN);
  403.     flock (LOCAL->ld,LOCK_UN);    /* release the lock */
  404.     close (LOCAL->ld);        /* close the lock file */
  405.     LOCAL->ld = -1;        /* no more lock fd */
  406.     unlink (LOCAL->lname);    /* delete it */
  407.   }
  408.                 /* reset UID validity */
  409.   stream->uid_validity = stream->uid_last = 0;
  410.   if (stream->silent && !stream->rdonly && (LOCAL->ld < 0))
  411.     mmdf_abort (stream);    /* abort if can't get RW silent stream */
  412.                 /* parse mailbox */
  413.   else if (mmdf_parse (stream,tmp,LOCK_SH)) {
  414.     mmdf_unlock (LOCAL->fd,stream,tmp);
  415.     mail_unlock (stream);
  416.     mm_nocritical (stream);    /* done with critical */
  417.   }
  418.   if (!LOCAL) return NIL;    /* failure if stream died */
  419.                 /* make sure upper level knows readonly */
  420.   stream->rdonly = (LOCAL->ld < 0);
  421.                 /* notify about empty mailbox */
  422.   if (!(stream->nmsgs || stream->silent)) mm_log ("Mailbox is empty",NIL);
  423.   if (!stream->rdonly) {    /* flags stick if readwrite */
  424.     stream->perm_seen = stream->perm_deleted =
  425.       stream->perm_flagged = stream->perm_answered = stream->perm_draft = T;
  426.     if (!stream->uid_nosticky) {/* users with lives get permanent keywords */
  427.       stream->perm_user_flags = 0xffffffff;
  428.                 /* and maybe can create them too! */
  429.       stream->kwd_create = stream->user_flags[NUSERFLAGS-1] ? NIL : T;
  430.     }
  431.   }
  432.   return stream;        /* return stream alive to caller */
  433. }
  434.  
  435.  
  436. /* MMDF mail close
  437.  * Accepts: MAIL stream
  438.  *        close options
  439.  */
  440.  
  441. void mmdf_close (MAILSTREAM *stream,long options)
  442. {
  443.   int silent = stream->silent;
  444.   stream->silent = T;        /* go silent */
  445.                 /* expunge if requested */
  446.   if (options & CL_EXPUNGE) mmdf_expunge (stream);
  447.                 /* else dump final checkpoint */
  448.   else if (LOCAL->dirty) mmdf_check (stream);
  449.   stream->silent = NIL;        /* restore old silence state */
  450.   mmdf_abort (stream);        /* now punt the file and local data */
  451. }
  452.  
  453. /* MMDF mail fetch message header
  454.  * Accepts: MAIL stream
  455.  *        message # to fetch
  456.  *        pointer to returned header text length
  457.  *        option flags
  458.  * Returns: message header in RFC822 format
  459.  */
  460.  
  461.                 /* lines to filter from header */
  462. static STRINGLIST *mmdf_hlines = NIL;
  463.  
  464. char *mmdf_header (MAILSTREAM *stream,unsigned long msgno,
  465.            unsigned long *length,long flags)
  466. {
  467.   MESSAGECACHE *elt;
  468.   char *s;
  469.   *length = 0;            /* default to empty */
  470.   if (flags & FT_UID) return "";/* UID call "impossible" */
  471.   elt = mail_elt (stream,msgno);/* get cache */
  472.   if (!mmdf_hlines) {        /* once only code */
  473.     STRINGLIST *lines = mmdf_hlines = mail_newstringlist ();
  474.     lines->text.size = strlen ((char *) (lines->text.data =
  475.                      (unsigned char *) "Status"));
  476.     lines = lines->next = mail_newstringlist ();
  477.     lines->text.size = strlen ((char *) (lines->text.data =
  478.                      (unsigned char *) "X-Status"));
  479.     lines = lines->next = mail_newstringlist ();
  480.     lines->text.size = strlen ((char *) (lines->text.data =
  481.                      (unsigned char *) "X-Keywords"));
  482.     lines = lines->next = mail_newstringlist ();
  483.     lines->text.size = strlen ((char *) (lines->text.data =
  484.                      (unsigned char *) "X-UID"));
  485.   }
  486.                 /* go to header position */
  487.   lseek (LOCAL->fd,elt->private.special.offset +
  488.      elt->private.msg.header.offset,L_SET);
  489.   if (flags & FT_INTERNAL) {    /* initial data OK? */
  490.     if (elt->private.msg.header.text.size > LOCAL->buflen) {
  491.       fs_give ((void **) &LOCAL->buf);
  492.       LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
  493.                      elt->private.msg.header.text.size) + 1);
  494.     }
  495.                 /* read message */
  496.     read (LOCAL->fd,LOCAL->buf,elt->private.msg.header.text.size);
  497.                 /* got text, tie off string */
  498.     LOCAL->buf[*length = elt->private.msg.header.text.size] = '\0';
  499.   }
  500.   else {            /* need to make a CRLF version */
  501.     read (LOCAL->fd,s = (char *) fs_get (elt->private.msg.header.text.size+1),
  502.       elt->private.msg.header.text.size);
  503.                 /* tie off string, and convert to CRLF */
  504.     s[elt->private.msg.header.text.size] = '\0';
  505.     *length = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,s,
  506.               elt->private.msg.header.text.size);
  507.     fs_give ((void **) &s);    /* free readin buffer */
  508.   }
  509.   *length = mail_filter (LOCAL->buf,*length,mmdf_hlines,FT_NOT);
  510.   return LOCAL->buf;        /* return processed copy */
  511. }
  512.  
  513. /* MMDF mail fetch message text
  514.  * Accepts: MAIL stream
  515.  *        message # to fetch
  516.  *        pointer to returned stringstruct
  517.  *        option flags
  518.  * Returns: T on success, NIL if failure
  519.  */
  520.  
  521. long mmdf_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
  522. {
  523.   char *s;
  524.   unsigned long i;
  525.   MESSAGECACHE *elt;
  526.                 /* UID call "impossible" */
  527.   if (flags & FT_UID) return NIL;
  528.   elt = mail_elt (stream,msgno);/* get cache element */
  529.                 /* if message not seen */
  530.   if (!(flags & FT_PEEK) && !elt->seen) {
  531.     elt->seen = T;        /* mark message as seen */
  532.     LOCAL->dirty = T;        /* note stream is now dirty */
  533.     mm_flags (stream,msgno);
  534.   }
  535.   s = mmdf_text_work (stream,elt,&i,flags);
  536.   INIT (bs,mail_string,s,i);    /* set up stringstruct */
  537.   return T;            /* success */
  538. }
  539.  
  540. /* MMDF mail fetch message text worker routine
  541.  * Accepts: MAIL stream
  542.  *        message cache element
  543.  *        pointer to returned header text length
  544.  *        option flags
  545.  */
  546.  
  547. char *mmdf_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
  548.               unsigned long *length,long flags)
  549. {
  550.   FDDATA d;
  551.   STRING bs;
  552.   char *s,tmp[CHUNK];
  553.                 /* go to text position */
  554.   lseek (LOCAL->fd,elt->private.special.offset +
  555.      elt->private.msg.text.offset,L_SET);
  556.   if (flags & FT_INTERNAL) {    /* initial data OK? */
  557.     if (elt->private.msg.text.text.size > LOCAL->buflen) {
  558.       fs_give ((void **) &LOCAL->buf);
  559.       LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
  560.                      elt->private.msg.text.text.size) + 1);
  561.     }
  562.                 /* read message */
  563.     read (LOCAL->fd,LOCAL->buf,elt->private.msg.text.text.size);
  564.                 /* got text, tie off string */
  565.     LOCAL->buf[*length = elt->private.msg.text.text.size] = '\0';
  566.   }
  567.   else {            /* need to make a CRLF version */
  568.     if (elt->rfc822_size > LOCAL->buflen) {
  569.       /* excessively conservative, but the right thing is too hard to do */
  570.       fs_give ((void **) &LOCAL->buf);
  571.       LOCAL->buf = (char *) fs_get ((LOCAL->buflen = elt->rfc822_size) + 1);
  572.     }
  573.     d.fd = LOCAL->fd;        /* yes, set up file descriptor */
  574.     d.pos = elt->private.special.offset + elt->private.msg.text.offset;
  575.     d.chunk = tmp;        /* initial buffer chunk */
  576.     d.chunksize = CHUNK;    /* file chunk size */
  577.     INIT (&bs,fd_string,&d,elt->private.msg.text.text.size);
  578.     for (s = LOCAL->buf; SIZE (&bs);) switch (CHR (&bs)) {
  579.     case '\015':        /* carriage return seen */
  580.       *s++ = SNX (&bs);        /* copy it and any succeeding LF */
  581.       if (SIZE (&bs) && (CHR (&bs) == '\012')) *s++ = SNX (&bs);
  582.       break;
  583.     case '\012':
  584.       *s++ = '\015';        /* insert a CR */
  585.     default:
  586.       *s++ = SNX (&bs);        /* copy characters */
  587.     }
  588.     *s = '\0';            /* tie off buffer */
  589.     *length = s - LOCAL->buf;    /* calculate length */
  590.   }
  591.   return LOCAL->buf;
  592. }
  593.  
  594. /* MMDF per-message modify flag
  595.  * Accepts: MAIL stream
  596.  *        message cache element
  597.  */
  598.  
  599. void mmdf_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
  600. {
  601.                 /* only after finishing */
  602.   if (elt->valid) LOCAL->dirty = T;
  603. }
  604.  
  605.  
  606. /* MMDF mail ping mailbox
  607.  * Accepts: MAIL stream
  608.  * Returns: T if stream alive, else NIL
  609.  */
  610.  
  611. long mmdf_ping (MAILSTREAM *stream)
  612. {
  613.   char lock[MAILTMPLEN];
  614.   struct stat sbuf;
  615.                 /* big no-op if not readwrite */
  616.   if (LOCAL && (LOCAL->ld >= 0) && !stream->lock) {
  617.     if (stream->rdonly) {    /* does he want to give up readwrite? */
  618.                 /* checkpoint if we changed something */
  619.       if (LOCAL->dirty) mmdf_check (stream);
  620.       flock (LOCAL->ld,LOCK_UN);/* release readwrite lock */
  621.       close (LOCAL->ld);    /* close the readwrite lock file */
  622.       LOCAL->ld = -1;        /* no more readwrite lock fd */
  623.       unlink (LOCAL->lname);    /* delete the readwrite lock file */
  624.     }
  625.     else {            /* get current mailbox size */
  626.       if (LOCAL->fd >= 0) fstat (LOCAL->fd,&sbuf);
  627.       else stat (LOCAL->name,&sbuf);
  628.                 /* parse if mailbox changed */
  629.       if ((sbuf.st_size != LOCAL->filesize) &&
  630.       mmdf_parse (stream,lock,LOCK_SH)) {
  631.                 /* unlock mailbox */
  632.     mmdf_unlock (LOCAL->fd,stream,lock);
  633.     mail_unlock (stream);    /* and stream */
  634.     mm_nocritical (stream);    /* done with critical */
  635.       }
  636.     }
  637.   }
  638.   return LOCAL ? LONGT : NIL;    /* return if still alive */
  639. }
  640.  
  641. /* MMDF mail check mailbox
  642.  * Accepts: MAIL stream
  643.  */
  644.  
  645. void mmdf_check (MAILSTREAM *stream)
  646. {
  647.   char lock[MAILTMPLEN];
  648.                 /* parse and lock mailbox */
  649.   if (LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
  650.       mmdf_parse (stream,lock,LOCK_EX)) {
  651.                 /* any unsaved changes? */
  652.     if (LOCAL->dirty && mmdf_rewrite (stream,NIL)) {
  653.       unlink (lock);        /* flush the lock file */
  654.       if (!stream->silent) mm_log ("Checkpoint completed",NIL);
  655.     }
  656.                 /* no checkpoint needed, just unlock */
  657.     else mmdf_unlock (LOCAL->fd,stream,lock);
  658.     mail_unlock (stream);    /* unlock the stream */
  659.     mm_nocritical (stream);    /* done with critical */
  660.   }
  661. }
  662.  
  663.  
  664. /* MMDF mail expunge mailbox
  665.  * Accepts: MAIL stream
  666.  */
  667.  
  668. void mmdf_expunge (MAILSTREAM *stream)
  669. {
  670.   unsigned long i;
  671.   char lock[MAILTMPLEN];
  672.   char *msg = NIL;
  673.                 /* parse and lock mailbox */
  674.   if (LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
  675.       mmdf_parse (stream,lock,LOCK_EX)) {
  676.                 /* count expunged messages if not dirty */
  677.     if (!LOCAL->dirty) for (i = 1; i <= stream->nmsgs; i++)
  678.       if (mail_elt (stream,i)->deleted) LOCAL->dirty = T;
  679.     if (!LOCAL->dirty) {    /* not dirty and no expunged messages */
  680.       mmdf_unlock (LOCAL->fd,stream,lock);
  681.       msg = "No messages deleted, so no update needed";
  682.     }
  683.     else if (mmdf_rewrite (stream,&i)) {
  684.       unlink (lock);        /* flush the lock file */
  685.       if (i) sprintf (msg = LOCAL->buf,"Expunged %lu messages",i);
  686.       else msg = "Mailbox checkpointed, but no messages expunged";
  687.     }
  688.                 /* rewrite failed */
  689.     else mmdf_unlock (LOCAL->fd,stream,lock);
  690.     mail_unlock (stream);    /* unlock the stream */
  691.     mm_nocritical (stream);    /* done with critical */
  692.     if (msg && !stream->silent) mm_log (msg,NIL);
  693.   }
  694.   else if (!stream->silent) mm_log("Expunge ignored on readonly mailbox",WARN);
  695. }
  696.  
  697. /* MMDF mail copy message(s)
  698.  * Accepts: MAIL stream
  699.  *        sequence
  700.  *        destination mailbox
  701.  *        copy options
  702.  * Returns: T if copy successful, else NIL
  703.  */
  704.  
  705. long mmdf_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
  706. {
  707.   struct stat sbuf;
  708.   int fd;
  709.   char *s,file[MAILTMPLEN],lock[MAILTMPLEN];
  710.   time_t tp[2];
  711.   unsigned long i,j;
  712.   MESSAGECACHE *elt;
  713.   long ret = T;
  714.   mailproxycopy_t pc =
  715.     (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
  716.   if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
  717.     mail_sequence (stream,sequence))) return NIL;
  718.                 /* make sure valid mailbox */
  719.   if (!mmdf_isvalid (mailbox,file)) switch (errno) {
  720.   case ENOENT:            /* no such file? */
  721.     mm_notify (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
  722.     return NIL;
  723.   case 0:            /* merely empty file? */
  724.     break;
  725.   case EINVAL:
  726.     if (pc) return (*pc) (stream,sequence,mailbox,options);
  727.     sprintf (LOCAL->buf,"Invalid MMDF-format mailbox name: %.80s",mailbox);
  728.     mm_log (LOCAL->buf,ERROR);
  729.     return NIL;
  730.   default:
  731.     if (pc) return (*pc) (stream,sequence,mailbox,options);
  732.     sprintf (LOCAL->buf,"Not a MMDF-format mailbox: %.80s",mailbox);
  733.     mm_log (LOCAL->buf,ERROR);
  734.     return NIL;
  735.   }
  736.   LOCAL->buf[0] = '\0';
  737.   mm_critical (stream);        /* go critical */
  738.   if ((fd = mmdf_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND|O_CREAT,
  739.                S_IREAD|S_IWRITE,lock,LOCK_EX)) < 0) {
  740.     mm_nocritical (stream);    /* done with critical */
  741.     sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
  742.     mm_log (LOCAL->buf,ERROR);    /* log the error */
  743.     return NIL;            /* failed */
  744.   }
  745.   fstat (fd,&sbuf);        /* get current file size */
  746.  
  747.                 /* write all requested messages to mailbox */
  748.   for (i = 1; ret && (i <= stream->nmsgs); i++)
  749.     if ((elt = mail_elt (stream,i))->sequence) {
  750.       lseek (LOCAL->fd,elt->private.special.offset,L_SET);
  751.       read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
  752.       if (write (fd,LOCAL->buf,elt->private.special.text.size) < 0) ret = NIL;
  753.       else {            /* internal header succeeded */
  754.     s = mmdf_header (stream,i,&j,FT_INTERNAL);
  755.                 /* header size, sans trailing newline */
  756.     if (j && (s[j - 2] == '\n')) j--;
  757.     if (write (fd,s,j) < 0) ret = NIL;
  758.     else {            /* message header succeeded */
  759.       j = mmdf_xstatus (stream,LOCAL->buf,elt,NIL);
  760.       if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
  761.       else {        /* message status succeeded */
  762.         s = mmdf_text_work (stream,elt,&j,FT_INTERNAL);
  763.         if ((write (fd,s,j) < 0) || (write (fd,mmdfhdr,MMDFHDRLEN) < 0))
  764.           ret = NIL;
  765.       }
  766.     }
  767.       }
  768.     }
  769.   if (!ret || fsync (fd)) {    /* force out the update */
  770.     sprintf (LOCAL->buf,"Message copy failed: %s",strerror (errno));
  771.     ftruncate (fd,sbuf.st_size);
  772.     ret = NIL;
  773.   }
  774.   tp[0] = sbuf.st_atime;    /* preserve atime */
  775.   tp[1] = time (0);        /* set mtime to now */
  776.   utime (file,tp);        /* set the times */
  777.   mmdf_unlock (fd,NIL,lock);    /* unlock and close mailbox */
  778.   mm_nocritical (stream);    /* release critical */
  779.                 /* log the error */
  780.   if (!ret) mm_log (LOCAL->buf,ERROR);
  781.                 /* delete if requested message */
  782.   else if (options & CP_MOVE) for (i = 1; i <= stream->nmsgs; i++)
  783.     if ((elt = mail_elt (stream,i))->sequence) {
  784.       elt->deleted = T;        /* mark message deleted */
  785.       LOCAL->dirty = T;        /* note stream is now dirty */
  786.     }
  787.   return ret;
  788. }
  789.  
  790. /* MMDF mail append message from stringstruct
  791.  * Accepts: MAIL stream
  792.  *        destination mailbox
  793.  *        initial flags
  794.  *        internal date
  795.  *        stringstruct of messages to append
  796.  * Returns: T if append successful, else NIL
  797.  */
  798.  
  799. #define BUFLEN 8*MAILTMPLEN
  800.  
  801. long mmdf_append (MAILSTREAM *stream,char *mailbox,char *flags,char *date,
  802.           STRING *message)
  803. {
  804.   MESSAGECACHE elt;
  805.   struct stat sbuf;
  806.   int fd,ti,zn;
  807.   long f,i,ok;
  808.   unsigned long j,n,uf,size;
  809.   char c,*x,buf[BUFLEN],tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
  810.   time_t tp[2],t = time (0);
  811.                 /* default stream to prototype */
  812.   if (!stream) stream = user_flags (&mmdfproto);
  813.                 /* get flags */
  814.   f = mail_parse_flags (stream,flags,&uf);
  815.                 /* parse date */
  816.   if (!date) rfc822_date (date = tmp);
  817.   if (!mail_parse_date (&elt,date)) {
  818.     sprintf (buf,"Bad date in append: %.80s",date);
  819.     mm_log (buf,ERROR);
  820.     return NIL;
  821.   }
  822.                 /* make sure valid mailbox */
  823.   if (!mmdf_isvalid (mailbox,buf)) switch (errno) {
  824.   case ENOENT:            /* no such file? */
  825.     if (((mailbox[0] == 'I') || (mailbox[0] == 'i')) &&
  826.     ((mailbox[1] == 'N') || (mailbox[1] == 'n')) &&
  827.     ((mailbox[2] == 'B') || (mailbox[2] == 'b')) &&
  828.     ((mailbox[3] == 'O') || (mailbox[3] == 'o')) &&
  829.     ((mailbox[4] == 'X') || (mailbox[4] == 'x')) && !mailbox[5])
  830.       mmdf_create (NIL,"INBOX");
  831.     else {
  832.       mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
  833.       return NIL;
  834.     }
  835.                 /* falls through */
  836.   case 0:            /* INBOX ENOENT or empty file? */
  837.     break;
  838.   case EINVAL:
  839.     sprintf (buf,"Invalid MMDF-format mailbox name: %.80s",mailbox);
  840.     mm_log (buf,ERROR);
  841.     return NIL;
  842.   default:
  843.     sprintf (buf,"Not a MMDF-format mailbox: %.80s",mailbox);
  844.     mm_log (buf,ERROR);
  845.     return NIL;
  846.   }
  847.  
  848.   mm_critical (stream);        /* go critical */
  849.   if ((fd = mmdf_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND|O_CREAT,
  850.                S_IREAD|S_IWRITE,lock,LOCK_EX)) < 0) {
  851.     mm_nocritical (stream);    /* done with critical */
  852.     sprintf (buf,"Can't open append mailbox: %s",strerror (errno));
  853.     mm_log (buf,ERROR);
  854.     return NIL;
  855.   }
  856.   fstat (fd,&sbuf);        /* get current file size */
  857.   sprintf (buf,"%sFrom %s@%s ",mmdfhdr,myusername (),mylocalhost ());
  858.                 /* write the date given */
  859.   mail_cdate (buf + strlen (buf),&elt);
  860.   sprintf (buf + strlen (buf),"Status: %s\nX-Status: %s%s%s%s\nX-Keywords:",
  861.        f&fSEEN ? "R" : "",f&fDELETED ? "D" : "",
  862.        f&fFLAGGED ? "F" : "",f&fANSWERED ? "A" : "",f&fDRAFT ? "T" : "");
  863.   while (uf)            /* write user flags */
  864.     sprintf(buf+strlen(buf)," %s",stream->user_flags[find_rightmost_bit(&uf)]);
  865.   strcat (buf,"\n");        /* tie off flags */
  866.                 /* copy text, tossing out CR's and CTRL/A */
  867.   for (i = strlen (buf), ok = T, size = SIZE (message); ok && size; --size) {
  868.     if (((c = SNX (message)) != '\015') && (c != MMDFCHR)) buf[i++] = c;
  869.     if (i == MAILTMPLEN) {    /* dump if filled buffer or no more data */
  870.       if ((write (fd,buf,i)) >= 0) i = 0;
  871.       else {
  872.     sprintf (buf,"Message append failed: %s",strerror (errno));
  873.     mm_log (buf,ERROR);
  874.     ftruncate (fd,sbuf.st_size);
  875.     ok = NIL;
  876.       }
  877.     }
  878.   }
  879.                 /* write trailing delimiter */
  880.   if (!(ok && (ok = ((!i || (write (fd,buf,i) >= 0)) &&
  881.              (write (fd,mmdfhdr,MMDFHDRLEN) > 0) && !fsync (fd))))) {
  882.     sprintf (buf,"Message append failed: %s",strerror (errno));
  883.     mm_log (buf,ERROR);
  884.     ftruncate (fd,sbuf.st_size);
  885.   }
  886.   tp[0] = sbuf.st_atime;    /* preserve atime */
  887.   tp[1] = time (0);        /* set mtime to now */
  888.   utime (file,tp);        /* set the times */
  889.   mmdf_unlock (fd,NIL,lock);    /* unlock and close mailbox */
  890.   mm_nocritical (stream);    /* release critical */
  891.   return ok;            /* return success */
  892. }
  893.  
  894. /* Internal routines */
  895.  
  896.  
  897. /* MMDF mail abort stream
  898.  * Accepts: MAIL stream
  899.  */
  900.  
  901. void mmdf_abort (MAILSTREAM *stream)
  902. {
  903.   if (LOCAL) {            /* only if a file is open */
  904.     if (LOCAL->name) fs_give ((void **) &LOCAL->name);
  905.     if (LOCAL->fd >= 0) close (LOCAL->fd);
  906.     if (LOCAL->ld >= 0) {    /* have a mailbox lock? */
  907.       flock (LOCAL->ld,LOCK_UN);/* yes, release the lock */
  908.       close (LOCAL->ld);    /* close the lock file */
  909.       unlink (LOCAL->lname);    /* and delete it */
  910.     }
  911.     if (LOCAL->lname) fs_give ((void **) &LOCAL->lname);
  912.                 /* free local text buffers */
  913.     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
  914.     if (LOCAL->line) fs_give ((void **) &LOCAL->line);
  915.                 /* nuke the local data */
  916.     fs_give ((void **) &stream->local);
  917.     stream->dtb = NIL;        /* log out the DTB */
  918.   }
  919. }
  920.  
  921. /* MMDF open and lock mailbox
  922.  * Accepts: file name to open/lock
  923.  *        file open mode
  924.  *        destination buffer for lock file name
  925.  *        type of locking operation (LOCK_SH or LOCK_EX)
  926.  */
  927.  
  928. int mmdf_lock (char *file,int flags,int mode,char *lock,int op)
  929. {
  930.   int fd,ld,j;
  931.   int i = LOCKTIMEOUT * 60 - 1;
  932.   char hitch[MAILTMPLEN],tmp[MAILTMPLEN];
  933.   time_t t;
  934.   struct stat sb;
  935.   sprintf (lock,"%s.lock",file);/* build lock filename */
  936.   if (chk_notsymlink (lock)) do{/* until OK or out of tries */
  937.     t = time (0);        /* get the time now */
  938. #ifdef NFSKLUDGE
  939.   /* SUN-OS had an NFS, As kludgy as an albatross;
  940.    * And everywhere that it was installed, It was a total loss.  -- MRC 9/25/91
  941.    */
  942.                 /* build hitching post file name */
  943.     sprintf (hitch,"%s.%d.%d.",lock,time (0),getpid ());
  944.     j = strlen (hitch);        /* append local host name */
  945.     gethostname (hitch + j,(MAILTMPLEN - j) - 1);
  946.                 /* try to get hitching-post file */
  947.     if ((ld = open (hitch,O_WRONLY|O_CREAT|O_EXCL,
  948.             (int) mail_parameters (NIL,GET_LOCKPROTECTION,NIL))) < 0) {
  949.       switch (errno) {        /* what happened? */
  950.       case EEXIST:        /* file already exists? */
  951.     break;            /* oops, just try again */
  952.       case EACCES:        /* protection failure */
  953.     if (stat (hitch,&sb)) {    /* try again if file exists(?) */
  954.                 /* punt silently if paranoid site */
  955.       if (mail_parameters (NIL,GET_LOCKEACCESERROR,NIL))
  956.         mm_log ("Mailbox vulnerable - directory must have 1777 protection",
  957.             WARN);
  958.       *lock = '\0';        /* give up on lock file */
  959.     }
  960.     break;
  961.       default:            /* some other error */
  962.     sprintf (tmp,"Mailbox vulnerable - error creating %.80s: %s",
  963.          hitch,strerror (errno));
  964.     mm_log (tmp,WARN);    /* this is probably not good */
  965.     *lock = '\0';        /* give up on lock file */
  966.     break;
  967.       }
  968.     }
  969.     else {            /* got a hitching-post */
  970.                 /* make sure others can break the lock */
  971.       chmod (hitch,(int) mail_parameters (NIL,GET_LOCKPROTECTION,NIL));
  972.       close (ld);        /* close the hitching-post */
  973.       link (hitch,lock);    /* tie hitching-post to lock, ignore failure */
  974.       stat (hitch,&sb);        /* get its data */
  975.       unlink (hitch);        /* flush hitching post */
  976.       /* If link count .ne. 2, hitch failed.  Set ld to -1 as if open() failed
  977.      so we try again.  If extant lock file and time now is .gt. file time
  978.      plus timeout interval, flush the lock so can win next time around. */
  979.       if ((ld = (sb.st_nlink != 2) ? -1 : 0) && (!stat (lock,&sb)) &&
  980.       (t > sb.st_ctime + LOCKTIMEOUT * 60)) unlink (lock);
  981.     }
  982.  
  983. #else
  984.   /* This works on modern Mmdf systems which are not afflicted with NFS mail.
  985.    * "Modern" means that O_EXCL works.  I think that NFS mail is a terrible
  986.    * idea -- that's what IMAP is for -- but some people insist upon losing...
  987.    */
  988.                 /* try to get the lock */
  989.     if ((ld = open (lock,O_WRONLY|O_CREAT|O_EXCL,
  990.             (int) mail_parameters (NIL,GET_LOCKPROTECTION,NIL))) < 0)
  991.       switch (errno) {        /* what happened? */
  992.       case EEXIST:        /* if extant and old, grab it for ourselves */
  993.     if ((!stat (lock,&sb)) && (t > sb.st_ctime + LOCKTIMEOUT * 60))
  994.       ld = open (lock,O_WRONLY|O_CREAT,
  995.              (int) mail_parameters (NIL,GET_LOCKPROTECTION,NIL));
  996.     break;
  997.       case EACCES:        /* protection fail, ignore if non-ex or old */
  998.     if (stat (lock,&sb)) {    /* try again if file exists(?) */
  999.                 /* punt silently if paranoid site */
  1000.       if (mail_parameters (NIL,GET_LOCKEACCESERROR,NIL))
  1001.         mm_log ("Mailbox vulnerable - directory must have 1777 protection",
  1002.             WARN);
  1003.       *lock = '\0';        /* give up on lock file */
  1004.     }
  1005.     else if (t > sb.st_ctime + LOCKTIMEOUT * 60) unlink (lock);
  1006.     break;
  1007.       default:            /* some other failure */
  1008.     sprintf (tmp,"Mailbox vulnerable - error creating %.80s: %s",
  1009.            lock,strerror (errno));
  1010.     mm_log (tmp,WARN);    /* this is probably not good */
  1011.     *lock = '\0';        /* don't use lock files */
  1012.     break;
  1013.       }
  1014.     if (ld >= 0) {        /* if made a lock file */
  1015.                 /* make sure others can break the lock */
  1016.       chmod (lock,(int) mail_parameters (NIL,GET_LOCKPROTECTION,NIL));
  1017.       close (ld);        /* close the lock file */
  1018.     }
  1019. #endif
  1020.     if ((ld < 0) && *lock) {    /* if failed to make lock file and retry OK */
  1021.       if (!(i%15)) {
  1022.     sprintf (tmp,"Mailbox %.80s is locked, will override in %d seconds...",
  1023.          file,i);
  1024.     mm_log (tmp,WARN);
  1025.       }
  1026.       sleep (1);        /* wait 1 second before next try */
  1027.     }
  1028.   } while (i-- && ld < 0 && *lock);
  1029.                 /* open file */
  1030.   if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
  1031.   else {            /* open failed */
  1032.     j = errno;            /* preserve error code */
  1033.     if (*lock) unlink (lock);    /* flush the lock file if any */
  1034.     errno = j;            /* restore error code */
  1035.   }
  1036.   return fd;
  1037. }
  1038.  
  1039. /* MMDF unlock and close mailbox
  1040.  * Accepts: file descriptor
  1041.  *        (optional) mailbox stream to check atime/mtime
  1042.  *        (optional) lock file name
  1043.  */
  1044.  
  1045. void mmdf_unlock (int fd,MAILSTREAM *stream,char *lock)
  1046. {
  1047.   struct stat sbuf;
  1048.   time_t tp[2];
  1049.   fstat (fd,&sbuf);        /* get file times */
  1050.                 /* if stream and csh would think new mail */
  1051.   if (stream && (sbuf.st_atime <= sbuf.st_mtime)) {
  1052.     tp[0] = time (0);        /* set atime to now */
  1053.                 /* set mtime to (now - 1) if necessary */
  1054.     tp[1] = tp[0] > sbuf.st_mtime ? sbuf.st_mtime : tp[0] - 1;
  1055.                 /* set the times, note change */
  1056.     if (!utime (LOCAL->name,tp)) LOCAL->filetime = tp[1];
  1057.   }
  1058.   flock (fd,LOCK_UN);        /* release flock'ers */
  1059.   if (!stream) close (fd);    /* close the file if no stream */
  1060.                 /* flush the lock file if any */
  1061.   if (lock && *lock) unlink (lock);
  1062. }
  1063.  
  1064. /* MMDF mail parse and lock mailbox
  1065.  * Accepts: MAIL stream
  1066.  *        space to write lock file name
  1067.  *        type of locking operation
  1068.  * Returns: T if parse OK, critical & mailbox is locked shared; NIL if failure
  1069.  */
  1070.  
  1071. int mmdf_parse (MAILSTREAM *stream,char *lock,int op)
  1072. {
  1073.   int ti,zn;
  1074.   unsigned long i,j,k;
  1075.   char c,*s,*t,*u,tmp[MAILTMPLEN],date[30];
  1076.   int pseudoseen = NIL;
  1077.   unsigned long nmsgs = stream->nmsgs;
  1078.   unsigned long prevuid = nmsgs ? mail_elt (stream,nmsgs)->private.uid : 0;
  1079.   unsigned long recent = stream->recent;
  1080.   unsigned long oldnmsgs = stream->nmsgs;
  1081.   short silent = stream->silent;
  1082.   struct stat sbuf;
  1083.   STRING bs;
  1084.   FDDATA d;
  1085.   MESSAGECACHE *elt;
  1086.   mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
  1087.   mail_lock (stream);        /* guard against recursion or pingers */
  1088.                 /* toss out previous descriptor */
  1089.   if (LOCAL->fd >= 0) close (LOCAL->fd);
  1090.   mm_critical (stream);        /* open and lock mailbox (shared OK) */
  1091.   if ((LOCAL->fd = mmdf_lock (LOCAL->name,(LOCAL->ld >= 0) ? O_RDWR : O_RDONLY,
  1092.                   NIL,lock,op)) < 0) {
  1093.     sprintf (tmp,"Mailbox open failed, aborted: %s",strerror (errno));
  1094.     mm_log (tmp,ERROR);
  1095.     mmdf_abort (stream);
  1096.     mail_unlock (stream);
  1097.     mm_nocritical (stream);    /* done with critical */
  1098.     return NIL;
  1099.   }
  1100.   fstat (LOCAL->fd,&sbuf);    /* get status */
  1101.                 /* validate change in size */
  1102.   if (sbuf.st_size < LOCAL->filesize) {
  1103.     sprintf (tmp,"Mailbox shrank from %lu to %lu bytes, aborted",
  1104.          LOCAL->filesize,sbuf.st_size);
  1105.     mm_log (tmp,ERROR);        /* this is pretty bad */
  1106.     mmdf_unlock (LOCAL->fd,stream,lock);
  1107.     mmdf_abort (stream);
  1108.     mail_unlock (stream);
  1109.     mm_nocritical (stream);    /* done with critical */
  1110.     return NIL;
  1111.   }
  1112.  
  1113.                 /* new data? */
  1114.   else if (i = sbuf.st_size - LOCAL->filesize) {
  1115.     d.fd = LOCAL->fd;        /* yes, set up file descriptor */
  1116.     d.pos = LOCAL->filesize;    /* get to that position in the file */
  1117.     d.chunk = LOCAL->buf;    /* initial buffer chunk */
  1118.     d.chunksize = CHUNK;    /* file chunk size */
  1119.     INIT (&bs,fd_string,&d,i);    /* initialize stringstruct */
  1120.                 /* skip leading whitespace for broken MTAs */
  1121.     while (((c = CHR (&bs)) == '\n') || (c == ' ') || (c == '\t')) SNX (&bs);
  1122.     if (SIZE (&bs)) {        /* read new data */
  1123.                 /* remember internal header position */
  1124.       j = LOCAL->filesize + GETPOS (&bs);
  1125.       s = mmdf_mbxline (stream,&bs,&i);
  1126.       stream->silent = T;    /* quell main program new message events */
  1127.       do {            /* read MMDF header */
  1128.     if (!(i && ISMMDF (s))){/* see if valid MMDF header */
  1129.       sprintf (tmp,"Unexpected changes to mailbox (try restarting): %.20s",
  1130.            s);
  1131.                 /* see if we can back up to a line */
  1132.       if (i && (j > MMDFHDRLEN)) {
  1133.         SETPOS (&bs,j -= MMDFHDRLEN);
  1134.                 /* read previous line */
  1135.         s = mmdf_mbxline (stream,&bs,&i);
  1136.                 /* kill the error if it looks good */
  1137.         if (i && ISMMDF (s)) tmp[0] = '\0';
  1138.       }
  1139.       if (tmp[0]) {
  1140.         mm_log (tmp,ERROR);
  1141.         mmdf_unlock (LOCAL->fd,stream,lock);
  1142.         mmdf_abort (stream);
  1143.         mail_unlock (stream);
  1144.         mm_nocritical(stream);
  1145.         return NIL;
  1146.       }
  1147.     }
  1148.                 /* instantiate first new message */
  1149.     mail_exists (stream,++nmsgs);
  1150.     (elt = mail_elt (stream,nmsgs))->valid = T;
  1151.     recent++;        /* assume recent by default */
  1152.     elt->recent = T;
  1153.                 /* note position/size of internal header */
  1154.     elt->private.special.offset = j;
  1155.     elt->private.special.text.size = i;
  1156.  
  1157.     s = mmdf_mbxline (stream,&bs,&i);
  1158.     ti = 0;            /* assume not a valid date */
  1159.     if (i) VALID (s,t,ti,zn);
  1160.     if (ti) {        /* generate plausible IMAPish date string */
  1161.                 /* this is also part of header */
  1162.       elt->private.special.text.size += i;
  1163.       date[2] = date[6] = date[20] = '-'; date[11] = ' ';
  1164.       date[14] = date[17] = ':';
  1165.                 /* dd */
  1166.       date[0] = t[ti - 2]; date[1] = t[ti - 1];
  1167.                 /* mmm */
  1168.       date[3] = t[ti - 6]; date[4] = t[ti - 5]; date[5] = t[ti - 4];
  1169.                 /* hh */
  1170.       date[12] = t[ti + 1]; date[13] = t[ti + 2];
  1171.                 /* mm */
  1172.       date[15] = t[ti + 4]; date[16] = t[ti + 5];
  1173.       if (t[ti += 6]==':'){    /* ss */
  1174.         date[18] = t[++ti]; date[19] = t[++ti];
  1175.         ti++;        /* move to space */
  1176.       }
  1177.       else date[18] = date[19] = '0';
  1178.                 /* yy -- advance over timezone if necessary */
  1179.       if (zn == ti) ti += (((t[zn+1] == '+') || (t[zn+1] == '-')) ? 6 : 4);
  1180.       date[7] = t[ti + 1]; date[8] = t[ti + 2];
  1181.       date[9] = t[ti + 3]; date[10] = t[ti + 4];
  1182.                 /* zzz */
  1183.       t = zn ? (t + zn + 1) : "LCL";
  1184.       date[21] = *t++; date[22] = *t++; date[23] = *t++;
  1185.       if ((date[21] != '+') && (date[21] != '-')) date[24] = '\0';
  1186.       else {        /* numeric time zone */
  1187.         date[24] = *t++; date[25] = *t++;
  1188.         date[26] = '\0'; date[20] = ' ';
  1189.       }
  1190.                 /* set internal date */
  1191.       if (!mail_parse_date (elt,date)) {
  1192.         sprintf (tmp,"Unable to parse internal date: %s",date);
  1193.         mm_log (tmp,WARN);
  1194.       }
  1195.     }
  1196.     else {            /* make date from file date */
  1197.       struct tm *tm = gmtime (&sbuf.st_mtime);
  1198.       elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  1199.       elt->year = tm->tm_year + 1900 - BASEYEAR;
  1200.       elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  1201.       elt->seconds = tm->tm_sec;
  1202.       elt->zhours = 0; elt->zminutes = 0;
  1203.       t = NIL;        /* suppress line read */
  1204.     }
  1205.                 /* header starts here */
  1206.     elt->private.msg.header.offset = elt->private.special.text.size;
  1207.  
  1208.     do {            /* look for message body */
  1209.       if (t) s = t = mmdf_mbxline (stream,&bs,&i);
  1210.       else t = s;        /* this line read was suppressed */
  1211.       if (ISMMDF (s)) break;
  1212.                 /* this line is part of header */
  1213.       elt->private.msg.header.text.size += i;
  1214.       if (i) switch (*s) {    /* check header lines */
  1215.       case 'X':        /* possible X-???: line */
  1216.         if (s[1] == '-') {    /* must be immediately followed by hyphen */
  1217.                 /* X-Status: becomes Status: in S case */
  1218.           if (s[2] == 'S' && s[3] == 't' && s[4] == 'a' && s[5] == 't' &&
  1219.           s[6] == 'u' && s[7] == 's' && s[8] == ':') s += 2;
  1220.                 /* possible X-Keywords */
  1221.           else if (s[2] == 'K' && s[3] == 'e' && s[4] == 'y' &&
  1222.                s[5] == 'w' && s[6] == 'o' && s[7] == 'r' &&
  1223.                s[8] == 'd' && s[9] == 's' && s[10] == ':') {
  1224.         char uf[MAILTMPLEN];
  1225.         s += 11;    /* flush leading whitespace */
  1226.         while (*s && (*s != '\n')) {
  1227.           while (*s == ' ') s++;
  1228.                 /* find end of keyword */
  1229.           if (!(u = strpbrk (s," \n"))) u = s + strlen (s);
  1230.                 /* got a keyword? */
  1231.           if ((k = (u - s)) && (k < MAILTMPLEN)) {
  1232.                 /* copy keyword */
  1233.             strncpy (uf,s,k);
  1234.             uf[k] = '\0';    /* make sure tied off */
  1235.             ucase (uf);    /* coerce upper case */
  1236.             for (j = 0; (j<NUSERFLAGS) && stream->user_flags[j]; ++j)
  1237.               if (!strcmp(uf,
  1238.                   ucase (strcpy(tmp,stream->user_flags[j])))) {
  1239.             elt->user_flags |= ((long) 1) << j;
  1240.             break;
  1241.               }
  1242.                 /* need to create it? */
  1243.             if (!stream->rdonly && (j < NUSERFLAGS) &&
  1244.             !stream->user_flags[j]) {
  1245.                 /* set the bit */
  1246.               *uf |= 1 << j;
  1247.               stream->user_flags[j] = (char *) fs_get (k + 1);
  1248.               strncpy (stream->user_flags[j],s,k);
  1249.               stream->user_flags[j][k] = '\0';
  1250.                 /* if now out of user flags */
  1251.               if (j == NUSERFLAGS - 1) stream->kwd_create = NIL;
  1252.             }
  1253.           }
  1254.           s = u;    /* advance to next keyword */
  1255.         }
  1256.         break;
  1257.           }
  1258.  
  1259.                 /* possible X-IMAP */
  1260.           else if (s[2] == 'I' && s[3] == 'M' && s[4] == 'A' &&
  1261.                s[5] == 'P' && s[6] == ':') {
  1262.         if ((nmsgs == 1) && !stream->uid_validity) {
  1263.           s += 7;    /* advance to data */
  1264.                 /* flush whitespace */
  1265.           while (*s == ' ') s++;
  1266.           j = 0;    /* slurp UID validity */
  1267.                 /* found a digit? */
  1268.           while (isdigit (*s)) {
  1269.             j *= 10;    /* yes, add it in */
  1270.             j += *s++ - '0';
  1271.           }
  1272.           if (!j) break; /* punt if invalid UID validity */
  1273.           stream->uid_validity = j;
  1274.                 /* flush whitespace */
  1275.           while (*s == ' ') s++;
  1276.                 /* must have UID last too */
  1277.           if (isdigit (*s)) {
  1278.             j = 0;    /* slurp UID last */
  1279.             while (isdigit (*s)) {
  1280.               j *= 10;    /* yes, add it in */
  1281.               j += *s++ - '0';
  1282.             }
  1283.             stream->uid_last = j;
  1284.                 /* process keywords */
  1285.             for (j = 0; *s != '\n'; j++) {
  1286.                 /* flush leading whitespace */
  1287.               while (*s == ' ') s++;
  1288.               u = strpbrk (s," \n");
  1289.                 /* got a keyword? */
  1290.               if ((k = (u - s)) && j < NUSERFLAGS) {
  1291.             if (stream->user_flags[j])
  1292.               fs_give ((void **) &stream->user_flags[j]);
  1293.             stream->user_flags[j] = (char *) fs_get (k + 1);
  1294.             strncpy (stream->user_flags[j],s,k);
  1295.             stream->user_flags[j][k] = '\0';
  1296.               }
  1297.               s = u;    /* advance to next keyword */
  1298.             }
  1299.                 /* pseudo-header seen */
  1300.             pseudoseen = T;
  1301.           }
  1302.         }
  1303.         break;
  1304.           }
  1305.  
  1306.                 /* possible X-UID */
  1307.           else if (s[2] == 'U' && s[3] == 'I' && s[4] == 'D' &&
  1308.                s[5] == ':') {
  1309.                 /* only believe if have a UID validity */
  1310.         if (stream->uid_validity && (nmsgs > 1)) {
  1311.           s += 6;    /* advance to UID value */
  1312.                 /* flush whitespace */
  1313.           while (*s == ' ') s++;
  1314.           j = 0;
  1315.                 /* found a digit? */
  1316.           while (isdigit (*s)) {
  1317.             j *= 10;    /* yes, add it in */
  1318.             j += *s++ - '0';
  1319.           }
  1320.                 /* flush remainder of line */
  1321.           while (*s != '\n') s++;
  1322.                 /* make sure not duplicated */
  1323.           if (elt->private.uid)
  1324.             sprintf (tmp,"Message %lu UID %lu already has UID %lu",
  1325.                  pseudoseen ? elt->msgno - 1 : elt->msgno,
  1326.                  j,elt->private.uid);
  1327.                 /* make sure UID doesn't go backwards */
  1328.           else if (j <= prevuid)
  1329.             sprintf (tmp,"Message %lu UID %lu less than %lu",
  1330.                  pseudoseen ? elt->msgno - 1 : elt->msgno,
  1331.                  j,prevuid + 1);
  1332.                 /* or skip by mailbox's recorded last */
  1333.           else if (j > stream->uid_last)
  1334.             sprintf (tmp,"Message %lu UID %lu greater than last %lu",
  1335.                  pseudoseen ? elt->msgno - 1 : elt->msgno,
  1336.                  j,stream->uid_last);
  1337.           else {    /* normal UID case */
  1338.             prevuid = elt->private.uid = j;
  1339.             break;        /* exit this cruft */
  1340.           }
  1341.           mm_log (tmp,WARN);
  1342.                 /* invalidate UID validity */
  1343.           stream->uid_validity = 0;
  1344.           elt->private.uid = 0;
  1345.         }
  1346.         break;
  1347.           }
  1348.         }
  1349.                 /* otherwise fall into S case */
  1350.  
  1351.       case 'S':        /* possible Status: line */
  1352.         if (s[0] == 'S' && s[1] == 't' && s[2] == 'a' && s[3] == 't' &&
  1353.         s[4] == 'u' && s[5] == 's' && s[6] == ':') {
  1354.           s += 6;        /* advance to status flags */
  1355.           do switch (*s++) {/* parse flags */
  1356.           case 'R':        /* message read */
  1357.         elt->seen = T;
  1358.         break;
  1359.           case 'O':        /* message old */
  1360.         if (elt->recent) {
  1361.           elt->recent = NIL;
  1362.           recent--;    /* it really wasn't recent */
  1363.         }
  1364.         break;
  1365.           case 'D':        /* message deleted */
  1366.         elt->deleted = T;
  1367.         break;
  1368.           case 'F':        /* message flagged */
  1369.         elt->flagged = T;
  1370.         break;
  1371.           case 'A':        /* message answered */
  1372.         elt->answered = T;
  1373.         break;
  1374.           case 'T':        /* message is a draft */
  1375.         elt->draft = T;
  1376.         break;
  1377.           default:        /* some other crap */
  1378.         break;
  1379.           } while (*s && *s != '\n');
  1380.           break;        /* all done */
  1381.         }
  1382.                 /* otherwise fall into default case */
  1383.       default:        /* ordinary header line */
  1384.         elt->rfc822_size += i + 1;
  1385.         break;
  1386.       }
  1387.     } while (i && (*t != '\n'));
  1388.                 /* assign a UID if none found */
  1389.     if (((nmsgs > 1) || !pseudoseen) && !elt->private.uid)
  1390.       prevuid = elt->private.uid = ++stream->uid_last;
  1391.  
  1392.                 /* note location of text */
  1393.     elt->private.msg.text.offset =
  1394.       (LOCAL->filesize + GETPOS (&bs)) - elt->private.special.offset;
  1395.     k = 0;            /* no previous line size yet */
  1396.                 /* note current position */
  1397.     j = LOCAL->filesize + GETPOS (&bs);
  1398.     if (i) do {        /* look for next message */
  1399.       s = mmdf_mbxline (stream,&bs,&i);
  1400.       if (i) {        /* got new data? */
  1401.         if (ISMMDF (s)) break;
  1402.         else {
  1403.           elt->rfc822_size += i + (((i > 1) && s[i-2] == '\015') ? 0 : 1);
  1404.                 /* update current position */
  1405.           j = LOCAL->filesize + GETPOS (&bs);
  1406.         }
  1407.       }
  1408.     } while (i);        /* until found a header */
  1409.     elt->private.msg.text.text.size = j -
  1410.       (elt->private.special.offset + elt->private.msg.text.offset);
  1411.     if (i) {        /* get next header line */
  1412.                 /* remember first internal header position */
  1413.       j = LOCAL->filesize + GETPOS (&bs);
  1414.       s = mmdf_mbxline (stream,&bs,&i);
  1415.     }
  1416.       } while (i);        /* until end of buffer */
  1417.       if (pseudoseen) {        /* flush pseudo-message if present */
  1418.                 /* decrement recent count */
  1419.     if (mail_elt (stream,1)->recent) recent--;
  1420.                 /* and the exists count */
  1421.     mail_exists (stream,nmsgs--);
  1422.     mail_expunged(stream,1);/* fake an expunge of that message */
  1423.       }
  1424.                 /* need to start a new UID validity? */
  1425.       if (!stream->uid_validity) {
  1426.     stream->uid_validity = time (0);
  1427.                 /* in case a whiner with no life */
  1428.     if (mail_parameters (NIL,GET_USERHASNOLIFE,NIL))
  1429.       stream->uid_nosticky = T;
  1430.     else LOCAL->dirty = T;    /* make dirty to create pseudo-message */
  1431.       }
  1432.       stream->nmsgs = oldnmsgs;    /* whack it back down */
  1433.       stream->silent = silent;    /* restore old silent setting */
  1434.                 /* notify upper level of new mailbox sizes */
  1435.       mail_exists (stream,nmsgs);
  1436.       mail_recent (stream,recent);
  1437.                 /* mark dirty so O flags are set */
  1438.       if (recent) LOCAL->dirty = T;
  1439.     }
  1440.   }
  1441.                 /* no change, don't babble if never got time */
  1442.   else if (LOCAL->filetime && LOCAL->filetime != sbuf.st_mtime)
  1443.     mm_log ("New mailbox modification time but apparently no changes",WARN);
  1444.                 /* update parsed file size and time */
  1445.   LOCAL->filesize = sbuf.st_size;
  1446.   LOCAL->filetime = sbuf.st_mtime;
  1447.   return T;            /* return the winnage */
  1448. }
  1449.  
  1450. /* MMDF read line from mailbox
  1451.  * Accepts: mail stream
  1452.  *        stringstruct
  1453.  *        pointer to line size
  1454.  * Returns: pointer to input line
  1455.  */
  1456.  
  1457. char *mmdf_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size)
  1458. {
  1459.   unsigned long i,j,k,m;
  1460.   char *s,p1[CHUNK];
  1461.   char *ret = "";
  1462.                 /* flush old buffer */
  1463.   if (LOCAL->line) fs_give ((void **) &LOCAL->line);
  1464.                 /* if buffer needs refreshing */
  1465.   if (!bs->cursize) SETPOS (bs,GETPOS (bs));
  1466.   if (SIZE (bs)) {        /* find newline */
  1467.     for (i = 0; (i < bs->cursize) && (bs->curpos[i] != '\n'); i++);
  1468.     if (i == bs->cursize) {    /* difficult case if line spans buffer */
  1469.       memcpy (p1,bs->curpos,i);    /* remember what we have so far */
  1470.                 /* load next buffer */
  1471.       SETPOS (bs,k = GETPOS (bs) + i);
  1472.       for (j = 0; (j < bs->cursize) && (bs->curpos[j] != '\n'); j++);
  1473.       if (j == bs->cursize) {    /* huge line? */
  1474.     SETPOS (bs,GETPOS (bs) + j);
  1475.                 /* look for end of line */
  1476.     for (m = SIZE (bs); m && (SNX (bs) != '\n'); --m,++j);
  1477.     SETPOS (bs,k);        /* go back to where it started */
  1478.       }
  1479.       ret = LOCAL->line = (char *) fs_get (i + j + 2);
  1480.       memcpy (ret,p1,i);    /* copy first chunk */
  1481.       while (j) {        /* copy remainder */
  1482.     if (!bs->cursize) SETPOS (bs,GETPOS (bs));
  1483.     memcpy (ret + i,bs->curpos,k = min (j,bs->cursize));
  1484.     i += k;            /* account for this much read in */
  1485.     j -= k;
  1486.     bs->curpos += k;    /* increment new position */
  1487.     bs->cursize -= k;    /* eat that many bytes */
  1488.       }
  1489.       if (SIZE (bs)) SNX (bs);    /* skip over newline if one seen */
  1490.       ret[i++] = '\n';        /* make sure newline at end */
  1491.       ret[i] = '\0';        /* makes debugging easier */
  1492.     }
  1493.     else {            /* this is easy */
  1494.       ret = bs->curpos;        /* string it at this position */
  1495.       bs->curpos += ++i;    /* increment new position */
  1496.       bs->cursize -= i;        /* eat that many bytes */
  1497.     }
  1498.     *size = i;            /* return size to user */
  1499.   }
  1500.   else *size = 0;        /* end of data, return empty */
  1501.                 /* embedded MMDF header at end of line? */
  1502.   if ((*size > sizeof (MMDFHDRTXT)) &&
  1503.       (s = ret + *size - (i = sizeof (MMDFHDRTXT) - 1)) && ISMMDF (s)) {
  1504.     SETPOS (bs,GETPOS (bs) - i);/* back up to start of MMDF header */
  1505.     *size -= i;            /* reduce length of line */
  1506.     ret[*size - 1] = '\n';    /* force newline at end */
  1507.   }
  1508.   return ret;
  1509. }
  1510.  
  1511. /* MMDF make pseudo-header
  1512.  * Accepts: MAIL stream
  1513.  *        buffer to write pseudo-header
  1514.  * Returns: length of pseudo-header
  1515.  */
  1516.  
  1517. unsigned long mmdf_pseudo (MAILSTREAM *stream,char *hdr)
  1518. {
  1519.   int i;
  1520.   char *s;
  1521.   time_t t = time(0);
  1522.   sprintf (hdr,"%sFrom %s %sDate: ",mmdfhdr,pseudo_from,ctime (&t));
  1523.   rfc822_date (s = hdr + strlen (hdr));
  1524.   sprintf (s += strlen (s),    /* write the pseudo-header */
  1525.        "\nFrom: %s <%s@%s>\nSubject: %s\nX-IMAP: %010lu %010lu",
  1526.        pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
  1527.        stream->uid_validity,stream->uid_last);
  1528.  
  1529.   for (i = 0; i < NUSERFLAGS; ++i) if (stream->user_flags[i])
  1530.     sprintf (s += strlen (s)," %s",stream->user_flags[i]);
  1531.   sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n%s",pseudo_msg,mmdfhdr);
  1532.   return strlen (hdr);
  1533. }
  1534.  
  1535. /* MMDF make status string
  1536.  * Accepts: MAIL stream
  1537.  *        destination string to write
  1538.  *        message cache entry
  1539.  *        non-zero flag to write UID as well
  1540.  * Returns: length of string
  1541.  */
  1542.  
  1543. unsigned long mmdf_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
  1544.                 long flag)
  1545. {
  1546.   char *t;
  1547.   char *s = status;
  1548.   unsigned long uf = elt->user_flags;
  1549.   /* This used to be an sprintf(), but thanks to certain cretinous C libraries
  1550.      with horribly slow implementations of sprintf() I had to change it to this
  1551.      mess.  At least it should be fast. */
  1552.   *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't'; *s++ = 'u'; *s++ = 's';
  1553.   *s++ = ':'; *s++ = ' ';
  1554.   if (elt->seen) *s++ = 'R';
  1555.   *s++ = 'O'; *s++ = '\n';
  1556.   *s++ = 'X'; *s++ = '-'; *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't';
  1557.   *s++ = 'u'; *s++ = 's'; *s++ = ':'; *s++ = ' ';
  1558.   if (elt->deleted) *s++ = 'D';
  1559.   if (elt->flagged) *s++ = 'F';
  1560.   if (elt->answered) *s++ = 'A';
  1561.   if (elt->draft) *s++ = 'T';
  1562.     *s++ = '\n';
  1563.   if (!stream->uid_nosticky) {    /* cretins with no life can't use this */
  1564.     *s++ = 'X'; *s++ = '-'; *s++ = 'K'; *s++ = 'e'; *s++ = 'y'; *s++ = 'w';
  1565.     *s++ = 'o'; *s++ = 'r'; *s++ = 'd'; *s++ = 's'; *s++ = ':';
  1566.     while (uf) {
  1567.       *s++ = ' ';
  1568.       for (t = stream->user_flags[find_rightmost_bit (&uf)]; *t; *s++ = *t++);
  1569.     }
  1570.     *s++ = '\n';
  1571.     if (flag) {            /* want to include UID? */
  1572.       char stack[64];
  1573.       char *p = stack;
  1574.                 /* push UID digits on the stack */
  1575.       unsigned long n = elt->private.uid;
  1576.       do *p++ = (char) (n % 10) + '0';
  1577.       while (n /= 10);
  1578.       *s++ = 'X'; *s++ = '-'; *s++ = 'U'; *s++ = 'I'; *s++ = 'D'; *s++ = ':';
  1579.       *s++ = ' ';
  1580.                 /* pop UID from stack */
  1581.       while (p > stack) *s++ = *--p;
  1582.       *s++ = '\n';
  1583.     }
  1584.   }
  1585.   *s++ = '\n'; *s = '\0';    /* end of extended message status */
  1586.   return s - status;        /* return size of resulting string */
  1587. }
  1588.  
  1589. /* Rewrite mailbox file
  1590.  * Accepts: MAIL stream, must be critical and locked
  1591.  *        return pointer to number of expunged messages if want expunge
  1592.  * Returns: T if success and mailbox unlocked, NIL if failure
  1593.  */
  1594.  
  1595. long mmdf_rewrite (MAILSTREAM *stream,unsigned long *nexp)
  1596. {
  1597.   unsigned long i,j;
  1598.   int e,retry;
  1599.   time_t tp[2];
  1600.   struct stat sbuf;
  1601.   FILE *f;
  1602.   MESSAGECACHE *elt;
  1603.   unsigned long recent = stream->recent;
  1604.   unsigned long size = 0;    /* initially nothing done */
  1605.   if (nexp) *nexp = 0;        /* initially nothing expunged */
  1606.                 /* open scratch file */
  1607.   if (!(f = tmpfile ())) return mmdf_punt_scratch (NIL);
  1608.   if (!(stream->uid_nosticky ||    /* write pseudo-header */
  1609.     mmdf_fwrite (f,LOCAL->buf,mmdf_pseudo (stream,LOCAL->buf),&size)))
  1610.     return mmdf_punt_scratch (f);
  1611.   if (nexp) {            /* expunging */
  1612.     for (i = 1; i <= stream->nmsgs; i++)
  1613.       if (!(elt = mail_elt (stream,i))->deleted &&
  1614.       !mmdf_write_message (f,stream,elt,&size))
  1615.     return mmdf_punt_scratch (f);
  1616.   }
  1617.   else for (i = 1; i <= stream->nmsgs; i++)
  1618.     if (!mmdf_write_message (f,stream,mail_elt (stream,i),&size))
  1619.       return mmdf_punt_scratch (f);
  1620.                 /* write remaining data */
  1621.   if (fflush (f) || fstat (fileno (f),&sbuf)) return mmdf_punt_scratch (f);
  1622.   if (size != sbuf.st_size) {    /* make damn sure stdio isn't lying */
  1623.     char tmp[MAILTMPLEN];
  1624.     sprintf (tmp,"Checkpoint file size mismatch (%lu != %lu)",size,
  1625.          sbuf.st_size);
  1626.     mm_log (tmp,ERROR);
  1627.     fclose (f);            /* flush the output file */
  1628.     return NIL;
  1629.   }
  1630.   if (size > LOCAL->filesize) {    /* does the mailbox need to grow? */
  1631.                 /* am I paranoid or what? */
  1632.     if ((i = size - LOCAL->filesize) > LOCAL->buflen) {
  1633.                 /* this user won the lottery all right */
  1634.       fs_give ((void **) &LOCAL->buf);
  1635.       LOCAL->buf = (char *) fs_get ((LOCAL->buflen = i) + 1);
  1636.     }
  1637.     memset (LOCAL->buf,'\01',i);/* get a block of CTRL/A's */
  1638.     while (i) {            /* until write successful or punt */
  1639.       lseek (LOCAL->fd,LOCAL->filesize,L_SET);
  1640.       if (write (LOCAL->fd,LOCAL->buf,i) < 0) {
  1641.     j = errno;        /* note error before doing ftrunctate */
  1642.     ftruncate (LOCAL->fd,LOCAL->filesize);
  1643.     fsync (LOCAL->fd);
  1644.     if (mm_diskerror (stream,j,NIL)) {
  1645.       sprintf (LOCAL->buf,"Unable to extend mailbox: %s",strerror (j));
  1646.       mm_log (LOCAL->buf,ERROR);
  1647.       fclose (f);        /* flush the output file */
  1648.       return NIL;
  1649.     }
  1650.       }
  1651.       else i = 0;        /* write was successful */
  1652.     }
  1653.   }
  1654.  
  1655.                 /* update the cache */
  1656.   for (i = 1; i <= stream->nmsgs;) {
  1657.     elt = mail_elt (stream,i);    /* get cache */
  1658.     if (nexp && elt->deleted) {    /* expunge this message? */
  1659.       if (elt->recent) recent--;/* one less recent message */
  1660.       mail_expunged (stream,i);    /* notify upper levels */
  1661.       ++*nexp;            /* count up one more expunged message */
  1662.     }
  1663.     else {            /* update file pointers from kludgey places */
  1664.       elt->private.special.offset = elt->private.msg.full.offset;
  1665.       elt->private.msg.text.offset = elt->private.msg.full.text.size;
  1666.                 /* in case header grew */
  1667.       elt->private.msg.header.text.size = elt->private.msg.text.offset -
  1668.     elt->private.msg.header.offset;
  1669.                 /* stomp on these two kludges */
  1670.       elt->private.msg.full.offset = elt->private.msg.full.text.size = 0;
  1671.       i++;            /* preserved message */
  1672.     }
  1673.   }
  1674.   do {                /* restart point if failure */
  1675.     retry = NIL;        /* no need to retry yet */
  1676.     fseek (f,0,L_SET);        /* rewind files */
  1677.     lseek (LOCAL->fd,0,L_SET);
  1678.     for (i = size; i; i -= j)
  1679.       if (!((j = fread (LOCAL->buf,1,min ((long) CHUNK,i),f)) &&
  1680.         (write (LOCAL->fd,LOCAL->buf,j) >= 0))) {
  1681.     sprintf (LOCAL->buf,"Mailbox rewrite error: %s",strerror (e = errno));
  1682.     mm_notify (stream,LOCAL->buf,WARN);
  1683.     mm_diskerror (stream,e,T);
  1684.     retry = T;        /* must retry */
  1685.     break;
  1686.       }
  1687.   } while (retry);        /* in case need to retry */
  1688.   fclose (f);            /* finished with scratch file */
  1689.                 /* make sure tied off */
  1690.   ftruncate (LOCAL->fd,LOCAL->filesize = size);
  1691.   fsync (LOCAL->fd);        /* make sure the updates take */
  1692.   LOCAL->dirty = NIL;        /* no longer dirty */
  1693.                   /* notify upper level of new mailbox sizes */
  1694.   mail_exists (stream,stream->nmsgs);
  1695.   mail_recent (stream,recent);
  1696.                 /* set atime to now, mtime a second earlier */
  1697.   tp[1] = (tp[0] = time (0)) - 1;
  1698.                 /* set the times, note change */
  1699.   if (!utime (LOCAL->name,tp)) LOCAL->filetime = tp[1];
  1700.   close (LOCAL->fd);        /* close and reopen file */
  1701.   if ((LOCAL->fd = open (LOCAL->name,O_RDWR,NIL)) < 0) {
  1702.     sprintf (LOCAL->buf,"Mailbox open failed, aborted: %s",strerror (errno));
  1703.     mm_log (LOCAL->buf,ERROR);
  1704.     mmdf_abort (stream);
  1705.   }
  1706.   return T;            /* looks good */
  1707. }
  1708.  
  1709. /* Write message
  1710.  * Accepts: destination file
  1711.  *        MAIL stream
  1712.  *        message number
  1713.  *        pointer to current filesize tally
  1714.  * Returns: T if success, NIL if failure
  1715.  */
  1716.  
  1717. long mmdf_write_message (FILE *f,MAILSTREAM *stream,MESSAGECACHE *elt,
  1718.              unsigned long *size)
  1719. {
  1720.   char *s;
  1721.   unsigned long i;
  1722.                 /* (kludge alert) note new message offset */
  1723.   elt->private.msg.full.offset = *size;
  1724.                 /* internal header */
  1725.   lseek (LOCAL->fd,elt->private.special.offset,L_SET);
  1726.   read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
  1727.   if (mmdf_fwrite (f,LOCAL->buf,elt->private.special.text.size,size)) {
  1728.                 /* get header */
  1729.     s = mmdf_header (stream,elt->msgno,&i,FT_INTERNAL);
  1730.                 /* header size, sans trailing newline */
  1731.     if (i && (s[i - 2] == '\n')) i--;
  1732.                 /* write header */
  1733.     if (mmdf_fwrite (f,s,i,size) &&
  1734.     mmdf_fwrite (f,LOCAL->buf,mmdf_xstatus(stream,LOCAL->buf,elt,T),size)){
  1735.                 /* (kludge alert) note new text offset */
  1736.       elt->private.msg.full.text.size = *size - elt->private.msg.full.offset;
  1737.                 /* get text */
  1738.       s = mmdf_text_work (stream,elt,&i,FT_INTERNAL);
  1739.                 /* write text and trailing newline */
  1740.       if (mmdf_fwrite (f,s,i,size) && mmdf_fwrite (f,mmdfhdr,MMDFHDRLEN,size))
  1741.     return T;
  1742.     }
  1743.   }
  1744.   return NIL;            /* failed */
  1745. }
  1746.  
  1747. /* Safely write buffer
  1748.  * Accepts: destination file
  1749.  *        buffer pointer
  1750.  *        number of octets
  1751.  *        pointer to current filesize tally
  1752.  * Returns: T if successful, NIL if failure
  1753.  */
  1754.  
  1755. long mmdf_fwrite (FILE *f,char *s,unsigned long i,unsigned long *size)
  1756. {
  1757.   unsigned long j;
  1758.   while (i && ((j = fwrite (s,1,i,f)) || (errno == EINTR))) {
  1759.     *size += j;
  1760.     s += j;
  1761.     i -= j;
  1762.   }
  1763.   return i ? NIL : T;        /* success if wrote all requested data */
  1764. }
  1765.  
  1766.  
  1767. /* Punt scratch file
  1768.  * Accepts: file pointer
  1769.  * Returns: NIL, always
  1770.  */
  1771.  
  1772. long mmdf_punt_scratch (FILE *f)
  1773. {
  1774.   char tmp[MAILTMPLEN];
  1775.   sprintf (tmp,"Checkpoint file failure: %s",strerror (errno));
  1776.   mm_log (tmp,ERROR);
  1777.   if (f) fclose (f);        /* flush the output file */
  1778.   return NIL;
  1779. }
  1780.