home *** CD-ROM | disk | FTP | other *** search
/ vsiftp.vmssoftware.com / VSIPUBLIC@vsiftp.vmssoftware.com.tar / FREEWARE / FREEWARE40.ZIP / pine / c-client / nntpclient.c < prev    next >
C/C++ Source or Header  |  1994-03-17  |  49KB  |  1,683 lines

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