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 / mh.c < prev    next >
C/C++ Source or Header  |  1998-10-02  |  32KB  |  1,017 lines

  1. /*
  2.  * Program:    MH mail routines
  3.  *
  4.  * Author(s):    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:    23 February 1992
  13.  * Last Edited:    2 October 1998
  14.  *
  15.  * Copyright 1998 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 "mail.h"
  41. #include "osdep.h"
  42. #include <pwd.h>
  43. #include <sys/stat.h>
  44. #include <sys/time.h>
  45. #include "mh.h"
  46. #include "misc.h"
  47. #include "dummy.h"
  48.  
  49. /* MH mail routines */
  50.  
  51.  
  52. /* Driver dispatch used by MAIL */
  53.  
  54. DRIVER mhdriver = {
  55.   "mh",                /* driver name */
  56.                 /* driver flags */
  57.   DR_MAIL|DR_LOCAL|DR_NOFAST|DR_NAMESPACE|DR_NOSTICKY,
  58.   (DRIVER *) NIL,        /* next driver */
  59.   mh_valid,            /* mailbox is valid for us */
  60.   mh_parameters,        /* manipulate parameters */
  61.   mh_scan,            /* scan mailboxes */
  62.   mh_list,            /* find mailboxes */
  63.   mh_lsub,            /* find subscribed mailboxes */
  64.   mh_subscribe,            /* subscribe to mailbox */
  65.   mh_unsubscribe,        /* unsubscribe from mailbox */
  66.   mh_create,            /* create mailbox */
  67.   mh_delete,            /* delete mailbox */
  68.   mh_rename,            /* rename mailbox */
  69.   NIL,                /* status of mailbox */
  70.   mh_open,            /* open mailbox */
  71.   mh_close,            /* close mailbox */
  72.   mh_fast,            /* fetch message "fast" attributes */
  73.   NIL,                /* fetch message flags */
  74.   NIL,                /* fetch overview */
  75.   NIL,                /* fetch message envelopes */
  76.   mh_header,            /* fetch message header */
  77.   mh_text,            /* fetch message body */
  78.   NIL,                /* fetch partial message text */
  79.   NIL,                /* unique identifier */
  80.   NIL,                /* message number */
  81.   NIL,                /* modify flags */
  82.   NIL,                /* per-message modify flags */
  83.   NIL,                /* search for message based on criteria */
  84.   NIL,                /* sort messages */
  85.   NIL,                /* thread messages */
  86.   mh_ping,            /* ping mailbox to see if still alive */
  87.   mh_check,            /* check for new messages */
  88.   mh_expunge,            /* expunge deleted messages */
  89.   mh_copy,            /* copy messages to another mailbox */
  90.   mh_append,            /* append string message to mailbox */
  91.   NIL                /* garbage collect stream */
  92. };
  93.  
  94.                 /* prototype stream */
  95. MAILSTREAM mhproto = {&mhdriver};
  96.  
  97.  
  98. /* MH mail validate mailbox
  99.  * Accepts: mailbox name
  100.  * Returns: our driver if name is valid, NIL otherwise
  101.  */
  102.  
  103. DRIVER *mh_valid (char *name)
  104. {
  105.   char tmp[MAILTMPLEN];
  106.   return mh_isvalid (name,tmp,T) ? &mhdriver : NIL;
  107. }
  108.  
  109. /* MH mail test for valid mailbox
  110.  * Accepts: mailbox name
  111.  *        temporary buffer to use
  112.  *        syntax only test flag
  113.  * Returns: T if valid, NIL otherwise
  114.  */
  115.  
  116. static char *mh_profile = NIL;    /* holds MH profile */
  117. static char *mh_path = NIL;    /* holds MH path name */
  118. static long mh_once = 0;    /* already through this code */
  119.  
  120. int mh_isvalid (char *name,char *tmp,long synonly)
  121. {
  122.   struct stat sbuf;
  123.                 /* name must be #MHINBOX or #mh/... */
  124.   if (strcmp (ucase (strcpy (tmp,name)),"#MHINBOX") &&
  125.       !(tmp[0] == '#' && tmp[1] == 'M' && tmp[2] == 'H' && tmp[3] == '/')) {
  126.     errno = EINVAL;        /* bogus name */
  127.     return NIL;
  128.   }
  129.   if (!mh_path) {        /* have MH path yet? */
  130.     char *s,*t,*v;
  131.     int fd;
  132.     if (mh_once++) return NIL;    /* only do this code once */
  133.     if (!mh_profile) {        /* have MH profile? */
  134.       sprintf (tmp,"%s/%s",myhomedir (),MHPROFILE);
  135.       mh_profile = cpystr (tmp);
  136.     }
  137.     if ((fd = open (tmp,O_RDONLY,NIL)) < 0) {
  138.       strcat (tmp," not found, mh format names disabled");
  139.       mm_log (tmp,WARN);
  140.       return NIL;
  141.     }
  142.     fstat (fd,&sbuf);        /* yes, get size and read file */
  143.     read (fd,(t = (char *) fs_get (sbuf.st_size + 1)),sbuf.st_size);
  144.     close (fd);            /* don't need the file any more */
  145.     t[sbuf.st_size] = '\0';    /* tie it off */
  146.                 /* parse profile file */
  147.     for (s = strtok (t,"\r\n"); s && *s; s = strtok (NIL,"\r\n")) {
  148.                 /* found space in line? */
  149.       if (v = strpbrk (s," \t")) {
  150.     *v++ = '\0';        /* tie off, is keyword "Path:"? */
  151.     if (!strcmp (lcase (s),"path:")) {
  152.                 /* skip whitespace */
  153.       while ((*v == ' ') || (*v == '\t')) ++v;
  154.       if (*v == '/') s = v;    /* absolute path? */
  155.       else sprintf (s = tmp,"%s/%s",myhomedir (),v);
  156.       mh_path = cpystr (s);    /* copy name */
  157.       break;        /* don't need to look at rest of file */
  158.     }
  159.       }
  160.     }
  161.     fs_give ((void **) &t);    /* flush profile text */
  162.     if (!mh_path) {        /* default path if not in the profile */
  163.       sprintf (tmp,"%s/%s",myhomedir (),MHPATH);
  164.       mh_path = cpystr (tmp);
  165.     }
  166.   }
  167.   if (synonly) return T;    /* all done if syntax only check */
  168.   errno = NIL;            /* zap error */
  169.                 /* validate name as directory */
  170.   return ((stat (mh_file (tmp,name),&sbuf) == 0) &&
  171.       (sbuf.st_mode & S_IFMT) == S_IFDIR);
  172. }
  173.  
  174. /* MH manipulate driver parameters
  175.  * Accepts: function code
  176.  *        function-dependent value
  177.  * Returns: function-dependent return value
  178.  */
  179.  
  180. void *mh_parameters (long function,void *value)
  181. {
  182.   switch ((int) function) {
  183.   case SET_MHPROFILE:
  184.     if (mh_profile) fs_give ((void **) &mh_profile);
  185.     mh_profile = cpystr ((char *) value);
  186.     break;
  187.   case GET_MHPROFILE:
  188.     value = (void *) mh_profile;
  189.     break;
  190.   case SET_MHPATH:
  191.     if (mh_path) fs_give ((void **) &mh_path);
  192.     mh_path = cpystr ((char *) value);
  193.     break;
  194.   case GET_MHPATH:
  195.     value = (void *) mh_path;
  196.     break;
  197.   default:
  198.     value = NIL;        /* error case */
  199.     break;
  200.   }
  201.   return NIL;
  202. }
  203.  
  204. /* MH scan mailboxes
  205.  * Accepts: mail stream
  206.  *        reference
  207.  *        pattern to search
  208.  *        string to scan
  209.  */
  210.  
  211. void mh_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
  212. {
  213.   char tmp[MAILTMPLEN];
  214.   if (mh_canonicalize (tmp,ref,pat))
  215.     mm_log ("Scan not valid for mh mailboxes",ERROR);
  216. }
  217.  
  218.  
  219. /* MH list mailboxes
  220.  * Accepts: mail stream
  221.  *        reference
  222.  *        pattern to search
  223.  */
  224.  
  225. void mh_list (MAILSTREAM *stream,char *ref,char *pat)
  226. {
  227.   char *s,test[MAILTMPLEN],file[MAILTMPLEN];
  228.   long i = 0;
  229.   if (!pat || !*pat) {        /* empty pattern? */
  230.     if (mh_canonicalize (test,ref,"*")) {
  231.                 /* tie off name at root */
  232.       if (s = strchr (test,'/')) *++s = '\0';
  233.       else test[0] = '\0';
  234.       mm_list (stream,'/',test,LATT_NOSELECT);
  235.     }
  236.   }
  237.                 /* get canonical form of name */
  238.   else if (mh_canonicalize (test,ref,pat)) {
  239.     if (test[3] == '/') {    /* looking down levels? */
  240.                 /* yes, found any wildcards? */
  241.       if (s = strpbrk (test,"%*")) {
  242.                 /* yes, copy name up to that point */
  243.     strncpy (file,test+4,i = s - (test+4));
  244.     file[i] = '\0';        /* tie off */
  245.       }
  246.       else strcpy (file,test+4);/* use just that name then */
  247.                 /* find directory name */
  248.       if (s = strrchr (file,'/')) {
  249.     *s = '\0';        /* found, tie off at that point */
  250.     s = file;
  251.       }
  252.                 /* do the work */
  253.       mh_list_work (stream,s,test,0);
  254.     }
  255.                 /* always an INBOX */
  256.     if (pmatch ("#MHINBOX",ucase (test)))
  257.       mm_list (stream,NIL,"#MHINBOX",LATT_NOINFERIORS);
  258.   }
  259. }
  260.  
  261. /* MH list subscribed mailboxes
  262.  * Accepts: mail stream
  263.  *        reference
  264.  *        pattern to search
  265.  */
  266.  
  267. void mh_lsub (MAILSTREAM *stream,char *ref,char *pat)
  268. {
  269.   void *sdb = NIL;
  270.   char *s,test[MAILTMPLEN];
  271.                 /* get canonical form of name */
  272.   if (mh_canonicalize (test,ref,pat) && (s = sm_read (&sdb))) {
  273.     do if (pmatch_full (s,test,'/')) mm_lsub (stream,'/',s,NIL);
  274.     while (s = sm_read (&sdb)); /* until no more subscriptions */
  275.   }
  276. }
  277.  
  278. /* MH list mailboxes worker routine
  279.  * Accepts: mail stream
  280.  *        directory name to search
  281.  *        search pattern
  282.  *        search level
  283.  */
  284.  
  285. void mh_list_work (MAILSTREAM *stream,char *dir,char *pat,long level)
  286. {
  287.   DIR *dp;
  288.   struct direct *d;
  289.   struct stat sbuf;
  290.   char *cp,*np,curdir[MAILTMPLEN],name[MAILTMPLEN];
  291.                 /* build MH name to search */
  292.   if (dir) sprintf (name,"#mh/%s/",dir);
  293.   else strcpy (name,"#mh/");
  294.                 /* make directory name, punt if bogus */
  295.   if (!mh_file (curdir,name)) return;
  296.   cp = curdir + strlen (curdir);/* end of directory name */
  297.   np = name + strlen (name);    /* end of MH name */
  298.   if (dp = opendir (curdir)) {    /* open directory */
  299.     while (d = readdir (dp))    /* scan directory, ignore all . names */
  300.       if (d->d_name[0] != '.') {/* build file name */
  301.     strcpy (cp,d->d_name);    /* make directory name */
  302.     if (!stat (curdir,&sbuf) && ((sbuf.st_mode &= S_IFMT) == S_IFDIR)) {
  303.       strcpy (np,d->d_name);/* make mh name of directory name */
  304.                 /* yes, an MH name if full match */
  305.       if (pmatch_full (name,pat,'/')) mm_list (stream,'/',name,NIL);
  306.                 /* check if should recurse */
  307.       if (dmatch (name,pat,'/') &&
  308.           (level < (long) mail_parameters (NIL,GET_LISTMAXLEVEL,NIL)))
  309.         mh_list_work (stream,name+4,pat,level+1);
  310.     }
  311.       }
  312.     closedir (dp);        /* all done, flush directory */
  313.   }
  314. }
  315.  
  316. /* MH mail subscribe to mailbox
  317.  * Accepts: mail stream
  318.  *        mailbox to add to subscription list
  319.  * Returns: T on success, NIL on failure
  320.  */
  321.  
  322. long mh_subscribe (MAILSTREAM *stream,char *mailbox)
  323. {
  324.   char tmp[MAILTMPLEN];
  325.   return sm_subscribe (mailbox);
  326. }
  327.  
  328.  
  329. /* MH mail unsubscribe to mailbox
  330.  * Accepts: mail stream
  331.  *        mailbox to delete from subscription list
  332.  * Returns: T on success, NIL on failure
  333.  */
  334.  
  335. long mh_unsubscribe (MAILSTREAM *stream,char *mailbox)
  336. {
  337.   char tmp[MAILTMPLEN];
  338.   return sm_unsubscribe (mailbox);
  339. }
  340.  
  341. /* MH mail create mailbox
  342.  * Accepts: mail stream
  343.  *        mailbox name to create
  344.  * Returns: T on success, NIL on failure
  345.  */
  346.  
  347. long mh_create (MAILSTREAM *stream,char *mailbox)
  348. {
  349.   char tmp[MAILTMPLEN];
  350.   if (!(mailbox[0] == '#' && (mailbox[1] == 'm' || mailbox[1] == 'M') &&
  351.     (mailbox[2] == 'h' || mailbox[2] == 'H') && mailbox[3] == '/')) {
  352.     sprintf (tmp,"Can't create mailbox %.80s: invalid MH-format name",mailbox);
  353.     mm_log (tmp,ERROR);
  354.     return NIL;
  355.   }
  356.                 /* must not already exist */
  357.   if (mh_isvalid (mailbox,tmp,NIL)) {
  358.     sprintf (tmp,"Can't create mailbox %.80s: mailbox already exists",mailbox);
  359.     mm_log (tmp,ERROR);
  360.     return NIL;
  361.   }
  362.   if (!mh_path) return NIL;    /* sorry */
  363.   sprintf (tmp,"%s/%.900s/",mh_path,mailbox + 4);
  364.                 /* try to make it */
  365.   if (!dummy_create_path (stream,tmp)) {
  366.     sprintf (tmp,"Can't create mailbox %.80s: %s",mailbox,strerror (errno));
  367.     mm_log (tmp,ERROR);
  368.     return NIL;
  369.   }
  370.   return T;            /* return success */
  371. }
  372.  
  373. /* MH mail delete mailbox
  374.  *        mailbox name to delete
  375.  * Returns: T on success, NIL on failure
  376.  */
  377.  
  378. long mh_delete (MAILSTREAM *stream,char *mailbox)
  379. {
  380.   DIR *dirp;
  381.   struct direct *d;
  382.   int i;
  383.   char tmp[MAILTMPLEN];
  384.   if (!(mailbox[0] == '#' && (mailbox[1] == 'm' || mailbox[1] == 'M') &&
  385.     (mailbox[2] == 'h' || mailbox[2] == 'H') && mailbox[3] == '/')) {
  386.     sprintf (tmp,"Can't delete mailbox %.80s: invalid MH-format name",mailbox);
  387.     mm_log (tmp,ERROR);
  388.     return NIL;
  389.   }
  390.                 /* is mailbox valid? */
  391.   if (!mh_isvalid (mailbox,tmp,NIL)){
  392.     sprintf (tmp,"Can't delete mailbox %.80s: no such mailbox",mailbox);
  393.     mm_log (tmp,ERROR);
  394.     return NIL;
  395.   }
  396.                 /* get name of directory */
  397.   i = strlen (mh_file (tmp,mailbox));
  398.   if (dirp = opendir (tmp)) {    /* open directory */
  399.     tmp[i++] = '/';        /* now apply trailing delimiter */
  400.     while (d = readdir (dirp))    /* massacre all numeric or comma files */
  401.       if (mh_select (d) || (*d->d_name == ',') ||
  402.       !strcmp (d->d_name,MHSEQUENCE)) {
  403.     strcpy (tmp + i,d->d_name);
  404.     unlink (tmp);        /* sayonara */
  405.       }
  406.     closedir (dirp);        /* flush directory */
  407.   }
  408.                 /* try to remove the directory */
  409.   if (rmdir (mh_file (tmp,mailbox))) {
  410.     sprintf (tmp,"Can't delete mailbox %.80s: %s",mailbox,strerror (errno));
  411.     mm_log (tmp,ERROR);
  412.     return NIL;
  413.   }
  414.   return T;            /* return success */
  415. }
  416.  
  417. /* MH mail rename mailbox
  418.  * Accepts: MH mail stream
  419.  *        old mailbox name
  420.  *        new mailbox name
  421.  * Returns: T on success, NIL on failure
  422.  */
  423.  
  424. long mh_rename (MAILSTREAM *stream,char *old,char *newname)
  425. {
  426.   char c,*s,tmp[MAILTMPLEN],tmp1[MAILTMPLEN];
  427.   struct stat sbuf;
  428.   if (!(old[0] == '#' && (old[1] == 'm' || old[1] == 'M') &&
  429.     (old[2] == 'h' || old[2] == 'H') && old[3] == '/'))
  430.     sprintf (tmp,"Can't delete mailbox %.80s: invalid MH-format name",old);
  431.                 /* old mailbox name must be valid */
  432.   else if (!mh_isvalid (old,tmp,NIL))
  433.     sprintf (tmp,"Can't rename mailbox %.80s: no such mailbox",old);
  434.   else if (!(newname[0] == '#' && (newname[1] == 'm' || newname[1] == 'M') &&
  435.     (newname[2] == 'h' || newname[2] == 'H') && newname[3] == '/'))
  436.     sprintf (tmp,"Can't rename to mailbox %.80s: invalid MH-format name",
  437.          newname);
  438.                 /* new mailbox name must not be valid */
  439.   else if (mh_isvalid (newname,tmp,NIL))
  440.     sprintf (tmp,"Can't rename to mailbox %.80s: destination already exists",
  441.          newname);
  442.                 /* success if can rename the directory */
  443.   else {            /* found superior to destination name? */
  444.     if (s = strrchr (mh_file (tmp1,newname),'/')) {
  445.       c = *++s;            /* remember first character of inferior */
  446.       *s = '\0';        /* tie off to get just superior */
  447.                 /* name doesn't exist, create it */
  448.       if ((stat (tmp1,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
  449.       !dummy_create (stream,tmp1)) return NIL;
  450.       *s = c;            /* restore full name */
  451.     }
  452.     if (!rename (mh_file (tmp,old),tmp1)) return T;
  453.     sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",
  454.          old,newname,strerror (errno));
  455.   }
  456.   mm_log (tmp,ERROR);        /* something failed */
  457.   return NIL;
  458. }
  459.  
  460. /* MH mail open
  461.  * Accepts: stream to open
  462.  * Returns: stream on success, NIL on failure
  463.  */
  464.  
  465. MAILSTREAM *mh_open (MAILSTREAM *stream)
  466. {
  467.   char tmp[MAILTMPLEN];
  468.   if (!stream) return &mhproto;    /* return prototype for OP_PROTOTYPE call */
  469.   if (stream->local) fatal ("mh recycle stream");
  470.   stream->local = fs_get (sizeof (MHLOCAL));
  471.                 /* note if an INBOX or not */
  472.   LOCAL->inbox = !strcmp (ucase (strcpy (tmp,stream->mailbox)),"#MHINBOX");
  473.   mh_file (tmp,stream->mailbox);/* get directory name */
  474.   LOCAL->dir = cpystr (tmp);    /* copy directory name for later */
  475.                 /* make temporary buffer */
  476.   LOCAL->buf = (char *) fs_get ((LOCAL->buflen = MAXMESSAGESIZE) + 1);
  477.   LOCAL->scantime = 0;        /* not scanned yet */
  478.   stream->sequence++;        /* bump sequence number */
  479.                 /* parse mailbox */
  480.   stream->nmsgs = stream->recent = 0;
  481.   if (!mh_ping (stream)) return NIL;
  482.   if (!(stream->nmsgs || stream->silent))
  483.     mm_log ("Mailbox is empty",(long) NIL);
  484.   return stream;        /* return stream to caller */
  485. }
  486.  
  487. /* MH mail close
  488.  * Accepts: MAIL stream
  489.  *        close options
  490.  */
  491.  
  492. void mh_close (MAILSTREAM *stream,long options)
  493. {
  494.   if (LOCAL) {            /* only if a file is open */
  495.     int silent = stream->silent;
  496.     stream->silent = T;        /* note this stream is dying */
  497.     if (options & CL_EXPUNGE) mh_expunge (stream);
  498.     if (LOCAL->dir) fs_give ((void **) &LOCAL->dir);
  499.                 /* free local scratch buffer */
  500.     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
  501.                 /* nuke the local data */
  502.     fs_give ((void **) &stream->local);
  503.     stream->dtb = NIL;        /* log out the DTB */
  504.     stream->silent = silent;    /* reset silent state */
  505.   }
  506. }
  507.  
  508.  
  509. /* MH mail fetch fast information
  510.  * Accepts: MAIL stream
  511.  *        sequence
  512.  *        option flags
  513.  */
  514.  
  515. void mh_fast (MAILSTREAM *stream,char *sequence,long flags)
  516. {
  517.   unsigned long i,j;
  518.                 /* ugly and slow */
  519.   if (stream && LOCAL && ((flags & FT_UID) ?
  520.               mail_uid_sequence (stream,sequence) :
  521.               mail_sequence (stream,sequence)))
  522.     for (i = 1; i <= stream->nmsgs; i++)
  523.       if (mail_elt (stream,i)->sequence) mh_header (stream,i,&j,NIL);
  524. }
  525.  
  526. /* MH mail fetch message header
  527.  * Accepts: MAIL stream
  528.  *        message # to fetch
  529.  *        pointer to returned header text length
  530.  *        option flags
  531.  * Returns: message header in RFC822 format
  532.  */
  533.  
  534. char *mh_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
  535.          long flags)
  536. {
  537.   unsigned long i,hdrsize;
  538.   int fd;
  539.   char *t;
  540.   struct stat sbuf;
  541.   struct tm *tm;
  542.   MESSAGECACHE *elt;
  543.   *length = 0;            /* default to empty */
  544.   if (flags & FT_UID) return "";/* UID call "impossible" */
  545.   elt = mail_elt (stream,msgno);/* get elt */
  546.   if (!elt->private.msg.header.text.data) {
  547.                 /* build message file name */
  548.     sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid);
  549.     if ((fd = open (LOCAL->buf,O_RDONLY,NIL)) < 0) return "";
  550.     fstat (fd,&sbuf);        /* get size of message */
  551.                 /* make plausible IMAPish date string */
  552.     tm = gmtime (&sbuf.st_mtime);
  553.     elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  554.     elt->year = tm->tm_year + 1900 - BASEYEAR;
  555.     elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  556.     elt->seconds = tm->tm_sec;
  557.     elt->zhours = 0; elt->zminutes = 0;
  558.                 /* is buffer big enough? */
  559.     if (sbuf.st_size > LOCAL->buflen) {
  560.       fs_give ((void **) &LOCAL->buf);
  561.       LOCAL->buf = (char *) fs_get ((LOCAL->buflen = sbuf.st_size) + 1);
  562.     }
  563.                 /* slurp message */
  564.     read (fd,LOCAL->buf,sbuf.st_size);
  565.                 /* tie off file */
  566.     LOCAL->buf[sbuf.st_size] = '\0';
  567.     close (fd);            /* flush message file */
  568.                 /* find end of header */
  569.     for (i = 0,t = LOCAL->buf; *t && !(i && (*t == '\n')); i = (*t++ == '\n'));
  570.                 /* number of header bytes */
  571.     hdrsize = (*t ? ++t : t) - LOCAL->buf;
  572.     elt->rfc822_size =        /* size of entire message in CRLF form */
  573.       (elt->private.msg.header.text.size =
  574.        strcrlfcpy ((char **) &elt->private.msg.header.text.data,&i,LOCAL->buf,
  575.            hdrsize)) +
  576.      (elt->private.msg.text.text.size =
  577.       strcrlfcpy ((char **) &elt->private.msg.text.text.data,&i,t,
  578.               sbuf.st_size - hdrsize));
  579.   }
  580.   *length = elt->private.msg.header.text.size;
  581.   return (char *) elt->private.msg.header.text.data;
  582. }
  583.  
  584. /* MH mail fetch message text (body only)
  585.  * Accepts: MAIL stream
  586.  *        message # to fetch
  587.  *        pointer to returned stringstruct
  588.  *        option flags
  589.  * Returns: T on success, NIL on failure
  590.  */
  591.  
  592. long mh_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
  593. {
  594.   unsigned long i;
  595.   MESSAGECACHE *elt;
  596.                 /* UID call "impossible" */
  597.   if (flags & FT_UID) return NIL;
  598.   elt = mail_elt (stream,msgno);/* get elt */
  599.                 /* snarf message if don't have it yet */
  600.   if (!elt->private.msg.text.text.data) {
  601.     mh_header (stream,msgno,&i,flags);
  602.     if (!elt->private.msg.text.text.data) return NIL;
  603.   }
  604.   if (!(flags & FT_PEEK)) {    /* mark as seen */
  605.     mail_elt (stream,msgno)->seen = T;
  606.     mm_flags (stream,msgno);
  607.   }
  608.   if (!elt->private.msg.text.text.data) return NIL;
  609.   INIT (bs,mail_string,elt->private.msg.text.text.data,
  610.     elt->private.msg.text.text.size);
  611.   return T;
  612. }
  613.  
  614. /* MH mail ping mailbox
  615.  * Accepts: MAIL stream
  616.  * Returns: T if stream alive, else NIL
  617.  */
  618.  
  619. long mh_ping (MAILSTREAM *stream)
  620. {
  621.   MAILSTREAM *sysibx = NIL;
  622.   MESSAGECACHE *elt,*selt;
  623.   struct stat sbuf;
  624.   char *s,tmp[MAILTMPLEN];
  625.   int fd;
  626.   unsigned long i,j,r,old;
  627.   long nmsgs = stream->nmsgs;
  628.   long recent = stream->recent;
  629.   int silent = stream->silent;
  630.   if (stat (LOCAL->dir,&sbuf)) { /* directory exists? */
  631.     if (LOCAL->inbox) return T;
  632.     sprintf (tmp,"Can't open mailbox %.80s: no such mailbox",stream->mailbox);
  633.     mm_log (tmp,ERROR);
  634.     return NIL;
  635.   }
  636.   stream->silent = T;        /* don't pass up mm_exists() events yet */
  637.   if (sbuf.st_ctime != LOCAL->scantime) {
  638.     struct direct **names = NIL;
  639.     long nfiles = scandir (LOCAL->dir,&names,mh_select,mh_numsort);
  640.     if (nfiles < 0) nfiles = 0;    /* in case error */
  641.     old = stream->uid_last;
  642.                 /* note scanned now */
  643.     LOCAL->scantime = sbuf.st_ctime;
  644.                 /* scan directory */
  645.     for (i = 0; i < nfiles; ++i) {
  646.                 /* if newly seen, add to list */
  647.       if ((j = atoi (names[i]->d_name)) > old) {
  648.     mail_exists (stream,++nmsgs);
  649.     stream->uid_last = (elt = mail_elt (stream,nmsgs))->private.uid = j;
  650.     elt->valid = T;        /* note valid flags */
  651.     if (old) {        /* other than the first pass? */
  652.       elt->recent = T;    /* yup, mark as recent */
  653.       recent++;        /* bump recent count */
  654.     }
  655.     else {            /* see if already read */
  656.       sprintf (tmp,"%s/%s",LOCAL->dir,names[i]->d_name);
  657.       stat (tmp,&sbuf);    /* get inode poop */
  658.       if (sbuf.st_atime > sbuf.st_mtime) elt->seen = T;
  659.     }
  660.       }
  661.       fs_give ((void **) &names[i]);
  662.     }
  663.                 /* free directory */
  664.     if (names) fs_give ((void **) &names);
  665.   }
  666.  
  667.                 /* if INBOX, snarf from system INBOX  */
  668.   if (LOCAL->inbox && strcmp (sysinbox (),stream->mailbox)) {
  669.     old = stream->uid_last;
  670.     mm_critical (stream);    /* go critical */
  671.     stat (sysinbox (),&sbuf);    /* see if anything there */
  672.                 /* can get sysinbox mailbox? */
  673.     if (sbuf.st_size && (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT))
  674.     && (!sysibx->rdonly) && (r = sysibx->nmsgs)) {
  675.       for (i = 1; i <= r; ++i) {/* for each message in sysinbox mailbox */
  676.                 /* build file name we will use */
  677.     sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,++old);
  678.                 /* snarf message from Berkeley mailbox */
  679.     selt = mail_elt (sysibx,i);
  680.     if (((fd = open (LOCAL->buf,O_WRONLY|O_CREAT|O_EXCL,
  681.              S_IREAD|S_IWRITE)) >= 0) &&
  682.         (s = mail_fetchheader_full (sysibx,i,NIL,&j,FT_INTERNAL)) &&
  683.         (write (fd,s,j) == j) &&
  684.         (s = mail_fetchtext_full (sysibx,i,&j,FT_INTERNAL|FT_PEEK)) &&
  685.         (write (fd,s,j) == j) && !fsync (fd) && !close (fd)) {
  686.                 /* swell the cache */
  687.       mail_exists (stream,++nmsgs);
  688.       stream->uid_last =    /* create new elt, note its file number */
  689.         (elt = mail_elt (stream,nmsgs))->private.uid = old;
  690.       recent++;        /* bump recent count */
  691.                 /* set up initial flags and date */
  692.       elt->valid = elt->recent = T;
  693.       elt->seen = selt->seen;
  694.       elt->deleted = selt->deleted;
  695.       elt->flagged = selt->flagged;
  696.       elt->answered = selt->answered;
  697.       elt->draft = selt->draft;
  698.       elt->day = selt->day;elt->month = selt->month;elt->year = selt->year;
  699.       elt->hours = selt->hours;elt->minutes = selt->minutes;
  700.       elt->seconds = selt->seconds;
  701.       elt->zhours = selt->zhours; elt->zminutes = selt->zminutes;
  702.       mh_setdate (LOCAL->buf,elt);
  703.     }
  704.  
  705.     else {            /* failed to snarf */
  706.       if (fd) {        /* did it ever get opened? */
  707.         mm_log ("Message copy to MH mailbox failed",ERROR);
  708.         close (fd);        /* close descriptor */
  709.         unlink (LOCAL->buf);/* flush this file */
  710.       }
  711.       else {
  712.         sprintf (tmp,"Can't add message: %s",strerror (errno));
  713.         mm_log (tmp,ERROR);
  714.       }
  715.       stream->silent = silent;
  716.       return NIL;        /* note that something is badly wrong */
  717.     }
  718.     sprintf (tmp,"%lu",i);    /* delete it from the sysinbox */
  719.     mail_flag (sysibx,tmp,"\\Deleted",ST_SET);
  720.       }
  721.       stat (LOCAL->dir,&sbuf);    /* update scan time */
  722.       LOCAL->scantime = sbuf.st_ctime;      
  723.       mail_expunge (sysibx);    /* now expunge all those messages */
  724.     }
  725.     if (sysibx) mail_close (sysibx);
  726.     mm_nocritical (stream);    /* release critical */
  727.   }
  728.   stream->silent = silent;    /* can pass up events now */
  729.   mail_exists (stream,nmsgs);    /* notify upper level of mailbox size */
  730.   mail_recent (stream,recent);
  731.   return T;            /* return that we are alive */
  732. }
  733.  
  734. /* MH mail check mailbox
  735.  * Accepts: MAIL stream
  736.  */
  737.  
  738. void mh_check (MAILSTREAM *stream)
  739. {
  740.   /* Perhaps in the future this will preserve flags */
  741.   if (mh_ping (stream)) mm_log ("Check completed",(long) NIL);
  742. }
  743.  
  744.  
  745. /* MH mail expunge mailbox
  746.  * Accepts: MAIL stream
  747.  */
  748.  
  749. void mh_expunge (MAILSTREAM *stream)
  750. {
  751.   MESSAGECACHE *elt;
  752.   unsigned long j;
  753.   unsigned long i = 1;
  754.   unsigned long n = 0;
  755.   unsigned long recent = stream->recent;
  756.   mm_critical (stream);        /* go critical */
  757.   while (i <= stream->nmsgs) {    /* for each message */
  758.                 /* if deleted, need to trash it */
  759.     if ((elt = mail_elt (stream,i))->deleted) {
  760.       sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid);
  761.       if (unlink (LOCAL->buf)) {/* try to delete the message */
  762.     sprintf (LOCAL->buf,"Expunge of message %ld failed, aborted: %s",i,
  763.          strerror (errno));
  764.     mm_log (LOCAL->buf,(long) NIL);
  765.     break;
  766.       }
  767.       mail_gc_msg (&elt->private.msg,GC_ENV | GC_TEXTS);
  768.       if (elt->recent) --recent;/* if recent, note one less recent message */
  769.       mail_expunged (stream,i);    /* notify upper levels */
  770.       n++;            /* count up one more expunged message */
  771.     }
  772.     else i++;            /* otherwise try next message */
  773.   }
  774.   if (n) {            /* output the news if any expunged */
  775.     sprintf (LOCAL->buf,"Expunged %ld messages",n);
  776.     mm_log (LOCAL->buf,(long) NIL);
  777.   }
  778.   else mm_log ("No messages deleted, so no update needed",(long) NIL);
  779.   mm_nocritical (stream);    /* release critical */
  780.                 /* notify upper level of new mailbox size */
  781.   mail_exists (stream,stream->nmsgs);
  782.   mail_recent (stream,recent);
  783. }
  784.  
  785. /* MH mail copy message(s)
  786.  * Accepts: MAIL stream
  787.  *        sequence
  788.  *        destination mailbox
  789.  *        copy options
  790.  * Returns: T if copy successful, else NIL
  791.  */
  792.  
  793. long mh_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
  794. {
  795.   STRING st;
  796.   MESSAGECACHE *elt;
  797.   struct stat sbuf;
  798.   int fd;
  799.   unsigned long i,j;
  800.   char *t,flags[MAILTMPLEN],date[MAILTMPLEN];
  801.                 /* copy the messages */
  802.   if ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
  803.       mail_sequence (stream,sequence))
  804.     for (i = 1; i <= stream->nmsgs; i++) 
  805.       if ((elt = mail_elt (stream,i))->sequence) {
  806.     sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid);
  807.     if ((fd = open (LOCAL->buf,O_RDONLY,NIL)) < 0) return NIL;
  808.     fstat (fd,&sbuf);    /* get size of message */
  809.     if (!elt->day) {    /* make plausible IMAPish date string */
  810.       struct tm *tm = gmtime (&sbuf.st_mtime);
  811.       elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  812.       elt->year = tm->tm_year + 1900 - BASEYEAR;
  813.       elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  814.       elt->seconds = tm->tm_sec;
  815.       elt->zhours = 0; elt->zminutes = 0;
  816.     }
  817.                 /* is buffer big enough? */
  818.     if (sbuf.st_size > LOCAL->buflen) {
  819.       fs_give ((void **) &LOCAL->buf);
  820.       LOCAL->buf = (char *) fs_get ((LOCAL->buflen = sbuf.st_size) + 1);
  821.     }
  822.                 /* slurp message */
  823.     read (fd,LOCAL->buf,sbuf.st_size);
  824.                 /* tie off file */
  825.     LOCAL->buf[sbuf.st_size] = '\0';
  826.     close (fd);        /* flush message file */
  827.     INIT (&st,mail_string,(void *) LOCAL->buf,sbuf.st_size);
  828.     flags[0] = '\0';    /* init flag string */
  829.     if (elt->seen) strcat (flags," \\Seen");
  830.     if (elt->deleted) strcat (flags," \\Deleted");
  831.     if (elt->flagged) strcat (flags," \\Flagged");
  832.     if (elt->answered) strcat (flags," \\Answered");
  833.     if (elt->draft) strcat (flags," \\Draft");
  834.     flags[0] = '(';        /* open list */
  835.     strcat (flags,")");    /* close list */
  836.     mail_date (date,elt);    /* generate internal date */
  837.     if (!mail_append_full (stream,mailbox,flags,date,&st)) return NIL;
  838.     if (options & CP_MOVE) elt->deleted = T;
  839.       }
  840.   return T;            /* return success */
  841. }
  842.  
  843. /* MH mail append message from stringstruct
  844.  * Accepts: MAIL stream
  845.  *        destination mailbox
  846.  *        stringstruct of messages to append
  847.  * Returns: T if append successful, else NIL
  848.  */
  849.  
  850. long mh_append (MAILSTREAM *stream,char *mailbox,char *flags,char *date,
  851.         STRING *message)
  852. {
  853.   struct stat sbuf;
  854.   struct direct **names;
  855.   int fd;
  856.   char c,*s,*t,tmp[MAILTMPLEN];
  857.   MESSAGECACHE elt;
  858.   long i,last,nfiles;
  859.   long size = 0;
  860.   long ret = LONGT;
  861.   unsigned long uf;
  862.   short f = mail_parse_flags (stream ? stream : &mhproto,flags,&uf);
  863.   if (date) {            /* want to preserve date? */
  864.                 /* yes, parse date into an elt */
  865.     if (!mail_parse_date (&elt,date)) {
  866.       sprintf (tmp,"Bad date in append: %.80s",date);
  867.       mm_log (tmp,ERROR);
  868.       return NIL;
  869.     }
  870.   }
  871.                 /* N.B.: can't use LOCAL->buf for tmp */
  872.                 /* make sure valid mailbox */
  873.   if (!mh_isvalid (mailbox,tmp,NIL)) switch (errno) {
  874.   case ENOENT:            /* no such file? */
  875.     if ((mailbox[0] == '#') && ((mailbox[1] == 'M') || (mailbox[1] == 'm')) &&
  876.     ((mailbox[2] == 'H') || (mailbox[2] == 'h')) &&
  877.     ((mailbox[3] == 'I') || (mailbox[3] == 'i')) &&
  878.     ((mailbox[4] == 'N') || (mailbox[4] == 'n')) &&
  879.     ((mailbox[5] == 'B') || (mailbox[5] == 'b')) &&
  880.     ((mailbox[6] == 'O') || (mailbox[6] == 'o')) &&
  881.     ((mailbox[7] == 'X') || (mailbox[7] == 'x')) && !mailbox[8])
  882.       mh_create (NIL,"INBOX");
  883.     else {
  884.       mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
  885.       return NIL;
  886.     }
  887.                 /* falls through */
  888.   case 0:            /* merely empty file? */
  889.     break;
  890.   case EINVAL:
  891.     sprintf (tmp,"Invalid MH-format mailbox name: %.80s",mailbox);
  892.     mm_log (tmp,ERROR);
  893.     return NIL;
  894.   default:
  895.     sprintf (tmp,"Not a MH-format mailbox: %.80s",mailbox);
  896.     mm_log (tmp,ERROR);
  897.     return NIL;
  898.   }
  899.   mh_file (tmp,mailbox);    /* build file name we will use */
  900.   if ((nfiles = scandir (tmp,&names,mh_select,mh_numsort)) > 0) {
  901.                 /* largest number */
  902.     last = atoi (names[nfiles-1]->d_name);    
  903.     for (i = 0; i < nfiles; ++i) /* free directory */
  904.       fs_give ((void **) &names[i]);
  905.   }
  906.   else last = 0;        /* no messages here yet */
  907.   if (names) fs_give ((void **) &names);
  908.  
  909.   sprintf (tmp + strlen (tmp),"/%lu",++last);
  910.   if ((fd = open (tmp,O_WRONLY|O_CREAT|O_EXCL,S_IREAD|S_IWRITE)) < 0) {
  911.     sprintf (tmp,"Can't open append message: %s",strerror (errno));
  912.     mm_log (tmp,ERROR);
  913.     return NIL;
  914.   }
  915.   i = SIZE (message);        /* get size of message */
  916.   s = (char *) fs_get (i + 1);    /* get space for the data */
  917.                 /* copy the data w/o CR's */
  918.   while (i--) if ((c = SNX (message)) != '\015') s[size++] = c;
  919.   mm_critical (stream);        /* go critical */
  920.                 /* write the data */
  921.   if ((write (fd,s,size) < 0) || fsync (fd)) {
  922.     unlink (tmp);        /* delete message */
  923.     sprintf (tmp,"Message append failed: %s",strerror (errno));
  924.     mm_log (tmp,ERROR);
  925.     ret = NIL;
  926.   }
  927.   close (fd);            /* close the file */
  928.                 /* set the date for this message */
  929.   if (date) mh_setdate (tmp,&elt);
  930.  
  931.   mm_nocritical (stream);    /* release critical */
  932.   fs_give ((void **) &s);    /* flush the buffer */
  933.   return ret;
  934. }
  935.  
  936. /* Internal routines */
  937.  
  938.  
  939. /* MH file name selection test
  940.  * Accepts: candidate directory entry
  941.  * Returns: T to use file name, NIL to skip it
  942.  */
  943.  
  944. int mh_select (struct direct *name)
  945. {
  946.   char c;
  947.   char *s = name->d_name;
  948.   while (c = *s++) if (!isdigit (c)) return NIL;
  949.   return T;
  950. }
  951.  
  952.  
  953. /* MH file name comparision
  954.  * Accepts: first candidate directory entry
  955.  *        second candidate directory entry
  956.  * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2
  957.  */
  958.  
  959. int mh_numsort (const void *d1,const void *d2)
  960. {
  961.   return atoi ((*(struct direct **) d1)->d_name) -
  962.     atoi ((*(struct direct **) d2)->d_name);
  963. }
  964.  
  965.  
  966. /* MH mail build file name
  967.  * Accepts: destination string
  968.  *          source
  969.  * Returns: destination
  970.  */
  971.  
  972. char *mh_file (char *dst,char *name)
  973. {
  974.   char tmp[MAILTMPLEN];
  975.                 /* build composite name */
  976.   sprintf (dst,"%s/%.900s",mh_path,
  977.        strcmp (ucase (strcpy (tmp,name)),"#MHINBOX") ? name + 4 : "inbox");
  978.   return dst;
  979. }
  980.  
  981.  
  982. /* MH canonicalize name
  983.  * Accepts: buffer to write name
  984.  *        reference
  985.  *        pattern
  986.  * Returns: T if success, NIL if failure
  987.  */
  988.  
  989. long mh_canonicalize (char *pattern,char *ref,char *pat)
  990. {
  991.   char tmp[MAILTMPLEN];
  992.   if (ref && *ref) {        /* have a reference */
  993.     strcpy (pattern,ref);    /* copy reference to pattern */
  994.                 /* # overrides mailbox field in reference */
  995.     if (*pat == '#') strcpy (pattern,pat);
  996.                 /* pattern starts, reference ends, with / */
  997.     else if ((*pat == '/') && (pattern[strlen (pattern) - 1] == '/'))
  998.       strcat (pattern,pat + 1);    /* append, omitting one of the period */
  999.     else strcat (pattern,pat);    /* anything else is just appended */
  1000.   }
  1001.   else strcpy (pattern,pat);    /* just have basic name */
  1002.   return (mh_isvalid (pattern,tmp,T));
  1003. }
  1004.  
  1005. /* Set date for message
  1006.  * Accepts: file name
  1007.  *        elt containing date
  1008.  */
  1009.  
  1010. void mh_setdate (char *file,MESSAGECACHE *elt)
  1011. {
  1012.   time_t tp[2];
  1013.   tp[0] = time (0);        /* atime is now */
  1014.   tp[1] = mail_longdate (elt);    /* modification time */
  1015.   utime (file,tp);        /* set the times */
  1016. }
  1017.