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

  1. /*
  2.  * Program:    Netnews 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:    4 September 1991
  13.  * Last Edited:    19 June 1993
  14.  *
  15.  * Copyright 1993 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 <netdb.h>
  39. #include <errno.h>
  40. extern int errno;        /* just in case */
  41. #include <sys/types.h>
  42. #include "mail.h"
  43. #include "osdep.h"
  44. #include <sys/file.h>
  45. #include <sys/stat.h>
  46. #include <sys/time.h>
  47. #include "news.h"
  48. #include "rfc822.h"
  49. #include "misc.h"
  50.  
  51. /* Netnews mail routines */
  52.  
  53.  
  54. /* Driver dispatch used by MAIL */
  55.  
  56. DRIVER newsdriver = {
  57.   "news",            /* driver name */
  58.   (DRIVER *) NIL,        /* next driver */
  59.   news_valid,            /* mailbox is valid for us */
  60.   news_parameters,        /* manipulate parameters */
  61.   news_find,            /* find mailboxes */
  62.   news_find_bboards,        /* find bboards */
  63.   news_find_all,        /* find all mailboxes */
  64.   news_find_all_bboards,    /* find all bboards */
  65.   news_subscribe,        /* subscribe to mailbox */
  66.   news_unsubscribe,        /* unsubscribe from mailbox */
  67.   news_subscribe_bboard,    /* subscribe to bboard */
  68.   news_unsubscribe_bboard,    /* unsubscribe from bboard */
  69.   news_create,            /* create mailbox */
  70.   news_delete,            /* delete mailbox */
  71.   news_rename,            /* rename mailbox */
  72.   news_open,            /* open mailbox */
  73.   news_close,            /* close mailbox */
  74.   news_fetchfast,        /* fetch message "fast" attributes */
  75.   news_fetchflags,        /* fetch message flags */
  76.   news_fetchstructure,        /* fetch message envelopes */
  77.   news_fetchheader,        /* fetch message header only */
  78.   news_fetchtext,        /* fetch message body only */
  79.   news_fetchbody,        /* fetch message body section */
  80.   news_setflag,            /* set message flag */
  81.   news_clearflag,        /* clear message flag */
  82.   news_search,            /* search for message based on criteria */
  83.   news_ping,            /* ping mailbox to see if still alive */
  84.   news_check,            /* check for new messages */
  85.   news_expunge,            /* expunge deleted messages */
  86.   news_copy,            /* copy messages to another mailbox */
  87.   news_move,            /* move messages to another mailbox */
  88.   news_append,            /* append string message to mailbox */
  89.   news_gc            /* garbage collect stream */
  90. };
  91.  
  92.                 /* prototype stream */
  93. MAILSTREAM newsproto = {&newsdriver};
  94.  
  95. /* Netnews mail validate mailbox
  96.  * Accepts: mailbox name
  97.  * Returns: our driver if name is valid, NIL otherwise
  98.  */
  99.  
  100. DRIVER *news_valid (name)
  101.     char *name;
  102. {
  103.   int fd;
  104.   char *s,*t,*u;
  105.   char tmp[MAILTMPLEN];
  106.   struct stat sbuf;
  107.   DRIVER *ret = NIL;
  108.                 /* looks plausible and news installed? */
  109.   if (name && (*name == '*') && !(strchr (name,'/')) && !stat (NEWSSPOOL,&sbuf)
  110.       && ((fd = open (ACTIVEFILE,O_RDONLY,NIL)) >= 0)) {
  111.     fstat (fd,&sbuf);        /* get size of active file */
  112.                 /* slurp in active file */
  113.     read (fd,t = s = (char *) fs_get (sbuf.st_size+1),sbuf.st_size);
  114.     s[sbuf.st_size] = '\0';    /* tie off file */
  115.     close (fd);            /* flush file */
  116.     lcase (strcpy (tmp,name+1));/* make sure compare with lower case */
  117.     while (*t && (u = strchr (t,' '))) {
  118.       *u++ = '\0';        /* tie off at end of name */
  119.       if (!strcmp (tmp,t)) {    /* name matches? */
  120.     ret = &newsdriver;    /* seems to be a valid name */
  121.     break;
  122.       }
  123.       t = 1 + strchr (u,'\n');    /* next line */
  124.     }
  125.     fs_give ((void **) &s);    /* flush data */
  126.  }
  127.   return ret;            /* return status */
  128. }
  129.  
  130.  
  131. /* News manipulate driver parameters
  132.  * Accepts: function code
  133.  *        function-dependent value
  134.  * Returns: function-dependent return value
  135.  */
  136.  
  137. void *news_parameters (function,value)
  138.     long function;
  139.     void *value;
  140. {
  141.   fatal ("Invalid news_parameters function");
  142.   return NIL;
  143. }
  144.  
  145. /* Netnews mail find list of mailboxes
  146.  * Accepts: mail stream
  147.  *        pattern to search
  148.  */
  149.  
  150. void news_find (stream,pat)
  151.     MAILSTREAM *stream;
  152.     char *pat;
  153. {
  154.   /* Always a no-op */
  155. }
  156.  
  157.  
  158. /* Netnews mail find list of bboards
  159.  * Accepts: mail stream
  160.  *        pattern to search
  161.  */
  162.  
  163. void news_find_bboards (stream,pat)
  164.     MAILSTREAM *stream;
  165.     char *pat;
  166. {
  167.   void *s = NIL;
  168.   char *t,*u,tmp[MAILTMPLEN],patx[MAILTMPLEN];
  169.   struct stat sbuf;
  170.   lcase (strcpy (patx,pat));    /* make sure compare with lower case */
  171.                 /* make sure have a local news spool */
  172.   if (!(stat (NEWSSPOOL,&sbuf) || stat (ACTIVEFILE,&sbuf) ||
  173.     (stream && stream->anonymous))) {
  174.                 /* check all newsgroups from .newsrc */
  175.     while (t = news_read (&s)) if (u = strchr (t,':')) {
  176.       *u = '\0';        /* tie off at end of name */
  177.       if (pmatch (lcase (t),patx)) {
  178.     sprintf (tmp,"*%s",t);
  179.     if (news_valid (tmp)) mm_bboard (t);
  180.       }
  181.     }
  182.   }
  183.   while (t = sm_read (&s))    /* get data from subscription manager */
  184.     if ((*t == '*') && pmatch (t+1,pat)) mm_bboard (t+1);
  185. }
  186.  
  187. /* Netnews mail find list of all mailboxes
  188.  * Accepts: mail stream
  189.  *        pattern to search
  190.  */
  191.  
  192. void news_find_all (stream,pat)
  193.     MAILSTREAM *stream;
  194.     char *pat;
  195. {
  196.   /* Always a no-op */
  197. }
  198.  
  199.  
  200. /* Netnews mail find list of all bboards
  201.  * Accepts: mail stream
  202.  *        pattern to search
  203.  */
  204.  
  205. void news_find_all_bboards (stream,pat)
  206.     MAILSTREAM *stream;
  207.     char *pat;
  208. {
  209.   int fd;
  210.   char patx[MAILTMPLEN];
  211.   char *s,*t,*u;
  212.   struct stat sbuf;
  213.   lcase (strcpy (patx,pat));    /* make sure compare with lower case */
  214.   if ((!stat (NEWSSPOOL,&sbuf)) &&
  215.       ((fd = open (ACTIVEFILE,O_RDONLY,NIL)) >= 0)) {
  216.     fstat (fd,&sbuf);        /* get file size and read data */
  217.     read (fd,s = (char *) fs_get (sbuf.st_size + 1),sbuf.st_size);
  218.     close (fd);            /* close file */
  219.     s[sbuf.st_size] = '\0';    /* tie off string */
  220.     if (t = strtok (s,"\n")) do if (u = strchr (t,' ')) {
  221.       *u = '\0';        /* tie off at end of name */
  222.       if (pmatch (lcase (t),patx)) mm_bboard (t);
  223.     } while (t = strtok (NIL,"\n"));
  224.     fs_give ((void **) &s);
  225.   }
  226. }
  227.  
  228. /* Netnews mail subscribe to mailbox
  229.  * Accepts: mail stream
  230.  *        mailbox to add to subscription list
  231.  * Returns: T on success, NIL on failure
  232.  */
  233.  
  234. long news_subscribe (stream,mailbox)
  235.     MAILSTREAM *stream;
  236.     char *mailbox;
  237. {
  238.   return NIL;            /* never valid for netnews */
  239. }
  240.  
  241.  
  242. /* Netnews mail unsubscribe to mailbox
  243.  * Accepts: mail stream
  244.  *        mailbox to delete from subscription list
  245.  * Returns: T on success, NIL on failure
  246.  */
  247.  
  248. long news_unsubscribe (stream,mailbox)
  249.     MAILSTREAM *stream;
  250.     char *mailbox;
  251. {
  252.   return NIL;            /* never valid for netnews */
  253. }
  254.  
  255. /* Netnews mail subscribe to bboard
  256.  * Accepts: mail stream
  257.  *        bboard to add to subscription list
  258.  * Returns: T on success, NIL on failure
  259.  */
  260.  
  261. long news_subscribe_bboard (stream,mailbox)
  262.     MAILSTREAM *stream;
  263.     char *mailbox;
  264. {
  265.   int fd,fdo;
  266.   char *s,*txt;
  267.   char tmp[MAILTMPLEN];
  268.   struct stat sbuf;
  269.   long ret = NIL;
  270.                 /* open .newsrc file */
  271.   if ((fd = open (NEWSRC,O_RDWR|O_CREAT,0600)) < 0) {
  272.     mm_log ("Can't update news state",ERROR);
  273.     return NIL;
  274.   }
  275.   flock (fd,LOCK_EX);        /* wait for exclusive access */
  276.   fstat (fd,&sbuf);        /* get size of data */
  277.   read (fd,1 + (txt = (char *) fs_get (sbuf.st_size + 2)),sbuf.st_size);
  278.   txt[0] = '\n';        /* make searches easier */
  279.   txt[sbuf.st_size + 1] = '\0';    /* tie off string */
  280.                 /* make backup file */
  281.   strcat (strcpy (tmp,myhomedir ()),"/.oldnewsrc");
  282.   if ((fdo = open (tmp,O_WRONLY|O_CREAT,0600)) >= 0) {
  283.     write (fdo,txt + 1,sbuf.st_size);
  284.     close (fdo);
  285.   }
  286.   sprintf (tmp,"\n%s:",mailbox);/* see if already subscribed */
  287.   if (strstr (txt,tmp)) {    /* well? */
  288.     sprintf (tmp,"Already subscribed to newsgroup %s",mailbox);
  289.     mm_log (tmp,ERROR);
  290.   }
  291.   else {
  292.     sprintf (tmp,"\n%s!",mailbox);
  293.     if (s = strstr (txt,tmp)) {    /* if unsubscribed, just change one byte */
  294.       lseek (fd,strchr (s,'!') - (txt + 1),L_SET);
  295.       write (fd,":",1);        /* now subscribed */
  296.     }
  297.     else {            /* write new entry at end of file */
  298.       lseek (fd,sbuf.st_size,L_SET);
  299.       sprintf (tmp,"%s:\n",mailbox);
  300.       write (fd,tmp,strlen (tmp));
  301.     }
  302.     fsync (fd);            /* drop changes */
  303.     ret = T;
  304.   }
  305.   flock (fd,LOCK_UN);        /* release lock */
  306.   close (fd);            /* close off file */
  307.   fs_give ((void **) &txt);    /* free database */
  308.   return ret;
  309. }
  310.  
  311. /* Netnews mail unsubscribe to bboard
  312.  * Accepts: mail stream
  313.  *        bboard to delete from subscription list
  314.  * Returns: T on success, NIL on failure
  315.  */
  316.  
  317. long news_unsubscribe_bboard (stream,mailbox)
  318.     MAILSTREAM *stream;
  319.     char *mailbox;
  320. {
  321.   int fd,fdo;
  322.   char *s,*txt;
  323.   char tmp[MAILTMPLEN];
  324.   struct stat sbuf;
  325.   long ret = NIL;
  326.                 /* open .newsrc file */
  327.   if ((fd = open (NEWSRC,O_RDWR|O_CREAT,0600)) < 0) {
  328.     mm_log ("Can't update news state",ERROR);
  329.     return NIL;
  330.   }
  331.   flock (fd,LOCK_EX);        /* wait for exclusive access */
  332.   fstat (fd,&sbuf);        /* get size of data */
  333.   read (fd,1 + (txt = (char *) fs_get (sbuf.st_size + 2)),sbuf.st_size);
  334.   txt[0] = '\n';        /* make searches easier */
  335.   txt[sbuf.st_size + 1] = '\0';    /* tie off string */
  336.                 /* make backup file */
  337.   strcat (strcpy (tmp,myhomedir ()),"/.oldnewsrc");
  338.   if ((fdo = open (tmp,O_WRONLY|O_CREAT,0600)) >= 0) {
  339.     write (fdo,txt + 1,sbuf.st_size);
  340.     close (fdo);
  341.   }
  342.   sprintf (tmp,"\n%s:",mailbox);/* see if subscribed */
  343.   if (!(s = strstr (txt,tmp))) {/* well? */
  344.     sprintf (tmp,"Not subscribed to newsgroup %s",mailbox);
  345.     mm_log (tmp,ERROR);
  346.   }
  347.   else {            /* unsubscribe */
  348.     lseek (fd,strchr (s,':') - (txt + 1),L_SET);
  349.     write (fd,"!",1);        /* now unsubscribed */
  350.     fsync (fd);            /* drop changes */
  351.     ret = T;
  352.   }
  353.   flock (fd,LOCK_UN);        /* release lock */
  354.   close (fd);            /* close off file */
  355.   fs_give ((void **) &txt);    /* free database */
  356.   return ret;
  357. }
  358.  
  359. /* Netnews mail create mailbox
  360.  * Accepts: mail stream
  361.  *        mailbox name to create
  362.  * Returns: T on success, NIL on failure
  363.  */
  364.  
  365. long news_create (stream,mailbox)
  366.     MAILSTREAM *stream;
  367.     char *mailbox;
  368. {
  369.   return NIL;            /* never valid for netnews */
  370. }
  371.  
  372.  
  373. /* Netnews mail delete mailbox
  374.  *        mailbox name to delete
  375.  * Returns: T on success, NIL on failure
  376.  */
  377.  
  378. long news_delete (stream,mailbox)
  379.     MAILSTREAM *stream;
  380.     char *mailbox;
  381. {
  382.   return NIL;            /* never valid for netnews */
  383. }
  384.  
  385.  
  386. /* Netnews mail rename mailbox
  387.  * Accepts: mail stream
  388.  *        old mailbox name
  389.  *        new mailbox name
  390.  * Returns: T on success, NIL on failure
  391.  */
  392.  
  393. long news_rename (stream,old,new)
  394.     MAILSTREAM *stream;
  395.     char *old;
  396.     char *new;
  397. {
  398.   return NIL;            /* never valid for netnews */
  399. }
  400.  
  401. /* Netnews mail open
  402.  * Accepts: stream to open
  403.  * Returns: stream on success, NIL on failure
  404.  */
  405.  
  406. MAILSTREAM *news_open (stream)
  407.     MAILSTREAM *stream;
  408. {
  409.   long i,nmsgs,j,k;
  410.   long recent = 0,unseen = 0;
  411.   char c = NIL,*s,*t,tmp[MAILTMPLEN];
  412.   void *sdb = NIL;
  413.   struct hostent *host_name;
  414.   struct direct **names;
  415.                   /* return prototype for OP_PROTOTYPE call */
  416.   if (!stream) return &newsproto;
  417.   if (LOCAL) {            /* close old file if stream being recycled */
  418.     news_close (stream);    /* dump and save the changes */
  419.     stream->dtb = &newsdriver;    /* reattach this driver */
  420.     mail_free_cache (stream);    /* clean up cache */
  421.   }
  422.   lcase (stream->mailbox);    /* build news directory name */
  423.   sprintf (s = tmp,"%s/%s",NEWSSPOOL,stream->mailbox + 1);
  424.   while (s = strchr (s,'.')) *s = '/';
  425.   if (!lhostn) {        /* have local host yet? */
  426.     gethostname(tmp,MAILTMPLEN);/* get local host name */
  427.     lhostn = cpystr ((host_name = gethostbyname (tmp)) ?
  428.               host_name->h_name : tmp);
  429.   }
  430.                 /* scan directory */
  431.   if ((nmsgs = scandir (tmp,&names,news_select,news_numsort)) >= 0) {
  432.     stream->local = fs_get (sizeof (NEWSLOCAL));
  433.     LOCAL->dirty = NIL;        /* no update to .newsrc needed yet */
  434.     LOCAL->dir = cpystr (tmp);    /* copy directory name for later */
  435.     LOCAL->name = cpystr (stream->mailbox + 1);
  436.                 /* create cache */
  437.     LOCAL->number = (unsigned long *) fs_get (nmsgs * sizeof (unsigned long));
  438.     LOCAL->header = (char **) fs_get (nmsgs * sizeof (char *));
  439.     LOCAL->body = (char **) fs_get (nmsgs * sizeof (char *));
  440.     LOCAL->seen = (char *) fs_get (nmsgs * sizeof (char));
  441.     for (i = 0; i<nmsgs; ++i) {    /* initialize cache */
  442.       LOCAL->number[i] = atoi (names[i]->d_name);
  443.       fs_give ((void **) &names[i]);
  444.       LOCAL->header[i] = LOCAL->body[i] = NIL;
  445.       LOCAL->seen[i] = NIL;
  446.     }
  447.     fs_give ((void **) &names);    /* free directory */
  448.                 /* make temporary buffer */
  449.     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = MAXMESSAGESIZE) + 1);
  450.     stream->sequence++;        /* bump sequence number */
  451.     stream->readonly = T;    /* make sure higher level knows readonly */
  452.     mail_exists (stream,nmsgs);    /* notify upper level that messages exist */
  453.  
  454.     i = 0;            /* nothing scanned yet */
  455.     if (!stream->anonymous) {    /* not if anonymous you don't */
  456.       while ((t = news_read (&sdb)) && (s = strpbrk (t,":!")) && (c = *s)) {
  457.     *s++ = '\0';        /* tie off newsgroup name, point to data */
  458.     if (strcmp (t,LOCAL->name)) s = NIL;
  459.     else break;        /* found it! */
  460.       }
  461.       if (s) {            /* newsgroup found? */
  462.     if (c == '!') mm_log ("Not subscribed to that newsgroup",WARN);
  463.     while (*s && i < nmsgs){/* process until run out of messages or list */
  464.       j = strtol (s,&s,10);    /* start of possible range */
  465.                 /* other end of range */
  466.       k = (*s == '-') ? strtol (++s,&s,10) : j;
  467.                 /* skip messages before this range */
  468.       while ((LOCAL->number[i] < j) && (i < nmsgs)) {
  469.         if (!unseen) unseen = i + 1;
  470.         LOCAL->seen[i++] = NIL;
  471.       }
  472.       while ((LOCAL->number[i] >= j) && (LOCAL->number[i] <= k) &&
  473.          (i < nmsgs)) {    /* mark messages within the range as seen */
  474.         LOCAL->seen[i++] = T;
  475.         mail_elt (stream,i)->deleted = T;
  476.       }
  477.       if (*s == ',') s++;    /* skip past comma */
  478.       else if (*s) {    /* better not be anything else then */
  479.         mm_log ("Bogus syntax in news state file",ERROR);
  480.         break;        /* give up fast!! */
  481.       }
  482.     }
  483.       }
  484.       else mm_log ("No state for newsgroup found, reading as new",WARN);
  485.       if (t) fs_give (&sdb);    /* free up database if necessary */
  486.     }
  487.     if (unseen) {        /* report first unseen message */
  488.       sprintf (tmp,"[UNSEEN] %ld is first unseen message",unseen);
  489.       mm_notify (stream,tmp,(long) NIL);
  490.     }
  491.     while (i < nmsgs) {        /* mark all remaining messages as new */
  492.       LOCAL->seen[i++] = NIL;
  493.       mail_elt (stream,i)->recent = T;
  494.       ++recent;            /* count another recent message */
  495.     }
  496.     mail_recent (stream,recent);/* notify upper level about recent */
  497.                 /* notify if empty bboard */
  498.     if (!(stream->nmsgs || stream->silent)) mm_log ("Newsgroup is empty",WARN);
  499.   }
  500.   return LOCAL ? stream : NIL;    /* if stream is alive, return to caller */
  501. }
  502.  
  503. /* Netnews file name selection test
  504.  * Accepts: candidate directory entry
  505.  * Returns: T to use file name, NIL to skip it
  506.  */
  507.  
  508. int news_select (name)
  509.     struct direct *name;
  510. {
  511.   char c;
  512.   char *s = name->d_name;
  513.   while (c = *s++) if (!isdigit (c)) return NIL;
  514.   return T;
  515. }
  516.  
  517.  
  518. /* Netnews file name comparision
  519.  * Accepts: first candidate directory entry
  520.  *        second candidate directory entry
  521.  * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2
  522.  */
  523.  
  524. int news_numsort (d1,d2)
  525.     struct direct **d1;
  526.     struct direct **d2;
  527. {
  528.   return (atoi ((*d1)->d_name) - atoi ((*d2)->d_name));
  529. }
  530.  
  531.  
  532. /* Netnews mail close
  533.  * Accepts: MAIL stream
  534.  */
  535.  
  536. void news_close (stream)
  537.     MAILSTREAM *stream;
  538. {
  539.   if (LOCAL) {            /* only if a file is open */
  540.     news_check (stream);    /* dump final checkpoint */
  541.     if (LOCAL->dir) fs_give ((void **) &LOCAL->dir);
  542.     if (LOCAL->name) fs_give ((void **) &LOCAL->name);
  543.     news_gc (stream,GC_TEXTS);    /* free local cache */
  544.     fs_give ((void **) &LOCAL->number);
  545.     fs_give ((void **) &LOCAL->header);
  546.     fs_give ((void **) &LOCAL->body);
  547.     fs_give ((void **) &LOCAL->seen);
  548.                 /* free local scratch buffer */
  549.     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
  550.                 /* nuke the local data */
  551.     fs_give ((void **) &stream->local);
  552.     stream->dtb = NIL;        /* log out the DTB */
  553.   }
  554. }
  555.  
  556. /* Netnews mail fetch fast information
  557.  * Accepts: MAIL stream
  558.  *        sequence
  559.  */
  560.  
  561. void news_fetchfast (stream,sequence)
  562.     MAILSTREAM *stream;
  563.     char *sequence;
  564. {
  565.   return;            /* no-op for local mail */
  566. }
  567.  
  568.  
  569. /* Netnews mail fetch flags
  570.  * Accepts: MAIL stream
  571.  *        sequence
  572.  */
  573.  
  574. void news_fetchflags (stream,sequence)
  575.     MAILSTREAM *stream;
  576.     char *sequence;
  577. {
  578.   return;            /* no-op for local mail */
  579. }
  580.  
  581.  
  582. /* Netnews mail fetch envelope
  583.  * Accepts: MAIL stream
  584.  *        message # to fetch
  585.  *        pointer to return body
  586.  * Returns: envelope of this message, body returned in body value
  587.  *
  588.  * Fetches the "fast" information as well
  589.  */
  590.  
  591. ENVELOPE *news_fetchstructure (stream,msgno,body)
  592.     MAILSTREAM *stream;
  593.     long msgno;
  594.     BODY **body;
  595. {
  596.   char *h,*t;
  597.   LONGCACHE *lelt;
  598.   ENVELOPE **env;
  599.   STRING bs;
  600.   BODY **b;
  601.   if (stream->scache) {        /* short cache */
  602.     if (msgno != stream->msgno){/* flush old poop if a different message */
  603.       mail_free_envelope (&stream->env);
  604.       mail_free_body (&stream->body);
  605.     }
  606.     stream->msgno = msgno;
  607.     env = &stream->env;        /* get pointers to envelope and body */
  608.     b = &stream->body;
  609.   }
  610.   else {            /* long cache */
  611.     lelt = mail_lelt (stream,msgno);
  612.     env = &lelt->env;        /* get pointers to envelope and body */
  613.     b = &lelt->body;
  614.   }
  615.   if ((body && !*b) || !*env) {    /* have the poop we need? */
  616.     mail_free_envelope (env);    /* flush old envelope and body */
  617.     mail_free_body (b);
  618.     h = news_fetchheader (stream,msgno);
  619.                 /* can't use fetchtext since it'll set seen */
  620.     t = LOCAL->body[msgno - 1] ? LOCAL->body[msgno - 1] : "";
  621.     INIT (&bs,mail_string,(void *) t,strlen (t));
  622.                 /* parse envelope and body */
  623.     rfc822_parse_msg (env,body ? b : NIL,h,strlen (h),&bs,lhostn,LOCAL->buf);
  624.   }
  625.   if (body) *body = *b;        /* return the body */
  626.   return *env;            /* return the envelope */
  627. }
  628.  
  629. /* Netnews mail fetch message header
  630.  * Accepts: MAIL stream
  631.  *        message # to fetch
  632.  * Returns: message header in RFC822 format
  633.  */
  634.  
  635. char *news_fetchheader (stream,msgno)
  636.     MAILSTREAM *stream;
  637.     long msgno;
  638. {
  639.   unsigned long i,j;
  640.   int fd;
  641.   char *s,*b,*t;
  642.   long m = msgno - 1;
  643.   long lst = NIL;
  644.   struct stat sbuf;
  645.   struct tm *tm;
  646.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  647.                 /* build message file name */
  648.   sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,LOCAL->number[m]);
  649.   if (!LOCAL->header[m] && ((fd = open (LOCAL->buf,O_RDONLY,NIL)) >= 0)) {
  650.     fstat (fd,&sbuf);        /* get size of message */
  651.                 /* make plausible IMAPish date string */
  652.     tm = gmtime (&sbuf.st_mtime);
  653.     elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  654.     elt->year = tm->tm_year + 1900 - BASEYEAR;
  655.     elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  656.     elt->seconds = tm->tm_sec;
  657.     elt->zhours = 0; elt->zminutes = 0;
  658.                 /* slurp message */
  659.     read (fd,s = (char *) fs_get (sbuf.st_size +1),sbuf.st_size);
  660.     s[sbuf.st_size] = '\0';    /* tie off file */
  661.     close (fd);            /* flush message file */
  662.                 /* find end of header and count lines */
  663.     for (i=1,b=s; *b && !(lst && (*b == '\n'));) if (lst = (*b++ == '\n')) i++;
  664.                 /* copy header in CRLF form */
  665.     LOCAL->header[m] = (char *) fs_get (i += (j = b - s));
  666.     elt->rfc822_size = i - 1;    /* size of message header */
  667.     strcrlfcpy (&LOCAL->header[m],&i,s,j);
  668.                 /* copy body in CRLF form */
  669.     for (i = 1,t = b; *t;) if (*t++ == '\n') i++;
  670.     LOCAL->body[m] = (char *) fs_get (i += (j = t - b));
  671.     elt->rfc822_size += i - 1;    /* size of entire message */
  672.     strcrlfcpy (&LOCAL->body[m],&i,b,j);
  673.     fs_give ((void **) &s);    /* flush old data */
  674.   }
  675.   return LOCAL->header[m] ? LOCAL->header[m] : "";
  676. }
  677.  
  678. /* Netnews mail fetch message text (only)
  679.     body only;
  680.  * Accepts: MAIL stream
  681.  *        message # to fetch
  682.  * Returns: message text in RFC822 format
  683.  */
  684.  
  685. char *news_fetchtext (stream,msgno)
  686.     MAILSTREAM *stream;
  687.     long msgno;
  688. {
  689.   long i = msgno - 1;
  690.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  691.                 /* snarf message in case don't have it yet */
  692.   news_fetchheader (stream,msgno);
  693.   elt->seen = T;        /* mark as seen */
  694.   return LOCAL->body[i] ? LOCAL->body[i] : "";
  695. }
  696.  
  697. /* Netnews fetch message body as a structure
  698.  * Accepts: Mail stream
  699.  *        message # to fetch
  700.  *        section specifier
  701.  *        pointer to length
  702.  * Returns: pointer to section of message body
  703.  */
  704.  
  705. char *news_fetchbody (stream,m,s,len)
  706.     MAILSTREAM *stream;
  707.     long m;
  708.     char *s;
  709.     unsigned long *len;
  710. {
  711.   BODY *b;
  712.   PART *pt;
  713.   unsigned long i;
  714.   char *base;
  715.   unsigned long offset = 0;
  716.   MESSAGECACHE *elt = mail_elt (stream,m);
  717.                 /* make sure have a body */
  718.   if (!(news_fetchstructure (stream,m,&b) && b && s && *s &&
  719.     ((i = strtol (s,&s,10)) > 0) && (base = news_fetchtext (stream,m))))
  720.     return NIL;
  721.   do {                /* until find desired body part */
  722.                 /* multipart content? */
  723.     if (b->type == TYPEMULTIPART) {
  724.       pt = b->contents.part;    /* yes, find desired part */
  725.       while (--i && (pt = pt->next));
  726.       if (!pt) return NIL;    /* bad specifier */
  727.                 /* note new body, check valid nesting */
  728.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  729.       offset = pt->offset;    /* get new offset */
  730.     }
  731.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  732.                 /* need to go down further? */
  733.     if (i = *s) switch (b->type) {
  734.     case TYPEMESSAGE:        /* embedded message, calculate new base */
  735.       offset = b->contents.msg.offset;
  736.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  737.     case TYPEMULTIPART:        /* multipart, get next section */
  738.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  739.     default:            /* bogus subpart specification */
  740.       return NIL;
  741.     }
  742.   } while (i);
  743.                 /* lose if body bogus */
  744.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  745.   elt->seen = T;        /* mark as seen */
  746.   return rfc822_contents (&LOCAL->buf,&LOCAL->buflen,len,base + offset,
  747.               b->size.ibytes,b->encoding);
  748. }
  749.  
  750. /* Netnews mail set flag
  751.  * Accepts: MAIL stream
  752.  *        sequence
  753.  *        flag(s)
  754.  */
  755.  
  756. void news_setflag (stream,sequence,flag)
  757.     MAILSTREAM *stream;
  758.     char *sequence;
  759.     char *flag;
  760. {
  761.   MESSAGECACHE *elt;
  762.   long i;
  763.   short f = news_getflags (stream,flag);
  764.   if (!f) return;        /* no-op if no flags to modify */
  765.                 /* get sequence and loop on it */
  766.   if (mail_sequence (stream,sequence)) for (i = 0; i < stream->nmsgs; i++)
  767.     if ((elt = mail_elt (stream,i + 1))->sequence) {
  768.       if (f&fSEEN) elt->seen=T;    /* set all requested flags */
  769.       if (f&fDELETED) {        /* deletion also purges the cache */
  770.     elt->deleted = T;    /* mark deleted */
  771.     if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  772.     if (LOCAL->body[i]) fs_give ((void **) &LOCAL->body[i]);
  773.     if (!LOCAL->seen[i]) LOCAL->seen[i] = LOCAL->dirty = T;
  774.       }
  775.       if (f&fFLAGGED) elt->flagged = T;
  776.       if (f&fANSWERED) elt->answered = T;
  777.     }
  778. }
  779.  
  780.  
  781. /* Netnews mail clear flag
  782.  * Accepts: MAIL stream
  783.  *        sequence
  784.  *        flag(s)
  785.  */
  786.  
  787. void news_clearflag (stream,sequence,flag)
  788.     MAILSTREAM *stream;
  789.     char *sequence;
  790.     char *flag;
  791. {
  792.   MESSAGECACHE *elt;
  793.   long i;
  794.   short f = news_getflags (stream,flag);
  795.   if (!f) return;        /* no-op if no flags to modify */
  796.                 /* get sequence and loop on it */
  797.   if (mail_sequence (stream,sequence)) for (i = 0; i < stream->nmsgs; i++)
  798.     if ((elt = mail_elt (stream,i + 1))->sequence) {
  799.                 /* clear all requested flags */
  800.       if (f&fSEEN) elt->seen = NIL;
  801.       if (f&fDELETED) {
  802.     elt->deleted = NIL;    /* undelete */
  803.     if (LOCAL->seen[i]) {    /* if marked in newsrc */
  804.       LOCAL->seen[i] = NIL;    /* unmark it now */
  805.       LOCAL->dirty = T;    /* mark stream as dirty */
  806.     }
  807.       }
  808.       if (f&fFLAGGED) elt->flagged = NIL;
  809.       if (f&fANSWERED) elt->answered = NIL;
  810.     }
  811. }
  812.  
  813. /* Netnews mail search for messages
  814.  * Accepts: MAIL stream
  815.  *        search criteria
  816.  */
  817.  
  818. void news_search (stream,criteria)
  819.     MAILSTREAM *stream;
  820.     char *criteria;
  821. {
  822.   long i,n;
  823.   char *d;
  824.   search_t f;
  825.                 /* initially all searched */
  826.   for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = T;
  827.                 /* get first criterion */
  828.   if (criteria && (criteria = strtok (criteria," "))) {
  829.                 /* for each criterion */
  830.     for (; criteria; (criteria = strtok (NIL," "))) {
  831.       f = NIL; d = NIL; n = 0;    /* init then scan the criterion */
  832.       switch (*ucase (criteria)) {
  833.       case 'A':            /* possible ALL, ANSWERED */
  834.     if (!strcmp (criteria+1,"LL")) f = news_search_all;
  835.     else if (!strcmp (criteria+1,"NSWERED")) f = news_search_answered;
  836.     break;
  837.       case 'B':            /* possible BCC, BEFORE, BODY */
  838.     if (!strcmp (criteria+1,"CC"))
  839.       f = news_search_string (news_search_bcc,&d,&n);
  840.     else if (!strcmp (criteria+1,"EFORE"))
  841.       f = news_search_date (news_search_before,&n);
  842.     else if (!strcmp (criteria+1,"ODY"))
  843.       f = news_search_string (news_search_body,&d,&n);
  844.     break;
  845.       case 'C':            /* possible CC */
  846.     if (!strcmp (criteria+1,"C"))
  847.       f = news_search_string (news_search_cc,&d,&n);
  848.     break;
  849.       case 'D':            /* possible DELETED */
  850.     if (!strcmp (criteria+1,"ELETED")) f = news_search_deleted;
  851.     break;
  852.       case 'F':            /* possible FLAGGED, FROM */
  853.     if (!strcmp (criteria+1,"LAGGED")) f = news_search_flagged;
  854.     else if (!strcmp (criteria+1,"ROM"))
  855.       f = news_search_string (news_search_from,&d,&n);
  856.     break;
  857.       case 'K':            /* possible KEYWORD */
  858.     if (!strcmp (criteria+1,"EYWORD"))
  859.       f = news_search_flag (news_search_keyword,&d);
  860.     break;
  861.       case 'N':            /* possible NEW */
  862.     if (!strcmp (criteria+1,"EW")) f = news_search_new;
  863.     break;
  864.  
  865.       case 'O':            /* possible OLD, ON */
  866.     if (!strcmp (criteria+1,"LD")) f = news_search_old;
  867.     else if (!strcmp (criteria+1,"N"))
  868.       f = news_search_date (news_search_on,&n);
  869.     break;
  870.       case 'R':            /* possible RECENT */
  871.     if (!strcmp (criteria+1,"ECENT")) f = news_search_recent;
  872.     break;
  873.       case 'S':            /* possible SEEN, SINCE, SUBJECT */
  874.     if (!strcmp (criteria+1,"EEN")) f = news_search_seen;
  875.     else if (!strcmp (criteria+1,"INCE"))
  876.       f = news_search_date (news_search_since,&n);
  877.     else if (!strcmp (criteria+1,"UBJECT"))
  878.       f = news_search_string (news_search_subject,&d,&n);
  879.     break;
  880.       case 'T':            /* possible TEXT, TO */
  881.     if (!strcmp (criteria+1,"EXT"))
  882.       f = news_search_string (news_search_text,&d,&n);
  883.     else if (!strcmp (criteria+1,"O"))
  884.       f = news_search_string (news_search_to,&d,&n);
  885.     break;
  886.       case 'U':            /* possible UN* */
  887.     if (criteria[1] == 'N') {
  888.       if (!strcmp (criteria+2,"ANSWERED")) f = news_search_unanswered;
  889.       else if (!strcmp (criteria+2,"DELETED")) f = news_search_undeleted;
  890.       else if (!strcmp (criteria+2,"FLAGGED")) f = news_search_unflagged;
  891.       else if (!strcmp (criteria+2,"KEYWORD"))
  892.         f = news_search_flag (news_search_unkeyword,&d);
  893.       else if (!strcmp (criteria+2,"SEEN")) f = news_search_unseen;
  894.     }
  895.     break;
  896.       default:            /* we will barf below */
  897.     break;
  898.       }
  899.       if (!f) {            /* if can't determine any criteria */
  900.     sprintf (LOCAL->buf,"Unknown search criterion: %.80s",criteria);
  901.     mm_log (LOCAL->buf,ERROR);
  902.     return;
  903.       }
  904.                 /* run the search criterion */
  905.       for (i = 1; i <= stream->nmsgs; ++i)
  906.     if (mail_elt (stream,i)->searched && !(*f) (stream,i,d,n))
  907.       mail_elt (stream,i)->searched = NIL;
  908.     }
  909.                 /* report search results to main program */
  910.     for (i = 1; i <= stream->nmsgs; ++i)
  911.       if (mail_elt (stream,i)->searched) mail_searched (stream,i);
  912.   }
  913. }
  914.  
  915. /* Netnews mail ping mailbox
  916.  * Accepts: MAIL stream
  917.  * Returns: T if stream alive, else NIL
  918.  */
  919.  
  920. long news_ping (stream)
  921.     MAILSTREAM *stream;
  922. {
  923.   return T;            /* always alive */
  924. }
  925.  
  926.  
  927. /* Netnews mail check mailbox
  928.  * Accepts: MAIL stream
  929.  */
  930.  
  931. void news_check (stream)
  932.     MAILSTREAM *stream;
  933. {
  934.   int fd;
  935.   long i,j,k;
  936.   char *s;
  937.   char tmp[MAILTMPLEN];
  938.   struct stat sbuf;
  939.   struct iovec iov[3];
  940.                 /* never do if anonymous or no updates */
  941.   if (stream->anonymous || !LOCAL->dirty) return;
  942.   *LOCAL->buf = '\n';        /* header to make for easier searches */
  943.                 /* open .newsrc file */
  944.   if ((fd = open (NEWSRC,O_RDWR|O_CREAT,0600)) < 0) {
  945.     mm_log ("Can't update news state",ERROR);
  946.     return;
  947.   }
  948.   flock (fd,LOCK_EX);        /* wait for exclusive access */
  949.   fstat (fd,&sbuf);        /* get size of data */
  950.                 /* ensure enough room */
  951.   if (sbuf.st_size >= (LOCAL->buflen + 1)) {
  952.                 /* fs_resize does an unnecessary copy */
  953.     fs_give ((void **) &LOCAL->buf);
  954.     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = sbuf.st_size + 1) + 1);
  955.   }
  956.   *LOCAL->buf = '\n';        /* slurp the silly thing in */
  957.   read (fd,iov[0].iov_base = LOCAL->buf + 1,iov[0].iov_len = sbuf.st_size);
  958.                 /* tie off file */
  959.   LOCAL->buf[sbuf.st_size + 1] = '\0';
  960.                 /* make backup file */
  961.   strcat (strcpy (tmp,myhomedir ()),"/.oldnewsrc");
  962.   if ((i = open (tmp,O_WRONLY|O_CREAT,0600)) >= 0) {
  963.     write (i,LOCAL->buf + 1,sbuf.st_size);
  964.     close (i);
  965.   }
  966.                 /* find as subscribed newsgroup */
  967.   sprintf (tmp,"\n%s:",LOCAL->name);
  968.   if (s = strstr (LOCAL->buf,tmp)) s += strlen (tmp);
  969.   else {            /* find as unsubscribed newsgroup */
  970.     sprintf (tmp,"\n%s!",LOCAL->name);
  971.     if (s = strstr (LOCAL->buf,tmp)) s += strlen (tmp);
  972.   }
  973.   iov[2].iov_base = "";        /* dummy in case no third block */
  974.   iov[2].iov_len = 0;
  975.  
  976.   if (s) {            /* found existing, calculate prefix length */
  977.     iov[0].iov_len = s - (LOCAL->buf + 1);
  978.     if (s = strchr (s+1,'\n')) {/* find suffix */
  979.       iov[2].iov_base = ++s;    /* suffix base and length */
  980.       iov[2].iov_len = sbuf.st_size - (s - (LOCAL->buf + 1));
  981.     }
  982.     s = tmp;            /* pointer to dump sequence numbers */
  983.   }
  984.   else {            /* not found, append as unsubscribed group */
  985.     sprintf (tmp,"%s!",LOCAL->name);
  986.     s = tmp + strlen (tmp);    /* point to end of string */
  987.   }
  988.   *s++ = ' ';            /* leading space */
  989.   *s = '\0';            /* go through list */
  990.   for (i = 0,j = 1,k = 0; i < stream->nmsgs; ++i) {
  991.     if (LOCAL->seen[i]) {    /* seen message? */
  992.       k = LOCAL->number[i];    /* this is the top of the current range */
  993.       if (j == 0) j = k;    /* if no range in progress, start one */
  994.     }
  995.     else if (j != 0) {        /* unread message, ending a range */
  996.                 /* calculate end of range */
  997.       if (k = LOCAL->number[i] - 1) {
  998.                 /* dump range */
  999.     sprintf (s,(j == k) ? "%d," : "%d-%d,",j,k);
  1000.     s += strlen (s);    /* find end of string */
  1001.       }
  1002.       j = 0;            /* no more range in progress */
  1003.     }
  1004.   }
  1005.   if (j) {            /* dump trailing range */
  1006.     sprintf (s,(j == k) ? "%d" : "%d-%d",j,k);
  1007.     s += strlen (s);        /* find end of string */
  1008.   }
  1009.   else if (s[-1] == ',') s--;    /* prepare to patch out any trailing comma */
  1010.   *s++ = '\n';            /* trailing newline */
  1011.   iov[1].iov_base = tmp;    /* this group text */
  1012.   iov[1].iov_len = s - tmp;    /* length of the text */
  1013.   lseek (fd,0,L_SET);        /* go to beginning of file */
  1014.   writev (fd,iov,iov[2].iov_len ? 3 : 2);
  1015.   ftruncate (fd,iov[0].iov_len + iov[1].iov_len + iov[2].iov_len);
  1016.   flock (fd,LOCK_UN);        /* unlock the file */
  1017.   close (fd);            /* flush .newsrc file */
  1018. }
  1019.  
  1020.  
  1021. /* Netnews mail expunge mailbox
  1022.  * Accepts: MAIL stream
  1023.  */
  1024.  
  1025. void news_expunge (stream)
  1026.     MAILSTREAM *stream;
  1027. {
  1028.   if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
  1029. }
  1030.  
  1031. /* Netnews mail copy message(s)
  1032.     s;
  1033.  * Accepts: MAIL stream
  1034.  *        sequence
  1035.  *        destination mailbox
  1036.  * Returns: T if copy successful, else NIL
  1037.  */
  1038.  
  1039. long news_copy (stream,sequence,mailbox)
  1040.     MAILSTREAM *stream;
  1041.     char *sequence;
  1042.     char *mailbox;
  1043. {
  1044.   char tmp[MAILTMPLEN];
  1045.   char lock[MAILTMPLEN];
  1046.   struct iovec iov[3];
  1047.   struct stat ssbuf,dsbuf;
  1048.   char *t,*v;
  1049.   int sfd,dfd;
  1050.   long i;
  1051.   long r = NIL;
  1052.                 /* get sequence to do */
  1053.   if (stream->anonymous || !mail_sequence (stream,sequence)) return NIL;
  1054.                 /* get destination mailbox */
  1055.   if ((dfd = bezerk_lock (bezerk_file (tmp,mailbox),O_WRONLY|O_APPEND|O_CREAT,
  1056.               S_IREAD|S_IWRITE,lock,LOCK_EX)) < 0) {
  1057.     sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
  1058.     mm_log (LOCAL->buf,ERROR);
  1059.     return NIL;
  1060.   }
  1061.   mm_critical (stream);        /* go critical */
  1062.   fstat (dfd,&dsbuf);        /* get current file size */
  1063.   iov[2].iov_base = "\n\n";    /* constant trailer */
  1064.   iov[2].iov_len = 2;
  1065.  
  1066.                 /* write all requested messages to mailbox */
  1067.   for (i = 1; i <= stream->nmsgs; i++) if (mail_elt (stream,i)->sequence) {
  1068.                 /* build message file name */
  1069.     sprintf (tmp,"%s/%lu",LOCAL->dir,LOCAL->number[i - 1]);
  1070.     if ((sfd = open (tmp,O_RDONLY,NIL)) >= 0) {
  1071.       fstat (sfd,&ssbuf);    /* get size of message */
  1072.                 /* ensure enough room */
  1073.       if (ssbuf.st_size > LOCAL->buflen) {
  1074.                 /* fs_resize does an unnecessary copy */
  1075.     fs_give ((void **) &LOCAL->buf);
  1076.     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = ssbuf.st_size) + 1);
  1077.       }
  1078.                 /* slurp the silly thing in */
  1079.       read (sfd,iov[1].iov_base = LOCAL->buf,iov[1].iov_len = ssbuf.st_size);
  1080.                 /* tie off file */
  1081.       iov[1].iov_base[ssbuf.st_size] = '\0';
  1082.       close (sfd);        /* flush message file */
  1083.                 /* get Path: data */
  1084.       if ((((t = iov[1].iov_base - 1) && t[1] == 'P' && t[2] == 'a' &&
  1085.         t[3] == 't' && t[4] == 'h' && t[5] == ':' && t[6] == ' ') ||
  1086.        (t = strstr (iov[1].iov_base,"\nPath: "))) &&
  1087.       (v = strchr (t += 7,'\n')) && (r = v - t)) {
  1088.     strcpy (tmp,"From ");    /* start text */
  1089.     strncpy (v = tmp+5,t,r);/* copy that many characters */
  1090.     v[r++] = ' ';        /* delimiter */
  1091.     v[r] = '\0';        /* tie it off */
  1092.       }
  1093.       else strcpy (tmp,"From somebody ");
  1094.                 /* add the time and a newline */
  1095.       strcat (tmp,ctime (&ssbuf.st_mtime));
  1096.                 /* build rn-compatible header lines */
  1097.       sprintf (tmp + strlen (tmp),"Article %lu of %s\n",
  1098.            LOCAL->number[i - 1],LOCAL->name);
  1099.       iov[0].iov_len = strlen (iov[0].iov_base = tmp);
  1100.                 /* now do the write */
  1101.       if (r = (writev (dfd,iov,3) < 0)) {
  1102.     sprintf (LOCAL->buf,"Message copy %d failed: %s",i,strerror (errno));
  1103.     mm_log (LOCAL->buf,ERROR);
  1104.     ftruncate (dfd,dsbuf.st_size);
  1105.     break;            /* give up */
  1106.       }
  1107.     }
  1108.   }
  1109.   fsync (dfd);            /* force out the update */
  1110.   bezerk_unlock (dfd,NIL,lock);    /* unlock and close mailbox */
  1111.   mm_nocritical (stream);    /* release critical */
  1112.   return !r;            /* return whether or not succeeded */
  1113. }
  1114.  
  1115. /* Netnews mail move message(s)
  1116.     s;
  1117.  * Accepts: MAIL stream
  1118.  *        sequence
  1119.  *        destination mailbox
  1120.  * Returns: T if move successful, else NIL
  1121.  */
  1122.  
  1123. long news_move (stream,sequence,mailbox)
  1124.     MAILSTREAM *stream;
  1125.     char *sequence;
  1126.     char *mailbox;
  1127. {
  1128.   long i;
  1129.   MESSAGECACHE *elt;
  1130.   if (stream->anonymous || !(mail_sequence (stream,sequence) &&
  1131.                  news_copy (stream,sequence,mailbox))) return NIL;
  1132.                 /* delete all requested messages */
  1133.   for (i = 1; i <= stream->nmsgs; i++)
  1134.     if ((elt = mail_elt (stream,i))->sequence) {
  1135.       elt->deleted = T;        /* mark message deleted */
  1136.       LOCAL->dirty = T;        /* mark mailbox as dirty */
  1137.       LOCAL->seen[i - 1] = T;    /* and seen for .newsrc update */
  1138.     }
  1139.   return T;
  1140. }
  1141.  
  1142.  
  1143. /* Netnews mail append message from stringstruct
  1144.  * Accepts: MAIL stream
  1145.  *        destination mailbox
  1146.  *        stringstruct of messages to append
  1147.  * Returns: T if append successful, else NIL
  1148.  */
  1149.  
  1150. long news_append (stream,mailbox,message)
  1151.     MAILSTREAM *stream;
  1152.     char *mailbox;
  1153.     STRING *message;
  1154. {
  1155.   mm_log ("Append not valid for netnews",ERROR);
  1156.   return NIL;
  1157. }
  1158.  
  1159.  
  1160. /* Netnews garbage collect stream
  1161.  * Accepts: Mail stream
  1162.  *        garbage collection flags
  1163.  */
  1164.  
  1165. void news_gc (stream,gcflags)
  1166.     MAILSTREAM *stream;
  1167.     long gcflags;
  1168. {
  1169.   unsigned long i;
  1170.   if (gcflags & GC_TEXTS)    /* garbage collect texts? */
  1171.                 /* flush texts from cache */
  1172.     for (i = 0; i < stream->nmsgs; i++) {
  1173.       if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  1174.       if (LOCAL->body[i]) fs_give ((void **) &LOCAL->body[i]);
  1175.     }
  1176. }
  1177.  
  1178. /* Internal routines */
  1179.  
  1180.  
  1181. /* Read netnews database
  1182.  * Accepts: pointer to netnews database handle (handle NIL if first time)
  1183.  * Returns: character string for netnews database or NIL if done
  1184.  * Note - uses strtok() mechanism
  1185.  */
  1186.  
  1187. char *news_read (sdb)
  1188.     void **sdb;
  1189. {
  1190.   int fd;
  1191.   char *s,*t,tmp[MAILTMPLEN];
  1192.   struct stat sbuf;
  1193.   if (!*sdb) {            /* first time through? */
  1194.     if ((fd = open (NEWSRC,O_RDONLY,NIL)) < 0) return NIL;
  1195.     fstat (fd,&sbuf);        /* get file size and read data */
  1196.     read (fd,s = (char *) (*sdb = fs_get (sbuf.st_size + 1)),sbuf.st_size);
  1197.     close (fd);            /* close file */
  1198.     s[sbuf.st_size] = '\0';    /* tie off string */
  1199.     if (t = strtok (s,"\n")) return t;
  1200.   }
  1201.                 /* subsequent times through database */
  1202.   else if (t = strtok (NIL,"\n")) return t;
  1203.   fs_give (sdb);        /* free database */
  1204.   return NIL;            /* all done */
  1205. }
  1206.  
  1207.  
  1208. /* Parse flag list
  1209.  * Accepts: MAIL stream
  1210.  *        flag list as a character string
  1211.  * Returns: flag command list
  1212.  */
  1213.  
  1214. short news_getflags (stream,flag)
  1215.     MAILSTREAM *stream;
  1216.     char *flag;
  1217. {
  1218.   char *t;
  1219.   short f = 0;
  1220.   short i,j;
  1221.   if (flag && *flag) {        /* no-op if no flag string */
  1222.                 /* check if a list and make sure valid */
  1223.     if ((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) {
  1224.       mm_log ("Bad flag list",ERROR);
  1225.       return NIL;
  1226.     }
  1227.                 /* copy the flag string w/o list construct */
  1228.     strncpy (LOCAL->buf,flag+i,(j = strlen (flag) - (2*i)));
  1229.     LOCAL->buf[j] = '\0';
  1230.     t = ucase (LOCAL->buf);    /* uppercase only from now on */
  1231.  
  1232.     while (*t) {        /* parse the flags */
  1233.       if (*t == '\\') {        /* system flag? */
  1234.     switch (*++t) {        /* dispatch based on first character */
  1235.     case 'S':        /* possible \Seen flag */
  1236.       if (t[1] == 'E' && t[2] == 'E' && t[3] == 'N') i = fSEEN;
  1237.       t += 4;        /* skip past flag name */
  1238.       break;
  1239.     case 'D':        /* possible \Deleted flag */
  1240.       if (t[1] == 'E' && t[2] == 'L' && t[3] == 'E' && t[4] == 'T' &&
  1241.           t[5] == 'E' && t[6] == 'D') i = fDELETED;
  1242.       t += 7;        /* skip past flag name */
  1243.       break;
  1244.     case 'F':        /* possible \Flagged flag */
  1245.       if (t[1] == 'L' && t[2] == 'A' && t[3] == 'G' && t[4] == 'G' &&
  1246.           t[5] == 'E' && t[6] == 'D') i = fFLAGGED;
  1247.       t += 7;        /* skip past flag name */
  1248.       break;
  1249.     case 'A':        /* possible \Answered flag */
  1250.       if (t[1] == 'N' && t[2] == 'S' && t[3] == 'W' && t[4] == 'E' &&
  1251.           t[5] == 'R' && t[6] == 'E' && t[7] == 'D') i = fANSWERED;
  1252.       t += 8;        /* skip past flag name */
  1253.       break;
  1254.     default:        /* unknown */
  1255.       i = 0;
  1256.       break;
  1257.     }
  1258.                 /* add flag to flags list */
  1259.     if (i && ((*t == '\0') || (*t++ == ' '))) f |= i;
  1260.     else {            /* bitch about bogus flag */
  1261.       mm_log ("Unknown system flag",ERROR);
  1262.       return NIL;
  1263.     }
  1264.       }
  1265.       else {            /* no user flags yet */
  1266.     mm_log ("Unknown flag",ERROR);
  1267.     return NIL;
  1268.       }
  1269.     }
  1270.   }
  1271.   return f;
  1272. }
  1273.  
  1274. /* Search support routines
  1275.  * Accepts: MAIL stream
  1276.  *        message number
  1277.  *        pointer to additional data
  1278.  *        pointer to temporary buffer
  1279.  * Returns: T if search matches, else NIL
  1280.  */
  1281.  
  1282. char news_search_all (stream,msgno,d,n)
  1283.     MAILSTREAM *stream;
  1284.     long msgno;
  1285.     char *d;
  1286.     long n;
  1287. {
  1288.   return T;            /* ALL always succeeds */
  1289. }
  1290.  
  1291.  
  1292. char news_search_answered (stream,msgno,d,n)
  1293.     MAILSTREAM *stream;
  1294.     long msgno;
  1295.     char *d;
  1296.     long n;
  1297. {
  1298.   return mail_elt (stream,msgno)->answered ? T : NIL;
  1299. }
  1300.  
  1301.  
  1302. char news_search_deleted (stream,msgno,d,n)
  1303.     MAILSTREAM *stream;
  1304.     long msgno;
  1305.     char *d;
  1306.     long n;
  1307. {
  1308.   return mail_elt (stream,msgno)->deleted ? T : NIL;
  1309. }
  1310.  
  1311.  
  1312. char news_search_flagged (stream,msgno,d,n)
  1313.     MAILSTREAM *stream;
  1314.     long msgno;
  1315.     char *d;
  1316.     long n;
  1317. {
  1318.   return mail_elt (stream,msgno)->flagged ? T : NIL;
  1319. }
  1320.  
  1321.  
  1322. char news_search_keyword (stream,msgno,d,n)
  1323.     MAILSTREAM *stream;
  1324.     long msgno;
  1325.     char *d;
  1326.     long n;
  1327. {
  1328.   return NIL;            /* keywords not supported yet */
  1329. }
  1330.  
  1331.  
  1332. char news_search_new (stream,msgno,d,n)
  1333.     MAILSTREAM *stream;
  1334.     long msgno;
  1335.     char *d;
  1336.     long n;
  1337. {
  1338.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1339.   return (elt->recent && !elt->seen) ? T : NIL;
  1340. }
  1341.  
  1342. char news_search_old (stream,msgno,d,n)
  1343.     MAILSTREAM *stream;
  1344.     long msgno;
  1345.     char *d;
  1346.     long n;
  1347. {
  1348.   return mail_elt (stream,msgno)->recent ? NIL : T;
  1349. }
  1350.  
  1351.  
  1352. char news_search_recent (stream,msgno,d,n)
  1353.     MAILSTREAM *stream;
  1354.     long msgno;
  1355.     char *d;
  1356.     long n;
  1357. {
  1358.   return mail_elt (stream,msgno)->recent ? T : NIL;
  1359. }
  1360.  
  1361.  
  1362. char news_search_seen (stream,msgno,d,n)
  1363.     MAILSTREAM *stream;
  1364.     long msgno;
  1365.     char *d;
  1366.     long n;
  1367. {
  1368.   return mail_elt (stream,msgno)->seen ? T : NIL;
  1369. }
  1370.  
  1371.  
  1372. char news_search_unanswered (stream,msgno,d,n)
  1373.     MAILSTREAM *stream;
  1374.     long msgno;
  1375.     char *d;
  1376.     long n;
  1377. {
  1378.   return mail_elt (stream,msgno)->answered ? NIL : T;
  1379. }
  1380.  
  1381.  
  1382. char news_search_undeleted (stream,msgno,d,n)
  1383.     MAILSTREAM *stream;
  1384.     long msgno;
  1385.     char *d;
  1386.     long n;
  1387. {
  1388.   return mail_elt (stream,msgno)->deleted ? NIL : T;
  1389. }
  1390.  
  1391.  
  1392. char news_search_unflagged (stream,msgno,d,n)
  1393.     MAILSTREAM *stream;
  1394.     long msgno;
  1395.     char *d;
  1396.     long n;
  1397. {
  1398.   return mail_elt (stream,msgno)->flagged ? NIL : T;
  1399. }
  1400.  
  1401.  
  1402. char news_search_unkeyword (stream,msgno,d,n)
  1403.     MAILSTREAM *stream;
  1404.     long msgno;
  1405.     char *d;
  1406.     long n;
  1407. {
  1408.   return T;            /* keywords not supported yet */
  1409. }
  1410.  
  1411.  
  1412. char news_search_unseen (stream,msgno,d,n)
  1413.     MAILSTREAM *stream;
  1414.     long msgno;
  1415.     char *d;
  1416.     long n;
  1417. {
  1418.   return mail_elt (stream,msgno)->seen ? NIL : T;
  1419. }
  1420.  
  1421. char news_search_before (stream,msgno,d,n)
  1422.     MAILSTREAM *stream;
  1423.     long msgno;
  1424.     char *d;
  1425.     long n;
  1426. {
  1427.   return (char) (news_msgdate (stream,msgno) < n);
  1428. }
  1429.  
  1430.  
  1431. char news_search_on (stream,msgno,d,n)
  1432.     MAILSTREAM *stream;
  1433.     long msgno;
  1434.     char *d;
  1435.     long n;
  1436. {
  1437.   return (char) (news_msgdate (stream,msgno) == n);
  1438. }
  1439.  
  1440.  
  1441. char news_search_since (stream,msgno,d,n)
  1442.     MAILSTREAM *stream;
  1443.     long msgno;
  1444.     char *d;
  1445.     long n;
  1446. {
  1447.                 /* everybody interprets "since" as .GE. */
  1448.   return (char) (news_msgdate (stream,msgno) >= n);
  1449. }
  1450.  
  1451.  
  1452. unsigned long news_msgdate (stream,msgno)
  1453.     MAILSTREAM *stream;
  1454.     long msgno;
  1455. {
  1456.   struct stat sbuf;
  1457.   struct tm *tm;
  1458.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1459.   if (!elt->day) {        /* get date if don't have it yet */
  1460.                 /* build message file name */
  1461.     sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,LOCAL->number[msgno - 1]);
  1462.     stat (LOCAL->buf,&sbuf);    /* get message date */
  1463.     tm = gmtime (&sbuf.st_mtime);
  1464.     elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  1465.     elt->year = tm->tm_year + 1900 - BASEYEAR;
  1466.     elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  1467.     elt->seconds = tm->tm_sec;
  1468.     elt->zhours = 0; elt->zminutes = 0;
  1469.   }
  1470.   return (long) (elt->year << 9) + (elt->month << 5) + elt->day;
  1471. }
  1472.  
  1473.  
  1474. char news_search_body (stream,msgno,d,n)
  1475.     MAILSTREAM *stream;
  1476.     long msgno;
  1477.     char *d;
  1478.     long n;
  1479. {
  1480.   long i = msgno - 1;
  1481.   news_fetchheader (stream,msgno);
  1482.   return LOCAL->body[i] ?
  1483.     search (LOCAL->body[i],strlen (LOCAL->body[i]),d,n) : NIL;
  1484. }
  1485.  
  1486.  
  1487. char news_search_subject (stream,msgno,d,n)
  1488.     MAILSTREAM *stream;
  1489.     long msgno;
  1490.     char *d;
  1491.     long n;
  1492. {
  1493.   char *t = news_fetchstructure (stream,msgno,NIL)->subject;
  1494.   return t ? search (t,strlen (t),d,n) : NIL;
  1495. }
  1496.  
  1497.  
  1498. char news_search_text (stream,msgno,d,n)
  1499.     MAILSTREAM *stream;
  1500.     long msgno;
  1501.     char *d;
  1502.     long n;
  1503. {
  1504.   char *t = news_fetchheader (stream,msgno);
  1505.   return (t && search (t,strlen (t),d,n)) ||
  1506.     news_search_body (stream,msgno,d,n);
  1507. }
  1508.  
  1509. char news_search_bcc (stream,msgno,d,n)
  1510.     MAILSTREAM *stream;
  1511.     long msgno;
  1512.     char *d;
  1513.     long n;
  1514. {
  1515.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1516.                 /* get text for address */
  1517.   rfc822_write_address (LOCAL->buf,news_fetchstructure(stream,msgno,NIL)->bcc);
  1518.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1519. }
  1520.  
  1521.  
  1522. char news_search_cc (stream,msgno,d,n)
  1523.     MAILSTREAM *stream;
  1524.     long msgno;
  1525.     char *d;
  1526.     long n;
  1527. {
  1528.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1529.                 /* get text for address */
  1530.   rfc822_write_address (LOCAL->buf,news_fetchstructure (stream,msgno,NIL)->cc);
  1531.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1532. }
  1533.  
  1534.  
  1535. char news_search_from (stream,msgno,d,n)
  1536.     MAILSTREAM *stream;
  1537.     long msgno;
  1538.     char *d;
  1539.     long n;
  1540. {
  1541.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1542.                 /* get text for address */
  1543.   rfc822_write_address (LOCAL->buf,
  1544.             news_fetchstructure (stream,msgno,NIL)->from);
  1545.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1546. }
  1547.  
  1548.  
  1549. char news_search_to (stream,msgno,d,n)
  1550.     MAILSTREAM *stream;
  1551.     long msgno;
  1552.     char *d;
  1553.     long n;
  1554. {
  1555.   LOCAL->buf[0] = '\0';            /* initially empty string */
  1556.                 /* get text for address */
  1557.   rfc822_write_address (LOCAL->buf,news_fetchstructure (stream,msgno,NIL)->to);
  1558.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1559. }
  1560.  
  1561. /* Search parsers */
  1562.  
  1563.  
  1564. /* Parse a date
  1565.  * Accepts: function to return
  1566.  *        pointer to date integer to return
  1567.  * Returns: function to return
  1568.  */
  1569.  
  1570. search_t news_search_date (f,n)
  1571.     search_t f;
  1572.     long *n;
  1573. {
  1574.   long i;
  1575.   char *s;
  1576.   MESSAGECACHE elt;
  1577.                 /* parse the date and return fn if OK */
  1578.   return (news_search_string (f,&s,&i) && mail_parse_date (&elt,s) &&
  1579.       (*n = (elt.year << 9) + (elt.month << 5) + elt.day)) ? f : NIL;
  1580. }
  1581.  
  1582. /* Parse a flag
  1583.  * Accepts: function to return
  1584.  *        pointer to string to return
  1585.  * Returns: function to return
  1586.  */
  1587.  
  1588. search_t news_search_flag (f,d)
  1589.     search_t f;
  1590.     char **d;
  1591. {
  1592.                 /* get a keyword, return if OK */
  1593.   return (*d = strtok (NIL," ")) ? f : NIL;
  1594. }
  1595.  
  1596.  
  1597. /* Parse a string
  1598.  * Accepts: function to return
  1599.  *        pointer to string to return
  1600.  *        pointer to string length to return
  1601.  * Returns: function to return
  1602.  */
  1603.  
  1604. search_t news_search_string (f,d,n)
  1605.     search_t f;
  1606.     char **d;
  1607.     long *n;
  1608. {
  1609.   char *c = strtok (NIL,"");    /* remainder of criteria */
  1610.   if (c) {            /* better be an argument */
  1611.     switch (*c) {        /* see what the argument is */
  1612.     case '\0':            /* catch bogons */
  1613.     case ' ':
  1614.       return NIL;
  1615.     case '"':            /* quoted string */
  1616.       if (!(strchr (c+1,'"') && (*d = strtok (c,"\"")) && (*n = strlen (*d))))
  1617.     return NIL;
  1618.       break;
  1619.     case '{':            /* literal string */
  1620.       *n = strtol (c+1,&c,10);    /* get its length */
  1621.       if (*c++ != '}' || *c++ != '\015' || *c++ != '\012' ||
  1622.       *n > strlen (*d = c)) return NIL;
  1623.       c[*n] = '\255';        /* write new delimiter */
  1624.       strtok (c,"\255");    /* reset the strtok mechanism */
  1625.       break;
  1626.     default:            /* atomic string */
  1627.       *n = strlen (*d = strtok (c," "));
  1628.       break;
  1629.     }
  1630.     return f;
  1631.   }
  1632.   else return NIL;
  1633. }
  1634.