imapext-2007

diff src/c-client/nntp.c @ 0:ada5e610ab86

imap-2007e
author yuuji@gentei.org
date Mon, 14 Sep 2009 15:17:45 +0900
parents
children
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/c-client/nntp.c	Mon Sep 14 15:17:45 2009 +0900
     1.3 @@ -0,0 +1,2224 @@
     1.4 +/* ========================================================================
     1.5 + * Copyright 1988-2007 University of Washington
     1.6 + *
     1.7 + * Licensed under the Apache License, Version 2.0 (the "License");
     1.8 + * you may not use this file except in compliance with the License.
     1.9 + * You may obtain a copy of the License at
    1.10 + *
    1.11 + *     http://www.apache.org/licenses/LICENSE-2.0
    1.12 + *
    1.13 + * 
    1.14 + * ========================================================================
    1.15 + */
    1.16 +
    1.17 +/*
    1.18 + * Program:	Network News Transfer Protocol (NNTP) routines
    1.19 + *
    1.20 + * Author:	Mark Crispin
    1.21 + *		Networks and Distributed Computing
    1.22 + *		Computing & Communications
    1.23 + *		University of Washington
    1.24 + *		Administration Building, AG-44
    1.25 + *		Seattle, WA  98195
    1.26 + *		Internet: MRC@CAC.Washington.EDU
    1.27 + *
    1.28 + * Date:	10 February 1992
    1.29 + * Last Edited:	6 September 2007
    1.30 + */
    1.31 +
    1.32 +
    1.33 +#include <ctype.h>
    1.34 +#include <stdio.h>
    1.35 +#include "c-client.h"
    1.36 +#include "newsrc.h"
    1.37 +#include "netmsg.h"
    1.38 +#include "flstring.h"
    1.39 +
    1.40 +/* Constants */
    1.41 +
    1.42 +#define NNTPSSLPORT (long) 563	/* assigned SSL TCP contact port */
    1.43 +#define NNTPGREET (long) 200	/* NNTP successful greeting */
    1.44 +				/* NNTP successful greeting w/o posting priv */
    1.45 +#define NNTPGREETNOPOST (long) 201
    1.46 +#define NNTPEXTOK (long) 202	/* NNTP extensions OK */
    1.47 +#define NNTPGOK (long) 211	/* NNTP group selection OK */
    1.48 +#define NNTPGLIST (long) 215	/* NNTP group list being returned */
    1.49 +#define NNTPARTICLE (long) 220	/* NNTP article file */
    1.50 +#define NNTPHEAD (long) 221	/* NNTP header text */
    1.51 +#define NNTPBODY (long) 222	/* NNTP body text */
    1.52 +#define NNTPOVER (long) 224	/* NNTP overview text */
    1.53 +#define NNTPOK (long) 240	/* NNTP OK code */
    1.54 +#define NNTPAUTHED (long) 281	/* NNTP successful authentication */
    1.55 +				/* NNTP successful authentication with data */
    1.56 +#define NNTPAUTHEDDATA (long) 282
    1.57 +#define NNTPREADY (long) 340	/* NNTP ready for data */
    1.58 +#define NNTPWANTAUTH2 (long) 380/* NNTP authentication needed (old) */
    1.59 +#define NNTPWANTPASS (long) 381	/* NNTP password needed */
    1.60 +#define NNTPTLSSTART (long) 382	/* NNTP continue with TLS negotiation */
    1.61 +#define NNTPCHALLENGE (long) 383/* NNTP challenge, want response */
    1.62 +#define NNTPSOFTFATAL (long) 400/* NNTP soft fatal code */
    1.63 +#define NNTPWANTAUTH (long) 480	/* NNTP authentication needed */
    1.64 +#define NNTPBADCMD (long) 500	/* NNTP unrecognized command */
    1.65 +#define IDLETIMEOUT (long) 3	/* defined in NNTPEXT WG base draft */
    1.66 +
    1.67 +
    1.68 +/* NNTP I/O stream local data */
    1.69 +	
    1.70 +typedef struct nntp_local {
    1.71 +  SENDSTREAM *nntpstream;	/* NNTP stream for I/O */
    1.72 +  unsigned int dirty : 1;	/* disk copy of .newsrc needs updating */
    1.73 +  unsigned int tlsflag : 1;	/* TLS session */
    1.74 +  unsigned int tlssslv23 : 1;	/* TLS using SSLv23 client method */
    1.75 +  unsigned int notlsflag : 1;	/* TLS not used in session */
    1.76 +  unsigned int sslflag : 1;	/* SSL session */
    1.77 +  unsigned int novalidate : 1;	/* certificate not validated */
    1.78 +  unsigned int xover : 1;	/* supports XOVER */
    1.79 +  unsigned int xhdr : 1;	/* supports XHDR */
    1.80 +  char *name;			/* remote newsgroup name */
    1.81 +  char *user;			/* mailbox user */
    1.82 +  char *newsrc;			/* newsrc file */
    1.83 +  char *over_fmt;		/* overview format */
    1.84 +  unsigned long msgno;		/* current text message number */
    1.85 +  FILE *txt;			/* current text */
    1.86 +  unsigned long txtsize;	/* current text size */
    1.87 +} NNTPLOCAL;
    1.88 +
    1.89 +
    1.90 +/* Convenient access to local data */
    1.91 +
    1.92 +#define LOCAL ((NNTPLOCAL *) stream->local)
    1.93 +
    1.94 +
    1.95 +/* Convenient access to protocol-specific data */
    1.96 +
    1.97 +#define NNTP stream->protocol.nntp
    1.98 +
    1.99 +
   1.100 +/* Convenient access to extensions */
   1.101 +
   1.102 +#define EXTENSION LOCAL->nntpstream->protocol.nntp.ext
   1.103 +
   1.104 +/* Function prototypes */
   1.105 +
   1.106 +DRIVER *nntp_valid (char *name);
   1.107 +DRIVER *nntp_isvalid (char *name,char *mbx);
   1.108 +void *nntp_parameters (long function,void *value);
   1.109 +void nntp_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
   1.110 +void nntp_list (MAILSTREAM *stream,char *ref,char *pat);
   1.111 +void nntp_lsub (MAILSTREAM *stream,char *ref,char *pat);
   1.112 +long nntp_canonicalize (char *ref,char *pat,char *pattern,char *wildmat);
   1.113 +long nntp_subscribe (MAILSTREAM *stream,char *mailbox);
   1.114 +long nntp_unsubscribe (MAILSTREAM *stream,char *mailbox);
   1.115 +long nntp_create (MAILSTREAM *stream,char *mailbox);
   1.116 +long nntp_delete (MAILSTREAM *stream,char *mailbox);
   1.117 +long nntp_rename (MAILSTREAM *stream,char *old,char *newname);
   1.118 +long nntp_status (MAILSTREAM *stream,char *mbx,long flags);
   1.119 +long nntp_getmap (MAILSTREAM *stream,char *name,
   1.120 +		  unsigned long first,unsigned long last,
   1.121 +		  unsigned long rnmsgs,unsigned long nmsgs,char *tmp);
   1.122 +MAILSTREAM *nntp_mopen (MAILSTREAM *stream);
   1.123 +void nntp_mclose (MAILSTREAM *stream,long options);
   1.124 +void nntp_fetchfast (MAILSTREAM *stream,char *sequence,long flags);
   1.125 +void nntp_flags (MAILSTREAM *stream,char *sequence,long flags);
   1.126 +long nntp_overview (MAILSTREAM *stream,overview_t ofn);
   1.127 +long nntp_parse_overview (OVERVIEW *ov,char *text,MESSAGECACHE *elt);
   1.128 +long nntp_over (MAILSTREAM *stream,char *sequence);
   1.129 +char *nntp_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *size,
   1.130 +		   long flags);
   1.131 +long nntp_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
   1.132 +FILE *nntp_article (MAILSTREAM *stream,char *msgid,unsigned long *size,
   1.133 +		    unsigned long *hsiz);
   1.134 +void nntp_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
   1.135 +long nntp_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags);
   1.136 +long nntp_search_msg (MAILSTREAM *stream,unsigned long msgno,SEARCHPGM *pgm,
   1.137 +		      OVERVIEW *ov);
   1.138 +unsigned long *nntp_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
   1.139 +			  SORTPGM *pgm,long flags);
   1.140 +SORTCACHE **nntp_sort_loadcache (MAILSTREAM *stream,SORTPGM *pgm,
   1.141 +				 unsigned long start,unsigned long last,
   1.142 +				 long flags);
   1.143 +THREADNODE *nntp_thread (MAILSTREAM *stream,char *type,char *charset,
   1.144 +			 SEARCHPGM *spg,long flags);
   1.145 +long nntp_ping (MAILSTREAM *stream);
   1.146 +void nntp_check (MAILSTREAM *stream);
   1.147 +long nntp_expunge (MAILSTREAM *stream,char *sequence,long options);
   1.148 +long nntp_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
   1.149 +long nntp_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
   1.150 +
   1.151 +long nntp_extensions (SENDSTREAM *stream,long flags);
   1.152 +long nntp_send (SENDSTREAM *stream,char *command,char *args);
   1.153 +long nntp_send_work (SENDSTREAM *stream,char *command,char *args);
   1.154 +long nntp_send_auth (SENDSTREAM *stream,long flags);
   1.155 +long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags);
   1.156 +void *nntp_challenge (void *s,unsigned long *len);
   1.157 +long nntp_response (void *s,char *response,unsigned long size);
   1.158 +long nntp_reply (SENDSTREAM *stream);
   1.159 +long nntp_fake (SENDSTREAM *stream,char *text);
   1.160 +long nntp_soutr (void *stream,char *s);
   1.161 +
   1.162 +/* Driver dispatch used by MAIL */
   1.163 +
   1.164 +DRIVER nntpdriver = {
   1.165 +  "nntp",			/* driver name */
   1.166 +				/* driver flags */
   1.167 +#ifdef INADEQUATE_MEMORY
   1.168 +  DR_LOWMEM |
   1.169 +#endif
   1.170 +  DR_NEWS|DR_READONLY|DR_NOFAST|DR_NAMESPACE|DR_CRLF|DR_RECYCLE|DR_XPOINT |
   1.171 +    DR_NOINTDATE|DR_NONEWMAIL|DR_HALFOPEN,
   1.172 +  (DRIVER *) NIL,		/* next driver */
   1.173 +  nntp_valid,			/* mailbox is valid for us */
   1.174 +  nntp_parameters,		/* manipulate parameters */
   1.175 +  nntp_scan,			/* scan mailboxes */
   1.176 +  nntp_list,			/* find mailboxes */
   1.177 +  nntp_lsub,			/* find subscribed mailboxes */
   1.178 +  nntp_subscribe,		/* subscribe to mailbox */
   1.179 +  nntp_unsubscribe,		/* unsubscribe from mailbox */
   1.180 +  nntp_create,			/* create mailbox */
   1.181 +  nntp_delete,			/* delete mailbox */
   1.182 +  nntp_rename,			/* rename mailbox */
   1.183 +  nntp_status,			/* status of mailbox */
   1.184 +  nntp_mopen,			/* open mailbox */
   1.185 +  nntp_mclose,			/* close mailbox */
   1.186 +  nntp_fetchfast,		/* fetch message "fast" attributes */
   1.187 +  nntp_flags,			/* fetch message flags */
   1.188 +  nntp_overview,		/* fetch overview */
   1.189 +  NIL,				/* fetch message structure */
   1.190 +  nntp_header,			/* fetch message header */
   1.191 +  nntp_text,			/* fetch message text */
   1.192 +  NIL,				/* fetch message */
   1.193 +  NIL,				/* unique identifier */
   1.194 +  NIL,				/* message number from UID */
   1.195 +  NIL,				/* modify flags */
   1.196 +  nntp_flagmsg,			/* per-message modify flags */
   1.197 +  nntp_search,			/* search for message based on criteria */
   1.198 +  nntp_sort,			/* sort messages */
   1.199 +  nntp_thread,			/* thread messages */
   1.200 +  nntp_ping,			/* ping mailbox to see if still alive */
   1.201 +  nntp_check,			/* check for new messages */
   1.202 +  nntp_expunge,			/* expunge deleted messages */
   1.203 +  nntp_copy,			/* copy messages to another mailbox */
   1.204 +  nntp_append,			/* append string message to mailbox */
   1.205 +  NIL				/* garbage collect stream */
   1.206 +};
   1.207 +
   1.208 +				/* prototype stream */
   1.209 +MAILSTREAM nntpproto = {&nntpdriver};
   1.210 +
   1.211 +
   1.212 +				/* driver parameters */
   1.213 +static unsigned long nntp_maxlogintrials = MAXLOGINTRIALS;
   1.214 +static long nntp_port = 0;
   1.215 +static long nntp_sslport = 0;
   1.216 +static unsigned long nntp_range = 0;
   1.217 +static long nntp_hidepath = 0;
   1.218 +
   1.219 +/* NNTP validate mailbox
   1.220 + * Accepts: mailbox name
   1.221 + * Returns: our driver if name is valid, NIL otherwise
   1.222 + */
   1.223 +
   1.224 +DRIVER *nntp_valid (char *name)
   1.225 +{
   1.226 +  char tmp[MAILTMPLEN];
   1.227 +  return nntp_isvalid (name,tmp);
   1.228 +}
   1.229 +
   1.230 +
   1.231 +/* NNTP validate mailbox work routine
   1.232 + * Accepts: mailbox name
   1.233 + *	    buffer for returned mailbox name
   1.234 + * Returns: our driver if name is valid, NIL otherwise
   1.235 + */
   1.236 +
   1.237 +DRIVER *nntp_isvalid (char *name,char *mbx)
   1.238 +{
   1.239 +  NETMBX mb;
   1.240 +  if (!mail_valid_net_parse (name,&mb) || strcmp (mb.service,nntpdriver.name)||
   1.241 +      mb.anoflag) return NIL;
   1.242 +  if (mb.mailbox[0] != '#') strcpy (mbx,mb.mailbox);
   1.243 +			/* namespace format name */
   1.244 +  else if ((mb.mailbox[1] == 'n') && (mb.mailbox[2] == 'e') &&
   1.245 +	   (mb.mailbox[3] == 'w') && (mb.mailbox[4] == 's') &&
   1.246 +	   (mb.mailbox[5] == '.')) strcpy (mbx,mb.mailbox+6);
   1.247 +  else return NIL;		/* bogus name */
   1.248 +  return &nntpdriver;
   1.249 +}
   1.250 +
   1.251 +/* News manipulate driver parameters
   1.252 + * Accepts: function code
   1.253 + *	    function-dependent value
   1.254 + * Returns: function-dependent return value
   1.255 + */
   1.256 +
   1.257 +void *nntp_parameters (long function,void *value)
   1.258 +{
   1.259 +  switch ((int) function) {
   1.260 +  case SET_MAXLOGINTRIALS:
   1.261 +    nntp_maxlogintrials = (unsigned long) value;
   1.262 +    break;
   1.263 +  case GET_MAXLOGINTRIALS:
   1.264 +    value = (void *) nntp_maxlogintrials;
   1.265 +    break;
   1.266 +  case SET_NNTPPORT:
   1.267 +    nntp_port = (long) value;
   1.268 +    break;
   1.269 +  case GET_NNTPPORT:
   1.270 +    value = (void *) nntp_port;
   1.271 +    break;
   1.272 +  case SET_SSLNNTPPORT:
   1.273 +    nntp_sslport = (long) value;
   1.274 +    break;
   1.275 +  case GET_SSLNNTPPORT:
   1.276 +    value = (void *) nntp_sslport;
   1.277 +    break;
   1.278 +  case SET_NNTPRANGE:
   1.279 +    nntp_range = (unsigned long) value;
   1.280 +    break;
   1.281 +  case GET_NNTPRANGE:
   1.282 +    value = (void *) nntp_range;
   1.283 +    break;
   1.284 +  case SET_NNTPHIDEPATH:
   1.285 +    nntp_hidepath = (long) value;
   1.286 +    break;
   1.287 +  case GET_NNTPHIDEPATH:
   1.288 +    value = (void *) nntp_hidepath;
   1.289 +    break;
   1.290 +  case GET_NEWSRC:
   1.291 +    if (value)
   1.292 +      value = (void *) ((NNTPLOCAL *) ((MAILSTREAM *) value)->local)->newsrc;
   1.293 +    break;
   1.294 +  case GET_IDLETIMEOUT:
   1.295 +    value = (void *) IDLETIMEOUT;
   1.296 +    break;
   1.297 +  case ENABLE_DEBUG:
   1.298 +    if (value)
   1.299 +      ((NNTPLOCAL *) ((MAILSTREAM *) value)->local)->nntpstream->debug = T;
   1.300 +    break;
   1.301 +  case DISABLE_DEBUG:
   1.302 +    if (value)
   1.303 +      ((NNTPLOCAL *) ((MAILSTREAM *) value)->local)->nntpstream->debug = NIL;
   1.304 +    break;
   1.305 +  default:
   1.306 +    value = NIL;		/* error case */
   1.307 +    break;
   1.308 +  }
   1.309 +  return value;
   1.310 +}
   1.311 +
   1.312 +/* NNTP mail scan mailboxes for string
   1.313 + * Accepts: mail stream
   1.314 + *	    reference
   1.315 + *	    pattern to search
   1.316 + *	    string to scan
   1.317 + */
   1.318 +
   1.319 +void nntp_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
   1.320 +{
   1.321 +  char tmp[MAILTMPLEN];
   1.322 +  if (nntp_canonicalize (ref,pat,tmp,NIL))
   1.323 +    mm_log ("Scan not valid for NNTP mailboxes",ERROR);
   1.324 +}
   1.325 +
   1.326 +
   1.327 +/* NNTP list newsgroups
   1.328 + * Accepts: mail stream
   1.329 + *	    reference
   1.330 + *	    pattern to search
   1.331 + */
   1.332 +
   1.333 +void nntp_list (MAILSTREAM *stream,char *ref,char *pat)
   1.334 +{
   1.335 +  MAILSTREAM *st = stream;
   1.336 +  char *s,*t,*lcl,pattern[MAILTMPLEN],name[MAILTMPLEN],wildmat[MAILTMPLEN];
   1.337 +  int showuppers = pat[strlen (pat) - 1] == '%';
   1.338 +  if (!*pat) {
   1.339 +    if (nntp_canonicalize (ref,"*",pattern,NIL)) {
   1.340 +				/* tie off name at root */
   1.341 +      if ((s = strchr (pattern,'}')) && (s = strchr (s+1,'.'))) *++s = '\0';
   1.342 +      else pattern[0] = '\0';
   1.343 +      mm_list (stream,'.',pattern,NIL);
   1.344 +    }
   1.345 +  }
   1.346 +				/* ask server for open newsgroups */
   1.347 +  else if (nntp_canonicalize (ref,pat,pattern,wildmat) &&
   1.348 +	   ((stream && LOCAL && LOCAL->nntpstream) ||
   1.349 +	    (stream = mail_open (NIL,pattern,OP_HALFOPEN|OP_SILENT))) &&
   1.350 +	   ((nntp_send (LOCAL->nntpstream,"LIST ACTIVE",
   1.351 +			wildmat[0] ? wildmat : NIL) == NNTPGLIST) ||
   1.352 +	    (nntp_send (LOCAL->nntpstream,"LIST",NIL) == NNTPGLIST))) {
   1.353 +				/* namespace format name? */
   1.354 +    if (*(lcl = strchr (strcpy (name,pattern),'}') + 1) == '#') lcl += 6;
   1.355 +				/* process data until we see final dot */
   1.356 +    while (s = net_getline (LOCAL->nntpstream->netstream)) {
   1.357 +      if ((*s == '.') && !s[1]){/* end of text */
   1.358 +	fs_give ((void **) &s);
   1.359 +	break;
   1.360 +      }
   1.361 +      if (t = strchr (s,' ')) {	/* tie off after newsgroup name */
   1.362 +	*t = '\0';
   1.363 +	strcpy (lcl,s);		/* make full form of name */
   1.364 +				/* report if match */
   1.365 +	if (pmatch_full (name,pattern,'.')) mm_list (stream,'.',name,NIL);
   1.366 +	else while (showuppers && (t = strrchr (lcl,'.'))) {
   1.367 +	  *t = '\0';		/* tie off the name */
   1.368 +	  if (pmatch_full (name,pattern,'.'))
   1.369 +	    mm_list (stream,'.',name,LATT_NOSELECT);
   1.370 +	}
   1.371 +      }
   1.372 +      fs_give ((void **) &s);	/* clean up */
   1.373 +    }
   1.374 +    if (stream != st) mail_close (stream);
   1.375 +  }
   1.376 +}
   1.377 +
   1.378 +/* NNTP list subscribed newsgroups
   1.379 + * Accepts: mail stream
   1.380 + *	    reference
   1.381 + *	    pattern to search
   1.382 + */
   1.383 +
   1.384 +void nntp_lsub (MAILSTREAM *stream,char *ref,char *pat)
   1.385 +{
   1.386 +  void *sdb = NIL;
   1.387 +  char *s,mbx[MAILTMPLEN];
   1.388 +				/* return data from newsrc */
   1.389 +  if (nntp_canonicalize (ref,pat,mbx,NIL)) newsrc_lsub (stream,mbx);
   1.390 +  if (*pat == '{') {		/* if remote pattern, must be NNTP */
   1.391 +    if (!nntp_valid (pat)) return;
   1.392 +    ref = NIL;			/* good NNTP pattern, punt reference */
   1.393 +  }
   1.394 +				/* if remote reference, must be valid NNTP */
   1.395 +  if (ref && (*ref == '{') && !nntp_valid (ref)) return;
   1.396 +				/* kludgy application of reference */
   1.397 +  if (ref && *ref) sprintf (mbx,"%s%s",ref,pat);
   1.398 +  else strcpy (mbx,pat);
   1.399 +
   1.400 +  if (s = sm_read (&sdb)) do if (nntp_valid (s) && pmatch (s,mbx))
   1.401 +    mm_lsub (stream,NIL,s,NIL);
   1.402 +  while (s = sm_read (&sdb));	/* until no more subscriptions */
   1.403 +}
   1.404 +
   1.405 +/* NNTP canonicalize newsgroup name
   1.406 + * Accepts: reference
   1.407 + *	    pattern
   1.408 + *	    returned single pattern
   1.409 + *	    returned wildmat pattern
   1.410 + * Returns: T on success, NIL on failure
   1.411 + */
   1.412 +
   1.413 +long nntp_canonicalize (char *ref,char *pat,char *pattern,char *wildmat)
   1.414 +{
   1.415 +  char *s;
   1.416 +  DRIVER *ret;
   1.417 +  if (ref && *ref) {		/* have a reference */
   1.418 +    if (!nntp_valid (ref)) return NIL;
   1.419 +    strcpy (pattern,ref);	/* copy reference to pattern */
   1.420 +				/* # overrides mailbox field in reference */
   1.421 +    if (*pat == '#') strcpy (strchr (pattern,'}') + 1,pat);
   1.422 +				/* pattern starts, reference ends, with . */
   1.423 +    else if ((*pat == '.') && (pattern[strlen (pattern) - 1] == '.'))
   1.424 +      strcat (pattern,pat + 1);	/* append, omitting one of the period */
   1.425 +    else strcat (pattern,pat);	/* anything else is just appended */
   1.426 +  }
   1.427 +  else strcpy (pattern,pat);	/* just have basic name */
   1.428 +  if ((ret = wildmat ?		/* if valid and wildmat */
   1.429 +       nntp_isvalid (pattern,wildmat) : nntp_valid (pattern)) && wildmat) {
   1.430 +				/* don't return wildmat if specials present */
   1.431 +    if (strpbrk (wildmat,",?![\\]")) wildmat[0] = '\0';
   1.432 +				/* replace all % with * */
   1.433 +    for (s = wildmat; s = strchr (s,'%'); *s = '*');
   1.434 +  }
   1.435 +  return ret ? LONGT : NIL;
   1.436 +}
   1.437 +
   1.438 +/* NNTP subscribe to mailbox
   1.439 + * Accepts: mail stream
   1.440 + *	    mailbox to add to subscription list
   1.441 + * Returns: T on success, NIL on failure
   1.442 + */
   1.443 +
   1.444 +long nntp_subscribe (MAILSTREAM *stream,char *mailbox)
   1.445 +{
   1.446 +  char mbx[MAILTMPLEN];
   1.447 +  return nntp_isvalid (mailbox,mbx) ? newsrc_update (stream,mbx,':') : NIL;
   1.448 +}
   1.449 +
   1.450 +
   1.451 +/* NNTP unsubscribe to mailbox
   1.452 + * Accepts: mail stream
   1.453 + *	    mailbox to delete from subscription list
   1.454 + * Returns: T on success, NIL on failure
   1.455 + */
   1.456 +
   1.457 +long nntp_unsubscribe (MAILSTREAM *stream,char *mailbox)
   1.458 +{
   1.459 +  char mbx[MAILTMPLEN];
   1.460 +  return nntp_isvalid (mailbox,mbx) ? newsrc_update (stream,mbx,'!') : NIL;
   1.461 +}
   1.462 +
   1.463 +/* NNTP create mailbox
   1.464 + * Accepts: mail stream
   1.465 + *	    mailbox name to create
   1.466 + * Returns: T on success, NIL on failure
   1.467 + */
   1.468 +
   1.469 +long nntp_create (MAILSTREAM *stream,char *mailbox)
   1.470 +{
   1.471 +  return NIL;			/* never valid for NNTP */
   1.472 +}
   1.473 +
   1.474 +
   1.475 +/* NNTP delete mailbox
   1.476 + *	    mailbox name to delete
   1.477 + * Returns: T on success, NIL on failure
   1.478 + */
   1.479 +
   1.480 +long nntp_delete (MAILSTREAM *stream,char *mailbox)
   1.481 +{
   1.482 +  return NIL;			/* never valid for NNTP */
   1.483 +}
   1.484 +
   1.485 +
   1.486 +/* NNTP rename mailbox
   1.487 + * Accepts: mail stream
   1.488 + *	    old mailbox name
   1.489 + *	    new mailbox name
   1.490 + * Returns: T on success, NIL on failure
   1.491 + */
   1.492 +
   1.493 +long nntp_rename (MAILSTREAM *stream,char *old,char *newname)
   1.494 +{
   1.495 +  return NIL;			/* never valid for NNTP */
   1.496 +}
   1.497 +
   1.498 +/* NNTP status
   1.499 + * Accepts: mail stream
   1.500 + *	    mailbox name
   1.501 + *	    status flags
   1.502 + * Returns: T on success, NIL on failure
   1.503 + */
   1.504 +
   1.505 +long nntp_status (MAILSTREAM *stream,char *mbx,long flags)
   1.506 +{
   1.507 +  MAILSTATUS status;
   1.508 +  NETMBX mb;
   1.509 +  unsigned long i,j,k,rnmsgs;
   1.510 +  long ret = NIL;
   1.511 +  char *s,*name,*state,tmp[MAILTMPLEN];
   1.512 +  char *old = (stream && !stream->halfopen) ? LOCAL->name : NIL;
   1.513 +  MAILSTREAM *tstream = NIL;
   1.514 +  if (!(mail_valid_net_parse (mbx,&mb) && !strcmp (mb.service,"nntp") &&
   1.515 +	*mb.mailbox &&
   1.516 +	((mb.mailbox[0] != '#') ||
   1.517 +	 ((mb.mailbox[1] == 'n') && (mb.mailbox[2] == 'e') &&
   1.518 +	  (mb.mailbox[3] == 'w') && (mb.mailbox[4] == 's') &&
   1.519 +	  (mb.mailbox[5] == '.'))))) {
   1.520 +    sprintf (tmp,"Invalid NNTP name %s",mbx);
   1.521 +    mm_log (tmp,ERROR);
   1.522 +    return NIL;
   1.523 +  }
   1.524 +				/* note mailbox name */
   1.525 +  name = (*mb.mailbox == '#') ? mb.mailbox+6 : mb.mailbox;
   1.526 +				/* stream to reuse? */
   1.527 +  if (!(stream && LOCAL->nntpstream &&
   1.528 +	mail_usable_network_stream (stream,mbx)) &&
   1.529 +      !(tstream = stream =
   1.530 +	mail_open (NIL,mbx,OP_HALFOPEN|OP_SILENT|
   1.531 +		   ((flags & SA_MULNEWSRC) ? OP_MULNEWSRC : NIL))))
   1.532 +    return NIL;			/* can't reuse or make a new one */
   1.533 +
   1.534 +  if (nntp_send (LOCAL->nntpstream,"GROUP",name) == NNTPGOK) {
   1.535 +    status.flags = flags;	/* status validity flags */
   1.536 +    k = strtoul (LOCAL->nntpstream->reply + 4,&s,10);
   1.537 +    i = strtoul (s,&s,10);	/* first assigned UID */
   1.538 +				/* next UID to be assigned */
   1.539 +    status.uidnext = (j = strtoul (s,NIL,10)) + 1;
   1.540 +				/* maximum number of messages */
   1.541 +    rnmsgs = status.messages = (i | j) ? status.uidnext - i : 0;
   1.542 +    if (k > status.messages) {	/* check for absurdity */
   1.543 +      sprintf (tmp,"NNTP SERVER BUG (impossible message count): %lu > %lu",
   1.544 +	       k,status.messages);
   1.545 +      mm_log (tmp,WARN);
   1.546 +    }
   1.547 +				/* restrict article range if needed */
   1.548 +    if (nntp_range && (status.messages > nntp_range)) {
   1.549 +      i = status.uidnext - (status.messages = nntp_range);
   1.550 +      if (k > nntp_range) k = nntp_range;
   1.551 +    }
   1.552 +				/* initially zero */
   1.553 +    status.recent = status.unseen = 0;
   1.554 +    if (!status.messages);	/* empty case */
   1.555 +				/* use server guesstimate in simple case */
   1.556 +    else if (!(flags & (SA_RECENT | SA_UNSEEN))) status.messages = k;
   1.557 +
   1.558 +				/* have newsrc state? */
   1.559 +    else if (state = newsrc_state (stream,name)) {
   1.560 +				/* yes, get the UID/sequence map */
   1.561 +      if (nntp_getmap (stream,name,i,status.uidnext - 1,rnmsgs,
   1.562 +		       status.messages,tmp)) {
   1.563 +				/* calculate true count */
   1.564 +	for (status.messages = 0;
   1.565 +	     (s = net_getline (LOCAL->nntpstream->netstream)) &&
   1.566 +	       strcmp (s,"."); ) {
   1.567 +				/* only count if in range */
   1.568 +	  if (((k = atol (s)) >= i) && (k < status.uidnext)) {
   1.569 +	    newsrc_check_uid (state,k,&status.recent,&status.unseen);
   1.570 +	    status.messages++;
   1.571 +	  }
   1.572 +	  fs_give ((void **) &s);
   1.573 +	}
   1.574 +	if (s) fs_give ((void **) &s);
   1.575 +      }
   1.576 +				/* assume c-client/NNTP map is entire range */
   1.577 +      else while (i < status.uidnext)
   1.578 +	newsrc_check_uid (state,i++,&status.recent,&status.unseen);
   1.579 +      fs_give ((void **) &state);
   1.580 +    }
   1.581 +				/* no .newsrc state, all messages new */
   1.582 +    else status.recent = status.unseen = status.messages;
   1.583 +				/* UID validity is a constant */
   1.584 +    status.uidvalidity = stream->uid_validity;
   1.585 +				/* pass status to main program */
   1.586 +    mm_status (stream,mbx,&status);
   1.587 +    ret = T;			/* succes */
   1.588 +  }
   1.589 +				/* flush temporary stream */
   1.590 +  if (tstream) mail_close (tstream);
   1.591 +				/* else reopen old newsgroup */
   1.592 +  else if (old && nntp_send (LOCAL->nntpstream,"GROUP",old) != NNTPGOK) {
   1.593 +    mm_log (LOCAL->nntpstream->reply,ERROR);
   1.594 +    stream->halfopen = T;	/* go halfopen */
   1.595 +  }
   1.596 +  return ret;			/* success */
   1.597 +}
   1.598 +
   1.599 +/* NNTP get map
   1.600 + * Accepts: stream
   1.601 + *	    newsgroup name
   1.602 + *	    first UID in map range
   1.603 + *	    last UID in map range
   1.604 + *	    reported total number of messages in newsgroup
   1.605 + *	    calculated number of messages in range
   1.606 + *	    temporary buffer
   1.607 + * Returns: T on success, NIL on failure
   1.608 + */
   1.609 +
   1.610 +long nntp_getmap (MAILSTREAM *stream,char *name,
   1.611 +		  unsigned long first,unsigned long last,
   1.612 +		  unsigned long rnmsgs,unsigned long nmsgs,char *tmp)
   1.613 +{
   1.614 +  short trylistgroup = NIL;
   1.615 +  if (rnmsgs > (nmsgs * 8))	/* small subrange? */
   1.616 +    trylistgroup = T;		/* yes, can try LISTGROUP if [X]HDR fails */
   1.617 +  else switch ((int) nntp_send (LOCAL->nntpstream,"LISTGROUP",name)) {
   1.618 +  case NNTPGOK:			/* got data */
   1.619 +    return LONGT;
   1.620 +  default:			/* else give up if server claims LISTGROUP */
   1.621 +    if (EXTENSION.listgroup) return NIL;
   1.622 +  }
   1.623 +				/* build range */
   1.624 +  sprintf (tmp,"%lu-%lu",first,last);
   1.625 +  if (EXTENSION.hdr)		/* have HDR extension? */
   1.626 +    return (nntp_send (LOCAL->nntpstream,"HDR Date",tmp) == NNTPHEAD) ?
   1.627 +      LONGT : NIL;
   1.628 +  if (LOCAL->xhdr)		/* try the experimental extension then */
   1.629 +    switch ((int) nntp_send (LOCAL->nntpstream,"XHDR Date",tmp)) {
   1.630 +    case NNTPHEAD:		/* got an overview? */
   1.631 +      return LONGT;
   1.632 +    case NNTPBADCMD:		/* unknown command? */
   1.633 +      LOCAL->xhdr = NIL;	/* disable future XHDR attempts */
   1.634 +    }
   1.635 +  if (trylistgroup &&		/* no [X]HDR, maybe do LISTGROUP after all */
   1.636 +      (nntp_send (LOCAL->nntpstream,"LISTGROUP",name) == NNTPGOK))
   1.637 +    return LONGT;
   1.638 +  return NIL;
   1.639 +}
   1.640 +
   1.641 +/* NNTP open
   1.642 + * Accepts: stream to open
   1.643 + * Returns: stream on success, NIL on failure
   1.644 + */
   1.645 +
   1.646 +MAILSTREAM *nntp_mopen (MAILSTREAM *stream)
   1.647 +{
   1.648 +  unsigned long i,j,k,nmsgs,rnmsgs;
   1.649 +  char *s,*mbx,tmp[MAILTMPLEN];
   1.650 +  FILE *f;
   1.651 +  NETMBX mb;
   1.652 +  char *newsrc = (char *) mail_parameters (NIL,GET_NEWSRC,NIL);
   1.653 +  newsrcquery_t nq = (newsrcquery_t) mail_parameters (NIL,GET_NEWSRCQUERY,NIL);
   1.654 +  SENDSTREAM *nstream = NIL;
   1.655 +				/* return prototype for OP_PROTOTYPE call */
   1.656 +  if (!stream) return &nntpproto;
   1.657 +  mail_valid_net_parse (stream->mailbox,&mb);
   1.658 +				/* note mailbox anme */
   1.659 +  mbx = (*mb.mailbox == '#') ? mb.mailbox+6 : mb.mailbox;
   1.660 +  if (LOCAL) {			/* recycle stream */
   1.661 +    nstream = LOCAL->nntpstream;/* remember NNTP protocol stream */
   1.662 +    sprintf (tmp,"Reusing connection to %s",net_host (nstream->netstream));
   1.663 +    if (!stream->silent) mm_log (tmp,(long) NIL);
   1.664 +    if (stream->rdonly) mb.readonlyflag = T;
   1.665 +    if (LOCAL->tlsflag) mb.tlsflag = T;
   1.666 +    if (LOCAL->tlssslv23) mb.tlssslv23 = T;
   1.667 +    if (LOCAL->notlsflag) mb.notlsflag = T;
   1.668 +    if (LOCAL->sslflag) mb.sslflag = T;
   1.669 +    if (LOCAL->novalidate) mb.novalidate = T;
   1.670 +    if (LOCAL->nntpstream->loser) mb.loser = T;
   1.671 +    if (stream->secure) mb.secflag = T;
   1.672 +    LOCAL->nntpstream = NIL;	/* keep nntp_mclose() from punting it */
   1.673 +    nntp_mclose (stream,NIL);	/* do close action */
   1.674 +    stream->dtb = &nntpdriver;	/* reattach this driver */
   1.675 +  }
   1.676 +				/* copy flags */
   1.677 +  if (mb.dbgflag) stream->debug = T;
   1.678 +  if (mb.readonlyflag) stream->rdonly = T;
   1.679 +  if (mb.secflag) stream->secure = T;
   1.680 +  mb.trysslflag = stream->tryssl = (mb.trysslflag || stream->tryssl) ? T : NIL;
   1.681 +  if (!nstream) {		/* open NNTP now if not already open */
   1.682 +    char *hostlist[2];
   1.683 +    hostlist[0] = strcpy (tmp,mb.host);
   1.684 +    if (mb.port || nntp_port)
   1.685 +      sprintf (tmp + strlen (tmp),":%lu",mb.port ? mb.port : nntp_port);
   1.686 +    if (mb.tlsflag) strcat (tmp,"/tls");
   1.687 +    if (mb.tlssslv23) strcat (tmp,"/tls-sslv23");
   1.688 +    if (mb.notlsflag) strcat (tmp,"/notls");
   1.689 +    if (mb.sslflag) strcat (tmp,"/ssl");
   1.690 +    if (mb.novalidate) strcat (tmp,"/novalidate-cert");
   1.691 +    if (mb.loser) strcat (tmp,"/loser");
   1.692 +    if (mb.secflag) strcat (tmp,"/secure");
   1.693 +    if (mb.user[0]) sprintf (tmp + strlen (tmp),"/user=\"%s\"",mb.user);
   1.694 +    hostlist[1] = NIL;
   1.695 +    if (!(nstream = nntp_open (hostlist,NOP_READONLY |
   1.696 +			       (stream->debug ? NOP_DEBUG : NIL)))) return NIL;
   1.697 +  }
   1.698 +
   1.699 +				/* always zero messages if halfopen */
   1.700 +  if (stream->halfopen) i = j = k = rnmsgs = nmsgs = 0;
   1.701 +				/* otherwise open the newsgroup */
   1.702 +  else if (nntp_send (nstream,"GROUP",mbx) == NNTPGOK) {
   1.703 +    k = strtoul (nstream->reply + 4,&s,10);
   1.704 +    i = strtoul (s,&s,10);
   1.705 +    stream->uid_last = j = strtoul (s,&s,10);
   1.706 +    rnmsgs = nmsgs = (i | j) ? 1 + j - i : 0;
   1.707 +    if (k > nmsgs) {		/* check for absurdity */
   1.708 +      sprintf (tmp,"NNTP SERVER BUG (impossible message count): %lu > %lu",
   1.709 +	       k,nmsgs);
   1.710 +      mm_log (tmp,WARN);
   1.711 +    }
   1.712 +				/* restrict article range if needed */
   1.713 +    if (nntp_range && (nmsgs > nntp_range)) i = 1 + j - (nmsgs = nntp_range);
   1.714 +  }
   1.715 +  else {			/* no such newsgroup */
   1.716 +    mm_log (nstream->reply,ERROR);
   1.717 +    nntp_close (nstream);	/* punt stream */
   1.718 +    return NIL;
   1.719 +  }
   1.720 +				/* instantiate local data */
   1.721 +  stream->local = memset (fs_get (sizeof (NNTPLOCAL)),0,sizeof (NNTPLOCAL));
   1.722 +  LOCAL->nntpstream = nstream;
   1.723 +				/* save state for future recycling */
   1.724 +  if (mb.tlsflag) LOCAL->tlsflag = T;
   1.725 +  if (mb.tlssslv23) LOCAL->tlssslv23 = T;
   1.726 +  if (mb.notlsflag) LOCAL->notlsflag = T;
   1.727 +  if (mb.sslflag) LOCAL->sslflag = T;
   1.728 +  if (mb.novalidate) LOCAL->novalidate = T;
   1.729 +  if (mb.loser) LOCAL->nntpstream->loser = T;
   1.730 +				/* assume present until proven otherwise */
   1.731 +  LOCAL->xhdr = LOCAL->xover = T;
   1.732 +  LOCAL->name = cpystr (mbx);	/* copy newsgroup name */
   1.733 +  if (stream->mulnewsrc) {	/* want to use multiple .newsrc files? */
   1.734 +    strcpy (tmp,newsrc);
   1.735 +    s = tmp + strlen (tmp);	/* end of string */
   1.736 +    *s++ = '-';			/* hyphen delimiter and host */
   1.737 +    lcase (strcpy (s,(long) mail_parameters (NIL,GET_NEWSRCCANONHOST,NIL) ?
   1.738 +		   net_host (nstream->netstream) : mb.host));
   1.739 +    LOCAL->newsrc = cpystr (nq ? (*nq) (stream,tmp,newsrc) : tmp);
   1.740 +  }
   1.741 +  else LOCAL->newsrc = cpystr (newsrc);
   1.742 +  if (mb.user[0]) LOCAL->user = cpystr (mb.user);
   1.743 +  stream->sequence++;		/* bump sequence number */
   1.744 +  stream->rdonly = stream->perm_deleted = T;
   1.745 +				/* UIDs are always valid */
   1.746 +  stream->uid_validity = 0xbeefface;
   1.747 +  sprintf (tmp,"{%s:%lu/nntp",(long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ?
   1.748 +	   net_host (nstream->netstream) : mb.host,
   1.749 +	   net_port (nstream->netstream));
   1.750 +  if (LOCAL->tlsflag) strcat (tmp,"/tls");
   1.751 +  if (LOCAL->tlssslv23) strcat (tmp,"/tls-sslv23");
   1.752 +  if (LOCAL->notlsflag) strcat (tmp,"/notls");
   1.753 +  if (LOCAL->sslflag) strcat (tmp,"/ssl");
   1.754 +  if (LOCAL->novalidate) strcat (tmp,"/novalidate-cert");
   1.755 +  if (LOCAL->nntpstream->loser) strcat (tmp,"/loser");
   1.756 +  if (stream->secure) strcat (tmp,"/secure");
   1.757 +  if (stream->rdonly) strcat (tmp,"/readonly");
   1.758 +  if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user=\"%s\"",LOCAL->user);
   1.759 +  if (stream->halfopen) strcat (tmp,"}<no_mailbox>");
   1.760 +  else sprintf (tmp + strlen (tmp),"}#news.%s",mbx);
   1.761 +  fs_give ((void **) &stream->mailbox);
   1.762 +  stream->mailbox = cpystr (tmp);
   1.763 +
   1.764 +  if (EXTENSION.over &&		/* get overview format if have OVER */
   1.765 +      (nntp_send (LOCAL->nntpstream,"LIST","OVERVIEW.FMT") == NNTPGLIST) &&
   1.766 +      (f = netmsg_slurp (LOCAL->nntpstream->netstream,&k,NIL))) {
   1.767 +    fread (LOCAL->over_fmt = (char *) fs_get ((size_t) k + 3),
   1.768 +	   (size_t) 1,(size_t) k,f);
   1.769 +    LOCAL->over_fmt[k] = '\0';
   1.770 +    fclose (f);			/* flush temp file */
   1.771 +  }
   1.772 +  if (nmsgs) {			/* if any messages exist */
   1.773 +    short silent = stream->silent;
   1.774 +    stream->silent = T;		/* don't notify main program yet */
   1.775 +    mail_exists (stream,nmsgs);	/* silently set the cache to the guesstimate */
   1.776 +				/* get UID/sequence map, nuke holes */
   1.777 +    if (nntp_getmap (stream,mbx,i,j,rnmsgs,nmsgs,tmp)) {
   1.778 +      for (nmsgs = 0;		/* calculate true count */
   1.779 +	   (s = net_getline (nstream->netstream)) && strcmp (s,"."); ) {
   1.780 +	if ((k = atol (s)) > j){/* discard too high article numbers */
   1.781 +	  sprintf (tmp,"NNTP SERVER BUG (out of range article ID): %lu > %lu",
   1.782 +		   k,j);
   1.783 +	  mm_notify (stream,tmp,NIL);
   1.784 +	  stream->unhealthy = T;
   1.785 +	}
   1.786 +	else if (k >= i) {	/* silently ignore too-low article numbers */
   1.787 +				/* guard against server returning extra msgs */
   1.788 +	  if (nmsgs == stream->nmsgs) mail_exists (stream,nmsgs+1);
   1.789 +				/* create elt for this message, set UID */
   1.790 +	  mail_elt (stream,++nmsgs)->private.uid = k;
   1.791 +	}
   1.792 +	fs_give ((void **) &s);
   1.793 +      }
   1.794 +      if (s) fs_give ((void **) &s);
   1.795 +    }
   1.796 +				/* assume c-client/NNTP map is entire range */
   1.797 +    else for (k = 1; k <= nmsgs; k++) mail_elt (stream,k)->private.uid = i++;
   1.798 +    stream->unhealthy = NIL;	/* set healthy */
   1.799 +    stream->nmsgs = 0;		/* whack it back down */
   1.800 +    stream->silent = silent;	/* restore old silent setting */
   1.801 +    mail_exists (stream,nmsgs);	/* notify upper level that messages exist */
   1.802 +				/* read .newsrc entries */
   1.803 +    mail_recent (stream,newsrc_read (mbx,stream));
   1.804 +  }
   1.805 +  else {			/* empty newsgroup or halfopen */
   1.806 +    if (!(stream->silent || stream->halfopen)) {
   1.807 +      sprintf (tmp,"Newsgroup %s is empty",mbx);
   1.808 +      mm_log (tmp,WARN);
   1.809 +    }
   1.810 +    mail_exists (stream,(long) 0);
   1.811 +    mail_recent (stream,(long) 0);
   1.812 +  }
   1.813 +  return stream;		/* return stream to caller */
   1.814 +}
   1.815 +
   1.816 +/* NNTP close
   1.817 + * Accepts: MAIL stream
   1.818 + *	    option flags
   1.819 + */
   1.820 +
   1.821 +void nntp_mclose (MAILSTREAM *stream,long options)
   1.822 +{
   1.823 +  unsigned long i;
   1.824 +  MESSAGECACHE *elt;
   1.825 +  if (LOCAL) {			/* only if a file is open */
   1.826 +    nntp_check (stream);	/* dump final checkpoint */
   1.827 +    if (LOCAL->over_fmt) fs_give ((void **) &LOCAL->over_fmt);
   1.828 +    if (LOCAL->name) fs_give ((void **) &LOCAL->name);
   1.829 +    if (LOCAL->user) fs_give ((void **) &LOCAL->user);
   1.830 +    if (LOCAL->newsrc) fs_give ((void **) &LOCAL->newsrc);
   1.831 +    if (LOCAL->txt) fclose (LOCAL->txt);
   1.832 +				/* close NNTP connection */
   1.833 +    if (LOCAL->nntpstream) nntp_close (LOCAL->nntpstream);
   1.834 +    for (i = 1; i <= stream->nmsgs; i++)
   1.835 +      if ((elt = mail_elt (stream,i))->private.spare.ptr)
   1.836 +	fs_give ((void **) &elt->private.spare.ptr);
   1.837 +				/* nuke the local data */
   1.838 +    fs_give ((void **) &stream->local);
   1.839 +    stream->dtb = NIL;		/* log out the DTB */
   1.840 +  }
   1.841 +}
   1.842 +
   1.843 +/* NNTP fetch fast information
   1.844 + * Accepts: MAIL stream
   1.845 + *	    sequence
   1.846 + *	    option flags
   1.847 + * This is ugly and slow
   1.848 + */
   1.849 +
   1.850 +void nntp_fetchfast (MAILSTREAM *stream,char *sequence,long flags)
   1.851 +{
   1.852 +  unsigned long i;
   1.853 +  MESSAGECACHE *elt;
   1.854 +				/* get sequence */
   1.855 +  if (stream && LOCAL && ((flags & FT_UID) ?
   1.856 +			  mail_uid_sequence (stream,sequence) :
   1.857 +			  mail_sequence (stream,sequence)))
   1.858 +    for (i = 1; i <= stream->nmsgs; i++) {
   1.859 +      if ((elt = mail_elt (stream,i))->sequence && (elt->valid = T) &&
   1.860 +	  !(elt->day && elt->rfc822_size)) {
   1.861 +	ENVELOPE **env = NIL;
   1.862 +	ENVELOPE *e = NIL;
   1.863 +	if (!stream->scache) env = &elt->private.msg.env;
   1.864 +	else if (stream->msgno == i) env = &stream->env;
   1.865 +	else env = &e;
   1.866 +	if (!*env || !elt->rfc822_size) {
   1.867 +	  STRING bs;
   1.868 +	  unsigned long hs;
   1.869 +	  char *ht = (*stream->dtb->header) (stream,i,&hs,NIL);
   1.870 +				/* need to make an envelope? */
   1.871 +	  if (!*env) rfc822_parse_msg (env,NIL,ht,hs,NIL,BADHOST,
   1.872 +				       stream->dtb->flags);
   1.873 +				/* need message size too, ugh */
   1.874 +	  if (!elt->rfc822_size) {
   1.875 +	    (*stream->dtb->text) (stream,i,&bs,FT_PEEK);
   1.876 +	    elt->rfc822_size = hs + SIZE (&bs) - GETPOS (&bs);
   1.877 +	  }
   1.878 +	}
   1.879 +				/* if need date, have date in envelope? */
   1.880 +	if (!elt->day && *env && (*env)->date)
   1.881 +	  mail_parse_date (elt,(*env)->date);
   1.882 +				/* sigh, fill in bogus default */
   1.883 +	if (!elt->day) elt->day = elt->month = 1;
   1.884 +	mail_free_envelope (&e);
   1.885 +      }
   1.886 +    }
   1.887 +}
   1.888 +
   1.889 +/* NNTP fetch flags
   1.890 + * Accepts: MAIL stream
   1.891 + *	    sequence
   1.892 + *	    option flags
   1.893 + */
   1.894 +
   1.895 +void nntp_flags (MAILSTREAM *stream,char *sequence,long flags)
   1.896 +{
   1.897 +  unsigned long i;
   1.898 +  if ((flags & FT_UID) ?	/* validate all elts */
   1.899 +      mail_uid_sequence (stream,sequence) : mail_sequence (stream,sequence))
   1.900 +    for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->valid = T;
   1.901 +}
   1.902 +
   1.903 +/* NNTP fetch overview
   1.904 + * Accepts: MAIL stream, sequence bits set
   1.905 + *	    overview return function
   1.906 + * Returns: T if successful, NIL otherwise
   1.907 + */
   1.908 +
   1.909 +long nntp_overview (MAILSTREAM *stream,overview_t ofn)
   1.910 +{
   1.911 +  unsigned long i,j,k,uid;
   1.912 +  char c,*s,*t,*v,tmp[MAILTMPLEN];
   1.913 +  MESSAGECACHE *elt;
   1.914 +  OVERVIEW ov;
   1.915 +  if (!LOCAL->nntpstream->netstream) return NIL;
   1.916 +				/* scan sequence to load cache */
   1.917 +  for (i = 1; i <= stream->nmsgs; i++)
   1.918 +				/* have cached overview yet? */
   1.919 +    if ((elt = mail_elt (stream,i))->sequence && !elt->private.spare.ptr) {
   1.920 +      for (j = i + 1;		/* no, find end of cache gap range */
   1.921 +	   (j <= stream->nmsgs) && (elt = mail_elt (stream,j))->sequence &&
   1.922 +	   !elt->private.spare.ptr; j++);
   1.923 +				/* make NNTP range */
   1.924 +      sprintf (tmp,(i == (j - 1)) ? "%lu" : "%lu-%lu",mail_uid (stream,i),
   1.925 +	       mail_uid (stream,j - 1));
   1.926 +      i = j;			/* advance beyond gap */
   1.927 +				/* ask server for overview data to cache */
   1.928 +      if (nntp_over (stream,tmp)) {
   1.929 +	while ((s = net_getline (LOCAL->nntpstream->netstream)) &&
   1.930 +	       strcmp (s,".")) {
   1.931 +				/* death to embedded newlines */
   1.932 +	  for (t = v = s; c = *v++;)
   1.933 +	    if ((c != '\012') && (c != '\015')) *t++ = c;
   1.934 +	  *t++ = '\0';		/* tie off string in case it was shortened */
   1.935 +				/* cache the overview if found its sequence */
   1.936 +	  if ((uid = atol (s)) && (k = mail_msgno (stream,uid)) &&
   1.937 +	      (t = strchr (s,'\t'))) {
   1.938 +	    if ((elt = mail_elt (stream,k))->private.spare.ptr)
   1.939 +	      fs_give ((void **) &elt->private.spare.ptr);
   1.940 +	    elt->private.spare.ptr = cpystr (t + 1);
   1.941 +	  }
   1.942 +	  else {		/* shouldn't happen, snarl if it does */
   1.943 +	    sprintf (tmp,"Server returned data for unknown UID %lu",uid);
   1.944 +	    mm_notify (stream,tmp,WARN);
   1.945 +	    stream->unhealthy = T;
   1.946 +	  }
   1.947 +				/* flush the overview */
   1.948 +	  fs_give ((void **) &s);
   1.949 +	}
   1.950 +	stream->unhealthy = NIL;/* set healthy */
   1.951 +				/* flush the terminating dot */
   1.952 +	if (s) fs_give ((void **) &s);
   1.953 +      }
   1.954 +      else i = stream->nmsgs;	/* OVER failed, punt cache load */
   1.955 +    }
   1.956 +
   1.957 +				/* now scan sequence to return overviews */
   1.958 +  if (ofn) for (i = 1; i <= stream->nmsgs; i++)
   1.959 +    if ((elt = mail_elt (stream,i))->sequence) {
   1.960 +      uid = mail_uid (stream,i);/* UID for this message */
   1.961 +				/* parse cached overview */
   1.962 +      if (nntp_parse_overview (&ov,s = (char *) elt->private.spare.ptr,elt))
   1.963 +	(*ofn) (stream,uid,&ov,i);
   1.964 +      else {			/* parse failed */
   1.965 +	(*ofn) (stream,uid,NIL,i);
   1.966 +	if (s && *s) {		/* unusable cached entry? */
   1.967 +	  sprintf (tmp,"Unable to parse overview for UID %lu: %.500s",uid,s);
   1.968 +	  mm_notify (stream,tmp,WARN);
   1.969 +	  stream->unhealthy = T;
   1.970 +				/* erase it from the cache */
   1.971 +	  fs_give ((void **) &s);
   1.972 +	}
   1.973 +	stream->unhealthy = NIL;/* set healthy */
   1.974 +				/* insert empty cached text as necessary */
   1.975 +	if (!s) elt->private.spare.ptr = cpystr ("");
   1.976 +      }
   1.977 +				/* clean up overview data */
   1.978 +      if (ov.from) mail_free_address (&ov.from);
   1.979 +      if (ov.subject) fs_give ((void **) &ov.subject);
   1.980 +    }
   1.981 +  return T;
   1.982 +}
   1.983 +
   1.984 +/* Send OVER to NNTP server
   1.985 + * Accepts: mail stream
   1.986 + *	    sequence to send
   1.987 + * Returns: T if success and overviews will follow, else NIL
   1.988 + */
   1.989 +
   1.990 +long nntp_over (MAILSTREAM *stream,char *sequence)
   1.991 +{
   1.992 +  unsigned char *s;
   1.993 +				/* test for Netscape Collabra server */
   1.994 +  if (EXTENSION.over && LOCAL->xover &&
   1.995 +      nntp_send (LOCAL->nntpstream,"OVER","0") == NNTPOVER) {
   1.996 +    /* "Netscape-Collabra/3.52 03615 NNTP" responds to the OVER command with
   1.997 +     * a bogus "Subject:From:Date:Bytes:Lines" response followed by overviews
   1.998 +     * which lack the Message-ID and References:.  This violates the draft
   1.999 +     * NNTP specification (draft-ietf-nntpext-base-18.txt as of this writing).
  1.1000 +     * XOVER works fine.
  1.1001 +     */
  1.1002 +    while ((s = net_getline (LOCAL->nntpstream->netstream)) && strcmp (s,".")){
  1.1003 +      if (!isdigit (*s)) {	/* is it that fetid piece of reptile dung? */
  1.1004 +	EXTENSION.over = NIL;	/* sure smells like it */
  1.1005 +	mm_log ("Working around Netscape Collabra bug",WARN);
  1.1006 +      }
  1.1007 +      fs_give ((void **) &s);	/* flush the overview */
  1.1008 +    }
  1.1009 +    if (s) fs_give ((void **) &s);
  1.1010 +				/* don't do this test again */
  1.1011 +    if (EXTENSION.over) LOCAL->xover = NIL;
  1.1012 +  }
  1.1013 +  if (EXTENSION.over)		/* have OVER extension? */
  1.1014 +    return (nntp_send (LOCAL->nntpstream,"OVER",sequence) == NNTPOVER) ?
  1.1015 +      LONGT : NIL;
  1.1016 +  if (LOCAL->xover)		/* try the experiment extension then */
  1.1017 +    switch ((int) nntp_send (LOCAL->nntpstream,"XOVER",sequence)) {
  1.1018 +    case NNTPOVER:		/* got an overview? */
  1.1019 +      return LONGT;
  1.1020 +    case NNTPBADCMD:		/* unknown command? */
  1.1021 +      LOCAL->xover = NIL;	/* disable future XOVER attempts */
  1.1022 +    }
  1.1023 +  return NIL;
  1.1024 +}
  1.1025 +
  1.1026 +/* Parse OVERVIEW struct from cached NNTP OVER response
  1.1027 + * Accepts: struct to load
  1.1028 + *	    cached OVER response
  1.1029 + *	    internaldate
  1.1030 + * Returns: T if success, NIL if fail
  1.1031 + */
  1.1032 +
  1.1033 +long nntp_parse_overview (OVERVIEW *ov,char *text,MESSAGECACHE *elt)
  1.1034 +{
  1.1035 +  char *t;
  1.1036 +				/* nothing in overview yet */
  1.1037 +  memset ((void *) ov,0,sizeof (OVERVIEW));
  1.1038 +				/* no cached data */
  1.1039 +  if (!(text && *text)) return NIL;
  1.1040 +  ov->subject = cpystr (text);	/* make hackable copy of overview */
  1.1041 +				/* find end of Subject */
  1.1042 +  if (t = strchr (ov->subject,'\t')) {
  1.1043 +    *t++ = '\0';		/* tie off Subject, point to From */
  1.1044 +				/* find end of From */
  1.1045 +    if (ov->date = strchr (t,'\t')) {
  1.1046 +      *ov->date++ = '\0';	/* tie off From, point to Date */
  1.1047 +				/* load internaldate too */
  1.1048 +      if (!elt->day) mail_parse_date (elt,ov->date);
  1.1049 +				/* parse From */
  1.1050 +      rfc822_parse_adrlist (&ov->from,t,BADHOST);
  1.1051 +				/* find end of Date */
  1.1052 +      if (ov->message_id = strchr (ov->date,'\t')) {
  1.1053 +				/* tie off Date, point to Message-ID */
  1.1054 +	*ov->message_id++ = '\0';
  1.1055 +				/* find end of Message-ID */
  1.1056 +	if (ov->references = strchr (ov->message_id,'\t')) {
  1.1057 +				/* tie off Message-ID, point to References */
  1.1058 +	  *ov->references++ = '\0';
  1.1059 +				/* fine end of References */
  1.1060 +	  if (t = strchr (ov->references,'\t')) {
  1.1061 +	    *t++ = '\0';	/* tie off References, point to octet size */
  1.1062 +				/* parse size of message in octets */
  1.1063 +	    ov->optional.octets = atol (t);
  1.1064 +				/* find end of size */
  1.1065 +	    if (t = strchr (t,'\t')) {
  1.1066 +				/* parse size of message in lines */
  1.1067 +	      ov->optional.lines = atol (++t);
  1.1068 +				/* find Xref */
  1.1069 +	      if (ov->optional.xref = strchr (t,'\t'))
  1.1070 +		*ov->optional.xref++ = '\0';
  1.1071 +	    }
  1.1072 +	  }
  1.1073 +	}
  1.1074 +      }
  1.1075 +    }
  1.1076 +  }
  1.1077 +  return ov->references ? T : NIL;
  1.1078 +}
  1.1079 +
  1.1080 +/* NNTP fetch header as text
  1.1081 + * Accepts: mail stream
  1.1082 + *	    message number
  1.1083 + *	    pointer to return size
  1.1084 + *	    flags
  1.1085 + * Returns: header text
  1.1086 + */
  1.1087 +
  1.1088 +char *nntp_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *size,
  1.1089 +		   long flags)
  1.1090 +{
  1.1091 +  char tmp[MAILTMPLEN];
  1.1092 +  MESSAGECACHE *elt;
  1.1093 +  FILE *f;
  1.1094 +  *size = 0;
  1.1095 +  if ((flags & FT_UID) && !(msgno = mail_msgno (stream,msgno))) return "";
  1.1096 +				/* have header text? */
  1.1097 +  if (!(elt = mail_elt (stream,msgno))->private.msg.header.text.data) {
  1.1098 +    sprintf (tmp,"%lu",mail_uid (stream,msgno));
  1.1099 +				/* get header text */
  1.1100 +    switch (nntp_send (LOCAL->nntpstream,"HEAD",tmp)) {
  1.1101 +    case NNTPHEAD:
  1.1102 +      if (f = netmsg_slurp (LOCAL->nntpstream->netstream,size,NIL)) {
  1.1103 +	fread (elt->private.msg.header.text.data =
  1.1104 +	       (unsigned char *) fs_get ((size_t) *size + 3),
  1.1105 +	       (size_t) 1,(size_t) *size,f);
  1.1106 +	fclose (f);		/* flush temp file */
  1.1107 +				/* tie off header with extra CRLF and NUL */
  1.1108 +	elt->private.msg.header.text.data[*size] = '\015';
  1.1109 +	elt->private.msg.header.text.data[++*size] = '\012';
  1.1110 +	elt->private.msg.header.text.data[++*size] = '\0';
  1.1111 +	elt->private.msg.header.text.size = *size;
  1.1112 +	elt->valid = T;		/* make elt valid now */
  1.1113 +	break;
  1.1114 +      }
  1.1115 +				/* fall into default case */
  1.1116 +    default:			/* failed, mark as deleted and empty */
  1.1117 +      elt->valid = elt->deleted = T;
  1.1118 +    case NNTPSOFTFATAL:		/* don't mark deleted if stream dead */
  1.1119 +      *size = elt->private.msg.header.text.size = 0;
  1.1120 +      break;
  1.1121 +    }
  1.1122 +  }
  1.1123 +				/* just return size of text */
  1.1124 +  else *size = elt->private.msg.header.text.size;
  1.1125 +  return elt->private.msg.header.text.data ?
  1.1126 +    (char *) elt->private.msg.header.text.data : "";
  1.1127 +}
  1.1128 +
  1.1129 +/* NNTP fetch body
  1.1130 + * Accepts: mail stream
  1.1131 + *	    message number
  1.1132 + *	    pointer to stringstruct to initialize
  1.1133 + *	    flags
  1.1134 + * Returns: T if successful, else NIL
  1.1135 + */
  1.1136 +
  1.1137 +long nntp_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
  1.1138 +{
  1.1139 +  char tmp[MAILTMPLEN];
  1.1140 +  MESSAGECACHE *elt;
  1.1141 +  INIT (bs,mail_string,(void *) "",0);
  1.1142 +  if ((flags & FT_UID) && !(msgno = mail_msgno (stream,msgno))) return NIL;
  1.1143 +  elt = mail_elt (stream,msgno);
  1.1144 +				/* different message, flush cache */
  1.1145 +  if (LOCAL->txt && (LOCAL->msgno != msgno)) {
  1.1146 +    fclose (LOCAL->txt);
  1.1147 +    LOCAL->txt = NIL;
  1.1148 +  }
  1.1149 +  LOCAL->msgno = msgno;		/* note cached message */
  1.1150 +  if (!LOCAL->txt) {		/* have file for this message? */
  1.1151 +    sprintf (tmp,"%lu",elt->private.uid);
  1.1152 +    switch (nntp_send (LOCAL->nntpstream,"BODY",tmp)) {
  1.1153 +    case NNTPBODY:
  1.1154 +      if (LOCAL->txt = netmsg_slurp (LOCAL->nntpstream->netstream,
  1.1155 +				     &LOCAL->txtsize,NIL)) break;
  1.1156 +				/* fall into default case */
  1.1157 +    default:			/* failed, mark as deleted */
  1.1158 +      elt->deleted = T;
  1.1159 +    case NNTPSOFTFATAL:		/* don't mark deleted if stream dead */
  1.1160 +      return NIL;
  1.1161 +    }
  1.1162 +  }
  1.1163 +  if (!(flags & FT_PEEK)) {	/* mark seen if needed */
  1.1164 +    elt->seen = T;
  1.1165 +    mm_flags (stream,elt->msgno);
  1.1166 +  }
  1.1167 +  INIT (bs,file_string,(void *) LOCAL->txt,LOCAL->txtsize);
  1.1168 +  return T;
  1.1169 +}
  1.1170 +
  1.1171 +/* NNTP fetch article from message ID (for news: URL support)
  1.1172 + * Accepts: mail stream
  1.1173 + *	    message ID
  1.1174 + *	    pointer to return total message size
  1.1175 + *	    pointer to return file size
  1.1176 + * Returns: FILE * to message if successful, else NIL
  1.1177 + */
  1.1178 +
  1.1179 +FILE *nntp_article (MAILSTREAM *stream,char *msgid,unsigned long *size,
  1.1180 +		    unsigned long *hsiz)
  1.1181 +{
  1.1182 +  return (nntp_send (LOCAL->nntpstream,"ARTICLE",msgid) == NNTPARTICLE) ?
  1.1183 +    netmsg_slurp (LOCAL->nntpstream->netstream,size,hsiz) : NIL;
  1.1184 +}
  1.1185 +
  1.1186 +
  1.1187 +/* NNTP per-message modify flag
  1.1188 + * Accepts: MAIL stream
  1.1189 + *	    message cache element
  1.1190 + */
  1.1191 +
  1.1192 +void nntp_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
  1.1193 +{
  1.1194 +  if (!LOCAL->dirty) {		/* only bother checking if not dirty yet */
  1.1195 +    if (elt->valid) {		/* if done, see if deleted changed */
  1.1196 +      if (elt->sequence != elt->deleted) LOCAL->dirty = T;
  1.1197 +      elt->sequence = T;	/* leave the sequence set */
  1.1198 +    }
  1.1199 +				/* note current setting of deleted flag */
  1.1200 +    else elt->sequence = elt->deleted;
  1.1201 +  }
  1.1202 +}
  1.1203 +
  1.1204 +/* NNTP search messages
  1.1205 + * Accepts: mail stream
  1.1206 + *	    character set
  1.1207 + *	    search program
  1.1208 + *	    option flags
  1.1209 + * Returns: T on success, NIL on failure
  1.1210 + */
  1.1211 +
  1.1212 +long nntp_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags)
  1.1213 +{
  1.1214 +  unsigned long i;
  1.1215 +  MESSAGECACHE *elt;
  1.1216 +  OVERVIEW ov;
  1.1217 +  char *msg;
  1.1218 +				/* make sure that charset is good */
  1.1219 +  if (msg = utf8_badcharset (charset)) {
  1.1220 +    MM_LOG (msg,ERROR);		/* output error */
  1.1221 +    fs_give ((void **) &msg);
  1.1222 +    return NIL;
  1.1223 +  }
  1.1224 +  utf8_searchpgm (pgm,charset);
  1.1225 +  if (flags & SO_OVERVIEW) {	/* only if specified to use overview */
  1.1226 +				/* identify messages that will be searched */
  1.1227 +    for (i = 1; i <= stream->nmsgs; ++i)
  1.1228 +      mail_elt (stream,i)->sequence = nntp_search_msg (stream,i,pgm,NIL);
  1.1229 +    nntp_overview (stream,NIL);	/* load the overview cache */
  1.1230 +  }
  1.1231 +				/* init in case no overview at cleanup */
  1.1232 +  memset ((void *) &ov,0,sizeof (OVERVIEW));
  1.1233 +				/* otherwise do default search */
  1.1234 +  for (i = 1; i <= stream->nmsgs; ++i) {
  1.1235 +    if (((flags & SO_OVERVIEW) && ((elt = mail_elt (stream,i))->sequence) &&
  1.1236 +	 nntp_parse_overview (&ov,(char *) elt->private.spare.ptr,elt)) ?
  1.1237 +	nntp_search_msg (stream,i,pgm,&ov) :
  1.1238 +	mail_search_msg (stream,i,NIL,pgm)) {
  1.1239 +      if (flags & SE_UID) mm_searched (stream,mail_uid (stream,i));
  1.1240 +      else {			/* mark as searched, notify mail program */
  1.1241 +	mail_elt (stream,i)->searched = T;
  1.1242 +	if (!stream->silent) mm_searched (stream,i);
  1.1243 +      }
  1.1244 +    }
  1.1245 +				/* clean up overview data */
  1.1246 +    if (ov.from) mail_free_address (&ov.from);
  1.1247 +    if (ov.subject) fs_give ((void **) &ov.subject);
  1.1248 +  }
  1.1249 +  return LONGT;
  1.1250 +}
  1.1251 +
  1.1252 +/* NNTP search message
  1.1253 + * Accepts: MAIL stream
  1.1254 + *	    message number
  1.1255 + *	    search program
  1.1256 + *	    overview to search (NIL means preliminary pass)
  1.1257 + * Returns: T if found, NIL otherwise
  1.1258 + */
  1.1259 +
  1.1260 +long nntp_search_msg (MAILSTREAM *stream,unsigned long msgno,SEARCHPGM *pgm,
  1.1261 +		      OVERVIEW *ov)
  1.1262 +{
  1.1263 +  unsigned short d;
  1.1264 +  unsigned long now = (unsigned long) time (0);
  1.1265 +  MESSAGECACHE *elt = mail_elt (stream,msgno);
  1.1266 +  SEARCHHEADER *hdr;
  1.1267 +  SEARCHOR *or;
  1.1268 +  SEARCHPGMLIST *not;
  1.1269 +  if (pgm->msgno || pgm->uid) {	/* message set searches */
  1.1270 +    SEARCHSET *set;
  1.1271 +				/* message sequences */
  1.1272 +    if (set = pgm->msgno) {	/* must be inside this sequence */
  1.1273 +      while (set) {		/* run down until find matching range */
  1.1274 +	if (set->last ? ((msgno < set->first) || (msgno > set->last)) :
  1.1275 +	    msgno != set->first) set = set->next;
  1.1276 +	else break;
  1.1277 +      }
  1.1278 +      if (!set) return NIL;	/* not found within sequence */
  1.1279 +    }
  1.1280 +    if (set = pgm->uid) {	/* must be inside this sequence */
  1.1281 +      unsigned long uid = mail_uid (stream,msgno);
  1.1282 +      while (set) {		/* run down until find matching range */
  1.1283 +	if (set->last ? ((uid < set->first) || (uid > set->last)) :
  1.1284 +	    uid != set->first) set = set->next;
  1.1285 +	else break;
  1.1286 +      }
  1.1287 +      if (!set) return NIL;	/* not found within sequence */
  1.1288 +    }
  1.1289 +  }
  1.1290 +
  1.1291 +  /* Fast data searches */
  1.1292 +				/* message flags */
  1.1293 +  if ((pgm->answered && !elt->answered) ||
  1.1294 +      (pgm->unanswered && elt->answered) ||
  1.1295 +      (pgm->deleted && !elt->deleted) ||
  1.1296 +      (pgm->undeleted && elt->deleted) ||
  1.1297 +      (pgm->draft && !elt->draft) ||
  1.1298 +      (pgm->undraft && elt->draft) ||
  1.1299 +      (pgm->flagged && !elt->flagged) ||
  1.1300 +      (pgm->unflagged && elt->flagged) ||
  1.1301 +      (pgm->recent && !elt->recent) ||
  1.1302 +      (pgm->old && elt->recent) ||
  1.1303 +      (pgm->seen && !elt->seen) ||
  1.1304 +      (pgm->unseen && elt->seen)) return NIL;
  1.1305 +				/* keywords */
  1.1306 +  if ((pgm->keyword && !mail_search_keyword (stream,elt,pgm->keyword,LONGT)) ||
  1.1307 +      (pgm->unkeyword && mail_search_keyword (stream,elt,pgm->unkeyword,NIL)))
  1.1308 +    return NIL;
  1.1309 +  if (ov) {			/* only do this if real searching */
  1.1310 +    MESSAGECACHE delt;
  1.1311 +				/* size ranges */
  1.1312 +    if ((pgm->larger && (ov->optional.octets <= pgm->larger)) ||
  1.1313 +	(pgm->smaller && (ov->optional.octets >= pgm->smaller))) return NIL;
  1.1314 +				/* date ranges */
  1.1315 +    if ((pgm->sentbefore || pgm->senton || pgm->sentsince ||
  1.1316 +	 pgm->before || pgm->on || pgm->since) &&
  1.1317 +	(!mail_parse_date (&delt,ov->date) ||
  1.1318 +	 !(d = mail_shortdate (delt.year,delt.month,delt.day)) ||
  1.1319 +	 (pgm->sentbefore && (d >= pgm->sentbefore)) ||
  1.1320 +	 (pgm->senton && (d != pgm->senton)) ||
  1.1321 +	 (pgm->sentsince && (d < pgm->sentsince)) ||
  1.1322 +	 (pgm->before && (d >= pgm->before)) ||
  1.1323 +	 (pgm->on && (d != pgm->on)) ||
  1.1324 +	 (pgm->since && (d < pgm->since)))) return NIL;
  1.1325 +    if (pgm->older || pgm->younger) {
  1.1326 +      unsigned long msgd = mail_longdate (elt);
  1.1327 +      if (pgm->older && msgd > (now - pgm->older)) return NIL;
  1.1328 +      if (pgm->younger && msgd < (now - pgm->younger)) return NIL;
  1.1329 +    }
  1.1330 +    if ((pgm->from && !mail_search_addr (ov->from,pgm->from)) ||
  1.1331 +	(pgm->subject && !mail_search_header_text (ov->subject,pgm->subject))||
  1.1332 +	(pgm->message_id &&
  1.1333 +	 !mail_search_header_text (ov->message_id,pgm->message_id)) ||
  1.1334 +	(pgm->references &&
  1.1335 +	 !mail_search_header_text (ov->references,pgm->references)))
  1.1336 +      return NIL;
  1.1337 +
  1.1338 +
  1.1339 +				/* envelope searches */
  1.1340 +    if (pgm->bcc || pgm->cc || pgm->to || pgm->return_path || pgm->sender ||
  1.1341 +	pgm->reply_to || pgm->in_reply_to || pgm->newsgroups ||
  1.1342 +	pgm->followup_to) {
  1.1343 +      ENVELOPE *env = mail_fetchenvelope (stream,msgno);
  1.1344 +      if (!env) return NIL;	/* no envelope obtained */
  1.1345 +				/* search headers */
  1.1346 +      if ((pgm->bcc && !mail_search_addr (env->bcc,pgm->bcc)) ||
  1.1347 +	  (pgm->cc && !mail_search_addr (env->cc,pgm->cc)) ||
  1.1348 +	  (pgm->to && !mail_search_addr (env->to,pgm->to)))
  1.1349 +	return NIL;
  1.1350 +      /* These criteria are not supported by IMAP and have to be emulated */
  1.1351 +      if ((pgm->return_path &&
  1.1352 +	   !mail_search_addr (env->return_path,pgm->return_path)) ||
  1.1353 +	  (pgm->sender && !mail_search_addr (env->sender,pgm->sender)) ||
  1.1354 +	  (pgm->reply_to && !mail_search_addr (env->reply_to,pgm->reply_to)) ||
  1.1355 +	  (pgm->in_reply_to &&
  1.1356 +	   !mail_search_header_text (env->in_reply_to,pgm->in_reply_to)) ||
  1.1357 +	  (pgm->newsgroups &&
  1.1358 +	   !mail_search_header_text (env->newsgroups,pgm->newsgroups)) ||
  1.1359 +	  (pgm->followup_to &&
  1.1360 +	   !mail_search_header_text (env->followup_to,pgm->followup_to)))
  1.1361 +	return NIL;
  1.1362 +    }
  1.1363 +
  1.1364 +				/* search header lines */
  1.1365 +    for (hdr = pgm->header; hdr; hdr = hdr->next) {
  1.1366 +      char *t,*e,*v;
  1.1367 +      SIZEDTEXT s;
  1.1368 +      STRINGLIST sth,stc;
  1.1369 +      sth.next = stc.next = NIL;/* only one at a time */
  1.1370 +      sth.text.data = hdr->line.data;
  1.1371 +      sth.text.size = hdr->line.size;
  1.1372 +				/* get the header text */
  1.1373 +      if ((t = mail_fetch_header (stream,msgno,NIL,&sth,&s.size,
  1.1374 +				  FT_INTERNAL | FT_PEEK)) && strchr (t,':')) {
  1.1375 +	if (hdr->text.size) {	/* anything matches empty search string */
  1.1376 +				/* non-empty, copy field data */
  1.1377 +	  s.data = (unsigned char *) fs_get (s.size + 1);
  1.1378 +				/* for each line */
  1.1379 +	  for (v = (char *) s.data, e = t + s.size; t < e;) switch (*t) {
  1.1380 +	  default:		/* non-continuation, skip leading field name */
  1.1381 +	    while ((t < e) && (*t++ != ':'));
  1.1382 +	    if ((t < e) && (*t == ':')) t++;
  1.1383 +	  case '\t': case ' ':	/* copy field data  */
  1.1384 +	    while ((t < e) && (*t != '\015') && (*t != '\012')) *v++ = *t++;
  1.1385 +	    *v++ = '\n';	/* tie off line */
  1.1386 +	    while (((*t == '\015') || (*t == '\012')) && (t < e)) t++;
  1.1387 +	  }
  1.1388 +				/* calculate true size */
  1.1389 +	  s.size = v - (char *) s.data;
  1.1390 +	  *v = '\0';		/* tie off results */
  1.1391 +	  stc.text.data = hdr->text.data;
  1.1392 +	  stc.text.size = hdr->text.size;
  1.1393 +				/* search header */
  1.1394 +	  if (mail_search_header (&s,&stc)) fs_give ((void **) &s.data);
  1.1395 +	  else {		/* search failed */
  1.1396 +	    fs_give ((void **) &s.data);
  1.1397 +	    return NIL;
  1.1398 +	  }
  1.1399 +	}
  1.1400 +      }
  1.1401 +      else return NIL;		/* no matching header text */
  1.1402 +    }
  1.1403 +				/* search strings */
  1.1404 +    if ((pgm->text &&
  1.1405 +	 !mail_search_text (stream,msgno,NIL,pgm->text,LONGT))||
  1.1406 +	(pgm->body && !mail_search_text (stream,msgno,NIL,pgm->body,NIL)))
  1.1407 +      return NIL;
  1.1408 +  }
  1.1409 +				/* logical conditions */
  1.1410 +  for (or = pgm->or; or; or = or->next)
  1.1411 +    if (!(nntp_search_msg (stream,msgno,or->first,ov) ||
  1.1412 +	  nntp_search_msg (stream,msgno,or->second,ov))) return NIL;
  1.1413 +  for (not = pgm->not; not; not = not->next)
  1.1414 +    if (nntp_search_msg (stream,msgno,not->pgm,ov)) return NIL;
  1.1415 +  return T;
  1.1416 +}
  1.1417 +
  1.1418 +/* NNTP sort messages
  1.1419 + * Accepts: mail stream
  1.1420 + *	    character set
  1.1421 + *	    search program
  1.1422 + *	    sort program
  1.1423 + *	    option flags
  1.1424 + * Returns: vector of sorted message sequences or NIL if error
  1.1425 + */
  1.1426 +
  1.1427 +unsigned long *nntp_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
  1.1428 +			  SORTPGM *pgm,long flags)
  1.1429 +{
  1.1430 +  unsigned long i,start,last;
  1.1431 +  SORTCACHE **sc;
  1.1432 +  mailcache_t mailcache = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
  1.1433 +  unsigned long *ret = NIL;
  1.1434 +  sortresults_t sr = (sortresults_t) mail_parameters (NIL,GET_SORTRESULTS,NIL);
  1.1435 +  if (spg) {			/* only if a search needs to be done */
  1.1436 +    int silent = stream->silent;
  1.1437 +    stream->silent = T;		/* don't pass up mm_searched() events */
  1.1438 +				/* search for messages */
  1.1439 +    mail_search_full (stream,charset,spg,NIL);
  1.1440 +    stream->silent = silent;	/* restore silence state */
  1.1441 +  }
  1.1442 +				/* initialize progress counters */
  1.1443 +  pgm->nmsgs = pgm->progress.cached = 0;
  1.1444 +				/* pass 1: count messages to sort */
  1.1445 +  for (i = 1,start = last = 0; i <= stream->nmsgs; ++i)
  1.1446 +    if (mail_elt (stream,i)->searched) {
  1.1447 +      pgm->nmsgs++;
  1.1448 +				/* have this in the sortcache already? */
  1.1449 +      if (!((SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE))->date) {
  1.1450 +				/* no, record as last message */
  1.1451 +	last = mail_uid (stream,i);
  1.1452 +				/* and as first too if needed */
  1.1453 +	if (!start) start = last;
  1.1454 +      }
  1.1455 +    }
  1.1456 +  if (pgm->nmsgs) {		/* pass 2: load sort cache */
  1.1457 +    sc = nntp_sort_loadcache (stream,pgm,start,last,flags);
  1.1458 +				/* pass 3: sort messages */
  1.1459 +    if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags);
  1.1460 +    fs_give ((void **) &sc);	/* don't need sort vector any more */
  1.1461 +  }
  1.1462 +				/* empty sort results */
  1.1463 +  else ret = (unsigned long *) memset (fs_get (sizeof (unsigned long)),0,
  1.1464 +				       sizeof (unsigned long));
  1.1465 +				/* also return via callback if requested */
  1.1466 +  if (sr) (*sr) (stream,ret,pgm->nmsgs);
  1.1467 +  return ret;
  1.1468 +}
  1.1469 +
  1.1470 +/* Mail load sortcache
  1.1471 + * Accepts: mail stream, already searched
  1.1472 + *	    sort program
  1.1473 + *	    first UID to OVER
  1.1474 + *	    last UID to OVER
  1.1475 + *	    option flags
  1.1476 + * Returns: vector of sortcache pointers matching search
  1.1477 + */
  1.1478 +
  1.1479 +SORTCACHE **nntp_sort_loadcache (MAILSTREAM *stream,SORTPGM *pgm,
  1.1480 +				 unsigned long start,unsigned long last,
  1.1481 +				 long flags)
  1.1482 +{
  1.1483 +  unsigned long i;
  1.1484 +  char c,*s,*t,*v,tmp[MAILTMPLEN];
  1.1485 +  SORTPGM *pg;
  1.1486 +  SORTCACHE **sc,*r;
  1.1487 +  MESSAGECACHE telt;
  1.1488 +  ADDRESS *adr = NIL;
  1.1489 +  mailcache_t mailcache = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
  1.1490 +				/* verify that the sortpgm is OK */
  1.1491 +  for (pg = pgm; pg; pg = pg->next) switch (pg->function) {
  1.1492 +  case SORTARRIVAL:		/* sort by arrival date */
  1.1493 +  case SORTSIZE:		/* sort by message size */
  1.1494 +  case SORTDATE:		/* sort by date */
  1.1495 +  case SORTFROM:		/* sort by first from */
  1.1496 +  case SORTSUBJECT:		/* sort by subject */
  1.1497 +    break;
  1.1498 +  case SORTTO:			/* sort by first to */
  1.1499 +    mm_notify (stream,"[NNTPSORT] Can't do To-field sorting in NNTP",WARN);
  1.1500 +    break;
  1.1501 +  case SORTCC:			/* sort by first cc */
  1.1502 +    mm_notify (stream,"[NNTPSORT] Can't do cc-field sorting in NNTP",WARN);
  1.1503 +    break;
  1.1504 +  default:
  1.1505 +    fatal ("Unknown sort function");
  1.1506 +  }
  1.1507 +
  1.1508 +  if (start) {			/* messages need to be loaded in sortcache? */
  1.1509 +				/* yes, build range */
  1.1510 +    if (start != last) sprintf (tmp,"%lu-%lu",start,last);
  1.1511 +    else sprintf (tmp,"%lu",start);
  1.1512 +				/* get it from the NNTP server */
  1.1513 +    if (!nntp_over (stream,tmp)) return mail_sort_loadcache (stream,pgm);
  1.1514 +    while ((s = net_getline (LOCAL->nntpstream->netstream)) && strcmp (s,".")){
  1.1515 +				/* death to embedded newlines */
  1.1516 +      for (t = v = s; c = *v++;) if ((c != '\012') && (c != '\015')) *t++ = c;
  1.1517 +      *t++ = '\0';		/* tie off resulting string */
  1.1518 +				/* parse OVER response */
  1.1519 +      if ((i = mail_msgno (stream,atol (s))) &&
  1.1520 +	  (t = strchr (s,'\t')) && (v = strchr (++t,'\t'))) {
  1.1521 +	*v++ = '\0';		/* tie off subject */
  1.1522 +				/* put stripped subject in sortcache */
  1.1523 +	r = (SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE);
  1.1524 +	  r->refwd = mail_strip_subject (t,&r->subject);
  1.1525 +	if (t = strchr (v,'\t')) {
  1.1526 +	  *t++ = '\0';		/* tie off from */
  1.1527 +	  if (adr = rfc822_parse_address (&adr,adr,&v,BADHOST,0)) {
  1.1528 +	    r->from = adr->mailbox;
  1.1529 +	    adr->mailbox = NIL;
  1.1530 +	    mail_free_address (&adr);
  1.1531 +	  }
  1.1532 +	  if (v = strchr (t,'\t')) {
  1.1533 +	    *v++ = '\0';	/* tie off date */
  1.1534 +	    if (mail_parse_date (&telt,t)) r->date = mail_longdate (&telt);
  1.1535 +	    if ((v = strchr (v,'\t')) && (v = strchr (++v,'\t')))
  1.1536 +	      r->size = atol (++v);
  1.1537 +	  }
  1.1538 +	}
  1.1539 +      }
  1.1540 +      fs_give ((void **) &s);
  1.1541 +    }
  1.1542 +    if (s) fs_give ((void **) &s);
  1.1543 +  }
  1.1544 +
  1.1545 +				/* calculate size of sortcache index */
  1.1546 +  i = pgm->nmsgs * sizeof (SORTCACHE *);
  1.1547 +				/* instantiate the index */
  1.1548 +  sc = (SORTCACHE **) memset (fs_get ((size_t) i),0,(size_t) i);
  1.1549 +				/* see what needs to be loaded */
  1.1550 +  for (i = 1; !pgm->abort && (i <= stream->nmsgs); i++)
  1.1551 +    if ((mail_elt (stream,i))->searched) {
  1.1552 +      sc[pgm->progress.cached++] =
  1.1553 +	r = (SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE);
  1.1554 +      r->pgm = pgm;	/* note sort program */
  1.1555 +      r->num = (flags & SE_UID) ? mail_uid (stream,i) : i;
  1.1556 +      if (!r->date) r->date = r->num;
  1.1557 +      if (!r->arrival) r->arrival = mail_uid (stream,i);
  1.1558 +      if (!r->size) r->size = 1;
  1.1559 +      if (!r->from) r->from = cpystr ("");
  1.1560 +      if (!r->to) r->to = cpystr ("");
  1.1561 +      if (!r->cc) r->cc = cpystr ("");
  1.1562 +      if (!r->subject) r->subject = cpystr ("");
  1.1563 +    }
  1.1564 +  return sc;
  1.1565 +}
  1.1566 +
  1.1567 +
  1.1568 +/* NNTP thread messages
  1.1569 + * Accepts: mail stream
  1.1570 + *	    thread type
  1.1571 + *	    character set
  1.1572 + *	    search program
  1.1573 + *	    option flags
  1.1574 + * Returns: thread node tree
  1.1575 + */
  1.1576 +
  1.1577 +THREADNODE *nntp_thread (MAILSTREAM *stream,char *type,char *charset,
  1.1578 +			 SEARCHPGM *spg,long flags)
  1.1579 +{
  1.1580 +  return mail_thread_msgs (stream,type,charset,spg,flags,nntp_sort);
  1.1581 +}
  1.1582 +
  1.1583 +/* NNTP ping mailbox
  1.1584 + * Accepts: MAIL stream
  1.1585 + * Returns: T if stream alive, else NIL
  1.1586 + */
  1.1587 +
  1.1588 +long nntp_ping (MAILSTREAM *stream)
  1.1589 +{
  1.1590 +  return (nntp_send (LOCAL->nntpstream,"STAT",NIL) != NNTPSOFTFATAL);
  1.1591 +}
  1.1592 +
  1.1593 +
  1.1594 +/* NNTP check mailbox
  1.1595 + * Accepts: MAIL stream
  1.1596 + */
  1.1597 +
  1.1598 +void nntp_check (MAILSTREAM *stream)
  1.1599 +{
  1.1600 +				/* never do if no updates */
  1.1601 +  if (LOCAL->dirty) newsrc_write (LOCAL->name,stream);
  1.1602 +  LOCAL->dirty = NIL;
  1.1603 +}
  1.1604 +
  1.1605 +
  1.1606 +/* NNTP expunge mailbox
  1.1607 + * Accepts: MAIL stream
  1.1608 + *	    sequence to expunge if non-NIL
  1.1609 + *	    expunge options
  1.1610 + * Returns: T if success, NIL if failure
  1.1611 + */
  1.1612 +
  1.1613 +long nntp_expunge (MAILSTREAM *stream,char *sequence,long options)
  1.1614 +{
  1.1615 +  if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
  1.1616 +  return LONGT;
  1.1617 +}
  1.1618 +
  1.1619 +/* NNTP copy message(s)
  1.1620 + * Accepts: MAIL stream
  1.1621 + *	    sequence
  1.1622 + *	    destination mailbox
  1.1623 + *	    option flags
  1.1624 + * Returns: T if copy successful, else NIL
  1.1625 + */
  1.1626 +
  1.1627 +long nntp_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
  1.1628 +{
  1.1629 +  mailproxycopy_t pc =
  1.1630 +    (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
  1.1631 +  if (pc) return (*pc) (stream,sequence,mailbox,options);
  1.1632 +  mm_log ("Copy not valid for NNTP",ERROR);
  1.1633 +  return NIL;
  1.1634 +}
  1.1635 +
  1.1636 +
  1.1637 +/* NNTP append message from stringstruct
  1.1638 + * Accepts: MAIL stream
  1.1639 + *	    destination mailbox
  1.1640 + *	    append callback
  1.1641 + *	    data for callback
  1.1642 + * Returns: T if append successful, else NIL
  1.1643 + */
  1.1644 +
  1.1645 +long nntp_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
  1.1646 +{
  1.1647 +  mm_log ("Append not valid for NNTP",ERROR);
  1.1648 +  return NIL;
  1.1649 +}
  1.1650 +
  1.1651 +/* NNTP open connection
  1.1652 + * Accepts: network driver
  1.1653 + *	    service host list
  1.1654 + *	    port number
  1.1655 + *	    service name
  1.1656 + *	    NNTP open options
  1.1657 + * Returns: SEND stream on success, NIL on failure
  1.1658 + */
  1.1659 +
  1.1660 +SENDSTREAM *nntp_open_full (NETDRIVER *dv,char **hostlist,char *service,
  1.1661 +			    unsigned long port,long options)
  1.1662 +{
  1.1663 +  SENDSTREAM *stream = NIL;
  1.1664 +  NETSTREAM *netstream = NIL;
  1.1665 +  NETMBX mb;
  1.1666 +  char tmp[MAILTMPLEN];
  1.1667 +  long extok = LONGT;
  1.1668 +  NETDRIVER *ssld = (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL);
  1.1669 +  sslstart_t stls = (sslstart_t) mail_parameters (NIL,GET_SSLSTART,NIL);
  1.1670 +  if (!(hostlist && *hostlist)) mm_log ("Missing NNTP service host",ERROR);
  1.1671 +  else do {			/* try to open connection */
  1.1672 +    sprintf (tmp,"{%.200s/%.20s}",*hostlist,service ? service : "nntp");
  1.1673 +    if (!mail_valid_net_parse (tmp,&mb) || mb.anoflag) {
  1.1674 +      sprintf (tmp,"Invalid host specifier: %.80s",*hostlist);
  1.1675 +      mm_log (tmp,ERROR);
  1.1676 +    }
  1.1677 +    else {			/* light tryssl flag if requested */
  1.1678 +      mb.trysslflag = (options & NOP_TRYSSL) ? T : NIL;
  1.1679 +				/* default port */
  1.1680 +      if (mb.port) port = mb.port;
  1.1681 +      else if (!port) port = nntp_port ? nntp_port : NNTPTCPPORT;
  1.1682 +      if (netstream =		/* try to open ordinary connection */
  1.1683 +	  net_open (&mb,dv,port,
  1.1684 +		    (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL),
  1.1685 +		    "*nntps",nntp_sslport ? nntp_sslport : NNTPSSLPORT)) {
  1.1686 +	stream = (SENDSTREAM *) fs_get (sizeof (SENDSTREAM));
  1.1687 +				/* initialize stream */
  1.1688 +	memset ((void *) stream,0,sizeof (SENDSTREAM));
  1.1689 +	stream->netstream = netstream;
  1.1690 +	stream->host = cpystr ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ?
  1.1691 +			       net_host (netstream) : mb.host);
  1.1692 +	stream->debug = (mb.dbgflag || (options & NOP_DEBUG)) ? T : NIL;
  1.1693 +	if (mb.loser) stream->loser = T;
  1.1694 +				/* process greeting */
  1.1695 +	switch ((int) nntp_reply (stream)) {
  1.1696 +	case NNTPGREET:		/* allow posting */
  1.1697 +	  NNTP.post = T;
  1.1698 +	  mm_notify (NIL,stream->reply + 4,(long) NIL);
  1.1699 +	  break;
  1.1700 +	case NNTPGREETNOPOST:	/* posting not allowed, must be readonly */
  1.1701 +	  NNTP.post = NIL;
  1.1702 +	  break;
  1.1703 +	default:
  1.1704 +	  mm_log (stream->reply,ERROR);
  1.1705 +	  stream = nntp_close (stream);
  1.1706 +	  break;
  1.1707 +	}
  1.1708 +      }
  1.1709 +    }
  1.1710 +  } while (!stream && *++hostlist);
  1.1711 +
  1.1712 +				/* get extensions */
  1.1713 +  if (stream && extok)
  1.1714 +    extok = nntp_extensions (stream,(mb.secflag ? AU_SECURE : NIL) |
  1.1715 +			     (mb.authuser[0] ? AU_AUTHUSER : NIL));
  1.1716 +  if (stream && !dv && stls && NNTP.ext.starttls &&
  1.1717 +      !mb.sslflag && !mb.notlsflag &&
  1.1718 +      (nntp_send_work (stream,"STARTTLS",NNTP.ext.multidomain ? mb.host : NIL)
  1.1719 +       == NNTPTLSSTART)) {
  1.1720 +    mb.tlsflag = T;		/* TLS OK, get into TLS at this end */
  1.1721 +    stream->netstream->dtb = ssld;
  1.1722 +				/* negotiate TLS */
  1.1723 +    if (stream->netstream->stream =
  1.1724 +	(*stls) (stream->netstream->stream,mb.host,
  1.1725 +		 (mb.tlssslv23 ? NIL : NET_TLSCLIENT) |
  1.1726 +		 (mb.novalidate ? NET_NOVALIDATECERT:NIL)))
  1.1727 +      extok = nntp_extensions (stream,(mb.secflag ? AU_SECURE : NIL) |
  1.1728 +			       (mb.authuser[0] ? AU_AUTHUSER : NIL));
  1.1729 +    else {
  1.1730 +      sprintf (tmp,"Unable to negotiate TLS with this server: %.80s",mb.host);
  1.1731 +      mm_log (tmp,ERROR);
  1.1732 +				/* close without doing QUIT */
  1.1733 +      if (stream->netstream) net_close (stream->netstream);
  1.1734 +      stream->netstream = NIL;
  1.1735 +      stream = nntp_close (stream);
  1.1736 +    }
  1.1737 +  }
  1.1738 +  else if (mb.tlsflag) {	/* user specified /tls but can't do it */
  1.1739 +    mm_log ("Unable to negotiate TLS with this server",ERROR);
  1.1740 +    return NIL;
  1.1741 +  }
  1.1742 +  if (stream) {			/* have a session? */
  1.1743 +    if (mb.user[0]) {		/* yes, have user name? */
  1.1744 +      if ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL)) {
  1.1745 +				/* remote name for authentication */
  1.1746 +	strncpy (mb.host,(long) mail_parameters (NIL,GET_SASLUSESPTRNAME,NIL) ?
  1.1747 +		 net_remotehost (netstream) : net_host (netstream),
  1.1748 +		 NETMAXHOST-1);
  1.1749 +	mb.host[NETMAXHOST-1] = '\0';
  1.1750 +      }
  1.1751 +      if (!nntp_send_auth_work (stream,&mb,tmp,NIL))
  1.1752 +	stream = nntp_close (stream);
  1.1753 +    }
  1.1754 +				/* authenticate if no-post and not readonly */
  1.1755 +    else if (!(NNTP.post || (options & NOP_READONLY) ||
  1.1756 +	       nntp_send_auth (stream,NIL))) stream = nntp_close (stream);
  1.1757 +  }
  1.1758 +
  1.1759 +				/* in case server demands MODE READER */
  1.1760 +  if (stream) switch ((int) nntp_send_work (stream,"MODE","READER")) {
  1.1761 +  case NNTPGREET:
  1.1762 +    NNTP.post = T;
  1.1763 +    break;
  1.1764 +  case NNTPGREETNOPOST:
  1.1765 +    NNTP.post = NIL;
  1.1766 +    break;
  1.1767 +  case NNTPWANTAUTH:		/* server wants auth first, do so and retry */
  1.1768 +  case NNTPWANTAUTH2:		/* remote name for authentication */
  1.1769 +    if ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL)) {
  1.1770 +      strncpy (mb.host,(long) mail_parameters (NIL,GET_SASLUSESPTRNAME,NIL) ?
  1.1771 +	       net_remotehost (netstream) : net_host (netstream),NETMAXHOST-1);
  1.1772 +      mb.host[NETMAXHOST-1] = '\0';
  1.1773 +    }
  1.1774 +    if (nntp_send_auth_work (stream,&mb,tmp,NIL))
  1.1775 +      switch ((int) nntp_send (stream,"MODE","READER")) {
  1.1776 +      case NNTPGREET:
  1.1777 +	NNTP.post = T;
  1.1778 +	break;
  1.1779 +      case NNTPGREETNOPOST:
  1.1780 +	NNTP.post = NIL;
  1.1781 +	break;
  1.1782 +      }
  1.1783 +    else stream = nntp_close (stream);
  1.1784 +    break;
  1.1785 +  }
  1.1786 +  if (stream) {			/* looks like we have a stream? */
  1.1787 +				/* yes, make sure can post if not readonly */
  1.1788 +    if (!(NNTP.post || (options & NOP_READONLY))) stream = nntp_close (stream);
  1.1789 +    else if (extok) nntp_extensions (stream,(mb.secflag ? AU_SECURE : NIL) |
  1.1790 +				     (mb.authuser[0] ? AU_AUTHUSER : NIL));
  1.1791 +  }
  1.1792 +  return stream;
  1.1793 +}
  1.1794 +
  1.1795 +/* NNTP extensions
  1.1796 + * Accepts: stream
  1.1797 + *	    authenticator flags
  1.1798 + * Returns: T on success, NIL on failure
  1.1799 + */
  1.1800 +
  1.1801 +long nntp_extensions (SENDSTREAM *stream,long flags)
  1.1802 +{
  1.1803 +  unsigned long i;
  1.1804 +  char *t,*r,*args;
  1.1805 +				/* zap all old extensions */
  1.1806 +  memset (&NNTP.ext,0,sizeof (NNTP.ext));
  1.1807 +  if (stream->loser) return NIL;/* nothing at all for losers */
  1.1808 +				/* get server extensions */
  1.1809 +  switch ((int) nntp_send_work (stream,"LIST","EXTENSIONS")) {
  1.1810 +  case NNTPEXTOK:		/* what NNTP base spec says */
  1.1811 +  case NNTPGLIST:		/* some servers do this instead */
  1.1812 +    break;
  1.1813 +  default:			/* no LIST EXTENSIONS on this server */
  1.1814 +    return NIL;
  1.1815 +  }
  1.1816 +  NNTP.ext.ok = T;		/* server offers extensions */
  1.1817 +  while ((t = net_getline (stream->netstream)) && (t[1] || (*t != '.'))) {
  1.1818 +    if (stream->debug) mm_dlog (t);
  1.1819 +				/* get optional capability arguments */
  1.1820 +    if (args = strchr (t,' ')) *args++ = '\0';
  1.1821 +    if (!compare_cstring (t,"LISTGROUP")) NNTP.ext.listgroup = T;
  1.1822 +    else if (!compare_cstring (t,"OVER")) NNTP.ext.over = T;
  1.1823 +    else if (!compare_cstring (t,"HDR")) NNTP.ext.hdr = T;
  1.1824 +    else if (!compare_cstring (t,"PAT")) NNTP.ext.pat = T;
  1.1825 +    else if (!compare_cstring (t,"STARTTLS")) NNTP.ext.starttls = T;
  1.1826 +    else if (!compare_cstring (t,"MULTIDOMAIN")) NNTP.ext.multidomain = T;
  1.1827 +
  1.1828 +    else if (!compare_cstring (t,"AUTHINFO") && args) {
  1.1829 +      char *sasl = NIL;
  1.1830 +      for (args = strtok_r (args," ",&r); args; args = strtok_r (NIL," ",&r)) {
  1.1831 +	if (!compare_cstring (args,"USER")) NNTP.ext.authuser = T;
  1.1832 +	else if (((args[0] == 'S') || (args[0] == 's')) &&
  1.1833 +		 ((args[1] == 'A') || (args[1] == 'a')) &&
  1.1834 +		 ((args[2] == 'S') || (args[2] == 's')) &&
  1.1835 +		 ((args[3] == 'L') || (args[3] == 'l')) && (args[4] == ':'))
  1.1836 +	  sasl = args + 5;
  1.1837 +      }
  1.1838 +      if (sasl) {		/* if SASL, look up authenticators */
  1.1839 +	for (sasl = strtok_r (sasl,",",&r); sasl; sasl = strtok_r (NIL,",",&r))
  1.1840 +	  if ((i = mail_lookup_auth_name (sasl,flags)) &&
  1.1841 +	      (--i < MAXAUTHENTICATORS))
  1.1842 +	    NNTP.ext.sasl |= (1 << i);
  1.1843 +				/* disable LOGIN if PLAIN also advertised */
  1.1844 +	if ((i = mail_lookup_auth_name ("PLAIN",NIL)) &&
  1.1845 +	    (--i < MAXAUTHENTICATORS) && (NNTP.ext.sasl & (1 << i)) &&
  1.1846 +	    (i = mail_lookup_auth_name ("LOGIN",NIL)) &&
  1.1847 +	    (--i < MAXAUTHENTICATORS)) NNTP.ext.sasl &= ~(1 << i);
  1.1848 +      }
  1.1849 +    }
  1.1850 +    fs_give ((void **) &t);
  1.1851 +  }
  1.1852 +  if (t) {			/* flush end of text indicator */
  1.1853 +    if (stream->debug) mm_dlog (t);
  1.1854 +    fs_give ((void **) &t);
  1.1855 +  }
  1.1856 +  return LONGT;
  1.1857 +}
  1.1858 +
  1.1859 +/* NNTP close connection
  1.1860 + * Accepts: SEND stream
  1.1861 + * Returns: NIL always
  1.1862 + */
  1.1863 +
  1.1864 +SENDSTREAM *nntp_close (SENDSTREAM *stream)
  1.1865 +{
  1.1866 +  if (stream) {			/* send "QUIT" */
  1.1867 +    if (stream->netstream) nntp_send (stream,"QUIT",NIL);
  1.1868 +				/* do close actions */
  1.1869 +    if (stream->netstream) net_close (stream->netstream);
  1.1870 +    if (stream->host) fs_give ((void **) &stream->host);
  1.1871 +    if (stream->reply) fs_give ((void **) &stream->reply);
  1.1872 +    fs_give ((void **) &stream);/* flush the stream */
  1.1873 +  }
  1.1874 +  return NIL;
  1.1875 +}
  1.1876 +
  1.1877 +/* NNTP deliver news
  1.1878 + * Accepts: SEND stream
  1.1879 + *	    message envelope
  1.1880 + *	    message body
  1.1881 + * Returns: T on success, NIL on failure
  1.1882 + */
  1.1883 +
  1.1884 +long nntp_mail (SENDSTREAM *stream,ENVELOPE *env,BODY *body)
  1.1885 +{
  1.1886 +  long ret;
  1.1887 +  RFC822BUFFER buf;
  1.1888 +  char *s,path[MAILTMPLEN],tmp[SENDBUFLEN+1];
  1.1889 +  long error = NIL;
  1.1890 +  long retry = NIL;
  1.1891 +  buf.f = nntp_soutr;		/* initialize buffer */
  1.1892 +  buf.s = stream->netstream;
  1.1893 +  buf.end = (buf.beg = buf.cur = tmp) + SENDBUFLEN;
  1.1894 +  tmp[SENDBUFLEN] = '\0';	/* must have additional null guard byte */
  1.1895 +  /* Gabba gabba hey, we need some brain damage to send netnews!!!
  1.1896 +   *
  1.1897 +   * First, we give ourselves a frontal lobotomy, and put in some UUCP
  1.1898 +   *  syntax.  It doesn't matter that it's completely bogus UUCP, and
  1.1899 +   *  that UUCP has nothing to do with anything we're doing.  It's been
  1.1900 +   *  alleged that "Path: not-for-mail" is also acceptable, but we won't
  1.1901 +   *  make assumptions unless the user says so.
  1.1902 +   *
  1.1903 +   * Second, we bop ourselves on the head with a ball-peen hammer.  How
  1.1904 +   *  dare we be so presumptious as to insert a *comment* in a Date:
  1.1905 +   *  header line.  Why, we were actually trying to be nice to a human
  1.1906 +   *  by giving a symbolic timezone (such as PST) in addition to a
  1.1907 +   *  numeric timezone (such as -0800).  But the gods of news transport
  1.1908 +   *  will have none of this.  Unix weenies, tried and true, rule!!!
  1.1909 +   *
  1.1910 +   * Third, Netscape Collabra server doesn't give the NNTPWANTAUTH error
  1.1911 +   *  until after requesting and receiving the entire message.  So we can't
  1.1912 +   *  call rely upon nntp_send() to do the auth retry.
  1.1913 +   */
  1.1914 +				/* RFC-1036 requires this cretinism */
  1.1915 +  sprintf (path,"Path: %s!%s\015\012",net_localhost (stream->netstream),
  1.1916 +	   env->sender ? env->sender->mailbox :
  1.1917 +	   (env->from ? env->from->mailbox : "not-for-mail"));
  1.1918 +				/* here's another cretinism */
  1.1919 +  if (s = strstr (env->date," (")) *s = NIL;
  1.1920 +  do if ((ret = nntp_send_work (stream,"POST",NIL)) == NNTPREADY)
  1.1921 +				/* output data, return success status */
  1.1922 +    ret = (net_soutr (stream->netstream,
  1.1923 +		      nntp_hidepath ? "Path: not-for-mail\015\012" : path) &&
  1.1924 +	   rfc822_output_full (&buf,env,body,T)) ?
  1.1925 +      nntp_send_work (stream,".",NIL) :
  1.1926 +      nntp_fake (stream,"NNTP connection broken (message text)");
  1.1927 +  while (((ret == NNTPWANTAUTH) || (ret == NNTPWANTAUTH2)) &&
  1.1928 +	 nntp_send_auth (stream,LONGT));
  1.1929 +  if (s) *s = ' ';		/* put the comment in the date back */
  1.1930 +  if (ret == NNTPOK) return LONGT;
  1.1931 +  else if (ret < 400) {		/* if not an error reply */
  1.1932 +    sprintf (tmp,"Unexpected NNTP posting reply code %ld",ret);
  1.1933 +    mm_log (tmp,WARN);		/* so someone looks at this eventually */
  1.1934 +    if ((ret >= 200) && (ret < 300)) return LONGT;
  1.1935 +  }
  1.1936 +  return NIL;
  1.1937 +}
  1.1938 +
  1.1939 +/* NNTP send command
  1.1940 + * Accepts: SEND stream
  1.1941 + *	    text
  1.1942 + * Returns: reply code
  1.1943 + */
  1.1944 +
  1.1945 +long nntp_send (SENDSTREAM *stream,char *command,char *args)
  1.1946 +{
  1.1947 +  long ret;
  1.1948 +  switch ((int) (ret = nntp_send_work (stream,command,args))) {
  1.1949 +  case NNTPWANTAUTH:		/* authenticate and retry */
  1.1950 +  case NNTPWANTAUTH2:
  1.1951 +    if (nntp_send_auth (stream,LONGT))
  1.1952 +      ret = nntp_send_work (stream,command,args);
  1.1953 +    else {			/* we're probably hosed, nuke the session */
  1.1954 +      nntp_send (stream,"QUIT",NIL);
  1.1955 +				/* close net connection */
  1.1956 +      if (stream->netstream) net_close (stream->netstream);
  1.1957 +      stream->netstream = NIL;
  1.1958 +    }
  1.1959 +  default:			/* all others just return */
  1.1960 +    break;
  1.1961 +  }
  1.1962 +  return ret;
  1.1963 +}
  1.1964 +
  1.1965 +
  1.1966 +/* NNTP send command worker routine
  1.1967 + * Accepts: SEND stream
  1.1968 + *	    text
  1.1969 + * Returns: reply code
  1.1970 + */
  1.1971 +
  1.1972 +long nntp_send_work (SENDSTREAM *stream,char *command,char *args)
  1.1973 +{
  1.1974 +  long ret;
  1.1975 +  char *s = (char *) fs_get (strlen (command) + (args ? strlen (args) + 1 : 0)
  1.1976 +			     + 3);
  1.1977 +  if (!stream->netstream) ret = nntp_fake (stream,"NNTP connection lost");
  1.1978 +  else {			/* build the complete command */
  1.1979 +    if (args) sprintf (s,"%s %s",command,args);
  1.1980 +    else strcpy (s,command);
  1.1981 +    if (stream->debug) mail_dlog (s,stream->sensitive);
  1.1982 +    strcat (s,"\015\012");
  1.1983 +				/* send the command */
  1.1984 +    ret = net_soutr (stream->netstream,s) ? nntp_reply (stream) :
  1.1985 +      nntp_fake (stream,"NNTP connection broken (command)");
  1.1986 +  }
  1.1987 +  fs_give ((void **) &s);
  1.1988 +  return ret;
  1.1989 +}
  1.1990 +
  1.1991 +/* NNTP send authentication if needed
  1.1992 + * Accepts: SEND stream
  1.1993 + *	    flags (non-NIL to get new extensions)
  1.1994 + * Returns: T if need to redo command, NIL otherwise
  1.1995 + */
  1.1996 +
  1.1997 +long nntp_send_auth (SENDSTREAM *stream,long flags)
  1.1998 +{
  1.1999 +  NETMBX mb;
  1.2000 +  char tmp[MAILTMPLEN];
  1.2001 +				/* remote name for authentication */
  1.2002 +  sprintf (tmp,"{%.200s/nntp",(long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ?
  1.2003 +	   ((long) mail_parameters (NIL,GET_SASLUSESPTRNAME,NIL) ?
  1.2004 +	    net_remotehost (stream->netstream) : net_host (stream->netstream)):
  1.2005 +	   stream->host);
  1.2006 +  if (stream->netstream->dtb ==
  1.2007 +      (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL))
  1.2008 +    strcat (tmp,"/ssl");
  1.2009 +  strcat (tmp,"}<none>");
  1.2010 +  mail_valid_net_parse (tmp,&mb);
  1.2011 +  return nntp_send_auth_work (stream,&mb,tmp,flags);
  1.2012 +}
  1.2013 +
  1.2014 +/* NNTP send authentication worker routine
  1.2015 + * Accepts: SEND stream
  1.2016 + *	    NETMBX structure
  1.2017 + *	    scratch buffer of length MAILTMPLEN
  1.2018 + *	    flags (non-NIL to get new extensions)
  1.2019 + * Returns: T if authenticated, NIL otherwise
  1.2020 + */
  1.2021 +
  1.2022 +long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags)
  1.2023 +{
  1.2024 +  unsigned long trial,auths;
  1.2025 +  char tmp[MAILTMPLEN],usr[MAILTMPLEN];
  1.2026 +  AUTHENTICATOR *at;
  1.2027 +  char *lsterr = NIL;
  1.2028 +  long ret = NIL;
  1.2029 +				/* try SASL first */
  1.2030 +  for (auths = NNTP.ext.sasl, stream->saslcancel = NIL;
  1.2031 +       !ret && stream->netstream && auths &&
  1.2032 +       (at = mail_lookup_auth (find_rightmost_bit (&auths) + 1)); ) {
  1.2033 +    if (lsterr) {		/* previous authenticator failed? */
  1.2034 +      sprintf (tmp,"Retrying using %s authentication after %.80s",
  1.2035 +	       at->name,lsterr);
  1.2036 +      mm_log (tmp,NIL);
  1.2037 +      fs_give ((void **) &lsterr);
  1.2038 +    }
  1.2039 +    trial = 0;			/* initial trial count */
  1.2040 +    tmp[0] = '\0';		/* empty buffer */
  1.2041 +    if (stream->netstream) do {
  1.2042 +      if (lsterr) {
  1.2043 +	sprintf (tmp,"Retrying %s authentication after %.80s",at->name,lsterr);
  1.2044 +	mm_log (tmp,WARN);
  1.2045 +	fs_give ((void **) &lsterr);
  1.2046 +      }
  1.2047 +      stream->saslcancel = NIL;
  1.2048 +      if (nntp_send (stream,"AUTHINFO SASL",at->name) == NNTPCHALLENGE) {
  1.2049 +				/* hide client authentication responses */
  1.2050 +	if (!(at->flags & AU_SECURE)) stream->sensitive = T;
  1.2051 +	if ((*at->client) (nntp_challenge,nntp_response,"nntp",mb,stream,
  1.2052 +			   &trial,usr)) {
  1.2053 +	  if (stream->replycode == NNTPAUTHED) ret = LONGT;
  1.2054 +				/* if main program requested cancellation */
  1.2055 +	  else if (!trial) mm_log ("NNTP Authentication cancelled",ERROR);
  1.2056 +	}
  1.2057 +	stream->sensitive = NIL;/* unhide */
  1.2058 +      }
  1.2059 +				/* remember response if error and no cancel */
  1.2060 +      if (!ret && trial) lsterr = cpystr (stream->reply);
  1.2061 +    } while (!ret && stream->netstream && trial &&
  1.2062 +	     (trial < nntp_maxlogintrials));
  1.2063 +  }
  1.2064 +
  1.2065 +  if (lsterr) {			/* SAIL failed? */
  1.2066 +    if (!stream->saslcancel) {	/* don't do this if a cancel */
  1.2067 +      sprintf (tmp,"Can not authenticate to NNTP server: %.80s",lsterr);
  1.2068 +      mm_log (tmp,ERROR);
  1.2069 +    }
  1.2070 +    fs_give ((void **) &lsterr);
  1.2071 +  }
  1.2072 +  else if (mb->secflag)		/* no SASL, can't do /secure */
  1.2073 +    mm_log ("Can't do secure authentication with this server",ERROR);
  1.2074 +  else if (mb->authuser[0])	/* or /authuser */
  1.2075 +    mm_log ("Can't do /authuser with this server",ERROR);
  1.2076 +  /* Always try AUTHINFO USER, even if NNTP.ext.authuser isn't set.  There
  1.2077 +   * are servers that require it but don't return it as an extension.
  1.2078 +   */
  1.2079 +  else for (trial = 0, pwd[0] = 'x';
  1.2080 +	    !ret && pwd[0] && (trial < nntp_maxlogintrials) &&
  1.2081 +	      stream->netstream; ) {
  1.2082 +    pwd[0] = NIL;		/* get user name and password */
  1.2083 +    mm_login (mb,usr,pwd,trial++);
  1.2084 +				/* do the authentication */
  1.2085 +    if (pwd[0]) switch ((int) nntp_send_work (stream,"AUTHINFO USER",usr)) {
  1.2086 +    case NNTPBADCMD:		/* give up if unrecognized command */
  1.2087 +      mm_log (NNTP.ext.authuser ? stream->reply :
  1.2088 +	      "Can't do AUTHINFO USER to this server",ERROR);
  1.2089 +      trial = nntp_maxlogintrials;
  1.2090 +      break;
  1.2091 +    case NNTPAUTHED:		/* successful authentication */
  1.2092 +      ret = LONGT;		/* guess no password was needed */
  1.2093 +      break;
  1.2094 +    case NNTPWANTPASS:		/* wants password */
  1.2095 +      stream->sensitive = T;	/* hide this command */
  1.2096 +      if (nntp_send_work (stream,"AUTHINFO PASS",pwd) == NNTPAUTHED)
  1.2097 +	ret = LONGT;		/* password OK */
  1.2098 +      stream->sensitive = NIL;	/* unhide */
  1.2099 +      if (ret) break;		/* OK if successful */
  1.2100 +    default:			/* authentication failed */
  1.2101 +      mm_log (stream->reply,WARN);
  1.2102 +      if (trial == nntp_maxlogintrials)
  1.2103 +	mm_log ("Too many NNTP authentication failures",ERROR);
  1.2104 +    }
  1.2105 +				/* user refused to give a password */
  1.2106 +    else mm_log ("Login aborted",ERROR);
  1.2107 +  }
  1.2108 +  memset (pwd,0,MAILTMPLEN);	/* erase password */
  1.2109 +				/* get new extensions if needed */
  1.2110 +  if (ret && flags) nntp_extensions (stream,(mb->secflag ? AU_SECURE : NIL) |
  1.2111 +				     (mb->authuser[0] ? AU_AUTHUSER : NIL));
  1.2112 +  return ret;
  1.2113 +}
  1.2114 +
  1.2115 +/* Get challenge to authenticator in binary
  1.2116 + * Accepts: stream
  1.2117 + *	    pointer to returned size
  1.2118 + * Returns: challenge or NIL if not challenge
  1.2119 + */
  1.2120 +
  1.2121 +void *nntp_challenge (void *s,unsigned long *len)
  1.2122 +{
  1.2123 +  char tmp[MAILTMPLEN];
  1.2124 +  void *ret = NIL;
  1.2125 +  SENDSTREAM *stream = (SENDSTREAM *) s;
  1.2126 +  if ((stream->replycode == NNTPCHALLENGE) &&
  1.2127 +      !(ret = rfc822_base64 ((unsigned char *) stream->reply + 4,
  1.2128 +			     strlen (stream->reply + 4),len))) {
  1.2129 +    sprintf (tmp,"NNTP SERVER BUG (invalid challenge): %.80s",stream->reply+4);
  1.2130 +    mm_log (tmp,ERROR);
  1.2131 +  }
  1.2132 +  return ret;
  1.2133 +}
  1.2134 +
  1.2135 +
  1.2136 +/* Send authenticator response in BASE64
  1.2137 + * Accepts: MAIL stream
  1.2138 + *	    string to send
  1.2139 + *	    length of string
  1.2140 + * Returns: T, always
  1.2141 + */
  1.2142 +
  1.2143 +long nntp_response (void *s,char *response,unsigned long size)
  1.2144 +{
  1.2145 +  SENDSTREAM *stream = (SENDSTREAM *) s;
  1.2146 +  unsigned long i,j;
  1.2147 +  char *t,*u;
  1.2148 +  if (response) {		/* make CRLFless BASE64 string */
  1.2149 +    if (size) {
  1.2150 +      for (t = (char *) rfc822_binary ((void *) response,size,&i),u = t,j = 0;
  1.2151 +	   j < i; j++) if (t[j] > ' ') *u++ = t[j];
  1.2152 +      *u = '\0';		/* tie off string */
  1.2153 +      i = nntp_send_work (stream,t,NIL);
  1.2154 +      fs_give ((void **) &t);
  1.2155 +    }
  1.2156 +    else i = nntp_send_work (stream,"",NIL);
  1.2157 +  }
  1.2158 +  else {			/* abort requested */
  1.2159 +    i = nntp_send_work (stream,"*",NIL);
  1.2160 +    stream->saslcancel = T;	/* mark protocol-requested SASL cancel */
  1.2161 +  }
  1.2162 +  return LONGT;
  1.2163 +}
  1.2164 +
  1.2165 +/* NNTP get reply
  1.2166 + * Accepts: SEND stream
  1.2167 + * Returns: reply code
  1.2168 + */
  1.2169 +
  1.2170 +long nntp_reply (SENDSTREAM *stream)
  1.2171 +{
  1.2172 +				/* flush old reply */
  1.2173 +  if (stream->reply) fs_give ((void **) &stream->reply);
  1.2174 +  				/* get reply */
  1.2175 +  if (!(stream->reply = net_getline (stream->netstream)))
  1.2176 +    return nntp_fake (stream,"NNTP connection broken (response)");
  1.2177 +  if (stream->debug) mm_dlog (stream->reply);
  1.2178 +				/* handle continuation by recursion */
  1.2179 +  if (stream->reply[3] == '-') return nntp_reply (stream);
  1.2180 +				/* return response code */
  1.2181 +  return stream->replycode = atol (stream->reply);
  1.2182 +}
  1.2183 +
  1.2184 +
  1.2185 +/* NNTP set fake error
  1.2186 + * Accepts: SEND stream
  1.2187 + *	    error text
  1.2188 + * Returns: error code
  1.2189 + */
  1.2190 +
  1.2191 +long nntp_fake (SENDSTREAM *stream,char *text)
  1.2192 +{
  1.2193 +  if (stream->netstream) {	/* close net connection if still open */
  1.2194 +    net_close (stream->netstream);
  1.2195 +    stream->netstream = NIL;
  1.2196 +  }
  1.2197 +				/* flush any old reply */
  1.2198 +  if (stream->reply) fs_give ((void **) &stream->reply);
  1.2199 +  				/* set up pseudo-reply string */
  1.2200 +  stream->reply = (char *) fs_get (20+strlen (text));
  1.2201 +  sprintf (stream->reply,"%ld %s",NNTPSOFTFATAL,text);
  1.2202 +  return NNTPSOFTFATAL;		/* return error code */
  1.2203 +}
  1.2204 +
  1.2205 +/* NNTP filter mail
  1.2206 + * Accepts: stream
  1.2207 + *	    string
  1.2208 + * Returns: T on success, NIL on failure
  1.2209 + */
  1.2210 +
  1.2211 +long nntp_soutr (void *stream,char *s)
  1.2212 +{
  1.2213 +  char c,*t;
  1.2214 +				/* "." on first line */
  1.2215 +  if (s[0] == '.') net_soutr (stream,".");
  1.2216 +				/* find lines beginning with a "." */
  1.2217 +  while (t = strstr (s,"\015\012.")) {
  1.2218 +    c = *(t += 3);		/* remember next character after "." */
  1.2219 +    *t = '\0';			/* tie off string */
  1.2220 +				/* output prefix */
  1.2221 +    if (!net_soutr (stream,s)) return NIL;
  1.2222 +    *t = c;			/* restore delimiter */
  1.2223 +    s = t - 1;			/* push pointer up to the "." */
  1.2224 +  }
  1.2225 +				/* output remainder of text */
  1.2226 +  return *s ? net_soutr (stream,s) : T;
  1.2227 +}

UW-IMAP'd extensions by yuuji