mmdf.c
上传用户:ycwykj01
上传日期:2007-01-04
资源大小:1819k
文件大小:56k
源码类别:

网络编程

开发平台:

Unix_Linux

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