home *** CD-ROM | disk | FTP | other *** search
/ vsiftp.vmssoftware.com / VSIPUBLIC@vsiftp.vmssoftware.com.tar / FREEWARE / FREEWARE40.ZIP / pine / c-client / mh.c < prev    next >
C/C++ Source or Header  |  1994-01-09  |  35KB  |  1,367 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.  *        Andrew Cohen
  13.  *        Internet: cohen@bucrf16.bu.edu
  14.  *
  15.  * Date:    23 February 1992
  16.  * Last Edited:    18 November 1993
  17.  *
  18.  * Copyright 1993 by the University of Washington
  19.  *
  20.  *  Permission to use, copy, modify, and distribute this software and its
  21.  * documentation for any purpose and without fee is hereby granted, provided
  22.  * that the above copyright notice appears in all copies and that both the
  23.  * above copyright notice and this permission notice appear in supporting
  24.  * documentation, and that the name of the University of Washington not be
  25.  * used in advertising or publicity pertaining to distribution of the software
  26.  * without specific, written prior permission.  This software is made
  27.  * available "as is", and
  28.  * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
  29.  * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
  30.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
  31.  * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
  32.  * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  33.  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
  34.  * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
  35.  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  36.  *
  37.  */
  38.  
  39. #include <stdio.h>
  40. #include <ctype.h>
  41. #include <netdb.h>
  42. #include <errno.h>
  43. extern int errno;        /* just in case */
  44. #include <sys/types.h>
  45. #include "mail.h"
  46. #include "osdep.h"
  47. #include <pwd.h>
  48. #include <sys/file.h>
  49. #include <sys/stat.h>
  50. #include <sys/time.h>
  51. #include "mh.h"
  52. #include "rfc822.h"
  53. #include "misc.h"
  54.  
  55. /* MH mail routines */
  56.  
  57.  
  58. /* Driver dispatch used by MAIL */
  59.  
  60. DRIVER mhdriver = {
  61.   "mh",                /* driver name */
  62.   (DRIVER *) NIL,        /* next driver */
  63.   mh_valid,            /* mailbox is valid for us */
  64.   mh_parameters,        /* manipulate parameters */
  65.   mh_find,            /* find mailboxes */
  66.   mh_find_bboards,        /* find bboards */
  67.   mh_find_all,            /* find all mailboxes */
  68.   mh_find_all_bboards,        /* find all bboards */
  69.   mh_subscribe,            /* subscribe to mailbox */
  70.   mh_unsubscribe,        /* unsubscribe from mailbox */
  71.   mh_subscribe_bboard,        /* subscribe to bboard */
  72.   mh_unsubscribe_bboard,    /* unsubscribe from bboard */
  73.   mh_create,            /* create mailbox */
  74.   mh_delete,            /* delete mailbox */
  75.   mh_rename,            /* rename mailbox */
  76.   mh_open,            /* open mailbox */
  77.   mh_close,            /* close mailbox */
  78.   mh_fetchfast,            /* fetch message "fast" attributes */
  79.   mh_fetchflags,        /* fetch message flags */
  80.   mh_fetchstructure,        /* fetch message envelopes */
  81.   mh_fetchheader,        /* fetch message header only */
  82.   mh_fetchtext,            /* fetch message body only */
  83.   mh_fetchbody,            /* fetch message body section */
  84.   mh_setflag,            /* set message flag */
  85.   mh_clearflag,            /* clear message flag */
  86.   mh_search,            /* search for message based on criteria */
  87.   mh_ping,            /* ping mailbox to see if still alive */
  88.   mh_check,            /* check for new messages */
  89.   mh_expunge,            /* expunge deleted messages */
  90.   mh_copy,            /* copy messages to another mailbox */
  91.   mh_move,            /* move messages to another mailbox */
  92.   mh_append,            /* append string message to mailbox */
  93.   mh_gc                /* garbage collect stream */
  94. };
  95.  
  96.                 /* prototype stream */
  97. MAILSTREAM mhproto = {&mhdriver};
  98.  
  99. /* MH mail validate mailbox
  100.  * Accepts: mailbox name
  101.  * Returns: our driver if name is valid, NIL otherwise
  102.  */
  103.  
  104. DRIVER *mh_valid (name)
  105.     char *name;
  106. {
  107.   char tmp[MAILTMPLEN];
  108.   return mh_isvalid (name,tmp) ? &mhdriver : NIL;
  109. }
  110.  
  111.  
  112. /* MH mail test for valid mailbox
  113.  * Accepts: mailbox name
  114.  * Returns: T if valid, NIL otherwise
  115.  */
  116.  
  117. int mh_isvalid (name,tmp)
  118.     char *name;
  119.     char *tmp;
  120. {
  121.   struct stat sbuf;
  122.                                 /* if file, get its status */
  123.   return (*name != '{' && *name != '.' &&
  124.       (stat (mh_file (tmp,name),&sbuf) == 0) &&
  125.       (sbuf.st_mode & S_IFMT) == S_IFDIR);
  126. }
  127.  
  128.  
  129. /* MH mail build file name
  130.  * Accepts: destination string
  131.  *          source
  132.  * Returns: destination
  133.  */
  134.  
  135. char *mh_file (dst,name)
  136.     char *dst;
  137.     char *name;
  138. {
  139. #if 0
  140.   struct passwd *pw;
  141.   char *s,*t,tmp[MAILTMPLEN];
  142.   switch (*name) {
  143.   case '/':            /* absolute file path */
  144.     strcpy (dst,name);        /* copy the mailbox name */
  145.     break;
  146.   case '~':            /* home directory */
  147.     if (name[1] == '/') t = myhomedir ();
  148.     else {
  149.       strcpy (tmp,name + 1);    /* copy user name */
  150.       if (s = strchr (tmp,'/')) *s = '\0';
  151.       t = ((pw = getpwnam (tmp)) && pw->pw_dir) ? pw->pw_dir : "/NOSUCHUSER";
  152.     }
  153.     sprintf (dst,"%s%s",t,(s = strchr (name,'/')) ? s : "");
  154.     break;
  155.   default:            /* relative path, goes in Mail folder */
  156.     sprintf (dst,"%s/Mail/%s",myhomedir (),name);
  157.     break;
  158.   }
  159. #else
  160.   sprintf (dst,"%s/Mail/%s",myhomedir (),name);
  161. #endif
  162.   return dst;
  163. }
  164.  
  165.  
  166. /* MH manipulate driver parameters
  167.  * Accepts: function code
  168.  *        function-dependent value
  169.  * Returns: function-dependent return value
  170.  */
  171.  
  172. void *mh_parameters (function,value)
  173.     long function;
  174.     void *value;
  175. {
  176.   fatal ("Invalid mh_parameters function");
  177.   return NIL;
  178. }
  179.  
  180. /* MH mail find list of mailboxes
  181.  * Accepts: mail stream
  182.  *        pattern to search
  183.  */
  184.  
  185. void mh_find (stream,pat)
  186.     MAILSTREAM *stream;
  187.     char *pat;
  188. {
  189.   void *s = NIL;
  190.   char *t,tmp[MAILTMPLEN];
  191.   while (t = sm_read (&s))    /* read subscription database */
  192.     if ((*t != '{') && strcmp (t,"INBOX") && pmatch (t,pat) &&
  193.     mh_isvalid (t,tmp)) mm_mailbox (t);
  194. }
  195.  
  196.  
  197. /* MH mail find list of bboards
  198.  * Accepts: mail stream
  199.  *        pattern to search
  200.  */
  201.  
  202. void mh_find_bboards (stream,pat)
  203.     MAILSTREAM *stream;
  204.     char *pat;
  205. {
  206.   /* Always a no-op */
  207. }
  208.  
  209. /* MH mail find list of all mailboxes
  210.  * Accepts: mail stream
  211.  *        pattern to search
  212.  */
  213.  
  214. void mh_find_all (stream,pat)
  215.     MAILSTREAM *stream;
  216.     char *pat;
  217. {
  218.   DIR *dirp;
  219.   struct direct *d;
  220.   char tmp[MAILTMPLEN],file[MAILTMPLEN];
  221.   int i = 0;
  222.   char *s,*t;
  223.   if (s = strrchr (pat,'/')) {    /* directory specified in pattern? */
  224.     strncpy (file,pat,i = (++s) - pat);
  225.     file[i] = '\0';        /* tie off prefix */
  226.     t = mh_file (tmp,pat);    /* make fully-qualified file name */
  227.                 /* tie off directory name */
  228.     if (s = strrchr (t,'/')) *s = '\0';
  229.   }
  230.   else t = myhomedir ();    /* use home directory to search */
  231.   if (dirp = opendir (t)) {    /* now open that directory */
  232.     while (d = readdir (dirp)) {/* for each directory entry */
  233.       strcpy (file + i,d->d_name);
  234.       if (pmatch (file,pat) && (mh_isvalid (file,tmp))) mm_mailbox (file);
  235.     }
  236.     closedir (dirp);        /* flush directory */
  237.   }
  238. }
  239.  
  240.  
  241. /* MH mail find list of all bboards
  242.  * Accepts: mail stream
  243.  *        pattern to search
  244.  */
  245.  
  246. void mh_find_all_bboards (stream,pat)
  247.     MAILSTREAM *stream;
  248.     char *pat;
  249. {
  250.   /* Always a no-op */
  251. }
  252.  
  253. /* MH mail subscribe to mailbox
  254.  * Accepts: mail stream
  255.  *        mailbox to add to subscription list
  256.  * Returns: T on success, NIL on failure
  257.  */
  258.  
  259. long mh_subscribe (stream,mailbox)
  260.     MAILSTREAM *stream;
  261.     char *mailbox;
  262. {
  263.   char tmp[MAILTMPLEN];
  264.   return sm_subscribe (mh_file (tmp,mailbox));
  265. }
  266.  
  267.  
  268. /* MH mail unsubscribe to mailbox
  269.  * Accepts: mail stream
  270.  *        mailbox to delete from subscription list
  271.  * Returns: T on success, NIL on failure
  272.  */
  273.  
  274. long mh_unsubscribe (stream,mailbox)
  275.     MAILSTREAM *stream;
  276.     char *mailbox;
  277. {
  278.   char tmp[MAILTMPLEN];
  279.   return sm_unsubscribe (mh_file (tmp,mailbox));
  280. }
  281.  
  282.  
  283. /* MH mail subscribe to bboard
  284.  * Accepts: mail stream
  285.  *        bboard to add to subscription list
  286.  * Returns: T on success, NIL on failure
  287.  */
  288.  
  289. long mh_subscribe_bboard (stream,mailbox)
  290.     MAILSTREAM *stream;
  291.     char *mailbox;
  292. {
  293.   return NIL;            /* never valid for MH */
  294. }
  295.  
  296.  
  297. /* MH mail unsubscribe to bboard
  298.  * Accepts: mail stream
  299.  *        bboard to delete from subscription list
  300.  * Returns: T on success, NIL on failure
  301.  */
  302.  
  303. long mh_unsubscribe_bboard (stream,mailbox)
  304.     MAILSTREAM *stream;
  305.     char *mailbox;
  306. {
  307.   return NIL;            /* never valid for MH */
  308. }
  309.  
  310. /* MH mail create mailbox
  311.  * Accepts: mail stream
  312.  *        mailbox name to create
  313.  * Returns: T on success, NIL on failure
  314.  */
  315.  
  316. long mh_create (stream,mailbox)
  317.     MAILSTREAM *stream;
  318.     char *mailbox;
  319. {
  320.   return NIL;            /* this driver is read-only */
  321. }
  322.  
  323.  
  324. /* MH mail delete mailbox
  325.  *        mailbox name to delete
  326.  * Returns: T on success, NIL on failure
  327.  */
  328.  
  329. long mh_delete (stream,mailbox)
  330.     MAILSTREAM *stream;
  331.     char *mailbox;
  332. {
  333.   return NIL;            /* this driver is read-only */
  334. }
  335.  
  336.  
  337. /* MH mail rename mailbox
  338.  * Accepts: MH mail stream
  339.  *        old mailbox name
  340.  *        new mailbox name
  341.  * Returns: T on success, NIL on failure
  342.  */
  343.  
  344. long mh_rename (stream,old,new)
  345.     MAILSTREAM *stream;
  346.     char *old;
  347.     char *new;
  348. {
  349.   return NIL;            /* this driver is read-only */
  350. }
  351.  
  352. /* MH mail open
  353.  * Accepts: stream to open
  354.  * Returns: stream on success, NIL on failure
  355.  */
  356.  
  357. MAILSTREAM *mh_open (stream)
  358.     MAILSTREAM *stream;
  359. {
  360.   long i,nmsgs;
  361.   long recent = 0;
  362.   char tmp[MAILTMPLEN];
  363.   struct hostent *host_name;
  364.   struct direct **names;
  365.   if (!stream) return &mhproto;    /* return prototype for OP_PROTOTYPE call */
  366.   if (LOCAL) {            /* close old file if stream being recycled */
  367.     mh_close (stream);        /* dump and save the changes */
  368.     stream->dtb = &mhdriver;    /* reattach this driver */
  369.     mail_free_cache (stream);    /* clean up cache */
  370.   }
  371.   mh_file (tmp,stream->mailbox);/* canonicalize the stream mailbox name */
  372.   if (!strcmp (tmp,stream->mailbox)) {
  373.     fs_give ((void **) &stream->mailbox);
  374.     stream->mailbox = cpystr (tmp);
  375.   }
  376.   if (!lhostn) {        /* have local host yet? */
  377.     gethostname(tmp,MAILTMPLEN);/* get local host name */
  378.     lhostn = cpystr ((host_name = gethostbyname (tmp)) ?
  379.              host_name->h_name : tmp);
  380.   }
  381.                 /* scan directory */
  382.   if ((nmsgs = scandir (tmp,&names,mh_select,mh_numsort)) >= 0) {
  383.     stream->local = fs_get (sizeof (MHLOCAL));
  384.     LOCAL->dirty = NIL;        /* no update to .mhrc needed yet */
  385.     LOCAL->dir = cpystr (tmp);    /* copy directory name for later */
  386.                 /* create cache */
  387.     LOCAL->number = (unsigned long *) fs_get (nmsgs * sizeof (unsigned long));
  388.     LOCAL->header = (char **) fs_get (nmsgs * sizeof (char *));
  389.     LOCAL->body = (char **) fs_get (nmsgs * sizeof (char *));
  390.     LOCAL->seen = (char *) fs_get (nmsgs * sizeof (char));
  391.     for (i = 0; i<nmsgs; ++i) {    /* initialize cache */
  392.       LOCAL->number[i] = atoi (names[i]->d_name);
  393.       fs_give ((void **) &names[i]);
  394.       LOCAL->header[i] = LOCAL->body[i] = NIL;
  395.       LOCAL->seen[i] = NIL;
  396.     }
  397.     fs_give ((void **) &names);    /* free directory */
  398.                 /* make temporary buffer */
  399.     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = MAXMESSAGESIZE) + 1);
  400.     stream->sequence++;        /* bump sequence number */
  401.     stream->readonly = T;    /* make sure higher level knows readonly */
  402.     mail_exists (stream,nmsgs);    /* notify upper level that messages exist */
  403.  
  404.     while (i < nmsgs) {        /* mark all remaining messages as new */
  405.       LOCAL->seen[i++] = NIL;
  406.       mail_elt (stream,i)->recent = T;
  407.       ++recent;            /* count another recent message */
  408.     }
  409.     mail_recent (stream,recent);/* notify upper level about recent */
  410.                 /* notify if empty bboard */
  411.     if (!(stream->nmsgs || stream->silent)) mm_log ("folder is empty",WARN);
  412.   }
  413.   return LOCAL ? stream : NIL;    /* if stream is alive, return to caller */
  414. }
  415.  
  416.  
  417. /* MH file name selection test
  418.  * Accepts: candidate directory entry
  419.  * Returns: T to use file name, NIL to skip it
  420.  */
  421.  
  422. int mh_select (name)
  423.     struct direct *name;
  424. {
  425.   char c;
  426.   char *s = name->d_name;
  427.   while (c = *s++) if (!isdigit (c)) return NIL;
  428.   return T;
  429. }
  430.  
  431.  
  432. /* MH file name comparision
  433.  * Accepts: first candidate directory entry
  434.  *        second candidate directory entry
  435.  * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2
  436.  */
  437.  
  438. int mh_numsort (d1,d2)
  439.     struct direct **d1;
  440.     struct direct **d2;
  441. {
  442.   return (atoi ((*d1)->d_name) - atoi ((*d2)->d_name));
  443. }
  444.  
  445. /* MH mail close
  446.  * Accepts: MAIL stream
  447.  */
  448.  
  449. void mh_close (stream)
  450.     MAILSTREAM *stream;
  451. {
  452.   if (LOCAL) {            /* only if a file is open */
  453.     mh_check (stream);        /* dump final checkpoint */
  454.     if (LOCAL->dir) fs_give ((void **) &LOCAL->dir);
  455.     mh_gc (stream,GC_TEXTS);    /* free local cache */
  456.     fs_give ((void **) &LOCAL->number);
  457.     fs_give ((void **) &LOCAL->header);
  458.     fs_give ((void **) &LOCAL->body);
  459.     fs_give ((void **) &LOCAL->seen);
  460.                 /* free local scratch buffer */
  461.     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
  462.                 /* nuke the local data */
  463.     fs_give ((void **) &stream->local);
  464.     stream->dtb = NIL;        /* log out the DTB */
  465.   }
  466. }
  467.  
  468. /* MH mail fetch fast information
  469.  * Accepts: MAIL stream
  470.  *        sequence
  471.  */
  472.  
  473. void mh_fetchfast (stream,sequence)
  474.     MAILSTREAM *stream;
  475.     char *sequence;
  476. {
  477.   return;            /* no-op for local mail */
  478. }
  479.  
  480.  
  481. /* MH mail fetch flags
  482.  * Accepts: MAIL stream
  483.  *        sequence
  484.  */
  485.  
  486. void mh_fetchflags (stream,sequence)
  487.     MAILSTREAM *stream;
  488.     char *sequence;
  489. {
  490.   return;            /* no-op for local mail */
  491. }
  492.  
  493.  
  494. /* MH mail fetch message structure
  495.  * Accepts: MAIL stream
  496.  *        message # to fetch
  497.  *        pointer to return body
  498.  * Returns: envelope of this message, body returned in body value
  499.  *
  500.  * Fetches the "fast" information as well
  501.  */
  502.  
  503. ENVELOPE *mh_fetchstructure (stream,msgno,body)
  504.     MAILSTREAM *stream;
  505.     long msgno;
  506.     BODY **body;
  507. {
  508.   char *h,*t;
  509.   LONGCACHE *lelt;
  510.   ENVELOPE **env;
  511.   STRING bs;
  512.   BODY **b;
  513.   if (stream->scache) {        /* short cache */
  514.     if (msgno != stream->msgno){/* flush old poop if a different message */
  515.       mail_free_envelope (&stream->env);
  516.       mail_free_body (&stream->body);
  517.     }
  518.     stream->msgno = msgno;
  519.     env = &stream->env;        /* get pointers to envelope and body */
  520.     b = &stream->body;
  521.   }
  522.   else {            /* long cache */
  523.     lelt = mail_lelt (stream,msgno);
  524.     env = &lelt->env;        /* get pointers to envelope and body */
  525.     b = &lelt->body;
  526.   }
  527.   if ((body && !*b) || !*env) {    /* have the poop we need? */
  528.     mail_free_envelope (env);    /* flush old envelope and body */
  529.     mail_free_body (b);
  530.     h = mh_fetchheader (stream,msgno);
  531.                 /* can't use fetchtext since it'll set seen */
  532.     t = LOCAL->body[msgno - 1] ? LOCAL->body[msgno - 1] : "";
  533.     INIT (&bs,mail_string,(void *) t,strlen (t));
  534.                 /* parse envelope and body */
  535.     rfc822_parse_msg (env,body ? b : NIL,h,strlen (h),&bs,lhostn,LOCAL->buf);
  536.   }
  537.   if (body) *body = *b;        /* return the body */
  538.   return *env;            /* return the envelope */
  539. }
  540.  
  541. /* MH mail fetch message header
  542.  * Accepts: MAIL stream
  543.  *        message # to fetch
  544.  * Returns: message header in RFC822 format
  545.  */
  546.  
  547. char *mh_fetchheader (stream,msgno)
  548.     MAILSTREAM *stream;
  549.     long msgno;
  550. {
  551.   unsigned long i,j;
  552.   int fd;
  553.   char *s,*b,*t;
  554.   long m = msgno - 1;
  555.   long lst = NIL;
  556.   struct stat sbuf;
  557.   struct tm *tm;
  558.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  559.                 /* build message file name */
  560.   sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,LOCAL->number[m]);
  561.   if (!LOCAL->header[m] && ((fd = open (LOCAL->buf,O_RDONLY,NIL)) >= 0)) {
  562.     fstat (fd,&sbuf);        /* get size of message */
  563.                 /* make plausible IMAPish date string */
  564.     tm = gmtime (&sbuf.st_mtime);
  565.     elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  566.     elt->year = tm->tm_year + 1900 - BASEYEAR;
  567.     elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  568.     elt->seconds = tm->tm_sec;
  569.     elt->zhours = 0; elt->zminutes = 0;
  570.                 /* slurp message */
  571.     read (fd,s = (char *) fs_get (sbuf.st_size +1),sbuf.st_size);
  572.     s[sbuf.st_size] = '\0';    /* tie off file */
  573.     close (fd);            /* flush message file */
  574.                 /* find end of header and count lines */
  575.     for (i=1,b=s; *b && !(lst && (*b == '\n'));) if (lst = (*b++ == '\n')) i++;
  576.                 /* copy header in CRLF form */
  577.     LOCAL->header[m] = (char *) fs_get (i += (j = b - s));
  578.     elt->rfc822_size = i - 1;    /* size of message header */
  579.     strcrlfcpy (&LOCAL->header[m],&i,s,j);
  580.                 /* copy body in CRLF form */
  581.     for (i = 1,t = b; *t;) if (*t++ == '\n') i++;
  582.     LOCAL->body[m] = (char *) fs_get (i += (j = t - b));
  583.     elt->rfc822_size += i - 1;    /* size of entire message */
  584.     strcrlfcpy (&LOCAL->body[m],&i,b,j);
  585.     fs_give ((void **) &s);    /* flush old data */
  586.   }
  587.   return LOCAL->header[m] ? LOCAL->header[m] : "";
  588. }
  589.  
  590. /* MH mail fetch message text (only)
  591.     body only;
  592.  * Accepts: MAIL stream
  593.  *        message # to fetch
  594.  * Returns: message text in RFC822 format
  595.  */
  596.  
  597. char *mh_fetchtext (stream,msgno)
  598.     MAILSTREAM *stream;
  599.     long msgno;
  600. {
  601.   long i = msgno - 1;
  602.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  603.                 /* snarf message in case don't have it yet */
  604.   mh_fetchheader (stream,msgno);
  605.   if (!elt->seen) {        /* if message not seen before */
  606.     elt->seen = T;        /* mark as seen */
  607.     LOCAL->dirty = T;        /* and that stream is now dirty */
  608.   }
  609.   LOCAL->seen[i] = T;
  610.   return LOCAL->body[i] ? LOCAL->body[i] : "";
  611. }
  612.  
  613. /* MH fetch message body as a structure
  614.  * Accepts: Mail stream
  615.  *        message # to fetch
  616.  *        section specifier
  617.  *        pointer to length
  618.  * Returns: pointer to section of message body
  619.  */
  620.  
  621. char *mh_fetchbody (stream,m,s,len)
  622.     MAILSTREAM *stream;
  623.     long m;
  624.     char *s;
  625.     unsigned long *len;
  626. {
  627.   BODY *b;
  628.   PART *pt;
  629.   unsigned long i;
  630.   char *base;
  631.   unsigned long offset = 0;
  632.   MESSAGECACHE *elt = mail_elt (stream,m);
  633.                 /* make sure have a body */
  634.   if (!(mh_fetchstructure (stream,m,&b) && b && s && *s &&
  635.     ((i = strtol (s,&s,10)) > 0) && (base = mh_fetchtext (stream,m))))
  636.     return NIL;
  637.   do {                /* until find desired body part */
  638.                 /* multipart content? */
  639.     if (b->type == TYPEMULTIPART) {
  640.       pt = b->contents.part;    /* yes, find desired part */
  641.       while (--i && (pt = pt->next));
  642.       if (!pt) return NIL;    /* bad specifier */
  643.                 /* note new body, check valid nesting */
  644.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  645.       offset = pt->offset;    /* get new offset */
  646.     }
  647.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  648.                 /* need to go down further? */
  649.     if (i = *s) switch (b->type) {
  650.     case TYPEMESSAGE:        /* embedded message */
  651.       offset = b->contents.msg.offset;
  652.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  653.     case TYPEMULTIPART:        /* multipart, get next section */
  654.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  655.     default:            /* bogus subpart specification */
  656.       return NIL;
  657.     }
  658.   } while (i);
  659.                 /* lose if body bogus */
  660.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  661.   if (!elt->seen) {        /* if message not seen before */
  662.     elt->seen = T;        /* mark as seen */
  663.     LOCAL->dirty = T;        /* and that stream is now dirty */
  664.   }
  665.   LOCAL->seen[m-1] = T;
  666.   return rfc822_contents (&LOCAL->buf,&LOCAL->buflen,len,base + offset,
  667.               b->size.ibytes,b->encoding);
  668. }
  669.  
  670. /* MH mail set flag
  671.  * Accepts: MAIL stream
  672.  *        sequence
  673.  *        flag(s)
  674.  */
  675.  
  676. void mh_setflag (stream,sequence,flag)
  677.     MAILSTREAM *stream;
  678.     char *sequence;
  679.     char *flag;
  680. {
  681.   MESSAGECACHE *elt;
  682.   long i;
  683.   short f = mh_getflags (stream,flag);
  684.   short f1 = f & (fSEEN|fDELETED);
  685.   if (!f) return;        /* no-op if no flags to modify */
  686.                 /* get sequence and loop on it */
  687.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  688.     if ((elt = mail_elt (stream,i))->sequence) {
  689.                 /* set all requested flags */
  690.       if (f&fSEEN) elt->seen = T;
  691.       if (f&fDELETED) elt->deleted = T;
  692.       if (f&fFLAGGED) elt->flagged = T;
  693.       if (f&fANSWERED) elt->answered = T;
  694.       if (f1 && !LOCAL->seen[i - 1]) LOCAL->seen[i - 1] = LOCAL->dirty = T;
  695.     }
  696. }
  697.  
  698.  
  699. /* MH mail clear flag
  700.  * Accepts: MAIL stream
  701.  *        sequence
  702.  *        flag(s)
  703.  */
  704.  
  705. void mh_clearflag (stream,sequence,flag)
  706.     MAILSTREAM *stream;
  707.     char *sequence;
  708.     char *flag;
  709. {
  710.   MESSAGECACHE *elt;
  711.   long i = stream->nmsgs;
  712.   short f = mh_getflags (stream,flag);
  713.   short f1 = f & (fSEEN|fDELETED);
  714.   if (!f) return;        /* no-op if no flags to modify */
  715.                 /* get sequence and loop on it */
  716.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  717.     if ((elt = mail_elt (stream,i))->sequence) {
  718.                 /* clear all requested flags */
  719.       if (f&fSEEN) elt->seen = NIL;
  720.       if (f&fDELETED) elt->deleted = NIL;
  721.       if (f&fFLAGGED) elt->flagged = NIL;
  722.       if (f&fANSWERED) elt->answered = NIL;
  723.                 /* clearing either seen or deleted does this */
  724.       if (f1 && LOCAL->seen[i - 1]) {
  725.     LOCAL->seen[i - 1] = NIL;
  726.     LOCAL->dirty = T;    /* mark stream as dirty */
  727.       }
  728.     }
  729. }
  730.  
  731. /* MH mail search for messages
  732.  * Accepts: MAIL stream
  733.  *        search criteria
  734.  */
  735.  
  736. void mh_search (stream,criteria)
  737.     MAILSTREAM *stream;
  738.     char *criteria;
  739. {
  740.   long i,n;
  741.   char *d;
  742.   search_t f;
  743.                 /* initially all searched */
  744.   for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = T;
  745.                 /* get first criterion */
  746.   if (criteria && (criteria = strtok (criteria," "))) {
  747.                 /* for each criterion */
  748.     for (; criteria; (criteria = strtok (NIL," "))) {
  749.       f = NIL; d = NIL; n = 0;    /* init then scan the criterion */
  750.       switch (*ucase (criteria)) {
  751.       case 'A':            /* possible ALL, ANSWERED */
  752.     if (!strcmp (criteria+1,"LL")) f = mh_search_all;
  753.     else if (!strcmp (criteria+1,"NSWERED")) f = mh_search_answered;
  754.     break;
  755.       case 'B':            /* possible BCC, BEFORE, BODY */
  756.     if (!strcmp (criteria+1,"CC"))
  757.       f = mh_search_string (mh_search_bcc,&d,&n);
  758.     else if (!strcmp (criteria+1,"EFORE"))
  759.       f = mh_search_date (mh_search_before,&n);
  760.     else if (!strcmp (criteria+1,"ODY"))
  761.       f = mh_search_string (mh_search_body,&d,&n);
  762.     break;
  763.       case 'C':            /* possible CC */
  764.     if (!strcmp (criteria+1,"C"))
  765.       f = mh_search_string (mh_search_cc,&d,&n);
  766.     break;
  767.       case 'D':            /* possible DELETED */
  768.     if (!strcmp (criteria+1,"ELETED")) f = mh_search_deleted;
  769.     break;
  770.       case 'F':            /* possible FLAGGED, FROM */
  771.     if (!strcmp (criteria+1,"LAGGED")) f = mh_search_flagged;
  772.     else if (!strcmp (criteria+1,"ROM"))
  773.       f = mh_search_string (mh_search_from,&d,&n);
  774.     break;
  775.       case 'K':            /* possible KEYWORD */
  776.     if (!strcmp (criteria+1,"EYWORD"))
  777.       f = mh_search_flag (mh_search_keyword,&d);
  778.     break;
  779.       case 'N':            /* possible NEW */
  780.     if (!strcmp (criteria+1,"EW")) f = mh_search_new;
  781.     break;
  782.  
  783.       case 'O':            /* possible OLD, ON */
  784.     if (!strcmp (criteria+1,"LD")) f = mh_search_old;
  785.     else if (!strcmp (criteria+1,"N"))
  786.       f = mh_search_date (mh_search_on,&n);
  787.     break;
  788.       case 'R':            /* possible RECENT */
  789.     if (!strcmp (criteria+1,"ECENT")) f = mh_search_recent;
  790.     break;
  791.       case 'S':            /* possible SEEN, SINCE, SUBJECT */
  792.     if (!strcmp (criteria+1,"EEN")) f = mh_search_seen;
  793.     else if (!strcmp (criteria+1,"INCE"))
  794.       f = mh_search_date (mh_search_since,&n);
  795.     else if (!strcmp (criteria+1,"UBJECT"))
  796.       f = mh_search_string (mh_search_subject,&d,&n);
  797.     break;
  798.       case 'T':            /* possible TEXT, TO */
  799.     if (!strcmp (criteria+1,"EXT"))
  800.       f = mh_search_string (mh_search_text,&d,&n);
  801.     else if (!strcmp (criteria+1,"O"))
  802.       f = mh_search_string (mh_search_to,&d,&n);
  803.     break;
  804.       case 'U':            /* possible UN* */
  805.     if (criteria[1] == 'N') {
  806.       if (!strcmp (criteria+2,"ANSWERED")) f = mh_search_unanswered;
  807.       else if (!strcmp (criteria+2,"DELETED")) f = mh_search_undeleted;
  808.       else if (!strcmp (criteria+2,"FLAGGED")) f = mh_search_unflagged;
  809.       else if (!strcmp (criteria+2,"KEYWORD"))
  810.         f = mh_search_flag (mh_search_unkeyword,&d);
  811.       else if (!strcmp (criteria+2,"SEEN")) f = mh_search_unseen;
  812.     }
  813.     break;
  814.       default:            /* we will barf below */
  815.     break;
  816.       }
  817.       if (!f) {            /* if can't determine any criteria */
  818.     sprintf (LOCAL->buf,"Unknown search criterion: %s",criteria);
  819.     mm_log (LOCAL->buf,ERROR);
  820.     return;
  821.       }
  822.                 /* run the search criterion */
  823.       for (i = 1; i <= stream->nmsgs; ++i)
  824.     if (mail_elt (stream,i)->searched && !(*f) (stream,i,d,n))
  825.       mail_elt (stream,i)->searched = NIL;
  826.     }
  827.                 /* report search results to main program */
  828.     for (i = 1; i <= stream->nmsgs; ++i)
  829.       if (mail_elt (stream,i)->searched) mail_searched (stream,i);
  830.   }
  831. }
  832.  
  833. /* MH mail ping mailbox
  834.  * Accepts: MAIL stream
  835.  * Returns: T if stream alive, else NIL
  836.  */
  837.  
  838. long mh_ping (stream)
  839.     MAILSTREAM *stream;
  840. {
  841.   return T;            /* always alive */
  842. }
  843.  
  844.  
  845. /* MH mail check mailbox
  846.  * Accepts: MAIL stream
  847.  */
  848.  
  849. void mh_check (stream)
  850.     MAILSTREAM *stream;
  851. {
  852.  /* A no-op for starters */
  853. }
  854.  
  855.  
  856. /* MH mail expunge mailbox
  857.  * Accepts: MAIL stream
  858.  */
  859.  
  860. void mh_expunge (stream)
  861.     MAILSTREAM *stream;
  862. {
  863.   if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
  864. }
  865.  
  866. /* MH mail copy message(s)
  867.     s;
  868.  * Accepts: MAIL stream
  869.  *        sequence
  870.  *        destination mailbox
  871.  * Returns: T if copy successful, else NIL
  872.  */
  873.  
  874. long mh_copy (stream,sequence,mailbox)
  875.     MAILSTREAM *stream;
  876.     char *sequence;
  877.     char *mailbox;
  878. {
  879.   mm_log ("Copy not valid for read-only mh mailbox",ERROR);
  880.   return NIL;
  881. }
  882.  
  883.  
  884. /* MH mail move message(s)
  885.     s;
  886.  * Accepts: MAIL stream
  887.  *        sequence
  888.  *        destination mailbox
  889.  * Returns: T if move successful, else NIL
  890.  */
  891.  
  892. long mh_move (stream,sequence,mailbox)
  893.     MAILSTREAM *stream;
  894.     char *sequence;
  895.     char *mailbox;
  896. {
  897.   mm_log ("Move not valid for read-only mh mailbox",ERROR);
  898.   return NIL;
  899. }
  900.  
  901.  
  902. /* MH mail append message from stringstruct
  903.  * Accepts: MAIL stream
  904.  *        destination mailbox
  905.  *        stringstruct of messages to append
  906.  * Returns: T if append successful, else NIL
  907.  */
  908.  
  909. long mh_append (stream,mailbox,message)
  910.     MAILSTREAM *stream;
  911.     char *mailbox;
  912.     STRING *message;
  913. {
  914.   mm_log ("Append not valid for read-only mh mailbox",ERROR);
  915.   return NIL;
  916. }
  917.  
  918. /* MH garbage collect stream
  919.  * Accepts: Mail stream
  920.  *        garbage collection flags
  921.  */
  922.  
  923. void mh_gc (stream,gcflags)
  924.     MAILSTREAM *stream;
  925.     long gcflags;
  926. {
  927.   unsigned long i;
  928.   if (gcflags & GC_TEXTS)    /* garbage collect texts? */
  929.                 /* flush texts from cache */
  930.     for (i = 0; i < stream->nmsgs; i++) {
  931.       if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  932.       if (LOCAL->body[i]) fs_give ((void **) &LOCAL->body[i]);
  933.     }
  934. }
  935.  
  936. /* Internal routines */
  937.  
  938.  
  939. /* Parse flag list
  940.  * Accepts: MAIL stream
  941.  *        flag list as a character string
  942.  * Returns: flag command list
  943.  */
  944.  
  945. short mh_getflags (stream,flag)
  946.     MAILSTREAM *stream;
  947.     char *flag;
  948. {
  949.   char *t;
  950.   short f = 0;
  951.   short i,j;
  952.   if (flag && *flag) {        /* no-op if no flag string */
  953.                 /* check if a list and make sure valid */
  954.     if ((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) {
  955.       mm_log ("Bad flag list",ERROR);
  956.       return NIL;
  957.     }
  958.                 /* copy the flag string w/o list construct */
  959.     strncpy (LOCAL->buf,flag+i,(j = strlen (flag) - (2*i)));
  960.     LOCAL->buf[j] = '\0';
  961.     t = ucase (LOCAL->buf);    /* uppercase only from now on */
  962.  
  963.     while (*t) {        /* parse the flags */
  964.       if (*t == '\\') {        /* system flag? */
  965.     switch (*++t) {        /* dispatch based on first character */
  966.     case 'S':        /* possible \Seen flag */
  967.       if (t[1] == 'E' && t[2] == 'E' && t[3] == 'N') i = fSEEN;
  968.       t += 4;        /* skip past flag name */
  969.       break;
  970.     case 'D':        /* possible \Deleted flag */
  971.       if (t[1] == 'E' && t[2] == 'L' && t[3] == 'E' && t[4] == 'T' &&
  972.           t[5] == 'E' && t[6] == 'D') i = fDELETED;
  973.       t += 7;        /* skip past flag name */
  974.       break;
  975.     case 'F':        /* possible \Flagged flag */
  976.       if (t[1] == 'L' && t[2] == 'A' && t[3] == 'G' && t[4] == 'G' &&
  977.           t[5] == 'E' && t[6] == 'D') i = fFLAGGED;
  978.       t += 7;        /* skip past flag name */
  979.       break;
  980.     case 'A':        /* possible \Answered flag */
  981.       if (t[1] == 'N' && t[2] == 'S' && t[3] == 'W' && t[4] == 'E' &&
  982.           t[5] == 'R' && t[6] == 'E' && t[7] == 'D') i = fANSWERED;
  983.       t += 8;        /* skip past flag name */
  984.       break;
  985.     default:        /* unknown */
  986.       i = 0;
  987.       break;
  988.     }
  989.                 /* add flag to flags list */
  990.     if (i && ((*t == '\0') || (*t++ == ' '))) f |= i;
  991.     else {            /* bitch about bogus flag */
  992.       mm_log ("Unknown system flag",ERROR);
  993.       return NIL;
  994.     }
  995.       }
  996.       else {            /* no user flags yet */
  997.     mm_log ("Unknown flag",ERROR);
  998.     return NIL;
  999.       }
  1000.     }
  1001.   }
  1002.   return f;
  1003. }
  1004.  
  1005. /* Search support routines
  1006.  * Accepts: MAIL stream
  1007.  *        message number
  1008.  *        pointer to additional data
  1009.  *        pointer to temporary buffer
  1010.  * Returns: T if search matches, else NIL
  1011.  */
  1012.  
  1013. char mh_search_all (stream,msgno,d,n)
  1014.     MAILSTREAM *stream;
  1015.     long msgno;
  1016.     char *d;
  1017.     long n;
  1018. {
  1019.   return T;            /* ALL always succeeds */
  1020. }
  1021.  
  1022.  
  1023. char mh_search_answered (stream,msgno,d,n)
  1024.     MAILSTREAM *stream;
  1025.     long msgno;
  1026.     char *d;
  1027.     long n;
  1028. {
  1029.   return mail_elt (stream,msgno)->answered ? T : NIL;
  1030. }
  1031.  
  1032.  
  1033. char mh_search_deleted (stream,msgno,d,n)
  1034.     MAILSTREAM *stream;
  1035.     long msgno;
  1036.     char *d;
  1037.     long n;
  1038. {
  1039.   return mail_elt (stream,msgno)->deleted ? T : NIL;
  1040. }
  1041.  
  1042.  
  1043. char mh_search_flagged (stream,msgno,d,n)
  1044.     MAILSTREAM *stream;
  1045.     long msgno;
  1046.     char *d;
  1047.     long n;
  1048. {
  1049.   return mail_elt (stream,msgno)->flagged ? T : NIL;
  1050. }
  1051.  
  1052.  
  1053. char mh_search_keyword (stream,msgno,d,n)
  1054.     MAILSTREAM *stream;
  1055.     long msgno;
  1056.     char *d;
  1057.     long n;
  1058. {
  1059.   return NIL;            /* keywords not supported yet */
  1060. }
  1061.  
  1062.  
  1063. char mh_search_new (stream,msgno,d,n)
  1064.     MAILSTREAM *stream;
  1065.     long msgno;
  1066.     char *d;
  1067.     long n;
  1068. {
  1069.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1070.   return (elt->recent && !elt->seen) ? T : NIL;
  1071. }
  1072.  
  1073. char mh_search_old (stream,msgno,d,n)
  1074.     MAILSTREAM *stream;
  1075.     long msgno;
  1076.     char *d;
  1077.     long n;
  1078. {
  1079.   return mail_elt (stream,msgno)->recent ? NIL : T;
  1080. }
  1081.  
  1082.  
  1083. char mh_search_recent (stream,msgno,d,n)
  1084.     MAILSTREAM *stream;
  1085.     long msgno;
  1086.     char *d;
  1087.     long n;
  1088. {
  1089.   return mail_elt (stream,msgno)->recent ? T : NIL;
  1090. }
  1091.  
  1092.  
  1093. char mh_search_seen (stream,msgno,d,n)
  1094.     MAILSTREAM *stream;
  1095.     long msgno;
  1096.     char *d;
  1097.     long n;
  1098. {
  1099.   return mail_elt (stream,msgno)->seen ? T : NIL;
  1100. }
  1101.  
  1102.  
  1103. char mh_search_unanswered (stream,msgno,d,n)
  1104.     MAILSTREAM *stream;
  1105.     long msgno;
  1106.     char *d;
  1107.     long n;
  1108. {
  1109.   return mail_elt (stream,msgno)->answered ? NIL : T;
  1110. }
  1111.  
  1112.  
  1113. char mh_search_undeleted (stream,msgno,d,n)
  1114.     MAILSTREAM *stream;
  1115.     long msgno;
  1116.     char *d;
  1117.     long n;
  1118. {
  1119.   return mail_elt (stream,msgno)->deleted ? NIL : T;
  1120. }
  1121.  
  1122.  
  1123. char mh_search_unflagged (stream,msgno,d,n)
  1124.     MAILSTREAM *stream;
  1125.     long msgno;
  1126.     char *d;
  1127.     long n;
  1128. {
  1129.   return mail_elt (stream,msgno)->flagged ? NIL : T;
  1130. }
  1131.  
  1132.  
  1133. char mh_search_unkeyword (stream,msgno,d,n)
  1134.     MAILSTREAM *stream;
  1135.     long msgno;
  1136.     char *d;
  1137.     long n;
  1138. {
  1139.   return T;            /* keywords not supported yet */
  1140. }
  1141.  
  1142.  
  1143. char mh_search_unseen (stream,msgno,d,n)
  1144.     MAILSTREAM *stream;
  1145.     long msgno;
  1146.     char *d;
  1147.     long n;
  1148. {
  1149.   return mail_elt (stream,msgno)->seen ? NIL : T;
  1150. }
  1151.  
  1152. char mh_search_before (stream,msgno,d,n)
  1153.     MAILSTREAM *stream;
  1154.     long msgno;
  1155.     char *d;
  1156.     long n;
  1157. {
  1158.   return (char) (mh_msgdate (stream,msgno) < n);
  1159. }
  1160.  
  1161.  
  1162. char mh_search_on (stream,msgno,d,n)
  1163.     MAILSTREAM *stream;
  1164.     long msgno;
  1165.     char *d;
  1166.     long n;
  1167. {
  1168.   return (char) (mh_msgdate (stream,msgno) == n);
  1169. }
  1170.  
  1171.  
  1172. char mh_search_since (stream,msgno,d,n)
  1173.     MAILSTREAM *stream;
  1174.     long msgno;
  1175.     char *d;
  1176.     long n;
  1177. {
  1178.                 /* everybody interprets "since" as .GE. */
  1179.   return (char) (mh_msgdate (stream,msgno) >= n);
  1180. }
  1181.  
  1182.  
  1183. unsigned long mh_msgdate (stream,msgno)
  1184.     MAILSTREAM *stream;
  1185.     long msgno;
  1186. {
  1187.   struct stat sbuf;
  1188.   struct tm *tm;
  1189.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1190.   if (!elt->day) {        /* get date if don't have it yet */
  1191.                 /* build message file name */
  1192.     sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,LOCAL->number[msgno - 1]);
  1193.     stat (LOCAL->buf,&sbuf);    /* get message date */
  1194.     tm = gmtime (&sbuf.st_mtime);
  1195.     elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  1196.     elt->year = tm->tm_year + 1900 - BASEYEAR;
  1197.     elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  1198.     elt->seconds = tm->tm_sec;
  1199.     elt->zhours = 0; elt->zminutes = 0;
  1200.   }
  1201.   return (long) (elt->year << 9) + (elt->month << 5) + elt->day;
  1202. }
  1203.  
  1204. char mh_search_body (stream,msgno,d,n)
  1205.     MAILSTREAM *stream;
  1206.     long msgno;
  1207.     char *d;
  1208.     long n;
  1209. {
  1210.   long i = msgno - 1;
  1211.   mh_fetchheader (stream,msgno);
  1212.   return LOCAL->body[i] ?
  1213.     search (LOCAL->body[i],strlen (LOCAL->body[i]),d,n) : NIL;
  1214. }
  1215.  
  1216.  
  1217. char mh_search_subject (stream,msgno,d,n)
  1218.     MAILSTREAM *stream;
  1219.     long msgno;
  1220.     char *d;
  1221.     long n;
  1222. {
  1223.   char *t = mh_fetchstructure (stream,msgno,NIL)->subject;
  1224.   return t ? search (t,strlen (t),d,n) : NIL;
  1225. }
  1226.  
  1227.  
  1228. char mh_search_text (stream,msgno,d,n)
  1229.     MAILSTREAM *stream;
  1230.     long msgno;
  1231.     char *d;
  1232.     long n;
  1233. {
  1234.   char *t = mh_fetchheader (stream,msgno);
  1235.   return (t && search (t,strlen (t),d,n)) ||
  1236.     mh_search_body (stream,msgno,d,n);
  1237. }
  1238.  
  1239. char mh_search_bcc (stream,msgno,d,n)
  1240.     MAILSTREAM *stream;
  1241.     long msgno;
  1242.     char *d;
  1243.     long n;
  1244. {
  1245.   ADDRESS *a = mh_fetchstructure (stream,msgno,NIL)->bcc;
  1246.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1247.                 /* get text for address */
  1248.   rfc822_write_address (LOCAL->buf,a);
  1249.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  1250. }
  1251.  
  1252.  
  1253. char mh_search_cc (stream,msgno,d,n)
  1254.     MAILSTREAM *stream;
  1255.     long msgno;
  1256.     char *d;
  1257.     long n;
  1258. {
  1259.   ADDRESS *a = mh_fetchstructure (stream,msgno,NIL)->cc;
  1260.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1261.                 /* get text for address */
  1262.   rfc822_write_address (LOCAL->buf,a);
  1263.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  1264. }
  1265.  
  1266.  
  1267. char mh_search_from (stream,msgno,d,n)
  1268.     MAILSTREAM *stream;
  1269.     long msgno;
  1270.     char *d;
  1271.     long n;
  1272. {
  1273.   ADDRESS *a = mh_fetchstructure (stream,msgno,NIL)->from;
  1274.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1275.                 /* get text for address */
  1276.   rfc822_write_address (LOCAL->buf,a);
  1277.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  1278. }
  1279.  
  1280.  
  1281. char mh_search_to (stream,msgno,d,n)
  1282.     MAILSTREAM *stream;
  1283.     long msgno;
  1284.     char *d;
  1285.     long n;
  1286. {
  1287.   ADDRESS *a = mh_fetchstructure (stream,msgno,NIL)->to;
  1288.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1289.                 /* get text for address */
  1290.   rfc822_write_address (LOCAL->buf,a);
  1291.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  1292. }
  1293.  
  1294. /* Search parsers */
  1295.  
  1296.  
  1297. /* Parse a date
  1298.  * Accepts: function to return
  1299.  *        pointer to date integer to return
  1300.  * Returns: function to return
  1301.  */
  1302.  
  1303. search_t mh_search_date (f,n)
  1304.     search_t f;
  1305.     long *n;
  1306. {
  1307.   long i;
  1308.   char *s;
  1309.   MESSAGECACHE elt;
  1310.                 /* parse the date and return fn if OK */
  1311.   return (mh_search_string (f,&s,&i) && mail_parse_date (&elt,s) &&
  1312.       (*n = (elt.year << 9) + (elt.month << 5) + elt.day)) ? f : NIL;
  1313. }
  1314.  
  1315. /* Parse a flag
  1316.  * Accepts: function to return
  1317.  *        pointer to string to return
  1318.  * Returns: function to return
  1319.  */
  1320.  
  1321. search_t mh_search_flag (f,d)
  1322.     search_t f;
  1323.     char **d;
  1324. {
  1325.                 /* get a keyword, return if OK */
  1326.   return (*d = strtok (NIL," ")) ? f : NIL;
  1327. }
  1328.  
  1329.  
  1330. /* Parse a string
  1331.  * Accepts: function to return
  1332.  *        pointer to string to return
  1333.  *        pointer to string length to return
  1334.  * Returns: function to return
  1335.  */
  1336.  
  1337. search_t mh_search_string (f,d,n)
  1338.     search_t f;
  1339.     char **d;
  1340.     long *n;
  1341. {
  1342.   char *c = strtok (NIL,"");    /* remainder of criteria */
  1343.   if (c) {            /* better be an argument */
  1344.     switch (*c) {        /* see what the argument is */
  1345.     case '\0':            /* catch bogons */
  1346.     case ' ':
  1347.       return NIL;
  1348.     case '"':            /* quoted string */
  1349.       if (!(strchr (c+1,'"') && (*d = strtok (c,"\"")) && (*n = strlen (*d))))
  1350.     return NIL;
  1351.       break;
  1352.     case '{':            /* literal string */
  1353.       *n = strtol (c+1,&c,10);    /* get its length */
  1354.       if (*c++ != '}' || *c++ != '\015' || *c++ != '\012' ||
  1355.       *n > strlen (*d = c)) return NIL;
  1356.       c[*n] = '\255';        /* write new delimiter */
  1357.       strtok (c,"\255");    /* reset the strtok mechanism */
  1358.       break;
  1359.     default:            /* atomic string */
  1360.       *n = strlen (*d = strtok (c," "));
  1361.       break;
  1362.     }
  1363.     return f;
  1364.   }
  1365.   else return NIL;
  1366. }
  1367.