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