nntp.c
上传用户:ycwykj01
上传日期:2007-01-04
资源大小:1819k
文件大小:52k
源码类别:

网络编程

开发平台:

Unix_Linux

  1. /*
  2.  * Program: Network News Transfer Protocol (NNTP) 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: 10 February 1992
  13.  * Last Edited: 20 September 1999
  14.  *
  15.  * Copyright 1999 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. #include <ctype.h>
  36. #include <stdio.h>
  37. #include "mail.h"
  38. #include "osdep.h"
  39. #include "nntp.h"
  40. #include "rfc822.h"
  41. #include "misc.h"
  42. #include "newsrc.h"
  43. #include "netmsg.h"
  44. #include "flstring.h"
  45. #include "utf8.h"
  46. /* Driver dispatch used by MAIL */
  47. DRIVER nntpdriver = {
  48.   "nntp", /* driver name */
  49. #ifdef INADEQUATE_MEMORY
  50.   DR_LOWMEM |
  51. #endif
  52. /* driver flags */
  53.   DR_NEWS|DR_READONLY|DR_NOFAST|DR_NAMESPACE|DR_CRLF|DR_RECYCLE,
  54.   (DRIVER *) NIL, /* next driver */
  55.   nntp_valid, /* mailbox is valid for us */
  56.   nntp_parameters, /* manipulate parameters */
  57.   nntp_scan, /* scan mailboxes */
  58.   nntp_list, /* find mailboxes */
  59.   nntp_lsub, /* find subscribed mailboxes */
  60.   nntp_subscribe, /* subscribe to mailbox */
  61.   nntp_unsubscribe, /* unsubscribe from mailbox */
  62.   nntp_create, /* create mailbox */
  63.   nntp_delete, /* delete mailbox */
  64.   nntp_rename, /* rename mailbox */
  65.   nntp_status, /* status of mailbox */
  66.   nntp_mopen, /* open mailbox */
  67.   nntp_mclose, /* close mailbox */
  68.   nntp_fetchfast, /* fetch message "fast" attributes */
  69.   nntp_flags, /* fetch message flags */
  70.   nntp_overview, /* fetch overview */
  71.   NIL, /* fetch message structure */
  72.   nntp_header, /* fetch message header */
  73.   nntp_text, /* fetch message text */
  74.   NIL, /* fetch message */
  75.   NIL, /* unique identifier */
  76.   NIL, /* message number from UID */
  77.   NIL, /* modify flags */
  78.   nntp_flagmsg, /* per-message modify flags */
  79.   nntp_search, /* search for message based on criteria */
  80.   nntp_sort, /* sort messages */
  81.   nntp_thread, /* thread messages */
  82.   nntp_ping, /* ping mailbox to see if still alive */
  83.   nntp_check, /* check for new messages */
  84.   nntp_expunge, /* expunge deleted messages */
  85.   nntp_copy, /* copy messages to another mailbox */
  86.   nntp_append, /* append string message to mailbox */
  87.   NIL /* garbage collect stream */
  88. };
  89. /* prototype stream */
  90. MAILSTREAM nntpproto = {&nntpdriver};
  91. /* driver parameters */
  92. static long nntp_maxlogintrials = MAXLOGINTRIALS;
  93. static long nntp_port = 0;
  94. static long nntp_altport = 0;
  95. static char *nntp_altname = NIL;
  96. /* NNTP validate mailbox
  97.  * Accepts: mailbox name
  98.  * Returns: our driver if name is valid, NIL otherwise
  99.  */
  100. DRIVER *nntp_valid (char *name)
  101. {
  102.   char tmp[MAILTMPLEN];
  103.   return nntp_isvalid (name,tmp);
  104. }
  105. /* NNTP validate mailbox work routine
  106.  * Accepts: mailbox name
  107.  *     buffer for returned mailbox name
  108.  * Returns: our driver if name is valid, NIL otherwise
  109.  */
  110. DRIVER *nntp_isvalid (char *name,char *mbx)
  111. {
  112.   DRIVER *ret = mail_valid_net (name,&nntpdriver,NIL,mbx);
  113.   if (ret && (mbx[0] == '#')) { /* namespace format name? */
  114.     if ((mbx[1] == 'n') && (mbx[2] == 'e') && (mbx[3] == 'w') &&
  115. (mbx[4] == 's') && (mbx[5] == '.'))
  116.       memmove (mbx,mbx+6,strlen (mbx+6) + 1);
  117.     else ret = NIL; /* bogus name */
  118.   }
  119.   return ret;
  120. }
  121. /* News manipulate driver parameters
  122.  * Accepts: function code
  123.  *     function-dependent value
  124.  * Returns: function-dependent return value
  125.  */
  126. void *nntp_parameters (long function,void *value)
  127. {
  128.   switch ((int) function) {
  129.   case SET_MAXLOGINTRIALS:
  130.     nntp_maxlogintrials = (unsigned long) value;
  131.     break;
  132.   case GET_MAXLOGINTRIALS:
  133.     value = (void *) nntp_maxlogintrials;
  134.     break;
  135.   case SET_NNTPPORT:
  136.     nntp_port = (long) value;
  137.     break;
  138.   case GET_NNTPPORT:
  139.     value = (void *) nntp_port;
  140.     break;
  141.   case SET_ALTNNTPPORT:
  142.     nntp_altport = (long) value;
  143.     break;
  144.   case GET_ALTNNTPPORT:
  145.     value = (void *) nntp_altport;
  146.     break;
  147.   case SET_ALTNNTPNAME:
  148.     nntp_altname = (char *) value;
  149.     break;
  150.   case GET_ALTNNTPNAME:
  151.     value = (void *) nntp_altname;
  152.     break;
  153.   default:
  154.     value = NIL; /* error case */
  155.     break;
  156.   }
  157.   return value;
  158. }
  159. /* NNTP mail scan mailboxes for string
  160.  * Accepts: mail stream
  161.  *     reference
  162.  *     pattern to search
  163.  *     string to scan
  164.  */
  165. void nntp_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
  166. {
  167.   char tmp[MAILTMPLEN];
  168.   if (nntp_canonicalize (ref,pat,tmp))
  169.     mm_log ("Scan not valid for NNTP mailboxes",ERROR);
  170. }
  171. /* NNTP list newsgroups
  172.  * Accepts: mail stream
  173.  *     reference
  174.  *     pattern to search
  175.  */
  176. void nntp_list (MAILSTREAM *stream,char *ref,char *pat)
  177. {
  178.   MAILSTREAM *st = stream;
  179.   char *s,*t,*lcl,pattern[MAILTMPLEN],name[MAILTMPLEN];
  180.   int showuppers = pat[strlen (pat) - 1] == '%';
  181.   if (!pat || !*pat) {
  182.     if (nntp_canonicalize (ref,"*",pattern)) {
  183. /* tie off name at root */
  184.       if ((s = strchr (pattern,'}')) && (s = strchr (s+1,'.'))) *++s = '';
  185.       else pattern[0] = '';
  186.       mm_list (stream,'.',pattern,NIL);
  187.     }
  188.   }
  189. /* ask server for open newsgroups */
  190.   else if (nntp_canonicalize (ref,pat,pattern) &&
  191.    ((stream && LOCAL && LOCAL->nntpstream) ||
  192.     (stream = mail_open (NIL,pattern,OP_HALFOPEN))) &&
  193.    ((nntp_send (LOCAL->nntpstream,"LIST","ACTIVE") == NNTPGLIST) ||
  194.     (nntp_send (LOCAL->nntpstream,"LIST",NIL) == NNTPGLIST))) {
  195. /* namespace format name? */
  196.     if (*(lcl = strchr (strcpy (name,pattern),'}') + 1) == '#') lcl += 6;
  197. /* process data until we see final dot */
  198.     while (s = net_getline (LOCAL->nntpstream->netstream)) {
  199.       if ((*s == '.') && !s[1]){/* end of text */
  200. fs_give ((void **) &s);
  201. break;
  202.       }
  203.       if (t = strchr (s,' ')) { /* tie off after newsgroup name */
  204. *t = '';
  205. strcpy (lcl,s); /* make full form of name */
  206. /* report if match */
  207. if (pmatch_full (name,pattern,'.')) mm_list (stream,'.',name,NIL);
  208. else while (showuppers && (t = strrchr (lcl,'.'))) {
  209.   *t = ''; /* tie off the name */
  210.   if (pmatch_full (name,pattern,'.'))
  211.     mm_list (stream,'.',name,LATT_NOSELECT);
  212. }
  213.       }
  214.       fs_give ((void **) &s); /* clean up */
  215.     }
  216.     if (stream != st) mail_close (stream);
  217.   }
  218. }
  219. /* NNTP list subscribed newsgroups
  220.  * Accepts: mail stream
  221.  *     reference
  222.  *     pattern to search
  223.  */
  224. void nntp_lsub (MAILSTREAM *stream,char *ref,char *pat)
  225. {
  226.   void *sdb = NIL;
  227.   char *s,mbx[MAILTMPLEN];
  228. /* return data from newsrc */
  229.   if (nntp_canonicalize (ref,pat,mbx)) newsrc_lsub (stream,mbx);
  230.   if (*pat == '{') { /* if remote pattern, must be NNTP */
  231.     if (!nntp_valid (pat)) return;
  232.     ref = NIL; /* good NNTP pattern, punt reference */
  233.   }
  234. /* if remote reference, must be valid NNTP */
  235.   if (ref && (*ref == '{') && !nntp_valid (ref)) return;
  236. /* kludgy application of reference */
  237.   if (ref && *ref) sprintf (mbx,"%s%s",ref,pat);
  238.   else strcpy (mbx,pat);
  239.   if (s = sm_read (&sdb)) do if (nntp_valid (s) && pmatch (s,mbx))
  240.     mm_lsub (stream,NIL,s,NIL);
  241.   while (s = sm_read (&sdb)); /* until no more subscriptions */
  242. }
  243. /* NNTP canonicalize newsgroup name
  244.  * Accepts: reference
  245.  *     pattern
  246.  *     returned single pattern
  247.  * Returns: T on success, NIL on failure
  248.  */
  249. long nntp_canonicalize (char *ref,char *pat,char *pattern)
  250. {
  251.   if (ref && *ref) { /* have a reference */
  252.     if (!nntp_valid (ref)) return NIL;
  253.     strcpy (pattern,ref); /* copy reference to pattern */
  254. /* # overrides mailbox field in reference */
  255.     if (*pat == '#') strcpy (strchr (pattern,'}') + 1,pat);
  256. /* pattern starts, reference ends, with . */
  257.     else if ((*pat == '.') && (pattern[strlen (pattern) - 1] == '.'))
  258.       strcat (pattern,pat + 1); /* append, omitting one of the period */
  259.     else strcat (pattern,pat); /* anything else is just appended */
  260.   }
  261.   else strcpy (pattern,pat); /* just have basic name */
  262.   return nntp_valid (pattern) ? T : NIL;
  263. }
  264. /* NNTP subscribe to mailbox
  265.  * Accepts: mail stream
  266.  *     mailbox to add to subscription list
  267.  * Returns: T on success, NIL on failure
  268.  */
  269. long nntp_subscribe (MAILSTREAM *stream,char *mailbox)
  270. {
  271.   char mbx[MAILTMPLEN];
  272.   return nntp_isvalid (mailbox,mbx) ? newsrc_update (stream,mbx,':') : NIL;
  273. }
  274. /* NNTP unsubscribe to mailbox
  275.  * Accepts: mail stream
  276.  *     mailbox to delete from subscription list
  277.  * Returns: T on success, NIL on failure
  278.  */
  279. long nntp_unsubscribe (MAILSTREAM *stream,char *mailbox)
  280. {
  281.   char mbx[MAILTMPLEN];
  282.   return nntp_isvalid (mailbox,mbx) ? newsrc_update (stream,mbx,'!') : NIL;
  283. }
  284. /* NNTP create mailbox
  285.  * Accepts: mail stream
  286.  *     mailbox name to create
  287.  * Returns: T on success, NIL on failure
  288.  */
  289. long nntp_create (MAILSTREAM *stream,char *mailbox)
  290. {
  291.   return NIL; /* never valid for NNTP */
  292. }
  293. /* NNTP delete mailbox
  294.  *     mailbox name to delete
  295.  * Returns: T on success, NIL on failure
  296.  */
  297. long nntp_delete (MAILSTREAM *stream,char *mailbox)
  298. {
  299.   return NIL; /* never valid for NNTP */
  300. }
  301. /* NNTP rename mailbox
  302.  * Accepts: mail stream
  303.  *     old mailbox name
  304.  *     new mailbox name
  305.  * Returns: T on success, NIL on failure
  306.  */
  307. long nntp_rename (MAILSTREAM *stream,char *old,char *newname)
  308. {
  309.   return NIL; /* never valid for NNTP */
  310. }
  311. /* NNTP status
  312.  * Accepts: mail stream
  313.  *     mailbox name
  314.  *     status flags
  315.  * Returns: T on success, NIL on failure
  316.  */
  317. long nntp_status (MAILSTREAM *stream,char *mbx,long flags)
  318. {
  319.   MAILSTATUS status;
  320.   NETMBX mb;
  321.   unsigned long i;
  322.   long ret = NIL;
  323.   char *s,*name,*state,tmp[MAILTMPLEN];
  324.   char *old = (stream && !stream->halfopen) ? LOCAL->name : NIL;
  325.   MAILSTREAM *tstream = NIL;
  326.   if (!(mail_valid_net_parse (mbx,&mb) && *mb.mailbox &&
  327. ((mb.mailbox[0] != '#') ||
  328.  ((mb.mailbox[1] == 'n') && (mb.mailbox[2] == 'e') &&
  329.   (mb.mailbox[3] == 'w') && (mb.mailbox[4] == 's') &&
  330.   (mb.mailbox[5] == '.'))))) {
  331.     sprintf (tmp,"Invalid NNTP name %s",mbx);
  332.     mm_log (tmp,ERROR);
  333.     return NIL;
  334.   }
  335. /* note mailbox name */
  336.   name = (*mb.mailbox == '#') ? mb.mailbox+6 : mb.mailbox;
  337. /* stream to reuse? */
  338.   if (!(stream && LOCAL->nntpstream &&
  339. mail_usable_network_stream (stream,mbx)) &&
  340.       !(tstream = stream = mail_open (NIL,mbx,OP_HALFOPEN|OP_SILENT)))
  341.     return NIL; /* can't reuse or make a new one */
  342.   if (nntp_send (LOCAL->nntpstream,"GROUP",name) == NNTPGOK) {
  343.     status.flags = flags; /* status validity flags */
  344.     status.messages = strtoul (LOCAL->nntpstream->reply + 4,&s,10);
  345.     i = strtoul (s,&s,10);
  346.     status.uidnext = strtoul (s,NIL,10) + 1;
  347. /* initially empty */
  348.     status.recent = status.unseen = 0;
  349. /* don't bother if empty or don't want this */
  350.     if (status.messages && (flags & (SA_RECENT | SA_UNSEEN))) {
  351.       if (state = newsrc_state (stream,name)) {
  352. /* in case have to do XHDR Date */
  353. sprintf (tmp,"%lu-%lu",i,status.uidnext - 1);
  354. /* only bother if there appear to be holes */
  355. if ((status.messages < (status.uidnext - i)) &&
  356.     ((nntp_send (LOCAL->nntpstream,"LISTGROUP",name) == NNTPGOK)
  357.      || (nntp_send (LOCAL->nntpstream,"XHDR Date",tmp) == NNTPHEAD))) {
  358.   while ((s = net_getline (LOCAL->nntpstream->netstream)) &&
  359.  strcmp (s,".")) {
  360.     newsrc_check_uid (state,strtoul (s,NIL,10),&status.recent,
  361.       &status.unseen);
  362.     fs_give ((void **) &s);
  363.   }
  364.   if (s) fs_give ((void **) &s);
  365. }
  366. /* assume c-client/NNTP map is entire range */
  367. else while (i < status.uidnext)
  368.   newsrc_check_uid (state,i++,&status.recent,&status.unseen);
  369. fs_give ((void **) &state);
  370.       }
  371. /* no .newsrc state, all messages new */
  372.       else status.recent = status.unseen = status.messages;
  373.     }
  374. /* UID validity is a constant */
  375.     status.uidvalidity = stream->uid_validity;
  376. /* pass status to main program */
  377.     mm_status (stream,mbx,&status);
  378.     ret = T; /* succes */
  379.   }
  380. /* flush temporary stream */
  381.   if (tstream) mail_close (tstream);
  382. /* else reopen old newsgroup */
  383.   else if (old && nntp_send (LOCAL->nntpstream,"GROUP",old) != NNTPGOK) {
  384.     mm_log (LOCAL->nntpstream->reply,ERROR);
  385.     stream->halfopen = T; /* go halfopen */
  386.   }
  387.   return ret; /* success */
  388. }
  389. /* NNTP open
  390.  * Accepts: stream to open
  391.  * Returns: stream on success, NIL on failure
  392.  */
  393. MAILSTREAM *nntp_mopen (MAILSTREAM *stream)
  394. {
  395.   unsigned long i,j,k,nmsgs;
  396.   char *s,*mbx,tmp[MAILTMPLEN];
  397.   NETMBX mb;
  398.   SENDSTREAM *nstream = NIL;
  399. /* return prototype for OP_PROTOTYPE call */
  400.   if (!stream) return &nntpproto;
  401.   if (!(mail_valid_net_parse (stream->mailbox,&mb) &&
  402. (stream->halfopen || /* insist upon valid name */
  403.  (*mb.mailbox && ((mb.mailbox[0] != '#') ||
  404.   ((mb.mailbox[1] == 'n') && (mb.mailbox[2] == 'e') &&
  405.    (mb.mailbox[3] == 'w') && (mb.mailbox[4] == 's') &&
  406.    (mb.mailbox[5] == '.'))))))) {
  407.     sprintf (tmp,"Invalid NNTP name %s",stream->mailbox);
  408.     mm_log (tmp,ERROR);
  409.     return NIL;
  410.   }
  411. /* note mailbox anme */
  412.   mbx = (*mb.mailbox == '#') ? mb.mailbox+6 : mb.mailbox;
  413.   if (LOCAL) { /* recycle stream */
  414.     sprintf (tmp,"Reusing connection to %s",LOCAL->host);
  415.     if (!stream->silent) mm_log (tmp,(long) NIL);
  416.     nstream = LOCAL->nntpstream;
  417.     LOCAL->nntpstream = NIL; /* keep nntp_mclose() from punting it */
  418.     nntp_mclose (stream,NIL); /* do close action */
  419.     stream->dtb = &nntpdriver; /* reattach this driver */
  420.   }
  421.   if (mb.secflag) { /* in case /secure switch given */
  422.     mm_log ("Secure NNTP login not available",ERROR);
  423.     return NIL;
  424.   }
  425. /* in case /debug switch given */
  426.   if (mb.dbgflag) stream->debug = T;
  427.   mb.tryaltflag = stream->tryalt;
  428.   if (!nstream) { /* open NNTP now if not already open */
  429.     char *hostlist[2];
  430.     hostlist[0] = strcpy (tmp,mb.host);
  431.     if (mb.port || nntp_port)
  432.       sprintf (tmp + strlen (tmp),":%lu",mb.port ? mb.port : nntp_port);
  433.     if (mb.altflag) sprintf (tmp + strlen (tmp),"/%s",(char *)
  434.      mail_parameters (NIL,GET_ALTDRIVERNAME,NIL));
  435.     if (mb.user[0]) sprintf (tmp + strlen (tmp),"/user="%s"",mb.user);
  436.     hostlist[1] = NIL;
  437.     nstream = nntp_open (hostlist,OP_READONLY+(stream->debug ? OP_DEBUG:NIL));
  438.   }
  439.   if (!nstream) return NIL; /* didn't get an NNTP session */
  440. /* always zero messages if halfopen */
  441.   if (stream->halfopen) i = j = k = nmsgs = 0;
  442. /* otherwise open the newsgroup */
  443.   else if (nntp_send (nstream,"GROUP",mbx) == NNTPGOK) {
  444.     k = strtoul (nstream->reply + 4,&s,10);
  445.     i = strtoul (s,&s,10);
  446.     stream->uid_last = j = strtoul (s,&s,10);
  447.     nmsgs = (i | j) ? 1 + j - i : 0;
  448.   }
  449.   else { /* no such newsgroup */
  450.     mm_log (nstream->reply,ERROR);
  451.     nntp_close (nstream); /* punt stream */
  452.     return NIL;
  453.   }
  454. /* newsgroup open, instantiate local data */
  455.   stream->local = memset (fs_get (sizeof (NNTPLOCAL)),0,sizeof (NNTPLOCAL));
  456.   LOCAL->nntpstream = nstream;
  457.   LOCAL->name = cpystr (mbx); /* copy newsgroup name */
  458.   if (mb.user[0]) LOCAL->user = cpystr (mb.user);
  459.   stream->sequence++; /* bump sequence number */
  460.   stream->rdonly = stream->perm_deleted = T;
  461. /* UIDs are always valid */
  462.   stream->uid_validity = 0xbeefface;
  463. /* copy host name */
  464.   LOCAL->host = cpystr (net_host (nstream->netstream));
  465.   sprintf (tmp,"{%s:%lu/nntp",LOCAL->host,net_port (nstream->netstream));
  466.   if (mb.altflag) sprintf (tmp + strlen (tmp),"/%s",(char *)
  467.    mail_parameters (NIL,GET_ALTDRIVERNAME,NIL));
  468.   if (mb.secflag) strcat (tmp,"/secure");
  469.   if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user="%s"",LOCAL->user);
  470.   if (stream->halfopen) strcat (tmp,"}<no_mailbox>");
  471.   else sprintf (tmp + strlen (tmp),"}#news.%s",mbx);
  472.   fs_give ((void **) &stream->mailbox);
  473.   stream->mailbox = cpystr (tmp);
  474.   if (nmsgs) { /* if any messages exist */
  475.     short silent = stream->silent;
  476.     stream->silent = T; /* don't notify main program yet */
  477.     mail_exists (stream,nmsgs); /* silently set the cache to the guesstimate */
  478.     sprintf (tmp,"%lu-%lu",i,j);/* in case have to do XHDR Date */
  479.     if ((k < nmsgs) && /* only bother if there appear to be holes */
  480. ((nntp_send (nstream,"LISTGROUP",mbx) == NNTPGOK) ||
  481.  (nntp_send (nstream,"XHDR Date",tmp) == NNTPHEAD))) {
  482.       nmsgs = 0; /* have holes, calculate true count */
  483.       while ((s = net_getline (nstream->netstream)) && strcmp (s,".")) {
  484. mail_elt (stream,++nmsgs)->private.uid = atol (s);
  485. fs_give ((void **) &s);
  486.       }
  487.       if (s) fs_give ((void **) &s);
  488.     }
  489. /* assume c-client/NNTP map is entire range */
  490.     else for (k = 1; k <= nmsgs; k++)
  491.       mail_elt (stream,k)->private.uid = i++;
  492.     stream->nmsgs = 0; /* whack it back down */
  493.     stream->silent = silent; /* restore old silent setting */
  494.     mail_exists (stream,nmsgs); /* notify upper level that messages exist */
  495. /* read .newsrc entries */
  496.     mail_recent (stream,newsrc_read (mbx,stream));
  497.   }
  498.   else { /* empty newsgroup or halfopen */
  499.     if (!(stream->silent || stream->halfopen)) {
  500.       sprintf (tmp,"Newsgroup %s is empty",mbx);
  501.       mm_log (tmp,WARN);
  502.     }
  503.     mail_exists (stream,(long) 0);
  504.     mail_recent (stream,(long) 0);
  505.   }
  506.   return stream; /* return stream to caller */
  507. }
  508. /* NNTP close
  509.  * Accepts: MAIL stream
  510.  *     option flags
  511.  */
  512. void nntp_mclose (MAILSTREAM *stream,long options)
  513. {
  514.   unsigned long i;
  515.   MESSAGECACHE *elt;
  516.   if (LOCAL) { /* only if a file is open */
  517.     nntp_check (stream); /* dump final checkpoint */
  518.     if (LOCAL->name) fs_give ((void **) &LOCAL->name);
  519.     if (LOCAL->host) fs_give ((void **) &LOCAL->host);
  520.     if (LOCAL->user) fs_give ((void **) &LOCAL->user);
  521.     if (LOCAL->txt) fclose (LOCAL->txt);
  522. /* close NNTP connection */
  523.     if (LOCAL->nntpstream) nntp_close (LOCAL->nntpstream);
  524.     for (i = 1; i <= stream->nmsgs; i++)
  525.       if ((elt = mail_elt (stream,i))->private.data)
  526. fs_give ((void **) &elt->private.data);
  527. /* nuke the local data */
  528.     fs_give ((void **) &stream->local);
  529.     stream->dtb = NIL; /* log out the DTB */
  530.   }
  531. }
  532. /* NNTP fetch fast information
  533.  * Accepts: MAIL stream
  534.  *     sequence
  535.  *     option flags
  536.  * This is ugly and slow
  537.  */
  538. void nntp_fetchfast (MAILSTREAM *stream,char *sequence,long flags)
  539. {
  540.   unsigned long i;
  541.   MESSAGECACHE *elt;
  542. /* get sequence */
  543.   if (stream && LOCAL && ((flags & FT_UID) ?
  544.   mail_uid_sequence (stream,sequence) :
  545.   mail_sequence (stream,sequence)))
  546.     for (i = 1; i <= stream->nmsgs; i++) {
  547.       if ((elt = mail_elt (stream,i))->sequence &&
  548.   !(elt->day && !elt->rfc822_size)) {
  549. ENVELOPE **env = NIL;
  550. ENVELOPE *e = NIL;
  551. if (!stream->scache) env = &elt->private.msg.env;
  552. else if (stream->msgno == i) env = &stream->env;
  553. else env = &e;
  554. if (!*env || !elt->rfc822_size) {
  555.   STRING bs;
  556.   unsigned long hs;
  557.   char *ht = (*stream->dtb->header) (stream,i,&hs,NIL);
  558. /* need to make an envelope? */
  559.   if (!*env) rfc822_parse_msg (env,NIL,ht,hs,NIL,BADHOST,
  560.        stream->dtb->flags);
  561. /* need message size too, ugh */
  562.   if (!elt->rfc822_size) {
  563.     (*stream->dtb->text) (stream,i,&bs,FT_PEEK);
  564.     elt->rfc822_size = hs + SIZE (&bs) - GETPOS (&bs);
  565.   }
  566. }
  567. /* if need date, have date in envelope? */
  568. if (!elt->day && *env && (*env)->date)
  569.   mail_parse_date (elt,(*env)->date);
  570. /* sigh, fill in bogus default */
  571. if (!elt->day) elt->day = elt->month = 1;
  572. mail_free_envelope (&e);
  573.       }
  574.     }
  575. }
  576. /* NNTP fetch flags
  577.  * Accepts: MAIL stream
  578.  *     sequence
  579.  *     option flags
  580.  */
  581. void nntp_flags (MAILSTREAM *stream,char *sequence,long flags)
  582. {
  583.   unsigned long i;
  584.   if ((flags & FT_UID) ? /* validate all elts */
  585.       mail_uid_sequence (stream,sequence) : mail_sequence (stream,sequence))
  586.     for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->valid = T;
  587. }
  588. /* NNTP fetch overview
  589.  * Accepts: MAIL stream
  590.  *     UID sequence (may be NIL for nntp_search() call)
  591.  *     overview return function
  592.  * Returns: T if successful, NIL otherwise
  593.  */
  594. long nntp_overview (MAILSTREAM *stream,char *sequence,overview_t ofn)
  595. {
  596.   unsigned long i,j,k,uid;
  597.   char c,*s,*t,*v,tmp[MAILTMPLEN];
  598.   MESSAGECACHE *elt;
  599.   OVERVIEW ov;
  600. /* parse the sequence */
  601.   if (!sequence || mail_uid_sequence (stream,sequence)) {
  602. /* scan sequence to load cache */
  603.     for (i = 1; i <= stream->nmsgs; i++)
  604. /* have cached overview yet? */
  605.       if ((elt = mail_elt (stream,i))->sequence && !elt->private.data) {
  606. for (j = i + 1; /* no, find end of cache gap range */
  607.      (j <= stream->nmsgs) && (elt = mail_elt (stream,j))->sequence &&
  608.      !elt->private.data; j++);
  609. /* make NNTP range */
  610. sprintf (tmp,(i == (j - 1)) ? "%lu" : "%lu-%lu",mail_uid (stream,i),
  611.  mail_uid (stream,j - 1));
  612. i = j; /* advance beyond gap */
  613. /* ask server for overview data to cache */
  614. if (nntp_send (LOCAL->nntpstream,"XOVER",tmp) == NNTPOVER) {
  615.   while ((s = net_getline (LOCAL->nntpstream->netstream)) &&
  616.  strcmp (s,".")) {
  617. /* death to embedded newlines */
  618.     for (t = v = s; c = *v++;)
  619.       if ((c != '12') && (c != '15')) *t++ = c;
  620.     *t++ = ''; /* tie off string in case it was shortened */
  621. /* cache the overview if found its sequence */
  622.     if ((uid = atol (s)) && (k = mail_msgno (stream,uid)) &&
  623. (t = strchr (s,'t'))) {
  624.       if ((elt = mail_elt (stream,k))->private.data)
  625. fs_give ((void **) &elt->private.data);
  626.       elt->private.data = (unsigned long) cpystr (t + 1);
  627.     }
  628.     else { /* shouldn't happen, snarl if it does */
  629.       sprintf (tmp,"Server returned data for unknown UID %lu",uid);
  630.       mm_log (tmp,WARN);
  631.     }
  632. /* flush the overview */
  633.     fs_give ((void **) &s);
  634.   }
  635. /* flush the terminating dot */
  636.   if (s) fs_give ((void **) &s);
  637. }
  638. else i = stream->nmsgs; /* XOVER failed, punt cache load */
  639.       }
  640. /* now scan sequence to return overviews */
  641.     if (ofn) for (i = 1; i <= stream->nmsgs; i++)
  642.       if ((elt = mail_elt (stream,i))->sequence) {
  643. /* UID for this message */
  644. uid = mail_uid (stream,i);
  645. /* parse cached overview */
  646. if (nntp_parse_overview (&ov,s = (char *) elt->private.data))
  647.   (*ofn) (stream,uid,&ov);
  648. else { /* parse failed */
  649.   (*ofn) (stream,uid,NIL);
  650.   if (s && *s) { /* unusable cached entry? */
  651.     sprintf (tmp,"Unable to parse overview for UID %lu: %.500s",uid,s);
  652.     mm_log (tmp,WARN);
  653. /* erase it from the cache */
  654.     fs_give ((void **) &s);
  655.   }
  656. /* insert empty cached text as necessary */
  657.   if (!s) elt->private.data = (unsigned long) cpystr ("");
  658. }
  659. /* clean up overview data */
  660. if (ov.from) mail_free_address (&ov.from);
  661. if (ov.subject) fs_give ((void **) &ov.subject);
  662.       }
  663.   }
  664.   return T;
  665. }
  666. /* Parse OVERVIEW struct from cached NNTP XOVER response
  667.  * Accepts: struct to load
  668.  *     cached XOVER response
  669.  * Returns: T if success, NIL if fail
  670.  */
  671. long nntp_parse_overview (OVERVIEW *ov,char *text)
  672. {
  673.   char *t;
  674. /* nothing in overview yet */
  675.   memset ((void *) ov,0,sizeof (OVERVIEW));
  676. /* no cached data */
  677.   if (!(text && *text)) return NIL;
  678.   ov->subject = cpystr (text); /* make hackable copy of overview */
  679. /* find end of Subject */
  680.   if (t = strchr (ov->subject,'t')) {
  681.     *t++ = ''; /* tie off Subject, point to From */
  682. /* find end of From */
  683.     if (ov->date = strchr (t,'t')) {
  684.       *ov->date++ = ''; /* tie off From, point to Date */
  685. /* parse From */
  686.       rfc822_parse_adrlist (&ov->from,t,BADHOST);
  687. /* find end of Date */
  688.       if (ov->message_id = strchr (ov->date,'t')) {
  689. /* tie off Date, point to Message-ID */
  690. *ov->message_id++ = '';
  691. /* find end of Message-ID */
  692. if (ov->references = strchr (ov->message_id,'t')) {
  693. /* tie off Message-ID, point to References */
  694.   *ov->references++ = '';
  695. /* fine end of References */
  696.   if (t = strchr (ov->references,'t')) {
  697.     *t++ = ''; /* tie off References, point to octet size */
  698. /* parse size of message in octets */
  699.     ov->optional.octets = atol (t);
  700. /* find end of size */
  701.     if (t = strchr (t,'t')) {
  702. /* parse size of message in lines */
  703.       ov->optional.lines = atol (++t);
  704. /* find Xref */
  705.       if (ov->optional.xref = strchr (t,'t'))
  706. *ov->optional.xref++ = '';
  707.     }
  708.   }
  709. }
  710.       }
  711.     }
  712.   }
  713.   return ov->references ? T : NIL;
  714. }
  715. /* NNTP fetch header as text
  716.  * Accepts: mail stream
  717.  *     message number
  718.  *     pointer to return size
  719.  *     flags
  720.  * Returns: header text
  721.  */
  722. char *nntp_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *size,
  723.    long flags)
  724. {
  725.   char tmp[MAILTMPLEN];
  726.   MESSAGECACHE *elt;
  727.   FILE *f;
  728.   if ((flags & FT_UID) && !(msgno = mail_msgno (stream,msgno))) return "";
  729. /* have header text? */
  730.   if (!(elt = mail_elt (stream,msgno))->private.msg.header.text.data) {
  731.     sprintf (tmp,"%lu",mail_uid (stream,msgno));
  732. /* get header text */
  733.     if ((nntp_send (LOCAL->nntpstream,"HEAD",tmp) == NNTPHEAD) &&
  734. (f = netmsg_slurp (LOCAL->nntpstream->netstream,size,NIL))) {
  735.       fread (elt->private.msg.header.text.data =
  736.      (unsigned char *) fs_get ((size_t) *size + 3),
  737.      (size_t) 1,(size_t) *size,f);
  738.       fclose (f); /* flush temp file */
  739. /* tie off header with extra CRLF and NUL */
  740.       elt->private.msg.header.text.data[*size] = '15';
  741.       elt->private.msg.header.text.data[++*size] = '12';
  742.       elt->private.msg.header.text.data[++*size] = '';
  743.       elt->private.msg.header.text.size = *size;
  744.       elt->valid = T; /* make elt valid now */
  745.     }
  746.     else { /* failed, mark as deleted and empty */
  747.       elt->valid = elt->deleted = T;
  748.       *size = elt->private.msg.header.text.size = 0;
  749.     }
  750.   }
  751. /* just return size of text */
  752.   else *size = elt->private.msg.header.text.size;
  753.   return elt->private.msg.header.text.data ?
  754.     (char *) elt->private.msg.header.text.data : "";
  755. }
  756. /* NNTP fetch body
  757.  * Accepts: mail stream
  758.  *     message number
  759.  *     pointer to stringstruct to initialize
  760.  *     flags
  761.  * Returns: T if successful, else NIL
  762.  */
  763. long nntp_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
  764. {
  765.   char tmp[MAILTMPLEN];
  766.   MESSAGECACHE *elt;
  767.   INIT (bs,mail_string,(void *) "",0);
  768.   if ((flags & FT_UID) && !(msgno = mail_msgno (stream,msgno))) return NIL;
  769.   elt = mail_elt (stream,msgno);
  770. /* different message, flush cache */
  771.   if (LOCAL->txt && (LOCAL->msgno != msgno)) {
  772.     fclose (LOCAL->txt);
  773.     LOCAL->txt = NIL;
  774.   }
  775.   LOCAL->msgno = msgno; /* note cached message */
  776.   if (!LOCAL->txt) { /* have file for this message? */
  777.     sprintf (tmp,"%lu",elt->private.uid);
  778.     if (nntp_send (LOCAL->nntpstream,"BODY",tmp) != NNTPBODY)
  779.       elt->deleted = T; /* failed mark as deleted */
  780.     else LOCAL->txt = netmsg_slurp (LOCAL->nntpstream->netstream,
  781.     &LOCAL->txtsize,NIL);
  782.   }
  783.   if (!LOCAL->txt) return NIL; /* error if don't have a file */
  784.   if (!(flags & FT_PEEK)) { /* mark seen if needed */
  785.     elt->seen = T;
  786.     mm_flags (stream,elt->msgno);
  787.   }
  788.   INIT (bs,file_string,(void *) LOCAL->txt,LOCAL->txtsize);
  789.   return T;
  790. }
  791. /* NNTP per-message modify flag
  792.  * Accepts: MAIL stream
  793.  *     message cache element
  794.  */
  795. void nntp_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
  796. {
  797.   if (!LOCAL->dirty) { /* only bother checking if not dirty yet */
  798.     if (elt->valid) { /* if done, see if deleted changed */
  799.       if (elt->sequence != elt->deleted) LOCAL->dirty = T;
  800.       elt->sequence = T; /* leave the sequence set */
  801.     }
  802. /* note current setting of deleted flag */
  803.     else elt->sequence = elt->deleted;
  804.   }
  805. }
  806. /* NNTP search messages
  807.  * Accepts: mail stream
  808.  *     character set
  809.  *     search program
  810.  *     option flags
  811.  */
  812. void nntp_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags)
  813. {
  814.   unsigned long i;
  815.   MESSAGECACHE *elt;
  816.   OVERVIEW ov;
  817.   if (charset && *charset && /* convert if charset not US-ASCII or UTF-8 */
  818.       !(((charset[0] == 'U') || (charset[0] == 'u')) &&
  819. ((((charset[1] == 'S') || (charset[1] == 's')) &&
  820.   (charset[2] == '-') &&
  821.   ((charset[3] == 'A') || (charset[3] == 'a')) &&
  822.   ((charset[4] == 'S') || (charset[4] == 's')) &&
  823.   ((charset[5] == 'C') || (charset[5] == 'c')) &&
  824.   ((charset[6] == 'I') || (charset[6] == 'i')) &&
  825.   ((charset[7] == 'I') || (charset[7] == 'i')) && !charset[8]) ||
  826.  (((charset[1] == 'T') || (charset[1] == 't')) &&
  827.   ((charset[2] == 'F') || (charset[2] == 'f')) &&
  828.   (charset[3] == '-') && (charset[4] == '8') && !charset[5])))) {
  829.     if (utf8_text (NIL,charset,NIL,T)) utf8_searchpgm (pgm,charset);
  830.     else return; /* charset unknown */
  831.   }
  832.   if (flags & SO_OVERVIEW) { /* only if specified to use overview */
  833. /* identify messages that will be searched */
  834.     for (i = 1; i <= stream->nmsgs; ++i)
  835.       mail_elt (stream,i)->sequence = nntp_search_msg (stream,i,pgm,NIL);
  836. /* load the overview cache */
  837.     nntp_overview (stream,NIL,NIL);
  838.   }
  839. /* init in case no overview at cleanup */
  840.   memset ((void *) &ov,0,sizeof (OVERVIEW));
  841. /* otherwise do default search */
  842.   for (i = 1; i <= stream->nmsgs; ++i) {
  843.     if (((flags & SO_OVERVIEW) && ((elt = mail_elt (stream,i))->sequence) &&
  844.  nntp_parse_overview (&ov,(char *) elt->private.data)) ?
  845. nntp_search_msg (stream,i,pgm,&ov) :
  846. mail_search_msg (stream,i,NIL,pgm)) {
  847.       if (flags & SE_UID) mm_searched (stream,mail_uid (stream,i));
  848.       else { /* mark as searched, notify mail program */
  849. mail_elt (stream,i)->searched = T;
  850. if (!stream->silent) mm_searched (stream,i);
  851.       }
  852.     }
  853. /* clean up overview data */
  854.     if (ov.from) mail_free_address (&ov.from);
  855.     if (ov.subject) fs_give ((void **) &ov.subject);
  856.   }
  857. }
  858. /* NNTP search message
  859.  * Accepts: MAIL stream
  860.  *     message number
  861.  *     search program
  862.  *     overview to search (NIL means preliminary pass)
  863.  * Returns: T if found, NIL otherwise
  864.  */
  865. long nntp_search_msg (MAILSTREAM *stream,unsigned long msgno,SEARCHPGM *pgm,
  866.       OVERVIEW *ov)
  867. {
  868.   unsigned short d;
  869.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  870.   SEARCHHEADER *hdr;
  871.   SEARCHOR *or;
  872.   SEARCHPGMLIST *not;
  873.   if (pgm->msgno || pgm->uid) { /* message set searches */
  874.     SEARCHSET *set;
  875. /* message sequences */
  876.     if (set = pgm->msgno) { /* must be inside this sequence */
  877.       while (set) { /* run down until find matching range */
  878. if (set->last ? ((msgno < set->first) || (msgno > set->last)) :
  879.     msgno != set->first) set = set->next;
  880. else break;
  881.       }
  882.       if (!set) return NIL; /* not found within sequence */
  883.     }
  884.     if (set = pgm->uid) { /* must be inside this sequence */
  885.       unsigned long uid = mail_uid (stream,msgno);
  886.       while (set) { /* run down until find matching range */
  887. if (set->last ? ((uid < set->first) || (uid > set->last)) :
  888.     uid != set->first) set = set->next;
  889. else break;
  890.       }
  891.       if (!set) return NIL; /* not found within sequence */
  892.     }
  893.   }
  894.   /* Fast data searches */
  895. /* message flags */
  896.   if ((pgm->answered && !elt->answered) ||
  897.       (pgm->unanswered && elt->answered) ||
  898.       (pgm->deleted && !elt->deleted) ||
  899.       (pgm->undeleted && elt->deleted) ||
  900.       (pgm->draft && !elt->draft) ||
  901.       (pgm->undraft && elt->draft) ||
  902.       (pgm->flagged && !elt->flagged) ||
  903.       (pgm->unflagged && elt->flagged) ||
  904.       (pgm->recent && !elt->recent) ||
  905.       (pgm->old && elt->recent) ||
  906.       (pgm->seen && !elt->seen) ||
  907.       (pgm->unseen && elt->seen)) return NIL;
  908. /* keywords */
  909.   if ((pgm->keyword && !mail_search_keyword (stream,elt,pgm->keyword)) ||
  910.       (pgm->unkeyword && mail_search_keyword (stream,elt,pgm->unkeyword)))
  911.     return NIL;
  912.   if (ov) { /* only do this if real searching */
  913.     MESSAGECACHE delt;
  914. /* size ranges */
  915.     if ((pgm->larger && (ov->optional.octets <= pgm->larger)) ||
  916. (pgm->smaller && (ov->optional.octets >= pgm->smaller))) return NIL;
  917. /* date ranges */
  918.     if ((pgm->sentbefore || pgm->senton || pgm->sentsince ||
  919.  (pgm->before || pgm->on || pgm->since)) &&
  920. (!mail_parse_date (&delt,ov->date) ||
  921.  !(d = (delt.year << 9) + (delt.month << 5) + delt.day) ||
  922.  (pgm->sentbefore && (d >= pgm->sentbefore)) ||
  923.  (pgm->senton && (d != pgm->senton)) ||
  924.  (pgm->sentsince && (d < pgm->sentsince)) ||
  925.  (pgm->before && (d >= pgm->before)) ||
  926.  (pgm->on && (d != pgm->on)) ||
  927.  (pgm->since && (d < pgm->since)))) return NIL;
  928.     if ((pgm->from && !mail_search_addr (ov->from,pgm->from)) ||
  929. (pgm->subject && !mail_search_header_text (ov->subject,pgm->subject))||
  930. (pgm->message_id &&
  931.  !mail_search_header_text (ov->message_id,pgm->message_id)) ||
  932. (pgm->references &&
  933.  !mail_search_header_text (ov->references,pgm->references)))
  934.       return NIL;
  935. /* envelope searches */
  936.     if (pgm->bcc || pgm->cc || pgm->to || pgm->return_path || pgm->sender ||
  937. pgm->reply_to || pgm->in_reply_to || pgm->newsgroups ||
  938. pgm->followup_to) {
  939.       ENVELOPE *env = mail_fetchenvelope (stream,msgno);
  940.       if (!env) return NIL; /* no envelope obtained */
  941. /* search headers */
  942.       if ((pgm->bcc && !mail_search_addr (env->bcc,pgm->bcc)) ||
  943.   (pgm->cc && !mail_search_addr (env->cc,pgm->cc)) ||
  944.   (pgm->to && !mail_search_addr (env->to,pgm->to)))
  945. return NIL;
  946.       /* These criteria are not supported by IMAP and have to be emulated */
  947.       if ((pgm->return_path &&
  948.    !mail_search_addr (env->return_path,pgm->return_path)) ||
  949.   (pgm->sender && !mail_search_addr (env->sender,pgm->sender)) ||
  950.   (pgm->reply_to && !mail_search_addr (env->reply_to,pgm->reply_to)) ||
  951.   (pgm->in_reply_to &&
  952.    !mail_search_header_text (env->in_reply_to,pgm->in_reply_to)) ||
  953.   (pgm->newsgroups &&
  954.    !mail_search_header_text (env->newsgroups,pgm->newsgroups)) ||
  955.   (pgm->followup_to &&
  956.    !mail_search_header_text (env->followup_to,pgm->followup_to)))
  957. return NIL;
  958.     }
  959. /* search header lines */
  960.     for (hdr = pgm->header; hdr; hdr = hdr->next) {
  961.       char *t,*e,*v;
  962.       SIZEDTEXT s;
  963.       STRINGLIST sth,stc;
  964.       sth.next = stc.next = NIL;/* only one at a time */
  965.       sth.text.data = hdr->line.data;
  966.       sth.text.size = hdr->line.size;
  967. /* get the header text */
  968.       if ((t = mail_fetch_header (stream,msgno,NIL,&sth,&s.size,
  969.   FT_INTERNAL | FT_PEEK)) && strchr (t,':')) {
  970. if (hdr->text.size) { /* anything matches empty search string */
  971. /* non-empty, copy field data */
  972.   s.data = (unsigned char *) fs_get (s.size + 1);
  973. /* for each line */
  974.   for (v = (char *) s.data, e = t + s.size; t < e;) switch (*t) {
  975.   default: /* non-continuation, skip leading field name */
  976.     while ((t < e) && (*t++ != ':'));
  977.     if ((t < e) && (*t == ':')) t++;
  978.   case 't': case ' ': /* copy field data  */
  979.     while ((t < e) && (*t != '15') && (*t != '12')) *v++ = *t++;
  980.     *v++ = 'n'; /* tie off line */
  981.     while (((*t == '15') || (*t == '12')) && (t < e)) t++;
  982.   }
  983. /* calculate true size */
  984.   s.size = v - (char *) s.data;
  985.   *v = ''; /* tie off results */
  986.   stc.text.data = hdr->text.data;
  987.   stc.text.size = hdr->text.size;
  988. /* search header */
  989.   if (mail_search_header (&s,&stc)) fs_give ((void **) &s.data);
  990.   else { /* search failed */
  991.     fs_give ((void **) &s.data);
  992.     return NIL;
  993.   }
  994. }
  995.       }
  996.       else return NIL; /* no matching header text */
  997.     }
  998. /* search strings */
  999.     if ((pgm->text &&
  1000.  !mail_search_text (stream,msgno,NIL,pgm->text,LONGT))||
  1001. (pgm->body && !mail_search_text (stream,msgno,NIL,pgm->body,NIL)))
  1002.       return NIL;
  1003.   }
  1004. /* logical conditions */
  1005.   for (or = pgm->or; or; or = or->next)
  1006.     if (!(nntp_search_msg (stream,msgno,or->first,ov) ||
  1007.   nntp_search_msg (stream,msgno,or->second,ov))) return NIL;
  1008.   for (not = pgm->not; not; not = not->next)
  1009.     if (nntp_search_msg (stream,msgno,not->pgm,ov)) return NIL;
  1010.   return T;
  1011. }
  1012. /* NNTP sort messages
  1013.  * Accepts: mail stream
  1014.  *     character set
  1015.  *     search program
  1016.  *     sort program
  1017.  *     option flags
  1018.  * Returns: vector of sorted message sequences or NIL if error
  1019.  */
  1020. unsigned long *nntp_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
  1021.   SORTPGM *pgm,long flags)
  1022. {
  1023.   unsigned long i,start,last;
  1024.   SORTCACHE **sc;
  1025.   mailcache_t mailcache = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
  1026.   unsigned long *ret = NIL;
  1027.   sortresults_t sr = (sortresults_t) mail_parameters (NIL,GET_SORTRESULTS,NIL);
  1028.   if (spg) { /* only if a search needs to be done */
  1029.     int silent = stream->silent;
  1030.     stream->silent = T; /* don't pass up mm_searched() events */
  1031. /* search for messages */
  1032.     mail_search_full (stream,charset,spg,NIL);
  1033.     stream->silent = silent; /* restore silence state */
  1034.   }
  1035. /* initialize progress counters */
  1036.   pgm->nmsgs = pgm->progress.cached = 0;
  1037. /* pass 1: count messages to sort */
  1038.   for (i = 1,start = last = 0; i <= stream->nmsgs; ++i)
  1039.     if (mail_elt (stream,i)->searched) {
  1040.       pgm->nmsgs++;
  1041. /* have this in the sortcache already? */
  1042.       if (!((SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE))->date) {
  1043. /* no, record as last message */
  1044. last = mail_uid (stream,i);
  1045. /* and as first too if needed */
  1046. if (!start) start = last;
  1047.       }
  1048.     }
  1049.   if (pgm->nmsgs) { /* pass 2: load sort cache */
  1050.     sc = nntp_sort_loadcache (stream,pgm,start,last,flags);
  1051. /* pass 3: sort messages */
  1052.     if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags);
  1053.     fs_give ((void **) &sc); /* don't need sort vector any more */
  1054.   }
  1055. /* empty sort results */
  1056.   else ret = (unsigned long *) memset (fs_get (sizeof (unsigned long)),0,
  1057.        sizeof (unsigned long));
  1058. /* also return via callback if requested */
  1059.   if (sr) (*sr) (stream,ret,pgm->nmsgs);
  1060.   return ret;
  1061. }
  1062. /* Mail load sortcache
  1063.  * Accepts: mail stream, already searched
  1064.  *     sort program
  1065.  *     first UID to XOVER
  1066.  *     last UID to XOVER
  1067.  *     option flags
  1068.  * Returns: vector of sortcache pointers matching search
  1069.  */
  1070. SORTCACHE **nntp_sort_loadcache (MAILSTREAM *stream,SORTPGM *pgm,
  1071.  unsigned long start,unsigned long last,
  1072.  long flags)
  1073. {
  1074.   unsigned long i;
  1075.   char c,*s,*t,*v,tmp[MAILTMPLEN];
  1076.   SORTPGM *pg;
  1077.   SORTCACHE **sc,*r;
  1078.   MESSAGECACHE telt;
  1079.   ADDRESS *adr = NIL;
  1080.   mailcache_t mailcache = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
  1081. /* verify that the sortpgm is OK */
  1082.   for (pg = pgm; pg; pg = pg->next) switch (pg->function) {
  1083.   case SORTARRIVAL: /* sort by arrival date */
  1084.   case SORTSIZE: /* sort by message size */
  1085.   case SORTDATE: /* sort by date */
  1086.   case SORTFROM: /* sort by first from */
  1087.   case SORTSUBJECT: /* sort by subject */
  1088.     break;
  1089.   case SORTTO: /* sort by first to */
  1090.     mm_notify (stream,"[NNTPSORT] Can't do To-field sorting in NNTP",WARN);
  1091.     break;
  1092.   case SORTCC: /* sort by first cc */
  1093.     mm_notify (stream,"[NNTPSORT] Can't do cc-field sorting in NNTP",WARN);
  1094.     break;
  1095.   default:
  1096.     fatal ("Unknown sort function");
  1097.   }
  1098.   if (start) { /* messages need to be loaded in sortcache? */
  1099. /* yes, build range */
  1100.     if (start != last) sprintf (tmp,"%lu-%lu",start,last);
  1101.     else sprintf (tmp,"%lu",start);
  1102. /* get it from the NNTP server */
  1103.     if (!nntp_send (LOCAL->nntpstream,"XOVER",tmp) == NNTPOVER)
  1104. /* ugh, have to get it the slow way */
  1105.       return mail_sort_loadcache (stream,pgm);
  1106.     while ((s = net_getline (LOCAL->nntpstream->netstream)) && strcmp (s,".")){
  1107. /* death to embedded newlines */
  1108.       for (t = v = s; c = *v++;) if ((c != '12') && (c != '15')) *t++ = c;
  1109.       *t++ = ''; /* tie off resulting string */
  1110. /* parse XOVER response */
  1111.       if ((i = mail_msgno (stream,atol (s))) &&
  1112.   (t = strchr (s,'t')) && (v = strchr (++t,'t'))) {
  1113. *v++ = ''; /* tie off subject */
  1114. /* put stripped subject in sortcache */
  1115. (r = (SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE))->subject =
  1116.   mail_strip_subject (t);
  1117. if (t = strchr (v,'t')) {
  1118.   *t++ = ''; /* tie off from */
  1119.   if (adr = rfc822_parse_address (&adr,adr,&v,BADHOST,0)) {
  1120.     r->from = adr->mailbox;
  1121.     adr->mailbox = NIL;
  1122.     mail_free_address (&adr);
  1123.   }
  1124.   if (v = strchr (t,'t')) {
  1125.     *v++ = ''; /* tie off date */
  1126.     if (mail_parse_date (&telt,t)) r->date = mail_longdate (&telt);
  1127.     if ((v = strchr (v,'t')) && (v = strchr (++v,'t')))
  1128.       r->size = atol (++v);
  1129.   }
  1130. }
  1131.       }
  1132.       fs_give ((void **) &s);
  1133.     }
  1134.     if (s) fs_give ((void **) &s);
  1135.   }
  1136. /* calculate size of sortcache index */
  1137.   i = pgm->nmsgs * sizeof (SORTCACHE *);
  1138. /* instantiate the index */
  1139.   sc = (SORTCACHE **) memset (fs_get ((size_t) i),0,(size_t) i);
  1140. /* see what needs to be loaded */
  1141.   for (i = 1; !pgm->abort && (i <= stream->nmsgs); i++)
  1142.     if ((mail_elt (stream,i))->searched) {
  1143.       sc[pgm->progress.cached++] =
  1144. r = (SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE);
  1145.       r->pgm = pgm; /* note sort program */
  1146.       r->num = (flags & SE_UID) ? mail_uid (stream,i) : i;
  1147.       if (!r->date) r->date = r->num;
  1148.       if (!r->arrival) r->arrival = mail_uid (stream,i);
  1149.       if (!r->size) r->size = 1;
  1150.       if (!r->from) r->from = cpystr ("");
  1151.       if (!r->to) r->to = cpystr ("");
  1152.       if (!r->cc) r->cc = cpystr ("");
  1153.       if (!r->subject) r->subject = cpystr ("");
  1154.     }
  1155.   return sc;
  1156. }
  1157. /* NNTP thread messages
  1158.  * Accepts: mail stream
  1159.  *     thread type
  1160.  *     character set
  1161.  *     search program
  1162.  *     option flags
  1163.  * Returns: thread node tree
  1164.  */
  1165. THREADNODE *nntp_thread (MAILSTREAM *stream,char *type,char *charset,
  1166.  SEARCHPGM *spg,long flags)
  1167. {
  1168.   return mail_thread_msgs (stream,type,charset,spg,flags,nntp_sort);
  1169. }
  1170. /* NNTP ping mailbox
  1171.  * Accepts: MAIL stream
  1172.  * Returns: T if stream alive, else NIL
  1173.  */
  1174. long nntp_ping (MAILSTREAM *stream)
  1175. {
  1176.   return (nntp_send (LOCAL->nntpstream,"STAT",NIL) != NNTPSOFTFATAL);
  1177. }
  1178. /* NNTP check mailbox
  1179.  * Accepts: MAIL stream
  1180.  */
  1181. void nntp_check (MAILSTREAM *stream)
  1182. {
  1183. /* never do if no updates */
  1184.   if (LOCAL->dirty) newsrc_write (LOCAL->name,stream);
  1185.   LOCAL->dirty = NIL;
  1186. }
  1187. /* NNTP expunge mailbox
  1188.  * Accepts: MAIL stream
  1189.  */
  1190. void nntp_expunge (MAILSTREAM *stream)
  1191. {
  1192.   if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
  1193. }
  1194. /* NNTP copy message(s)
  1195.  * Accepts: MAIL stream
  1196.  *     sequence
  1197.  *     destination mailbox
  1198.  *     option flags
  1199.  * Returns: T if copy successful, else NIL
  1200.  */
  1201. long nntp_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
  1202. {
  1203.   mailproxycopy_t pc =
  1204.     (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
  1205.   if (pc) return (*pc) (stream,sequence,mailbox,options);
  1206.   mm_log ("Copy not valid for NNTP",ERROR);
  1207.   return NIL;
  1208. }
  1209. /* NNTP append message from stringstruct
  1210.  * Accepts: MAIL stream
  1211.  *     destination mailbox
  1212.  *     stringstruct of messages to append
  1213.  * Returns: T if append successful, else NIL
  1214.  */
  1215. long nntp_append (MAILSTREAM *stream,char *mailbox,char *flags,char *date,
  1216.   STRING *message)
  1217. {
  1218.   mm_log ("Append not valid for NNTP",ERROR);
  1219.   return NIL;
  1220. }
  1221. /* NNTP open connection
  1222.  * Accepts: network driver
  1223.  *     service host list
  1224.  *     port number
  1225.  *     service name
  1226.  *     NNTP open options
  1227.  * Returns: SEND stream on success, NIL on failure
  1228.  */
  1229. SENDSTREAM *nntp_open_full (NETDRIVER *dv,char **hostlist,char *service,
  1230.     unsigned long port,long options)
  1231. {
  1232.   SENDSTREAM *stream = NIL;
  1233.   NETSTREAM *netstream;
  1234.   NETMBX mb;
  1235.   char tmp[MAILTMPLEN];
  1236.   long reply;
  1237.   if (!(hostlist && *hostlist)) mm_log ("Missing NNTP service host",ERROR);
  1238.   else do { /* try to open connection */
  1239.     sprintf (tmp,"{%.1000s/%.20s}",*hostlist,service ? service : "nntp");
  1240.     if (!mail_valid_net_parse (tmp,&mb) || mb.anoflag || mb.secflag) {
  1241.       sprintf (tmp,"Invalid host specifier: %.80s",*hostlist);
  1242.       mm_log (tmp,ERROR);
  1243.     }
  1244.     else {
  1245. /* light tryalt flag if requested */
  1246.       mb.tryaltflag = (options & OP_TRYALT) ? T : NIL;
  1247.       if (netstream = /* try to open ordinary connection */
  1248.   net_open (&mb,dv,nntp_port ? nntp_port : port,
  1249.     (NETDRIVER *) mail_parameters (NIL,GET_ALTDRIVER,NIL),
  1250.     (char *) mail_parameters (NIL,GET_ALTNNTPNAME,NIL),
  1251.     (unsigned long)mail_parameters(NIL,GET_ALTNNTPPORT,NIL))) {
  1252. stream = (SENDSTREAM *) fs_get (sizeof (SENDSTREAM));
  1253. /* initialize stream */
  1254. memset ((void *) stream,0,sizeof (SENDSTREAM));
  1255. stream->netstream = netstream;
  1256. stream->debug = (mb.dbgflag || (options & OP_DEBUG)) ? T : NIL;
  1257. /* get server greeting */
  1258. switch ((int) (reply = nntp_reply (stream))) {
  1259. case NNTPGREET: /* allow posting */
  1260.   NNTP.post = T;
  1261.   mm_notify (NIL,stream->reply + 4,(long) NIL);
  1262.   break;
  1263. case NNTPGREETNOPOST: /* posting not allowed, must be readonly */
  1264.   if (options & OP_READONLY) {
  1265.     mm_notify (NIL,stream->reply + 4,(long) NIL);
  1266.     break;
  1267.   }
  1268. /* falls through */
  1269. default: /* anything else is an error */
  1270.   mm_log (stream->reply,ERROR);
  1271.   stream = nntp_close (stream);
  1272. }
  1273.       }
  1274.     }
  1275.   } while (!stream && *++hostlist);
  1276. /* have a session, log in if have user name */
  1277.   if (mb.user[0] && !nntp_send_auth_work (stream,&mb,tmp)) {
  1278.     nntp_close (stream); /* punt stream */
  1279.     return NIL;
  1280.   }
  1281. /* in case server demands MODE READER */
  1282.   if (stream) switch ((int) nntp_send_work (stream,"MODE","READER")) {
  1283.   case NNTPWANTAUTH: /* server wants auth first, do so and retry */
  1284.   case NNTPWANTAUTH2:
  1285.     if (nntp_send_auth_work(stream,&mb,tmp)) nntp_send(stream,"MODE","READER");
  1286.     else stream = nntp_close (stream);
  1287.     break;
  1288.   default: /* only authenticate if requested */
  1289.     if (mb.user[0] && !nntp_send_auth_work (stream,&mb,tmp))
  1290.       stream = nntp_close (stream);
  1291.     break;
  1292.   }
  1293.   return stream;
  1294. }
  1295. /* NNTP close connection
  1296.  * Accepts: SEND stream
  1297.  * Returns: NIL always
  1298.  */
  1299. SENDSTREAM *nntp_close (SENDSTREAM *stream)
  1300. {
  1301.   if (stream) { /* send "QUIT" */
  1302.     if (stream->netstream) { /* only if a living stream */
  1303.       nntp_send (stream,"QUIT",NIL);
  1304. /* close NET connection */
  1305.       net_close (stream->netstream);
  1306.     }
  1307.     if (stream->reply) fs_give ((void **) &stream->reply);
  1308.     fs_give ((void **) &stream);/* flush the stream */
  1309.   }
  1310.   return NIL;
  1311. }
  1312. /* NNTP deliver news
  1313.  * Accepts: SEND stream
  1314.  *     message envelope
  1315.  *     message body
  1316.  * Returns: T on success, NIL on failure
  1317.  */
  1318. long nntp_mail (SENDSTREAM *stream,ENVELOPE *env,BODY *body)
  1319. {
  1320.   long ret;
  1321.   char *s,path[MAILTMPLEN],tmp[8*MAILTMPLEN];
  1322.   /* Gabba gabba hey, we need some brain damage to send netnews!!!
  1323.    *
  1324.    * First, we give ourselves a frontal lobotomy, and put in some UUCP
  1325.    *  syntax.  It doesn't matter that it's completely bogus UUCP, and
  1326.    *  that UUCP has nothing to do with anything we're doing.  It's been
  1327.    *  alleged that "Path: not-for-mail" is all that's needed, but without
  1328.    *  proof, it's probably not safe to make assumptions.
  1329.    *
  1330.    * Second, we bop ourselves on the head with a ball-peen hammer.  How
  1331.    *  dare we be so presumptious as to insert a *comment* in a Date:
  1332.    *  header line.  Why, we were actually trying to be nice to a human
  1333.    *  by giving a symbolic timezone (such as PST) in addition to a
  1334.    *  numeric timezone (such as -0800).  But the gods of news transport
  1335.    *  will have none of this.  Unix weenies, tried and true, rule!!!
  1336.    *
  1337.    * Third, Netscape Collabra server doesn't give the NNTPWANTAUTH error
  1338.    *  until after requesting and receiving the entire message.  So we can't
  1339.    *  call rely upon nntp_send()'s to do the auth retry.
  1340.    */
  1341. /* RFC-1036 requires this cretinism */
  1342.   sprintf (path,"Path: %s!%s1512",net_localhost (stream->netstream),
  1343.    env->sender ? env->sender->mailbox :
  1344.    (env->from ? env->from->mailbox : "not-for-mail"));
  1345. /* here's another cretinism */
  1346.   if (s = strstr (env->date," (")) *s = NIL;
  1347.   do if ((ret = nntp_send_work (stream,"POST",NIL)) == NNTPREADY)
  1348. /* output data, return success status */
  1349.     ret = (net_soutr (stream->netstream,path) &&
  1350.    rfc822_output (tmp,env,body,nntp_soutr,stream->netstream,T)) ?
  1351.      nntp_send_work (stream,".",NIL) :
  1352.        nntp_fake (stream,NNTPSOFTFATAL,
  1353.   "NNTP connection broken (message text)");
  1354.   while (((ret == NNTPWANTAUTH) || (ret == NNTPWANTAUTH2)) &&
  1355.  nntp_send_auth (stream));
  1356.   if (s) *s = ' '; /* put the comment in the date back */
  1357.   return ret;
  1358. }
  1359. /* NNTP send command
  1360.  * Accepts: SEND stream
  1361.  *     text
  1362.  * Returns: reply code
  1363.  */
  1364. long nntp_send (SENDSTREAM *stream,char *command,char *args)
  1365. {
  1366.   long ret;
  1367.   switch ((int) (ret = nntp_send_work (stream,command,args))) {
  1368.   case NNTPWANTAUTH: /* authenticate and retry */
  1369.   case NNTPWANTAUTH2:
  1370.     if (nntp_send_auth (stream)) ret = nntp_send_work (stream,command,args);
  1371.     else { /* we're probably hosed, nuke the session */
  1372.       nntp_send (stream,"QUIT",NIL);
  1373. /* close net connection */
  1374.       net_close (stream->netstream);
  1375.       stream->netstream = NIL;
  1376.     }
  1377.   default: /* all others just return */
  1378.     break;
  1379.   }
  1380.   return ret;
  1381. }
  1382. /* NNTP send command worker routine
  1383.  * Accepts: SEND stream
  1384.  *     text
  1385.  * Returns: reply code
  1386.  */
  1387. long nntp_send_work (SENDSTREAM *stream,char *command,char *args)
  1388. {
  1389.   char tmp[MAILTMPLEN];
  1390. /* build the complete command */
  1391.   if (args) sprintf (tmp,"%s %s",command,args);
  1392.   else strcpy (tmp,command);
  1393.   if (stream->debug) mm_dlog (tmp);
  1394.   strcat (tmp,"1512");
  1395. /* send the command */
  1396.   return net_soutr (stream->netstream,tmp) ? nntp_reply (stream) :
  1397.     nntp_fake (stream,NNTPSOFTFATAL,"NNTP connection broken (command)");
  1398. }
  1399. /* NNTP send authentication if needed
  1400.  * Accepts: SEND stream
  1401.  * Returns: T if need to redo command, NIL otherwise
  1402.  */
  1403. long nntp_send_auth (SENDSTREAM *stream)
  1404. {
  1405.   NETMBX mb;
  1406.   char tmp[MAILTMPLEN];
  1407.   sprintf (tmp,"{%s/nntp}<none>",net_host (stream->netstream));
  1408.   mail_valid_net_parse (tmp,&mb);
  1409.   return nntp_send_auth_work (stream,&mb,tmp);
  1410. }
  1411. /* NNTP send authentication worker routine
  1412.  * Accepts: SEND stream
  1413.  *     NETMBX structure
  1414.  *     temporary buffer
  1415.  * Returns: T if authenticated, NIL otherwise
  1416.  */
  1417. long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *tmp)
  1418. {
  1419.   long i;
  1420.   long trial = 0;
  1421.   do { /* get user name and password */
  1422.     mm_login (mb,mb->user,tmp,trial++);
  1423.     if (!*tmp) { /* abort if he refused to give password */
  1424.       mm_log ("Login aborted",ERROR);
  1425.       break;
  1426.     }
  1427. /* do the authentication */
  1428.     if ((i = nntp_send_work (stream,"AUTHINFO USER",mb->user)) == NNTPWANTPASS)
  1429.       i = nntp_send_work (stream,"AUTHINFO PASS",tmp);
  1430. /* successful authentication */
  1431.     if (i == NNTPAUTHED) return T;
  1432.     mm_log (stream->reply,WARN);
  1433.   }
  1434.   while ((i != NNTPSOFTFATAL) && (trial < nntp_maxlogintrials));
  1435.   mm_log ("Too many NNTP authentication failures",ERROR);
  1436.   return NIL; /* authentication failed */
  1437. }
  1438. /* NNTP get reply
  1439.  * Accepts: SEND stream
  1440.  * Returns: reply code
  1441.  */
  1442. long nntp_reply (SENDSTREAM *stream)
  1443. {
  1444. /* flush old reply */
  1445.   if (stream->reply) fs_give ((void **) &stream->reply);
  1446.    /* get reply */
  1447.   if (!(stream->reply = net_getline (stream->netstream)))
  1448.     return nntp_fake(stream,NNTPSOFTFATAL,"NNTP connection broken (response)");
  1449.   if (stream->debug) mm_dlog (stream->reply);
  1450. /* handle continuation by recursion */
  1451.   if (stream->reply[3] == '-') return nntp_reply (stream);
  1452. /* return response code */
  1453.   return stream->replycode = atol (stream->reply);
  1454. }
  1455. /* NNTP set fake error
  1456.  * Accepts: SEND stream
  1457.  *     NNTP error code
  1458.  *     error text
  1459.  * Returns: error code
  1460.  */
  1461. long nntp_fake (SENDSTREAM *stream,long code,char *text)
  1462. {
  1463. /* flush any old reply */
  1464.   if (stream->reply) fs_give ((void **) &stream->reply);
  1465.    /* set up pseudo-reply string */
  1466.   stream->reply = (char *) fs_get (20+strlen (text));
  1467.   sprintf (stream->reply,"%ld %s",code,text);
  1468.   return code; /* return error code */
  1469. }
  1470. /* NNTP filter mail
  1471.  * Accepts: stream
  1472.  *     string
  1473.  * Returns: T on success, NIL on failure
  1474.  */
  1475. long nntp_soutr (void *stream,char *s)
  1476. {
  1477.   char c,*t;
  1478. /* "." on first line */
  1479.   if (s[0] == '.') net_soutr (stream,".");
  1480. /* find lines beginning with a "." */
  1481.   while (t = strstr (s,"1512.")) {
  1482.     c = *(t += 3); /* remember next character after "." */
  1483.     *t = ''; /* tie off string */
  1484. /* output prefix */
  1485.     if (!net_soutr (stream,s)) return NIL;
  1486.     *t = c; /* restore delimiter */
  1487.     s = t - 1; /* push pointer up to the "." */
  1488.   }
  1489. /* output remainder of text */
  1490.   return *s ? net_soutr (stream,s) : T;
  1491. }