1 /* ========================================================================
2  * Copyright 1988-2007 University of Washington
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *
11  * ========================================================================
12  */
13 
14 /*
15  * Program:	Network News Transfer Protocol (NNTP) routines
16  *
17  * Author:	Mark Crispin
18  *		Networks and Distributed Computing
19  *		Computing & Communications
20  *		University of Washington
21  *		Administration Building, AG-44
22  *		Seattle, WA  98195
23  *		Internet: MRC@CAC.Washington.EDU
24  *
25  * Date:	10 February 1992
26  * Last Edited:	6 September 2007
27  */
28 
29 
30 #include <ctype.h>
31 #include <stdio.h>
32 #include "c-client.h"
33 #include "newsrc.h"
34 #include "netmsg.h"
35 #include "flstring.h"
36 
37 /* Constants */
38 
39 #define NNTPSSLPORT (long) 563	/* assigned SSL TCP contact port */
40 #define NNTPGREET (long) 200	/* NNTP successful greeting */
41 				/* NNTP successful greeting w/o posting priv */
42 #define NNTPGREETNOPOST (long) 201
43 #define NNTPEXTOK (long) 202	/* NNTP extensions OK */
44 #define NNTPGOK (long) 211	/* NNTP group selection OK */
45 #define NNTPGLIST (long) 215	/* NNTP group list being returned */
46 #define NNTPARTICLE (long) 220	/* NNTP article file */
47 #define NNTPHEAD (long) 221	/* NNTP header text */
48 #define NNTPBODY (long) 222	/* NNTP body text */
49 #define NNTPOVER (long) 224	/* NNTP overview text */
50 #define NNTPOK (long) 240	/* NNTP OK code */
51 #define NNTPAUTHED (long) 281	/* NNTP successful authentication */
52 				/* NNTP successful authentication with data */
53 #define NNTPAUTHEDDATA (long) 282
54 #define NNTPREADY (long) 340	/* NNTP ready for data */
55 #define NNTPWANTAUTH2 (long) 380/* NNTP authentication needed (old) */
56 #define NNTPWANTPASS (long) 381	/* NNTP password needed */
57 #define NNTPTLSSTART (long) 382	/* NNTP continue with TLS negotiation */
58 #define NNTPCHALLENGE (long) 383/* NNTP challenge, want response */
59 #define NNTPSOFTFATAL (long) 400/* NNTP soft fatal code */
60 #define NNTPWANTAUTH (long) 480	/* NNTP authentication needed */
61 #define NNTPBADCMD (long) 500	/* NNTP unrecognized command */
62 #define IDLETIMEOUT (long) 3	/* defined in NNTPEXT WG base draft */
63 
64 
65 /* NNTP I/O stream local data */
66 
67 typedef struct nntp_local {
68   SENDSTREAM *nntpstream;	/* NNTP stream for I/O */
69   unsigned int dirty : 1;	/* disk copy of .newsrc needs updating */
70   unsigned int tlsflag : 1;	/* TLS session */
71   unsigned int tlssslv23 : 1;	/* TLS using SSLv23 client method */
72   unsigned int notlsflag : 1;	/* TLS not used in session */
73   unsigned int sslflag : 1;	/* SSL session */
74   unsigned int novalidate : 1;	/* certificate not validated */
75   unsigned int xover : 1;	/* supports XOVER */
76   unsigned int xhdr : 1;	/* supports XHDR */
77   char *name;			/* remote newsgroup name */
78   char *user;			/* mailbox user */
79   char *newsrc;			/* newsrc file */
80   char *over_fmt;		/* overview format */
81   unsigned long msgno;		/* current text message number */
82   FILE *txt;			/* current text */
83   unsigned long txtsize;	/* current text size */
84 } NNTPLOCAL;
85 
86 
87 /* Convenient access to local data */
88 
89 #define LOCAL ((NNTPLOCAL *) stream->local)
90 
91 
92 /* Convenient access to protocol-specific data */
93 
94 #define NNTP stream->protocol.nntp
95 
96 
97 /* Convenient access to extensions */
98 
99 #define EXTENSION LOCAL->nntpstream->protocol.nntp.ext
100 
101 /* Function prototypes */
102 
103 DRIVER *nntp_valid (char *name);
104 DRIVER *nntp_isvalid (char *name,char *mbx);
105 void *nntp_parameters (long function,void *value);
106 void nntp_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
107 void nntp_list (MAILSTREAM *stream,char *ref,char *pat);
108 void nntp_lsub (MAILSTREAM *stream,char *ref,char *pat);
109 long nntp_canonicalize (char *ref,char *pat,char *pattern,char *wildmat);
110 long nntp_subscribe (MAILSTREAM *stream,char *mailbox);
111 long nntp_unsubscribe (MAILSTREAM *stream,char *mailbox);
112 long nntp_create (MAILSTREAM *stream,char *mailbox);
113 long nntp_delete (MAILSTREAM *stream,char *mailbox);
114 long nntp_rename (MAILSTREAM *stream,char *old,char *newname);
115 long nntp_status (MAILSTREAM *stream,char *mbx,long flags);
116 long nntp_getmap (MAILSTREAM *stream,char *name,
117 		  unsigned long first,unsigned long last,
118 		  unsigned long rnmsgs,unsigned long nmsgs,char *tmp);
119 MAILSTREAM *nntp_mopen (MAILSTREAM *stream);
120 void nntp_mclose (MAILSTREAM *stream,long options);
121 void nntp_fetchfast (MAILSTREAM *stream,char *sequence,long flags);
122 void nntp_flags (MAILSTREAM *stream,char *sequence,long flags);
123 long nntp_overview (MAILSTREAM *stream,overview_t ofn);
124 long nntp_parse_overview (OVERVIEW *ov,char *text,MESSAGECACHE *elt);
125 long nntp_over (MAILSTREAM *stream,char *sequence);
126 char *nntp_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *size,
127 		   long flags);
128 long nntp_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
129 FILE *nntp_article (MAILSTREAM *stream,char *msgid,unsigned long *size,
130 		    unsigned long *hsiz);
131 void nntp_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
132 long nntp_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags);
133 long nntp_search_msg (MAILSTREAM *stream,unsigned long msgno,SEARCHPGM *pgm,
134 		      OVERVIEW *ov);
135 unsigned long *nntp_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
136 			  SORTPGM *pgm,long flags);
137 SORTCACHE **nntp_sort_loadcache (MAILSTREAM *stream,SORTPGM *pgm,
138 				 unsigned long start,unsigned long last,
139 				 long flags);
140 THREADNODE *nntp_thread (MAILSTREAM *stream,char *type,char *charset,
141 			 SEARCHPGM *spg,long flags);
142 long nntp_ping (MAILSTREAM *stream);
143 void nntp_check (MAILSTREAM *stream);
144 long nntp_expunge (MAILSTREAM *stream,char *sequence,long options);
145 long nntp_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
146 long nntp_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
147 
148 long nntp_extensions (SENDSTREAM *stream,long flags);
149 long nntp_send (SENDSTREAM *stream,char *command,char *args);
150 long nntp_send_work (SENDSTREAM *stream,char *command,char *args);
151 long nntp_send_auth (SENDSTREAM *stream,long flags);
152 long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags);
153 void *nntp_challenge (void *s,unsigned long *len);
154 long nntp_response (void *s,char *response,unsigned long size);
155 long nntp_reply (SENDSTREAM *stream);
156 long nntp_fake (SENDSTREAM *stream,char *text);
157 long nntp_soutr (void *stream,char *s);
158 
159 /* Driver dispatch used by MAIL */
160 
161 DRIVER nntpdriver = {
162   "nntp",			/* driver name */
163 				/* driver flags */
164 #ifdef INADEQUATE_MEMORY
165   DR_LOWMEM |
166 #endif
167   DR_NEWS|DR_READONLY|DR_NOFAST|DR_NAMESPACE|DR_CRLF|DR_RECYCLE|DR_XPOINT |
168     DR_NOINTDATE|DR_NONEWMAIL|DR_HALFOPEN,
169   (DRIVER *) NIL,		/* next driver */
170   nntp_valid,			/* mailbox is valid for us */
171   nntp_parameters,		/* manipulate parameters */
172   nntp_scan,			/* scan mailboxes */
173   nntp_list,			/* find mailboxes */
174   nntp_lsub,			/* find subscribed mailboxes */
175   nntp_subscribe,		/* subscribe to mailbox */
176   nntp_unsubscribe,		/* unsubscribe from mailbox */
177   nntp_create,			/* create mailbox */
178   nntp_delete,			/* delete mailbox */
179   nntp_rename,			/* rename mailbox */
180   nntp_status,			/* status of mailbox */
181   nntp_mopen,			/* open mailbox */
182   nntp_mclose,			/* close mailbox */
183   nntp_fetchfast,		/* fetch message "fast" attributes */
184   nntp_flags,			/* fetch message flags */
185   nntp_overview,		/* fetch overview */
186   NIL,				/* fetch message structure */
187   nntp_header,			/* fetch message header */
188   nntp_text,			/* fetch message text */
189   NIL,				/* fetch message */
190   NIL,				/* unique identifier */
191   NIL,				/* message number from UID */
192   NIL,				/* modify flags */
193   nntp_flagmsg,			/* per-message modify flags */
194   nntp_search,			/* search for message based on criteria */
195   nntp_sort,			/* sort messages */
196   nntp_thread,			/* thread messages */
197   nntp_ping,			/* ping mailbox to see if still alive */
198   nntp_check,			/* check for new messages */
199   nntp_expunge,			/* expunge deleted messages */
200   nntp_copy,			/* copy messages to another mailbox */
201   nntp_append,			/* append string message to mailbox */
202   NIL				/* garbage collect stream */
203 };
204 
205 				/* prototype stream */
206 MAILSTREAM nntpproto = {&nntpdriver};
207 
208 
209 				/* driver parameters */
210 static unsigned long nntp_maxlogintrials = MAXLOGINTRIALS;
211 static long nntp_port = 0;
212 static long nntp_sslport = 0;
213 static unsigned long nntp_range = 0;
214 static long nntp_hidepath = 0;
215 
216 /* NNTP validate mailbox
217  * Accepts: mailbox name
218  * Returns: our driver if name is valid, NIL otherwise
219  */
220 
nntp_valid(char * name)221 DRIVER *nntp_valid (char *name)
222 {
223   char tmp[MAILTMPLEN];
224   return nntp_isvalid (name,tmp);
225 }
226 
227 
228 /* NNTP validate mailbox work routine
229  * Accepts: mailbox name
230  *	    buffer for returned mailbox name
231  * Returns: our driver if name is valid, NIL otherwise
232  */
233 
nntp_isvalid(char * name,char * mbx)234 DRIVER *nntp_isvalid (char *name,char *mbx)
235 {
236   NETMBX mb;
237   if (!mail_valid_net_parse (name,&mb) || strcmp (mb.service,nntpdriver.name)||
238       mb.anoflag) return NIL;
239   if (mb.mailbox[0] != '#') strcpy (mbx,mb.mailbox);
240 			/* namespace format name */
241   else if ((mb.mailbox[1] == 'n') && (mb.mailbox[2] == 'e') &&
242 	   (mb.mailbox[3] == 'w') && (mb.mailbox[4] == 's') &&
243 	   (mb.mailbox[5] == '.')) strcpy (mbx,mb.mailbox+6);
244   else return NIL;		/* bogus name */
245   return &nntpdriver;
246 }
247 
248 /* News manipulate driver parameters
249  * Accepts: function code
250  *	    function-dependent value
251  * Returns: function-dependent return value
252  */
253 
nntp_parameters(long function,void * value)254 void *nntp_parameters (long function,void *value)
255 {
256   switch ((int) function) {
257   case SET_MAXLOGINTRIALS:
258     nntp_maxlogintrials = (unsigned long) value;
259     break;
260   case GET_MAXLOGINTRIALS:
261     value = (void *) nntp_maxlogintrials;
262     break;
263   case SET_NNTPPORT:
264     nntp_port = (long) value;
265     break;
266   case GET_NNTPPORT:
267     value = (void *) nntp_port;
268     break;
269   case SET_SSLNNTPPORT:
270     nntp_sslport = (long) value;
271     break;
272   case GET_SSLNNTPPORT:
273     value = (void *) nntp_sslport;
274     break;
275   case SET_NNTPRANGE:
276     nntp_range = (unsigned long) value;
277     break;
278   case GET_NNTPRANGE:
279     value = (void *) nntp_range;
280     break;
281   case SET_NNTPHIDEPATH:
282     nntp_hidepath = (long) value;
283     break;
284   case GET_NNTPHIDEPATH:
285     value = (void *) nntp_hidepath;
286     break;
287   case GET_NEWSRC:
288     if (value)
289       value = (void *) ((NNTPLOCAL *) ((MAILSTREAM *) value)->local)->newsrc;
290     break;
291   case GET_IDLETIMEOUT:
292     value = (void *) IDLETIMEOUT;
293     break;
294   case ENABLE_DEBUG:
295     if (value)
296       ((NNTPLOCAL *) ((MAILSTREAM *) value)->local)->nntpstream->debug = T;
297     break;
298   case DISABLE_DEBUG:
299     if (value)
300       ((NNTPLOCAL *) ((MAILSTREAM *) value)->local)->nntpstream->debug = NIL;
301     break;
302   default:
303     value = NIL;		/* error case */
304     break;
305   }
306   return value;
307 }
308 
309 /* NNTP mail scan mailboxes for string
310  * Accepts: mail stream
311  *	    reference
312  *	    pattern to search
313  *	    string to scan
314  */
315 
nntp_scan(MAILSTREAM * stream,char * ref,char * pat,char * contents)316 void nntp_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
317 {
318   char tmp[MAILTMPLEN];
319   if (nntp_canonicalize (ref,pat,tmp,NIL))
320     mm_log ("Scan not valid for NNTP mailboxes",ERROR);
321 }
322 
323 
324 /* NNTP list newsgroups
325  * Accepts: mail stream
326  *	    reference
327  *	    pattern to search
328  */
329 
nntp_list(MAILSTREAM * stream,char * ref,char * pat)330 void nntp_list (MAILSTREAM *stream,char *ref,char *pat)
331 {
332   MAILSTREAM *st = stream;
333   char *s,*t,*lcl,pattern[MAILTMPLEN],name[MAILTMPLEN],wildmat[MAILTMPLEN];
334   int showuppers = pat[strlen (pat) - 1] == '%';
335   if (!*pat) {
336     if (nntp_canonicalize (ref,"*",pattern,NIL)) {
337 				/* tie off name at root */
338       if ((s = strchr (pattern,'}')) && (s = strchr (s+1,'.'))) *++s = '\0';
339       else pattern[0] = '\0';
340       mm_list (stream,'.',pattern,NIL);
341     }
342   }
343 				/* ask server for open newsgroups */
344   else if (nntp_canonicalize (ref,pat,pattern,wildmat) &&
345 	   ((stream && LOCAL && LOCAL->nntpstream) ||
346 	    (stream = mail_open (NIL,pattern,OP_HALFOPEN|OP_SILENT))) &&
347 	   ((nntp_send (LOCAL->nntpstream,"LIST ACTIVE",
348 			wildmat[0] ? wildmat : NIL) == NNTPGLIST) ||
349 	    (nntp_send (LOCAL->nntpstream,"LIST",NIL) == NNTPGLIST))) {
350 				/* namespace format name? */
351     if (*(lcl = strchr (strcpy (name,pattern),'}') + 1) == '#') lcl += 6;
352 				/* process data until we see final dot */
353     while (s = net_getline (LOCAL->nntpstream->netstream)) {
354       if ((*s == '.') && !s[1]){/* end of text */
355 	fs_give ((void **) &s);
356 	break;
357       }
358       if (t = strchr (s,' ')) {	/* tie off after newsgroup name */
359 	*t = '\0';
360 	strcpy (lcl,s);		/* make full form of name */
361 				/* report if match */
362 	if (pmatch_full (name,pattern,'.')) mm_list (stream,'.',name,NIL);
363 	else while (showuppers && (t = strrchr (lcl,'.'))) {
364 	  *t = '\0';		/* tie off the name */
365 	  if (pmatch_full (name,pattern,'.'))
366 	    mm_list (stream,'.',name,LATT_NOSELECT);
367 	}
368       }
369       fs_give ((void **) &s);	/* clean up */
370     }
371     if (stream != st) mail_close (stream);
372   }
373 }
374 
375 /* NNTP list subscribed newsgroups
376  * Accepts: mail stream
377  *	    reference
378  *	    pattern to search
379  */
380 
nntp_lsub(MAILSTREAM * stream,char * ref,char * pat)381 void nntp_lsub (MAILSTREAM *stream,char *ref,char *pat)
382 {
383   void *sdb = NIL;
384   char *s,mbx[MAILTMPLEN];
385 				/* return data from newsrc */
386   if (nntp_canonicalize (ref,pat,mbx,NIL)) newsrc_lsub (stream,mbx);
387   if (*pat == '{') {		/* if remote pattern, must be NNTP */
388     if (!nntp_valid (pat)) return;
389     ref = NIL;			/* good NNTP pattern, punt reference */
390   }
391 				/* if remote reference, must be valid NNTP */
392   if (ref && (*ref == '{') && !nntp_valid (ref)) return;
393 				/* kludgy application of reference */
394   if (ref && *ref) sprintf (mbx,"%s%s",ref,pat);
395   else strcpy (mbx,pat);
396 
397   if (s = sm_read (&sdb)) do if (nntp_valid (s) && pmatch (s,mbx))
398     mm_lsub (stream,NIL,s,NIL);
399   while (s = sm_read (&sdb));	/* until no more subscriptions */
400 }
401 
402 /* NNTP canonicalize newsgroup name
403  * Accepts: reference
404  *	    pattern
405  *	    returned single pattern
406  *	    returned wildmat pattern
407  * Returns: T on success, NIL on failure
408  */
409 
nntp_canonicalize(char * ref,char * pat,char * pattern,char * wildmat)410 long nntp_canonicalize (char *ref,char *pat,char *pattern,char *wildmat)
411 {
412   char *s;
413   DRIVER *ret;
414   if (ref && *ref) {		/* have a reference */
415     if (!nntp_valid (ref)) return NIL;
416     strcpy (pattern,ref);	/* copy reference to pattern */
417 				/* # overrides mailbox field in reference */
418     if (*pat == '#') strcpy (strchr (pattern,'}') + 1,pat);
419 				/* pattern starts, reference ends, with . */
420     else if ((*pat == '.') && (pattern[strlen (pattern) - 1] == '.'))
421       strcat (pattern,pat + 1);	/* append, omitting one of the period */
422     else strcat (pattern,pat);	/* anything else is just appended */
423   }
424   else strcpy (pattern,pat);	/* just have basic name */
425   if ((ret = wildmat ?		/* if valid and wildmat */
426        nntp_isvalid (pattern,wildmat) : nntp_valid (pattern)) && wildmat) {
427 				/* don't return wildmat if specials present */
428     if (strpbrk (wildmat,",?![\\]")) wildmat[0] = '\0';
429 				/* replace all % with * */
430     for (s = wildmat; s = strchr (s,'%'); *s = '*');
431   }
432   return ret ? LONGT : NIL;
433 }
434 
435 /* NNTP subscribe to mailbox
436  * Accepts: mail stream
437  *	    mailbox to add to subscription list
438  * Returns: T on success, NIL on failure
439  */
440 
nntp_subscribe(MAILSTREAM * stream,char * mailbox)441 long nntp_subscribe (MAILSTREAM *stream,char *mailbox)
442 {
443   char mbx[MAILTMPLEN];
444   return nntp_isvalid (mailbox,mbx) ? newsrc_update (stream,mbx,':') : NIL;
445 }
446 
447 
448 /* NNTP unsubscribe to mailbox
449  * Accepts: mail stream
450  *	    mailbox to delete from subscription list
451  * Returns: T on success, NIL on failure
452  */
453 
nntp_unsubscribe(MAILSTREAM * stream,char * mailbox)454 long nntp_unsubscribe (MAILSTREAM *stream,char *mailbox)
455 {
456   char mbx[MAILTMPLEN];
457   return nntp_isvalid (mailbox,mbx) ? newsrc_update (stream,mbx,'!') : NIL;
458 }
459 
460 /* NNTP create mailbox
461  * Accepts: mail stream
462  *	    mailbox name to create
463  * Returns: T on success, NIL on failure
464  */
465 
nntp_create(MAILSTREAM * stream,char * mailbox)466 long nntp_create (MAILSTREAM *stream,char *mailbox)
467 {
468   return NIL;			/* never valid for NNTP */
469 }
470 
471 
472 /* NNTP delete mailbox
473  *	    mailbox name to delete
474  * Returns: T on success, NIL on failure
475  */
476 
nntp_delete(MAILSTREAM * stream,char * mailbox)477 long nntp_delete (MAILSTREAM *stream,char *mailbox)
478 {
479   return NIL;			/* never valid for NNTP */
480 }
481 
482 
483 /* NNTP rename mailbox
484  * Accepts: mail stream
485  *	    old mailbox name
486  *	    new mailbox name
487  * Returns: T on success, NIL on failure
488  */
489 
nntp_rename(MAILSTREAM * stream,char * old,char * newname)490 long nntp_rename (MAILSTREAM *stream,char *old,char *newname)
491 {
492   return NIL;			/* never valid for NNTP */
493 }
494 
495 /* NNTP status
496  * Accepts: mail stream
497  *	    mailbox name
498  *	    status flags
499  * Returns: T on success, NIL on failure
500  */
501 
nntp_status(MAILSTREAM * stream,char * mbx,long flags)502 long nntp_status (MAILSTREAM *stream,char *mbx,long flags)
503 {
504   MAILSTATUS status;
505   NETMBX mb;
506   unsigned long i,j,k,rnmsgs;
507   long ret = NIL;
508   char *s,*name,*state,tmp[MAILTMPLEN];
509   char *old = (stream && !stream->halfopen) ? LOCAL->name : NIL;
510   MAILSTREAM *tstream = NIL;
511   if (!(mail_valid_net_parse (mbx,&mb) && !strcmp (mb.service,"nntp") &&
512 	*mb.mailbox &&
513 	((mb.mailbox[0] != '#') ||
514 	 ((mb.mailbox[1] == 'n') && (mb.mailbox[2] == 'e') &&
515 	  (mb.mailbox[3] == 'w') && (mb.mailbox[4] == 's') &&
516 	  (mb.mailbox[5] == '.'))))) {
517     sprintf (tmp,"Invalid NNTP name %s",mbx);
518     mm_log (tmp,ERROR);
519     return NIL;
520   }
521 				/* note mailbox name */
522   name = (*mb.mailbox == '#') ? mb.mailbox+6 : mb.mailbox;
523 				/* stream to reuse? */
524   if (!(stream && LOCAL->nntpstream &&
525 	mail_usable_network_stream (stream,mbx)) &&
526       !(tstream = stream =
527 	mail_open (NIL,mbx,OP_HALFOPEN|OP_SILENT|
528 		   ((flags & SA_MULNEWSRC) ? OP_MULNEWSRC : NIL))))
529     return NIL;			/* can't reuse or make a new one */
530 
531   if (nntp_send (LOCAL->nntpstream,"GROUP",name) == NNTPGOK) {
532     status.flags = flags;	/* status validity flags */
533     k = strtoul (LOCAL->nntpstream->reply + 4,&s,10);
534     i = strtoul (s,&s,10);	/* first assigned UID */
535 				/* next UID to be assigned */
536     status.uidnext = (j = strtoul (s,NIL,10)) + 1;
537 				/* maximum number of messages */
538     rnmsgs = status.messages = (i | j) ? status.uidnext - i : 0;
539     if (k > status.messages) {	/* check for absurdity */
540       sprintf (tmp,"NNTP SERVER BUG (impossible message count): %lu > %lu",
541 	       k,status.messages);
542       mm_log (tmp,WARN);
543     }
544 				/* restrict article range if needed */
545     if (nntp_range && (status.messages > nntp_range)) {
546       i = status.uidnext - (status.messages = nntp_range);
547       if (k > nntp_range) k = nntp_range;
548     }
549 				/* initially zero */
550     status.recent = status.unseen = 0;
551     if (!status.messages);	/* empty case */
552 				/* use server guesstimate in simple case */
553     else if (!(flags & (SA_RECENT | SA_UNSEEN))) status.messages = k;
554 
555 				/* have newsrc state? */
556     else if (state = newsrc_state (stream,name)) {
557 				/* yes, get the UID/sequence map */
558       if (nntp_getmap (stream,name,i,status.uidnext - 1,rnmsgs,
559 		       status.messages,tmp)) {
560 				/* calculate true count */
561 	for (status.messages = 0;
562 	     (s = net_getline (LOCAL->nntpstream->netstream)) &&
563 	       strcmp (s,"."); ) {
564 				/* only count if in range */
565 	  if (((k = atol (s)) >= i) && (k < status.uidnext)) {
566 	    newsrc_check_uid (state,k,&status.recent,&status.unseen);
567 	    status.messages++;
568 	  }
569 	  fs_give ((void **) &s);
570 	}
571 	if (s) fs_give ((void **) &s);
572       }
573 				/* assume c-client/NNTP map is entire range */
574       else while (i < status.uidnext)
575 	newsrc_check_uid (state,i++,&status.recent,&status.unseen);
576       fs_give ((void **) &state);
577     }
578 				/* no .newsrc state, all messages new */
579     else status.recent = status.unseen = status.messages;
580 				/* UID validity is a constant */
581     status.uidvalidity = stream->uid_validity;
582 				/* pass status to main program */
583     mm_status (stream,mbx,&status);
584     ret = T;			/* succes */
585   }
586 				/* flush temporary stream */
587   if (tstream) mail_close (tstream);
588 				/* else reopen old newsgroup */
589   else if (old && nntp_send (LOCAL->nntpstream,"GROUP",old) != NNTPGOK) {
590     mm_log (LOCAL->nntpstream->reply,ERROR);
591     stream->halfopen = T;	/* go halfopen */
592   }
593   return ret;			/* success */
594 }
595 
596 /* NNTP get map
597  * Accepts: stream
598  *	    newsgroup name
599  *	    first UID in map range
600  *	    last UID in map range
601  *	    reported total number of messages in newsgroup
602  *	    calculated number of messages in range
603  *	    temporary buffer
604  * Returns: T on success, NIL on failure
605  */
606 
nntp_getmap(MAILSTREAM * stream,char * name,unsigned long first,unsigned long last,unsigned long rnmsgs,unsigned long nmsgs,char * tmp)607 long nntp_getmap (MAILSTREAM *stream,char *name,
608 		  unsigned long first,unsigned long last,
609 		  unsigned long rnmsgs,unsigned long nmsgs,char *tmp)
610 {
611   short trylistgroup = NIL;
612   if (rnmsgs > (nmsgs * 8))	/* small subrange? */
613     trylistgroup = T;		/* yes, can try LISTGROUP if [X]HDR fails */
614   else switch ((int) nntp_send (LOCAL->nntpstream,"LISTGROUP",name)) {
615   case NNTPGOK:			/* got data */
616     return LONGT;
617   default:			/* else give up if server claims LISTGROUP */
618     if (EXTENSION.listgroup) return NIL;
619   }
620 				/* build range */
621   sprintf (tmp,"%lu-%lu",first,last);
622   if (EXTENSION.hdr)		/* have HDR extension? */
623     return (nntp_send (LOCAL->nntpstream,"HDR Date",tmp) == NNTPHEAD) ?
624       LONGT : NIL;
625   if (LOCAL->xhdr)		/* try the experimental extension then */
626     switch ((int) nntp_send (LOCAL->nntpstream,"XHDR Date",tmp)) {
627     case NNTPHEAD:		/* got an overview? */
628       return LONGT;
629     case NNTPBADCMD:		/* unknown command? */
630       LOCAL->xhdr = NIL;	/* disable future XHDR attempts */
631     }
632   if (trylistgroup &&		/* no [X]HDR, maybe do LISTGROUP after all */
633       (nntp_send (LOCAL->nntpstream,"LISTGROUP",name) == NNTPGOK))
634     return LONGT;
635   return NIL;
636 }
637 
638 /* NNTP open
639  * Accepts: stream to open
640  * Returns: stream on success, NIL on failure
641  */
642 
nntp_mopen(MAILSTREAM * stream)643 MAILSTREAM *nntp_mopen (MAILSTREAM *stream)
644 {
645   unsigned long i,j,k,nmsgs,rnmsgs;
646   char *s,*mbx,tmp[MAILTMPLEN];
647   FILE *f;
648   NETMBX mb;
649   char *newsrc = (char *) mail_parameters (NIL,GET_NEWSRC,NIL);
650   newsrcquery_t nq = (newsrcquery_t) mail_parameters (NIL,GET_NEWSRCQUERY,NIL);
651   SENDSTREAM *nstream = NIL;
652 				/* return prototype for OP_PROTOTYPE call */
653   if (!stream) return &nntpproto;
654   mail_valid_net_parse (stream->mailbox,&mb);
655 				/* note mailbox anme */
656   mbx = (*mb.mailbox == '#') ? mb.mailbox+6 : mb.mailbox;
657   if (LOCAL) {			/* recycle stream */
658     nstream = LOCAL->nntpstream;/* remember NNTP protocol stream */
659     sprintf (tmp,"Reusing connection to %s",net_host (nstream->netstream));
660     if (!stream->silent) mm_log (tmp,(long) NIL);
661     if (stream->rdonly) mb.readonlyflag = T;
662     if (LOCAL->tlsflag) mb.tlsflag = T;
663     if (LOCAL->tlssslv23) mb.tlssslv23 = T;
664     if (LOCAL->notlsflag) mb.notlsflag = T;
665     if (LOCAL->sslflag) mb.sslflag = T;
666     if (LOCAL->novalidate) mb.novalidate = T;
667     if (LOCAL->nntpstream->loser) mb.loser = T;
668     if (stream->secure) mb.secflag = T;
669     LOCAL->nntpstream = NIL;	/* keep nntp_mclose() from punting it */
670     nntp_mclose (stream,NIL);	/* do close action */
671     stream->dtb = &nntpdriver;	/* reattach this driver */
672   }
673 				/* copy flags */
674   if (mb.dbgflag) stream->debug = T;
675   if (mb.readonlyflag) stream->rdonly = T;
676   if (mb.secflag) stream->secure = T;
677   mb.trysslflag = stream->tryssl = (mb.trysslflag || stream->tryssl) ? T : NIL;
678   if (!nstream) {		/* open NNTP now if not already open */
679     char *hostlist[2];
680     hostlist[0] = strcpy (tmp,mb.host);
681     if (mb.port || nntp_port)
682       sprintf (tmp + strlen (tmp),":%lu",mb.port ? mb.port : nntp_port);
683     if (mb.tlsflag) strcat (tmp,"/tls");
684     if (mb.tlssslv23) strcat (tmp,"/tls-sslv23");
685     if (mb.notlsflag) strcat (tmp,"/notls");
686     if (mb.sslflag) strcat (tmp,"/ssl");
687     if (mb.novalidate) strcat (tmp,"/novalidate-cert");
688     if (mb.loser) strcat (tmp,"/loser");
689     if (mb.secflag) strcat (tmp,"/secure");
690     if (mb.user[0]) sprintf (tmp + strlen (tmp),"/user=\"%s\"",mb.user);
691     hostlist[1] = NIL;
692     if (!(nstream = nntp_open (hostlist,NOP_READONLY |
693 			       (stream->debug ? NOP_DEBUG : NIL)))) return NIL;
694   }
695 
696 				/* always zero messages if halfopen */
697   if (stream->halfopen) i = j = k = rnmsgs = nmsgs = 0;
698 				/* otherwise open the newsgroup */
699   else if (nntp_send (nstream,"GROUP",mbx) == NNTPGOK) {
700     k = strtoul (nstream->reply + 4,&s,10);
701     i = strtoul (s,&s,10);
702     stream->uid_last = j = strtoul (s,&s,10);
703     rnmsgs = nmsgs = (i | j) ? 1 + j - i : 0;
704     if (k > nmsgs) {		/* check for absurdity */
705       sprintf (tmp,"NNTP SERVER BUG (impossible message count): %lu > %lu",
706 	       k,nmsgs);
707       mm_log (tmp,WARN);
708     }
709 				/* restrict article range if needed */
710     if (nntp_range && (nmsgs > nntp_range)) i = 1 + j - (nmsgs = nntp_range);
711   }
712   else {			/* no such newsgroup */
713     mm_log (nstream->reply,ERROR);
714     nntp_close (nstream);	/* punt stream */
715     return NIL;
716   }
717 				/* instantiate local data */
718   stream->local = memset (fs_get (sizeof (NNTPLOCAL)),0,sizeof (NNTPLOCAL));
719   LOCAL->nntpstream = nstream;
720 				/* save state for future recycling */
721   if (mb.tlsflag) LOCAL->tlsflag = T;
722   if (mb.tlssslv23) LOCAL->tlssslv23 = T;
723   if (mb.notlsflag) LOCAL->notlsflag = T;
724   if (mb.sslflag) LOCAL->sslflag = T;
725   if (mb.novalidate) LOCAL->novalidate = T;
726   if (mb.loser) LOCAL->nntpstream->loser = T;
727 				/* assume present until proven otherwise */
728   LOCAL->xhdr = LOCAL->xover = T;
729   LOCAL->name = cpystr (mbx);	/* copy newsgroup name */
730   if (stream->mulnewsrc) {	/* want to use multiple .newsrc files? */
731     strcpy (tmp,newsrc);
732     s = tmp + strlen (tmp);	/* end of string */
733     *s++ = '-';			/* hyphen delimiter and host */
734     lcase (strcpy (s,(long) mail_parameters (NIL,GET_NEWSRCCANONHOST,NIL) ?
735 		   net_host (nstream->netstream) : mb.host));
736     LOCAL->newsrc = cpystr (nq ? (*nq) (stream,tmp,newsrc) : tmp);
737   }
738   else LOCAL->newsrc = cpystr (newsrc);
739   if (mb.user[0]) LOCAL->user = cpystr (mb.user);
740   stream->sequence++;		/* bump sequence number */
741   stream->rdonly = stream->perm_deleted = T;
742 				/* UIDs are always valid */
743   stream->uid_validity = 0xbeefface;
744   sprintf (tmp,"{%s:%lu/nntp",(long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ?
745 	   net_host (nstream->netstream) : mb.host,
746 	   net_port (nstream->netstream));
747   if (LOCAL->tlsflag) strcat (tmp,"/tls");
748   if (LOCAL->tlssslv23) strcat (tmp,"/tls-sslv23");
749   if (LOCAL->notlsflag) strcat (tmp,"/notls");
750   if (LOCAL->sslflag) strcat (tmp,"/ssl");
751   if (LOCAL->novalidate) strcat (tmp,"/novalidate-cert");
752   if (LOCAL->nntpstream->loser) strcat (tmp,"/loser");
753   if (stream->secure) strcat (tmp,"/secure");
754   if (stream->rdonly) strcat (tmp,"/readonly");
755   if (LOCAL->user) sprintf (tmp + strlen (tmp),"/user=\"%s\"",LOCAL->user);
756   if (stream->halfopen) strcat (tmp,"}<no_mailbox>");
757   else sprintf (tmp + strlen (tmp),"}#news.%s",mbx);
758   fs_give ((void **) &stream->mailbox);
759   stream->mailbox = cpystr (tmp);
760 
761   if (EXTENSION.over &&		/* get overview format if have OVER */
762       (nntp_send (LOCAL->nntpstream,"LIST","OVERVIEW.FMT") == NNTPGLIST) &&
763       (f = netmsg_slurp (LOCAL->nntpstream->netstream,&k,NIL))) {
764     fread (LOCAL->over_fmt = (char *) fs_get ((size_t) k + 3),
765 	   (size_t) 1,(size_t) k,f);
766     LOCAL->over_fmt[k] = '\0';
767     fclose (f);			/* flush temp file */
768   }
769   if (nmsgs) {			/* if any messages exist */
770     short silent = stream->silent;
771     stream->silent = T;		/* don't notify main program yet */
772     mail_exists (stream,nmsgs);	/* silently set the cache to the guesstimate */
773 				/* get UID/sequence map, nuke holes */
774     if (nntp_getmap (stream,mbx,i,j,rnmsgs,nmsgs,tmp)) {
775       for (nmsgs = 0;		/* calculate true count */
776 	   (s = net_getline (nstream->netstream)) && strcmp (s,"."); ) {
777 	if ((k = atol (s)) > j){/* discard too high article numbers */
778 	  sprintf (tmp,"NNTP SERVER BUG (out of range article ID): %lu > %lu",
779 		   k,j);
780 	  mm_notify (stream,tmp,NIL);
781 	  stream->unhealthy = T;
782 	}
783 	else if (k >= i) {	/* silently ignore too-low article numbers */
784 				/* guard against server returning extra msgs */
785 	  if (nmsgs == stream->nmsgs) mail_exists (stream,nmsgs+1);
786 				/* create elt for this message, set UID */
787 	  mail_elt (stream,++nmsgs)->private.uid = k;
788 	}
789 	fs_give ((void **) &s);
790       }
791       if (s) fs_give ((void **) &s);
792     }
793 				/* assume c-client/NNTP map is entire range */
794     else for (k = 1; k <= nmsgs; k++) mail_elt (stream,k)->private.uid = i++;
795     stream->unhealthy = NIL;	/* set healthy */
796     stream->nmsgs = 0;		/* whack it back down */
797     stream->silent = silent;	/* restore old silent setting */
798     mail_exists (stream,nmsgs);	/* notify upper level that messages exist */
799 				/* read .newsrc entries */
800     mail_recent (stream,newsrc_read (mbx,stream));
801   }
802   else {			/* empty newsgroup or halfopen */
803     if (!(stream->silent || stream->halfopen)) {
804       sprintf (tmp,"Newsgroup %s is empty",mbx);
805       mm_log (tmp,WARN);
806     }
807     mail_exists (stream,(long) 0);
808     mail_recent (stream,(long) 0);
809   }
810   return stream;		/* return stream to caller */
811 }
812 
813 /* NNTP close
814  * Accepts: MAIL stream
815  *	    option flags
816  */
817 
nntp_mclose(MAILSTREAM * stream,long options)818 void nntp_mclose (MAILSTREAM *stream,long options)
819 {
820   unsigned long i;
821   MESSAGECACHE *elt;
822   if (LOCAL) {			/* only if a file is open */
823     nntp_check (stream);	/* dump final checkpoint */
824     if (LOCAL->over_fmt) fs_give ((void **) &LOCAL->over_fmt);
825     if (LOCAL->name) fs_give ((void **) &LOCAL->name);
826     if (LOCAL->user) fs_give ((void **) &LOCAL->user);
827     if (LOCAL->newsrc) fs_give ((void **) &LOCAL->newsrc);
828     if (LOCAL->txt) fclose (LOCAL->txt);
829 				/* close NNTP connection */
830     if (LOCAL->nntpstream) nntp_close (LOCAL->nntpstream);
831     for (i = 1; i <= stream->nmsgs; i++)
832       if ((elt = mail_elt (stream,i))->private.spare.ptr)
833 	fs_give ((void **) &elt->private.spare.ptr);
834 				/* nuke the local data */
835     fs_give ((void **) &stream->local);
836     stream->dtb = NIL;		/* log out the DTB */
837   }
838 }
839 
840 /* NNTP fetch fast information
841  * Accepts: MAIL stream
842  *	    sequence
843  *	    option flags
844  * This is ugly and slow
845  */
846 
nntp_fetchfast(MAILSTREAM * stream,char * sequence,long flags)847 void nntp_fetchfast (MAILSTREAM *stream,char *sequence,long flags)
848 {
849   unsigned long i;
850   MESSAGECACHE *elt;
851 				/* get sequence */
852   if (stream && LOCAL && ((flags & FT_UID) ?
853 			  mail_uid_sequence (stream,sequence) :
854 			  mail_sequence (stream,sequence)))
855     for (i = 1; i <= stream->nmsgs; i++) {
856       if ((elt = mail_elt (stream,i))->sequence && (elt->valid = T) &&
857 	  !(elt->day && elt->rfc822_size)) {
858 	ENVELOPE **env = NIL;
859 	ENVELOPE *e = NIL;
860 	if (!stream->scache) env = &elt->private.msg.env;
861 	else if (stream->msgno == i) env = &stream->env;
862 	else env = &e;
863 	if (!*env || !elt->rfc822_size) {
864 	  STRING bs;
865 	  unsigned long hs;
866 	  char *ht = (*stream->dtb->header) (stream,i,&hs,NIL);
867 				/* need to make an envelope? */
868 	  if (!*env) rfc822_parse_msg (env,NIL,ht,hs,NIL,BADHOST,
869 				       stream->dtb->flags);
870 				/* need message size too, ugh */
871 	  if (!elt->rfc822_size) {
872 	    (*stream->dtb->text) (stream,i,&bs,FT_PEEK);
873 	    elt->rfc822_size = hs + SIZE (&bs) - GETPOS (&bs);
874 	  }
875 	}
876 				/* if need date, have date in envelope? */
877 	if (!elt->day && *env && (*env)->date)
878 	  mail_parse_date (elt,(*env)->date);
879 				/* sigh, fill in bogus default */
880 	if (!elt->day) elt->day = elt->month = 1;
881 	mail_free_envelope (&e);
882       }
883     }
884 }
885 
886 /* NNTP fetch flags
887  * Accepts: MAIL stream
888  *	    sequence
889  *	    option flags
890  */
891 
nntp_flags(MAILSTREAM * stream,char * sequence,long flags)892 void nntp_flags (MAILSTREAM *stream,char *sequence,long flags)
893 {
894   unsigned long i;
895   if ((flags & FT_UID) ?	/* validate all elts */
896       mail_uid_sequence (stream,sequence) : mail_sequence (stream,sequence))
897     for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->valid = T;
898 }
899 
900 /* NNTP fetch overview
901  * Accepts: MAIL stream, sequence bits set
902  *	    overview return function
903  * Returns: T if successful, NIL otherwise
904  */
905 
nntp_overview(MAILSTREAM * stream,overview_t ofn)906 long nntp_overview (MAILSTREAM *stream,overview_t ofn)
907 {
908   unsigned long i,j,k,uid;
909   char c,*s,*t,*v,tmp[MAILTMPLEN];
910   MESSAGECACHE *elt;
911   OVERVIEW ov;
912   if (!LOCAL->nntpstream->netstream) return NIL;
913 				/* scan sequence to load cache */
914   for (i = 1; i <= stream->nmsgs; i++)
915 				/* have cached overview yet? */
916     if ((elt = mail_elt (stream,i))->sequence && !elt->private.spare.ptr) {
917       for (j = i + 1;		/* no, find end of cache gap range */
918 	   (j <= stream->nmsgs) && (elt = mail_elt (stream,j))->sequence &&
919 	   !elt->private.spare.ptr; j++);
920 				/* make NNTP range */
921       sprintf (tmp,(i == (j - 1)) ? "%lu" : "%lu-%lu",mail_uid (stream,i),
922 	       mail_uid (stream,j - 1));
923       i = j;			/* advance beyond gap */
924 				/* ask server for overview data to cache */
925       if (nntp_over (stream,tmp)) {
926 	while ((s = net_getline (LOCAL->nntpstream->netstream)) &&
927 	       strcmp (s,".")) {
928 				/* death to embedded newlines */
929 	  for (t = v = s; c = *v++;)
930 	    if ((c != '\012') && (c != '\015')) *t++ = c;
931 	  *t++ = '\0';		/* tie off string in case it was shortened */
932 				/* cache the overview if found its sequence */
933 	  if ((uid = atol (s)) && (k = mail_msgno (stream,uid)) &&
934 	      (t = strchr (s,'\t'))) {
935 	    if ((elt = mail_elt (stream,k))->private.spare.ptr)
936 	      fs_give ((void **) &elt->private.spare.ptr);
937 	    elt->private.spare.ptr = cpystr (t + 1);
938 	  }
939 	  else {		/* shouldn't happen, snarl if it does */
940 	    sprintf (tmp,"Server returned data for unknown UID %lu",uid);
941 	    mm_notify (stream,tmp,WARN);
942 	    stream->unhealthy = T;
943 	  }
944 				/* flush the overview */
945 	  fs_give ((void **) &s);
946 	}
947 	stream->unhealthy = NIL;/* set healthy */
948 				/* flush the terminating dot */
949 	if (s) fs_give ((void **) &s);
950       }
951       else i = stream->nmsgs;	/* OVER failed, punt cache load */
952     }
953 
954 				/* now scan sequence to return overviews */
955   if (ofn) for (i = 1; i <= stream->nmsgs; i++)
956     if ((elt = mail_elt (stream,i))->sequence) {
957       uid = mail_uid (stream,i);/* UID for this message */
958 				/* parse cached overview */
959       if (nntp_parse_overview (&ov,s = (char *) elt->private.spare.ptr,elt))
960 	(*ofn) (stream,uid,&ov,i);
961       else {			/* parse failed */
962 	(*ofn) (stream,uid,NIL,i);
963 	if (s && *s) {		/* unusable cached entry? */
964 	  sprintf (tmp,"Unable to parse overview for UID %lu: %.500s",uid,s);
965 	  mm_notify (stream,tmp,WARN);
966 	  stream->unhealthy = T;
967 				/* erase it from the cache */
968 	  fs_give ((void **) &s);
969 	}
970 	stream->unhealthy = NIL;/* set healthy */
971 				/* insert empty cached text as necessary */
972 	if (!s) elt->private.spare.ptr = cpystr ("");
973       }
974 				/* clean up overview data */
975       if (ov.from) mail_free_address (&ov.from);
976       if (ov.subject) fs_give ((void **) &ov.subject);
977     }
978   return T;
979 }
980 
981 /* Send OVER to NNTP server
982  * Accepts: mail stream
983  *	    sequence to send
984  * Returns: T if success and overviews will follow, else NIL
985  */
986 
nntp_over(MAILSTREAM * stream,char * sequence)987 long nntp_over (MAILSTREAM *stream,char *sequence)
988 {
989   unsigned char *s;
990 				/* test for Netscape Collabra server */
991   if (EXTENSION.over && LOCAL->xover &&
992       nntp_send (LOCAL->nntpstream,"OVER","0") == NNTPOVER) {
993     /* "Netscape-Collabra/3.52 03615 NNTP" responds to the OVER command with
994      * a bogus "Subject:From:Date:Bytes:Lines" response followed by overviews
995      * which lack the Message-ID and References:.  This violates the draft
996      * NNTP specification (draft-ietf-nntpext-base-18.txt as of this writing).
997      * XOVER works fine.
998      */
999     while ((s = net_getline (LOCAL->nntpstream->netstream)) && strcmp (s,".")){
1000       if (!isdigit (*s)) {	/* is it that fetid piece of reptile dung? */
1001 	EXTENSION.over = NIL;	/* sure smells like it */
1002 	mm_log ("Working around Netscape Collabra bug",WARN);
1003       }
1004       fs_give ((void **) &s);	/* flush the overview */
1005     }
1006     if (s) fs_give ((void **) &s);
1007 				/* don't do this test again */
1008     if (EXTENSION.over) LOCAL->xover = NIL;
1009   }
1010   if (EXTENSION.over)		/* have OVER extension? */
1011     return (nntp_send (LOCAL->nntpstream,"OVER",sequence) == NNTPOVER) ?
1012       LONGT : NIL;
1013   if (LOCAL->xover)		/* try the experiment extension then */
1014     switch ((int) nntp_send (LOCAL->nntpstream,"XOVER",sequence)) {
1015     case NNTPOVER:		/* got an overview? */
1016       return LONGT;
1017     case NNTPBADCMD:		/* unknown command? */
1018       LOCAL->xover = NIL;	/* disable future XOVER attempts */
1019     }
1020   return NIL;
1021 }
1022 
1023 /* Parse OVERVIEW struct from cached NNTP OVER response
1024  * Accepts: struct to load
1025  *	    cached OVER response
1026  *	    internaldate
1027  * Returns: T if success, NIL if fail
1028  */
1029 
nntp_parse_overview(OVERVIEW * ov,char * text,MESSAGECACHE * elt)1030 long nntp_parse_overview (OVERVIEW *ov,char *text,MESSAGECACHE *elt)
1031 {
1032   char *t;
1033 				/* nothing in overview yet */
1034   memset ((void *) ov,0,sizeof (OVERVIEW));
1035 				/* no cached data */
1036   if (!(text && *text)) return NIL;
1037   ov->subject = cpystr (text);	/* make hackable copy of overview */
1038 				/* find end of Subject */
1039   if (t = strchr (ov->subject,'\t')) {
1040     *t++ = '\0';		/* tie off Subject, point to From */
1041 				/* find end of From */
1042     if (ov->date = strchr (t,'\t')) {
1043       *ov->date++ = '\0';	/* tie off From, point to Date */
1044 				/* load internaldate too */
1045       if (!elt->day) mail_parse_date (elt,ov->date);
1046 				/* parse From */
1047       rfc822_parse_adrlist (&ov->from,t,BADHOST);
1048 				/* find end of Date */
1049       if (ov->message_id = strchr (ov->date,'\t')) {
1050 				/* tie off Date, point to Message-ID */
1051 	*ov->message_id++ = '\0';
1052 				/* find end of Message-ID */
1053 	if (ov->references = strchr (ov->message_id,'\t')) {
1054 				/* tie off Message-ID, point to References */
1055 	  *ov->references++ = '\0';
1056 				/* fine end of References */
1057 	  if (t = strchr (ov->references,'\t')) {
1058 	    *t++ = '\0';	/* tie off References, point to octet size */
1059 				/* parse size of message in octets */
1060 	    ov->optional.octets = atol (t);
1061 				/* find end of size */
1062 	    if (t = strchr (t,'\t')) {
1063 				/* parse size of message in lines */
1064 	      ov->optional.lines = atol (++t);
1065 				/* find Xref */
1066 	      if (ov->optional.xref = strchr (t,'\t'))
1067 		*ov->optional.xref++ = '\0';
1068 	    }
1069 	  }
1070 	}
1071       }
1072     }
1073   }
1074   return ov->references ? T : NIL;
1075 }
1076 
1077 /* NNTP fetch header as text
1078  * Accepts: mail stream
1079  *	    message number
1080  *	    pointer to return size
1081  *	    flags
1082  * Returns: header text
1083  */
1084 
nntp_header(MAILSTREAM * stream,unsigned long msgno,unsigned long * size,long flags)1085 char *nntp_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *size,
1086 		   long flags)
1087 {
1088   char tmp[MAILTMPLEN];
1089   MESSAGECACHE *elt;
1090   FILE *f;
1091   *size = 0;
1092   if ((flags & FT_UID) && !(msgno = mail_msgno (stream,msgno))) return "";
1093 				/* have header text? */
1094   if (!(elt = mail_elt (stream,msgno))->private.msg.header.text.data) {
1095     sprintf (tmp,"%lu",mail_uid (stream,msgno));
1096 				/* get header text */
1097     switch (nntp_send (LOCAL->nntpstream,"HEAD",tmp)) {
1098     case NNTPHEAD:
1099       if (f = netmsg_slurp (LOCAL->nntpstream->netstream,size,NIL)) {
1100 	fread (elt->private.msg.header.text.data =
1101 	       (unsigned char *) fs_get ((size_t) *size + 3),
1102 	       (size_t) 1,(size_t) *size,f);
1103 	fclose (f);		/* flush temp file */
1104 				/* tie off header with extra CRLF and NUL */
1105 	elt->private.msg.header.text.data[*size] = '\015';
1106 	elt->private.msg.header.text.data[++*size] = '\012';
1107 	elt->private.msg.header.text.data[++*size] = '\0';
1108 	elt->private.msg.header.text.size = *size;
1109 	elt->valid = T;		/* make elt valid now */
1110 	break;
1111       }
1112 				/* fall into default case */
1113     default:			/* failed, mark as deleted and empty */
1114       elt->valid = elt->deleted = T;
1115     case NNTPSOFTFATAL:		/* don't mark deleted if stream dead */
1116       *size = elt->private.msg.header.text.size = 0;
1117       break;
1118     }
1119   }
1120 				/* just return size of text */
1121   else *size = elt->private.msg.header.text.size;
1122   return elt->private.msg.header.text.data ?
1123     (char *) elt->private.msg.header.text.data : "";
1124 }
1125 
1126 /* NNTP fetch body
1127  * Accepts: mail stream
1128  *	    message number
1129  *	    pointer to stringstruct to initialize
1130  *	    flags
1131  * Returns: T if successful, else NIL
1132  */
1133 
nntp_text(MAILSTREAM * stream,unsigned long msgno,STRING * bs,long flags)1134 long nntp_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
1135 {
1136   char tmp[MAILTMPLEN];
1137   MESSAGECACHE *elt;
1138   INIT (bs,mail_string,(void *) "",0);
1139   if ((flags & FT_UID) && !(msgno = mail_msgno (stream,msgno))) return NIL;
1140   elt = mail_elt (stream,msgno);
1141 				/* different message, flush cache */
1142   if (LOCAL->txt && (LOCAL->msgno != msgno)) {
1143     fclose (LOCAL->txt);
1144     LOCAL->txt = NIL;
1145   }
1146   LOCAL->msgno = msgno;		/* note cached message */
1147   if (!LOCAL->txt) {		/* have file for this message? */
1148     sprintf (tmp,"%lu",elt->private.uid);
1149     switch (nntp_send (LOCAL->nntpstream,"BODY",tmp)) {
1150     case NNTPBODY:
1151       if (LOCAL->txt = netmsg_slurp (LOCAL->nntpstream->netstream,
1152 				     &LOCAL->txtsize,NIL)) break;
1153 				/* fall into default case */
1154     default:			/* failed, mark as deleted */
1155       elt->deleted = T;
1156     case NNTPSOFTFATAL:		/* don't mark deleted if stream dead */
1157       return NIL;
1158     }
1159   }
1160   if (!(flags & FT_PEEK)) {	/* mark seen if needed */
1161     elt->seen = T;
1162     mm_flags (stream,elt->msgno);
1163   }
1164   INIT (bs,file_string,(void *) LOCAL->txt,LOCAL->txtsize);
1165   return T;
1166 }
1167 
1168 /* NNTP fetch article from message ID (for news: URL support)
1169  * Accepts: mail stream
1170  *	    message ID
1171  *	    pointer to return total message size
1172  *	    pointer to return file size
1173  * Returns: FILE * to message if successful, else NIL
1174  */
1175 
nntp_article(MAILSTREAM * stream,char * msgid,unsigned long * size,unsigned long * hsiz)1176 FILE *nntp_article (MAILSTREAM *stream,char *msgid,unsigned long *size,
1177 		    unsigned long *hsiz)
1178 {
1179   return (nntp_send (LOCAL->nntpstream,"ARTICLE",msgid) == NNTPARTICLE) ?
1180     netmsg_slurp (LOCAL->nntpstream->netstream,size,hsiz) : NIL;
1181 }
1182 
1183 
1184 /* NNTP per-message modify flag
1185  * Accepts: MAIL stream
1186  *	    message cache element
1187  */
1188 
nntp_flagmsg(MAILSTREAM * stream,MESSAGECACHE * elt)1189 void nntp_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
1190 {
1191   if (!LOCAL->dirty) {		/* only bother checking if not dirty yet */
1192     if (elt->valid) {		/* if done, see if deleted changed */
1193       if (elt->sequence != elt->deleted) LOCAL->dirty = T;
1194       elt->sequence = T;	/* leave the sequence set */
1195     }
1196 				/* note current setting of deleted flag */
1197     else elt->sequence = elt->deleted;
1198   }
1199 }
1200 
1201 /* NNTP search messages
1202  * Accepts: mail stream
1203  *	    character set
1204  *	    search program
1205  *	    option flags
1206  * Returns: T on success, NIL on failure
1207  */
1208 
nntp_search(MAILSTREAM * stream,char * charset,SEARCHPGM * pgm,long flags)1209 long nntp_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags)
1210 {
1211   unsigned long i;
1212   MESSAGECACHE *elt;
1213   OVERVIEW ov;
1214   char *msg;
1215 				/* make sure that charset is good */
1216   if (msg = utf8_badcharset (charset)) {
1217     MM_LOG (msg,ERROR);		/* output error */
1218     fs_give ((void **) &msg);
1219     return NIL;
1220   }
1221   utf8_searchpgm (pgm,charset);
1222   if (flags & SO_OVERVIEW) {	/* only if specified to use overview */
1223 				/* identify messages that will be searched */
1224     for (i = 1; i <= stream->nmsgs; ++i)
1225       mail_elt (stream,i)->sequence = nntp_search_msg (stream,i,pgm,NIL);
1226     nntp_overview (stream,NIL);	/* load the overview cache */
1227   }
1228 				/* init in case no overview at cleanup */
1229   memset ((void *) &ov,0,sizeof (OVERVIEW));
1230 				/* otherwise do default search */
1231   for (i = 1; i <= stream->nmsgs; ++i) {
1232     if (((flags & SO_OVERVIEW) && ((elt = mail_elt (stream,i))->sequence) &&
1233 	 nntp_parse_overview (&ov,(char *) elt->private.spare.ptr,elt)) ?
1234 	nntp_search_msg (stream,i,pgm,&ov) :
1235 	mail_search_msg (stream,i,NIL,pgm)) {
1236       if (flags & SE_UID) mm_searched (stream,mail_uid (stream,i));
1237       else {			/* mark as searched, notify mail program */
1238 	mail_elt (stream,i)->searched = T;
1239 	if (!stream->silent) mm_searched (stream,i);
1240       }
1241     }
1242 				/* clean up overview data */
1243     if (ov.from) mail_free_address (&ov.from);
1244     if (ov.subject) fs_give ((void **) &ov.subject);
1245   }
1246   return LONGT;
1247 }
1248 
1249 /* NNTP search message
1250  * Accepts: MAIL stream
1251  *	    message number
1252  *	    search program
1253  *	    overview to search (NIL means preliminary pass)
1254  * Returns: T if found, NIL otherwise
1255  */
1256 
nntp_search_msg(MAILSTREAM * stream,unsigned long msgno,SEARCHPGM * pgm,OVERVIEW * ov)1257 long nntp_search_msg (MAILSTREAM *stream,unsigned long msgno,SEARCHPGM *pgm,
1258 		      OVERVIEW *ov)
1259 {
1260   unsigned short d;
1261   unsigned long now = (unsigned long) time (0);
1262   MESSAGECACHE *elt = mail_elt (stream,msgno);
1263   SEARCHHEADER *hdr;
1264   SEARCHOR *or;
1265   SEARCHPGMLIST *not;
1266   if (pgm->msgno || pgm->uid) {	/* message set searches */
1267     SEARCHSET *set;
1268 				/* message sequences */
1269     if (set = pgm->msgno) {	/* must be inside this sequence */
1270       while (set) {		/* run down until find matching range */
1271 	if (set->last ? ((msgno < set->first) || (msgno > set->last)) :
1272 	    msgno != set->first) set = set->next;
1273 	else break;
1274       }
1275       if (!set) return NIL;	/* not found within sequence */
1276     }
1277     if (set = pgm->uid) {	/* must be inside this sequence */
1278       unsigned long uid = mail_uid (stream,msgno);
1279       while (set) {		/* run down until find matching range */
1280 	if (set->last ? ((uid < set->first) || (uid > set->last)) :
1281 	    uid != set->first) set = set->next;
1282 	else break;
1283       }
1284       if (!set) return NIL;	/* not found within sequence */
1285     }
1286   }
1287 
1288   /* Fast data searches */
1289 				/* message flags */
1290   if ((pgm->answered && !elt->answered) ||
1291       (pgm->unanswered && elt->answered) ||
1292       (pgm->deleted && !elt->deleted) ||
1293       (pgm->undeleted && elt->deleted) ||
1294       (pgm->draft && !elt->draft) ||
1295       (pgm->undraft && elt->draft) ||
1296       (pgm->flagged && !elt->flagged) ||
1297       (pgm->unflagged && elt->flagged) ||
1298       (pgm->recent && !elt->recent) ||
1299       (pgm->old && elt->recent) ||
1300       (pgm->seen && !elt->seen) ||
1301       (pgm->unseen && elt->seen)) return NIL;
1302 				/* keywords */
1303   if ((pgm->keyword && !mail_search_keyword (stream,elt,pgm->keyword,LONGT)) ||
1304       (pgm->unkeyword && mail_search_keyword (stream,elt,pgm->unkeyword,NIL)))
1305     return NIL;
1306   if (ov) {			/* only do this if real searching */
1307     MESSAGECACHE delt;
1308 				/* size ranges */
1309     if ((pgm->larger && (ov->optional.octets <= pgm->larger)) ||
1310 	(pgm->smaller && (ov->optional.octets >= pgm->smaller))) return NIL;
1311 				/* date ranges */
1312     if ((pgm->sentbefore || pgm->senton || pgm->sentsince ||
1313 	 pgm->before || pgm->on || pgm->since) &&
1314 	(!mail_parse_date (&delt,ov->date) ||
1315 	 !(d = mail_shortdate (delt.year,delt.month,delt.day)) ||
1316 	 (pgm->sentbefore && (d >= pgm->sentbefore)) ||
1317 	 (pgm->senton && (d != pgm->senton)) ||
1318 	 (pgm->sentsince && (d < pgm->sentsince)) ||
1319 	 (pgm->before && (d >= pgm->before)) ||
1320 	 (pgm->on && (d != pgm->on)) ||
1321 	 (pgm->since && (d < pgm->since)))) return NIL;
1322     if (pgm->older || pgm->younger) {
1323       unsigned long msgd = mail_longdate (elt);
1324       if (pgm->older && msgd > (now - pgm->older)) return NIL;
1325       if (pgm->younger && msgd < (now - pgm->younger)) return NIL;
1326     }
1327     if ((pgm->from && !mail_search_addr (ov->from,pgm->from)) ||
1328 	(pgm->subject && !mail_search_header_text (ov->subject,pgm->subject))||
1329 	(pgm->message_id &&
1330 	 !mail_search_header_text (ov->message_id,pgm->message_id)) ||
1331 	(pgm->references &&
1332 	 !mail_search_header_text (ov->references,pgm->references)))
1333       return NIL;
1334 
1335 
1336 				/* envelope searches */
1337     if (pgm->bcc || pgm->cc || pgm->to || pgm->return_path || pgm->sender ||
1338 	pgm->reply_to || pgm->in_reply_to || pgm->newsgroups ||
1339 	pgm->followup_to) {
1340       ENVELOPE *env = mail_fetchenvelope (stream,msgno);
1341       if (!env) return NIL;	/* no envelope obtained */
1342 				/* search headers */
1343       if ((pgm->bcc && !mail_search_addr (env->bcc,pgm->bcc)) ||
1344 	  (pgm->cc && !mail_search_addr (env->cc,pgm->cc)) ||
1345 	  (pgm->to && !mail_search_addr (env->to,pgm->to)))
1346 	return NIL;
1347       /* These criteria are not supported by IMAP and have to be emulated */
1348       if ((pgm->return_path &&
1349 	   !mail_search_addr (env->return_path,pgm->return_path)) ||
1350 	  (pgm->sender && !mail_search_addr (env->sender,pgm->sender)) ||
1351 	  (pgm->reply_to && !mail_search_addr (env->reply_to,pgm->reply_to)) ||
1352 	  (pgm->in_reply_to &&
1353 	   !mail_search_header_text (env->in_reply_to,pgm->in_reply_to)) ||
1354 	  (pgm->newsgroups &&
1355 	   !mail_search_header_text (env->newsgroups,pgm->newsgroups)) ||
1356 	  (pgm->followup_to &&
1357 	   !mail_search_header_text (env->followup_to,pgm->followup_to)))
1358 	return NIL;
1359     }
1360 
1361 				/* search header lines */
1362     for (hdr = pgm->header; hdr; hdr = hdr->next) {
1363       char *t,*e,*v;
1364       SIZEDTEXT s;
1365       STRINGLIST sth,stc;
1366       sth.next = stc.next = NIL;/* only one at a time */
1367       sth.text.data = hdr->line.data;
1368       sth.text.size = hdr->line.size;
1369 				/* get the header text */
1370       if ((t = mail_fetch_header (stream,msgno,NIL,&sth,&s.size,
1371 				  FT_INTERNAL | FT_PEEK)) && strchr (t,':')) {
1372 	if (hdr->text.size) {	/* anything matches empty search string */
1373 				/* non-empty, copy field data */
1374 	  s.data = (unsigned char *) fs_get (s.size + 1);
1375 				/* for each line */
1376 	  for (v = (char *) s.data, e = t + s.size; t < e;) switch (*t) {
1377 	  default:		/* non-continuation, skip leading field name */
1378 	    while ((t < e) && (*t++ != ':'));
1379 	    if ((t < e) && (*t == ':')) t++;
1380 	  case '\t': case ' ':	/* copy field data  */
1381 	    while ((t < e) && (*t != '\015') && (*t != '\012')) *v++ = *t++;
1382 	    *v++ = '\n';	/* tie off line */
1383 	    while (((*t == '\015') || (*t == '\012')) && (t < e)) t++;
1384 	  }
1385 				/* calculate true size */
1386 	  s.size = v - (char *) s.data;
1387 	  *v = '\0';		/* tie off results */
1388 	  stc.text.data = hdr->text.data;
1389 	  stc.text.size = hdr->text.size;
1390 				/* search header */
1391 	  if (mail_search_header (&s,&stc)) fs_give ((void **) &s.data);
1392 	  else {		/* search failed */
1393 	    fs_give ((void **) &s.data);
1394 	    return NIL;
1395 	  }
1396 	}
1397       }
1398       else return NIL;		/* no matching header text */
1399     }
1400 				/* search strings */
1401     if ((pgm->text &&
1402 	 !mail_search_text (stream,msgno,NIL,pgm->text,LONGT))||
1403 	(pgm->body && !mail_search_text (stream,msgno,NIL,pgm->body,NIL)))
1404       return NIL;
1405   }
1406 				/* logical conditions */
1407   for (or = pgm->or; or; or = or->next)
1408     if (!(nntp_search_msg (stream,msgno,or->first,ov) ||
1409 	  nntp_search_msg (stream,msgno,or->second,ov))) return NIL;
1410   for (not = pgm->not; not; not = not->next)
1411     if (nntp_search_msg (stream,msgno,not->pgm,ov)) return NIL;
1412   return T;
1413 }
1414 
1415 /* NNTP sort messages
1416  * Accepts: mail stream
1417  *	    character set
1418  *	    search program
1419  *	    sort program
1420  *	    option flags
1421  * Returns: vector of sorted message sequences or NIL if error
1422  */
1423 
nntp_sort(MAILSTREAM * stream,char * charset,SEARCHPGM * spg,SORTPGM * pgm,long flags)1424 unsigned long *nntp_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
1425 			  SORTPGM *pgm,long flags)
1426 {
1427   unsigned long i,start,last;
1428   SORTCACHE **sc;
1429   mailcache_t mailcache = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
1430   unsigned long *ret = NIL;
1431   sortresults_t sr = (sortresults_t) mail_parameters (NIL,GET_SORTRESULTS,NIL);
1432   if (spg) {			/* only if a search needs to be done */
1433     int silent = stream->silent;
1434     stream->silent = T;		/* don't pass up mm_searched() events */
1435 				/* search for messages */
1436     mail_search_full (stream,charset,spg,NIL);
1437     stream->silent = silent;	/* restore silence state */
1438   }
1439 				/* initialize progress counters */
1440   pgm->nmsgs = pgm->progress.cached = 0;
1441 				/* pass 1: count messages to sort */
1442   for (i = 1,start = last = 0; i <= stream->nmsgs; ++i)
1443     if (mail_elt (stream,i)->searched) {
1444       pgm->nmsgs++;
1445 				/* have this in the sortcache already? */
1446       if (!((SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE))->date) {
1447 				/* no, record as last message */
1448 	last = mail_uid (stream,i);
1449 				/* and as first too if needed */
1450 	if (!start) start = last;
1451       }
1452     }
1453   if (pgm->nmsgs) {		/* pass 2: load sort cache */
1454     sc = nntp_sort_loadcache (stream,pgm,start,last,flags);
1455 				/* pass 3: sort messages */
1456     if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags);
1457     fs_give ((void **) &sc);	/* don't need sort vector any more */
1458   }
1459 				/* empty sort results */
1460   else ret = (unsigned long *) memset (fs_get (sizeof (unsigned long)),0,
1461 				       sizeof (unsigned long));
1462 				/* also return via callback if requested */
1463   if (sr) (*sr) (stream,ret,pgm->nmsgs);
1464   return ret;
1465 }
1466 
1467 /* Mail load sortcache
1468  * Accepts: mail stream, already searched
1469  *	    sort program
1470  *	    first UID to OVER
1471  *	    last UID to OVER
1472  *	    option flags
1473  * Returns: vector of sortcache pointers matching search
1474  */
1475 
nntp_sort_loadcache(MAILSTREAM * stream,SORTPGM * pgm,unsigned long start,unsigned long last,long flags)1476 SORTCACHE **nntp_sort_loadcache (MAILSTREAM *stream,SORTPGM *pgm,
1477 				 unsigned long start,unsigned long last,
1478 				 long flags)
1479 {
1480   unsigned long i;
1481   char c,*s,*t,*v,tmp[MAILTMPLEN];
1482   SORTPGM *pg;
1483   SORTCACHE **sc,*r;
1484   MESSAGECACHE telt;
1485   ADDRESS *adr = NIL;
1486   mailcache_t mailcache = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
1487 				/* verify that the sortpgm is OK */
1488   for (pg = pgm; pg; pg = pg->next) switch (pg->function) {
1489   case SORTARRIVAL:		/* sort by arrival date */
1490   case SORTSIZE:		/* sort by message size */
1491   case SORTDATE:		/* sort by date */
1492   case SORTFROM:		/* sort by first from */
1493   case SORTSUBJECT:		/* sort by subject */
1494     break;
1495   case SORTTO:			/* sort by first to */
1496     mm_notify (stream,"[NNTPSORT] Can't do To-field sorting in NNTP",WARN);
1497     break;
1498   case SORTCC:			/* sort by first cc */
1499     mm_notify (stream,"[NNTPSORT] Can't do cc-field sorting in NNTP",WARN);
1500     break;
1501   default:
1502     fatal ("Unknown sort function");
1503   }
1504 
1505   if (start) {			/* messages need to be loaded in sortcache? */
1506 				/* yes, build range */
1507     if (start != last) sprintf (tmp,"%lu-%lu",start,last);
1508     else sprintf (tmp,"%lu",start);
1509 				/* get it from the NNTP server */
1510     if (!nntp_over (stream,tmp)) return mail_sort_loadcache (stream,pgm);
1511     while ((s = net_getline (LOCAL->nntpstream->netstream)) && strcmp (s,".")){
1512 				/* death to embedded newlines */
1513       for (t = v = s; c = *v++;) if ((c != '\012') && (c != '\015')) *t++ = c;
1514       *t++ = '\0';		/* tie off resulting string */
1515 				/* parse OVER response */
1516       if ((i = mail_msgno (stream,atol (s))) &&
1517 	  (t = strchr (s,'\t')) && (v = strchr (++t,'\t'))) {
1518 	*v++ = '\0';		/* tie off subject */
1519 				/* put stripped subject in sortcache */
1520 	r = (SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE);
1521 	  r->refwd = mail_strip_subject (t,&r->subject);
1522 	if (t = strchr (v,'\t')) {
1523 	  *t++ = '\0';		/* tie off from */
1524 	  if (adr = rfc822_parse_address (&adr,adr,&v,BADHOST,0)) {
1525 	    r->from = adr->mailbox;
1526 	    adr->mailbox = NIL;
1527 	    mail_free_address (&adr);
1528 	  }
1529 	  if (v = strchr (t,'\t')) {
1530 	    *v++ = '\0';	/* tie off date */
1531 	    if (mail_parse_date (&telt,t)) r->date = mail_longdate (&telt);
1532 	    if ((v = strchr (v,'\t')) && (v = strchr (++v,'\t')))
1533 	      r->size = atol (++v);
1534 	  }
1535 	}
1536       }
1537       fs_give ((void **) &s);
1538     }
1539     if (s) fs_give ((void **) &s);
1540   }
1541 
1542 				/* calculate size of sortcache index */
1543   i = pgm->nmsgs * sizeof (SORTCACHE *);
1544 				/* instantiate the index */
1545   sc = (SORTCACHE **) memset (fs_get ((size_t) i),0,(size_t) i);
1546 				/* see what needs to be loaded */
1547   for (i = 1; !pgm->abort && (i <= stream->nmsgs); i++)
1548     if ((mail_elt (stream,i))->searched) {
1549       sc[pgm->progress.cached++] =
1550 	r = (SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE);
1551       r->pgm = pgm;	/* note sort program */
1552       r->num = (flags & SE_UID) ? mail_uid (stream,i) : i;
1553       if (!r->date) r->date = r->num;
1554       if (!r->arrival) r->arrival = mail_uid (stream,i);
1555       if (!r->size) r->size = 1;
1556       if (!r->from) r->from = cpystr ("");
1557       if (!r->to) r->to = cpystr ("");
1558       if (!r->cc) r->cc = cpystr ("");
1559       if (!r->subject) r->subject = cpystr ("");
1560     }
1561   return sc;
1562 }
1563 
1564 
1565 /* NNTP thread messages
1566  * Accepts: mail stream
1567  *	    thread type
1568  *	    character set
1569  *	    search program
1570  *	    option flags
1571  * Returns: thread node tree
1572  */
1573 
nntp_thread(MAILSTREAM * stream,char * type,char * charset,SEARCHPGM * spg,long flags)1574 THREADNODE *nntp_thread (MAILSTREAM *stream,char *type,char *charset,
1575 			 SEARCHPGM *spg,long flags)
1576 {
1577   return mail_thread_msgs (stream,type,charset,spg,flags,nntp_sort);
1578 }
1579 
1580 /* NNTP ping mailbox
1581  * Accepts: MAIL stream
1582  * Returns: T if stream alive, else NIL
1583  */
1584 
nntp_ping(MAILSTREAM * stream)1585 long nntp_ping (MAILSTREAM *stream)
1586 {
1587   return (nntp_send (LOCAL->nntpstream,"STAT",NIL) != NNTPSOFTFATAL);
1588 }
1589 
1590 
1591 /* NNTP check mailbox
1592  * Accepts: MAIL stream
1593  */
1594 
nntp_check(MAILSTREAM * stream)1595 void nntp_check (MAILSTREAM *stream)
1596 {
1597 				/* never do if no updates */
1598   if (LOCAL->dirty) newsrc_write (LOCAL->name,stream);
1599   LOCAL->dirty = NIL;
1600 }
1601 
1602 
1603 /* NNTP expunge mailbox
1604  * Accepts: MAIL stream
1605  *	    sequence to expunge if non-NIL
1606  *	    expunge options
1607  * Returns: T if success, NIL if failure
1608  */
1609 
nntp_expunge(MAILSTREAM * stream,char * sequence,long options)1610 long nntp_expunge (MAILSTREAM *stream,char *sequence,long options)
1611 {
1612   if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
1613   return LONGT;
1614 }
1615 
1616 /* NNTP copy message(s)
1617  * Accepts: MAIL stream
1618  *	    sequence
1619  *	    destination mailbox
1620  *	    option flags
1621  * Returns: T if copy successful, else NIL
1622  */
1623 
nntp_copy(MAILSTREAM * stream,char * sequence,char * mailbox,long options)1624 long nntp_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
1625 {
1626   mailproxycopy_t pc =
1627     (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
1628   if (pc) return (*pc) (stream,sequence,mailbox,options);
1629   mm_log ("Copy not valid for NNTP",ERROR);
1630   return NIL;
1631 }
1632 
1633 
1634 /* NNTP append message from stringstruct
1635  * Accepts: MAIL stream
1636  *	    destination mailbox
1637  *	    append callback
1638  *	    data for callback
1639  * Returns: T if append successful, else NIL
1640  */
1641 
nntp_append(MAILSTREAM * stream,char * mailbox,append_t af,void * data)1642 long nntp_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
1643 {
1644   mm_log ("Append not valid for NNTP",ERROR);
1645   return NIL;
1646 }
1647 
1648 /* NNTP open connection
1649  * Accepts: network driver
1650  *	    service host list
1651  *	    port number
1652  *	    service name
1653  *	    NNTP open options
1654  * Returns: SEND stream on success, NIL on failure
1655  */
1656 
nntp_open_full(NETDRIVER * dv,char ** hostlist,char * service,unsigned long port,long options)1657 SENDSTREAM *nntp_open_full (NETDRIVER *dv,char **hostlist,char *service,
1658 			    unsigned long port,long options)
1659 {
1660   SENDSTREAM *stream = NIL;
1661   NETSTREAM *netstream = NIL;
1662   NETMBX mb;
1663   char tmp[MAILTMPLEN];
1664   long extok = LONGT;
1665   NETDRIVER *ssld = (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL);
1666   sslstart_t stls = (sslstart_t) mail_parameters (NIL,GET_SSLSTART,NIL);
1667   if (!(hostlist && *hostlist)) mm_log ("Missing NNTP service host",ERROR);
1668   else do {			/* try to open connection */
1669     sprintf (tmp,"{%.200s/%.20s}",*hostlist,service ? service : "nntp");
1670     if (!mail_valid_net_parse (tmp,&mb) || mb.anoflag) {
1671       sprintf (tmp,"Invalid host specifier: %.80s",*hostlist);
1672       mm_log (tmp,ERROR);
1673     }
1674     else {			/* light tryssl flag if requested */
1675       mb.trysslflag = (options & NOP_TRYSSL) ? T : NIL;
1676 				/* default port */
1677       if (mb.port) port = mb.port;
1678       else if (!port) port = nntp_port ? nntp_port : NNTPTCPPORT;
1679       if (netstream =		/* try to open ordinary connection */
1680 	  net_open (&mb,dv,port,
1681 		    (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL),
1682 		    "*nntps",nntp_sslport ? nntp_sslport : NNTPSSLPORT)) {
1683 	stream = (SENDSTREAM *) fs_get (sizeof (SENDSTREAM));
1684 				/* initialize stream */
1685 	memset ((void *) stream,0,sizeof (SENDSTREAM));
1686 	stream->netstream = netstream;
1687 	stream->host = cpystr ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ?
1688 			       net_host (netstream) : mb.host);
1689 	stream->debug = (mb.dbgflag || (options & NOP_DEBUG)) ? T : NIL;
1690 	if (mb.loser) stream->loser = T;
1691 				/* process greeting */
1692 	switch ((int) nntp_reply (stream)) {
1693 	case NNTPGREET:		/* allow posting */
1694 	  NNTP.post = T;
1695 	  mm_notify (NIL,stream->reply + 4,(long) NIL);
1696 	  break;
1697 	case NNTPGREETNOPOST:	/* posting not allowed, must be readonly */
1698 	  NNTP.post = NIL;
1699 	  break;
1700 	default:
1701 	  mm_log (stream->reply,ERROR);
1702 	  stream = nntp_close (stream);
1703 	  break;
1704 	}
1705       }
1706     }
1707   } while (!stream && *++hostlist);
1708 
1709 				/* get extensions */
1710   if (stream && extok)
1711     extok = nntp_extensions (stream,(mb.secflag ? AU_SECURE : NIL) |
1712 			     (mb.authuser[0] ? AU_AUTHUSER : NIL));
1713   if (stream && !dv && stls && NNTP.ext.starttls &&
1714       !mb.sslflag && !mb.notlsflag &&
1715       (nntp_send_work (stream,"STARTTLS",NNTP.ext.multidomain ? mb.host : NIL)
1716        == NNTPTLSSTART)) {
1717     mb.tlsflag = T;		/* TLS OK, get into TLS at this end */
1718     stream->netstream->dtb = ssld;
1719 				/* negotiate TLS */
1720     if (stream->netstream->stream =
1721 	(*stls) (stream->netstream->stream,mb.host,
1722 		 (mb.tlssslv23 ? NIL : NET_TLSCLIENT) |
1723 		 (mb.novalidate ? NET_NOVALIDATECERT:NIL)))
1724       extok = nntp_extensions (stream,(mb.secflag ? AU_SECURE : NIL) |
1725 			       (mb.authuser[0] ? AU_AUTHUSER : NIL));
1726     else {
1727       sprintf (tmp,"Unable to negotiate TLS with this server: %.80s",mb.host);
1728       mm_log (tmp,ERROR);
1729 				/* close without doing QUIT */
1730       if (stream->netstream) net_close (stream->netstream);
1731       stream->netstream = NIL;
1732       stream = nntp_close (stream);
1733     }
1734   }
1735   else if (mb.tlsflag) {	/* user specified /tls but can't do it */
1736     mm_log ("Unable to negotiate TLS with this server",ERROR);
1737     return NIL;
1738   }
1739   if (stream) {			/* have a session? */
1740     if (mb.user[0]) {		/* yes, have user name? */
1741       if ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL)) {
1742 				/* remote name for authentication */
1743 	strncpy (mb.host,(long) mail_parameters (NIL,GET_SASLUSESPTRNAME,NIL) ?
1744 		 net_remotehost (netstream) : net_host (netstream),
1745 		 NETMAXHOST-1);
1746 	mb.host[NETMAXHOST-1] = '\0';
1747       }
1748       if (!nntp_send_auth_work (stream,&mb,tmp,NIL))
1749 	stream = nntp_close (stream);
1750     }
1751 				/* authenticate if no-post and not readonly */
1752     else if (!(NNTP.post || (options & NOP_READONLY) ||
1753 	       nntp_send_auth (stream,NIL))) stream = nntp_close (stream);
1754   }
1755 
1756 				/* in case server demands MODE READER */
1757   if (stream) switch ((int) nntp_send_work (stream,"MODE","READER")) {
1758   case NNTPGREET:
1759     NNTP.post = T;
1760     break;
1761   case NNTPGREETNOPOST:
1762     NNTP.post = NIL;
1763     break;
1764   case NNTPWANTAUTH:		/* server wants auth first, do so and retry */
1765   case NNTPWANTAUTH2:		/* remote name for authentication */
1766     if ((long) mail_parameters (NIL,GET_TRUSTDNS,NIL)) {
1767       strncpy (mb.host,(long) mail_parameters (NIL,GET_SASLUSESPTRNAME,NIL) ?
1768 	       net_remotehost (netstream) : net_host (netstream),NETMAXHOST-1);
1769       mb.host[NETMAXHOST-1] = '\0';
1770     }
1771     if (nntp_send_auth_work (stream,&mb,tmp,NIL))
1772       switch ((int) nntp_send (stream,"MODE","READER")) {
1773       case NNTPGREET:
1774 	NNTP.post = T;
1775 	break;
1776       case NNTPGREETNOPOST:
1777 	NNTP.post = NIL;
1778 	break;
1779       }
1780     else stream = nntp_close (stream);
1781     break;
1782   }
1783   if (stream) {			/* looks like we have a stream? */
1784 				/* yes, make sure can post if not readonly */
1785     if (!(NNTP.post || (options & NOP_READONLY))) stream = nntp_close (stream);
1786     else if (extok) nntp_extensions (stream,(mb.secflag ? AU_SECURE : NIL) |
1787 				     (mb.authuser[0] ? AU_AUTHUSER : NIL));
1788   }
1789   return stream;
1790 }
1791 
1792 /* NNTP extensions
1793  * Accepts: stream
1794  *	    authenticator flags
1795  * Returns: T on success, NIL on failure
1796  */
1797 
nntp_extensions(SENDSTREAM * stream,long flags)1798 long nntp_extensions (SENDSTREAM *stream,long flags)
1799 {
1800   unsigned long i;
1801   char *t,*r,*args;
1802 				/* zap all old extensions */
1803   memset (&NNTP.ext,0,sizeof (NNTP.ext));
1804   if (stream->loser) return NIL;/* nothing at all for losers */
1805 				/* get server extensions */
1806   switch ((int) nntp_send_work (stream,"LIST","EXTENSIONS")) {
1807   case NNTPEXTOK:		/* what NNTP base spec says */
1808   case NNTPGLIST:		/* some servers do this instead */
1809     break;
1810   default:			/* no LIST EXTENSIONS on this server */
1811     return NIL;
1812   }
1813   NNTP.ext.ok = T;		/* server offers extensions */
1814   while ((t = net_getline (stream->netstream)) && (t[1] || (*t != '.'))) {
1815     if (stream->debug) mm_dlog (t);
1816 				/* get optional capability arguments */
1817     if (args = strchr (t,' ')) *args++ = '\0';
1818     if (!compare_cstring (t,"LISTGROUP")) NNTP.ext.listgroup = T;
1819     else if (!compare_cstring (t,"OVER")) NNTP.ext.over = T;
1820     else if (!compare_cstring (t,"HDR")) NNTP.ext.hdr = T;
1821     else if (!compare_cstring (t,"PAT")) NNTP.ext.pat = T;
1822     else if (!compare_cstring (t,"STARTTLS")) NNTP.ext.starttls = T;
1823     else if (!compare_cstring (t,"MULTIDOMAIN")) NNTP.ext.multidomain = T;
1824 
1825     else if (!compare_cstring (t,"AUTHINFO") && args) {
1826       char *sasl = NIL;
1827       for (args = strtok_r (args," ",&r); args; args = strtok_r (NIL," ",&r)) {
1828 	if (!compare_cstring (args,"USER")) NNTP.ext.authuser = T;
1829 	else if (((args[0] == 'S') || (args[0] == 's')) &&
1830 		 ((args[1] == 'A') || (args[1] == 'a')) &&
1831 		 ((args[2] == 'S') || (args[2] == 's')) &&
1832 		 ((args[3] == 'L') || (args[3] == 'l')) && (args[4] == ':'))
1833 	  sasl = args + 5;
1834       }
1835       if (sasl) {		/* if SASL, look up authenticators */
1836 	for (sasl = strtok_r (sasl,",",&r); sasl; sasl = strtok_r (NIL,",",&r))
1837 	  if ((i = mail_lookup_auth_name (sasl,flags)) &&
1838 	      (--i < MAXAUTHENTICATORS))
1839 	    NNTP.ext.sasl |= (1 << i);
1840 				/* disable LOGIN if PLAIN also advertised */
1841 	if ((i = mail_lookup_auth_name ("PLAIN",NIL)) &&
1842 	    (--i < MAXAUTHENTICATORS) && (NNTP.ext.sasl & (1 << i)) &&
1843 	    (i = mail_lookup_auth_name ("LOGIN",NIL)) &&
1844 	    (--i < MAXAUTHENTICATORS)) NNTP.ext.sasl &= ~(1 << i);
1845       }
1846     }
1847     fs_give ((void **) &t);
1848   }
1849   if (t) {			/* flush end of text indicator */
1850     if (stream->debug) mm_dlog (t);
1851     fs_give ((void **) &t);
1852   }
1853   return LONGT;
1854 }
1855 
1856 /* NNTP close connection
1857  * Accepts: SEND stream
1858  * Returns: NIL always
1859  */
1860 
nntp_close(SENDSTREAM * stream)1861 SENDSTREAM *nntp_close (SENDSTREAM *stream)
1862 {
1863   if (stream) {			/* send "QUIT" */
1864     if (stream->netstream) nntp_send (stream,"QUIT",NIL);
1865 				/* do close actions */
1866     if (stream->netstream) net_close (stream->netstream);
1867     if (stream->host) fs_give ((void **) &stream->host);
1868     if (stream->reply) fs_give ((void **) &stream->reply);
1869     fs_give ((void **) &stream);/* flush the stream */
1870   }
1871   return NIL;
1872 }
1873 
1874 /* NNTP deliver news
1875  * Accepts: SEND stream
1876  *	    message envelope
1877  *	    message body
1878  * Returns: T on success, NIL on failure
1879  */
1880 
nntp_mail(SENDSTREAM * stream,ENVELOPE * env,BODY * body)1881 long nntp_mail (SENDSTREAM *stream,ENVELOPE *env,BODY *body)
1882 {
1883   long ret;
1884   RFC822BUFFER buf;
1885   char *s,path[MAILTMPLEN],tmp[SENDBUFLEN+1];
1886   long error = NIL;
1887   long retry = NIL;
1888   buf.f = nntp_soutr;		/* initialize buffer */
1889   buf.s = stream->netstream;
1890   buf.end = (buf.beg = buf.cur = tmp) + SENDBUFLEN;
1891   tmp[SENDBUFLEN] = '\0';	/* must have additional null guard byte */
1892   /* Gabba gabba hey, we need some brain damage to send netnews!!!
1893    *
1894    * First, we give ourselves a frontal lobotomy, and put in some UUCP
1895    *  syntax.  It doesn't matter that it's completely bogus UUCP, and
1896    *  that UUCP has nothing to do with anything we're doing.  It's been
1897    *  alleged that "Path: not-for-mail" is also acceptable, but we won't
1898    *  make assumptions unless the user says so.
1899    *
1900    * Second, we bop ourselves on the head with a ball-peen hammer.  How
1901    *  dare we be so presumptious as to insert a *comment* in a Date:
1902    *  header line.  Why, we were actually trying to be nice to a human
1903    *  by giving a symbolic timezone (such as PST) in addition to a
1904    *  numeric timezone (such as -0800).  But the gods of news transport
1905    *  will have none of this.  Unix weenies, tried and true, rule!!!
1906    *
1907    * Third, Netscape Collabra server doesn't give the NNTPWANTAUTH error
1908    *  until after requesting and receiving the entire message.  So we can't
1909    *  call rely upon nntp_send() to do the auth retry.
1910    */
1911 				/* RFC-1036 requires this cretinism */
1912   sprintf (path,"Path: %s!%s\015\012",net_localhost (stream->netstream),
1913 	   env->sender ? env->sender->mailbox :
1914 	   (env->from ? env->from->mailbox : "not-for-mail"));
1915 				/* here's another cretinism */
1916   if (s = strstr (env->date," (")) *s = NIL;
1917   do if ((ret = nntp_send_work (stream,"POST",NIL)) == NNTPREADY)
1918 				/* output data, return success status */
1919     ret = (net_soutr (stream->netstream,
1920 		      nntp_hidepath ? "Path: not-for-mail\015\012" : path) &&
1921 	   rfc822_output_full (&buf,env,body,T)) ?
1922       nntp_send_work (stream,".",NIL) :
1923       nntp_fake (stream,"NNTP connection broken (message text)");
1924   while (((ret == NNTPWANTAUTH) || (ret == NNTPWANTAUTH2)) &&
1925 	 nntp_send_auth (stream,LONGT));
1926   if (s) *s = ' ';		/* put the comment in the date back */
1927   if (ret == NNTPOK) return LONGT;
1928   else if (ret < 400) {		/* if not an error reply */
1929     sprintf (tmp,"Unexpected NNTP posting reply code %ld",ret);
1930     mm_log (tmp,WARN);		/* so someone looks at this eventually */
1931     if ((ret >= 200) && (ret < 300)) return LONGT;
1932   }
1933   return NIL;
1934 }
1935 
1936 /* NNTP send command
1937  * Accepts: SEND stream
1938  *	    text
1939  * Returns: reply code
1940  */
1941 
nntp_send(SENDSTREAM * stream,char * command,char * args)1942 long nntp_send (SENDSTREAM *stream,char *command,char *args)
1943 {
1944   long ret;
1945   switch ((int) (ret = nntp_send_work (stream,command,args))) {
1946   case NNTPWANTAUTH:		/* authenticate and retry */
1947   case NNTPWANTAUTH2:
1948     if (nntp_send_auth (stream,LONGT))
1949       ret = nntp_send_work (stream,command,args);
1950     else {			/* we're probably hosed, nuke the session */
1951       nntp_send (stream,"QUIT",NIL);
1952 				/* close net connection */
1953       if (stream->netstream) net_close (stream->netstream);
1954       stream->netstream = NIL;
1955     }
1956   default:			/* all others just return */
1957     break;
1958   }
1959   return ret;
1960 }
1961 
1962 
1963 /* NNTP send command worker routine
1964  * Accepts: SEND stream
1965  *	    text
1966  * Returns: reply code
1967  */
1968 
nntp_send_work(SENDSTREAM * stream,char * command,char * args)1969 long nntp_send_work (SENDSTREAM *stream,char *command,char *args)
1970 {
1971   long ret;
1972   char *s = (char *) fs_get (strlen (command) + (args ? strlen (args) + 1 : 0)
1973 			     + 3);
1974   if (!stream->netstream) ret = nntp_fake (stream,"NNTP connection lost");
1975   else {			/* build the complete command */
1976     if (args) sprintf (s,"%s %s",command,args);
1977     else strcpy (s,command);
1978     if (stream->debug) mail_dlog (s,stream->sensitive);
1979     strcat (s,"\015\012");
1980 				/* send the command */
1981     ret = net_soutr (stream->netstream,s) ? nntp_reply (stream) :
1982       nntp_fake (stream,"NNTP connection broken (command)");
1983   }
1984   fs_give ((void **) &s);
1985   return ret;
1986 }
1987 
1988 /* NNTP send authentication if needed
1989  * Accepts: SEND stream
1990  *	    flags (non-NIL to get new extensions)
1991  * Returns: T if need to redo command, NIL otherwise
1992  */
1993 
nntp_send_auth(SENDSTREAM * stream,long flags)1994 long nntp_send_auth (SENDSTREAM *stream,long flags)
1995 {
1996   NETMBX mb;
1997   char tmp[MAILTMPLEN];
1998 				/* remote name for authentication */
1999   sprintf (tmp,"{%.200s/nntp",(long) mail_parameters (NIL,GET_TRUSTDNS,NIL) ?
2000 	   ((long) mail_parameters (NIL,GET_SASLUSESPTRNAME,NIL) ?
2001 	    net_remotehost (stream->netstream) : net_host (stream->netstream)):
2002 	   stream->host);
2003   if (stream->netstream->dtb ==
2004       (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL))
2005     strcat (tmp,"/ssl");
2006   strcat (tmp,"}<none>");
2007   mail_valid_net_parse (tmp,&mb);
2008   return nntp_send_auth_work (stream,&mb,tmp,flags);
2009 }
2010 
2011 /* NNTP send authentication worker routine
2012  * Accepts: SEND stream
2013  *	    NETMBX structure
2014  *	    scratch buffer of length MAILTMPLEN
2015  *	    flags (non-NIL to get new extensions)
2016  * Returns: T if authenticated, NIL otherwise
2017  */
2018 
nntp_send_auth_work(SENDSTREAM * stream,NETMBX * mb,char * pwd,long flags)2019 long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags)
2020 {
2021   unsigned long trial,auths;
2022   char tmp[MAILTMPLEN],usr[MAILTMPLEN];
2023   AUTHENTICATOR *at;
2024   char *lsterr = NIL;
2025   long ret = NIL;
2026 				/* try SASL first */
2027   for (auths = NNTP.ext.sasl, stream->saslcancel = NIL;
2028        !ret && stream->netstream && auths &&
2029        (at = mail_lookup_auth (find_rightmost_bit (&auths) + 1)); ) {
2030     if (lsterr) {		/* previous authenticator failed? */
2031       sprintf (tmp,"Retrying using %s authentication after %.80s",
2032 	       at->name,lsterr);
2033       mm_log (tmp,NIL);
2034       fs_give ((void **) &lsterr);
2035     }
2036     trial = 0;			/* initial trial count */
2037     tmp[0] = '\0';		/* empty buffer */
2038     if (stream->netstream) do {
2039       if (lsterr) {
2040 	sprintf (tmp,"Retrying %s authentication after %.80s",at->name,lsterr);
2041 	mm_log (tmp,WARN);
2042 	fs_give ((void **) &lsterr);
2043       }
2044       stream->saslcancel = NIL;
2045       if (nntp_send (stream,"AUTHINFO SASL",at->name) == NNTPCHALLENGE) {
2046 				/* hide client authentication responses */
2047 	if (!(at->flags & AU_SECURE)) stream->sensitive = T;
2048 	if ((*at->client) (nntp_challenge,nntp_response,"nntp",mb,stream,
2049 			   &trial,usr)) {
2050 	  if (stream->replycode == NNTPAUTHED) ret = LONGT;
2051 				/* if main program requested cancellation */
2052 	  else if (!trial) mm_log ("NNTP Authentication cancelled",ERROR);
2053 	}
2054 	stream->sensitive = NIL;/* unhide */
2055       }
2056 				/* remember response if error and no cancel */
2057       if (!ret && trial) lsterr = cpystr (stream->reply);
2058     } while (!ret && stream->netstream && trial &&
2059 	     (trial < nntp_maxlogintrials));
2060   }
2061 
2062   if (lsterr) {			/* SAIL failed? */
2063     if (!stream->saslcancel) {	/* don't do this if a cancel */
2064       sprintf (tmp,"Can not authenticate to NNTP server: %.80s",lsterr);
2065       mm_log (tmp,ERROR);
2066     }
2067     fs_give ((void **) &lsterr);
2068   }
2069   else if (mb->secflag)		/* no SASL, can't do /secure */
2070     mm_log ("Can't do secure authentication with this server",ERROR);
2071   else if (mb->authuser[0])	/* or /authuser */
2072     mm_log ("Can't do /authuser with this server",ERROR);
2073   /* Always try AUTHINFO USER, even if NNTP.ext.authuser isn't set.  There
2074    * are servers that require it but don't return it as an extension.
2075    */
2076   else for (trial = 0, pwd[0] = 'x';
2077 	    !ret && pwd[0] && (trial < nntp_maxlogintrials) &&
2078 	      stream->netstream; ) {
2079     pwd[0] = NIL;		/* get user name and password */
2080     mm_login (mb,usr,pwd,trial++);
2081 				/* do the authentication */
2082     if (pwd[0]) switch ((int) nntp_send_work (stream,"AUTHINFO USER",usr)) {
2083     case NNTPBADCMD:		/* give up if unrecognized command */
2084       mm_log (NNTP.ext.authuser ? stream->reply :
2085 	      "Can't do AUTHINFO USER to this server",ERROR);
2086       trial = nntp_maxlogintrials;
2087       break;
2088     case NNTPAUTHED:		/* successful authentication */
2089       ret = LONGT;		/* guess no password was needed */
2090       break;
2091     case NNTPWANTPASS:		/* wants password */
2092       stream->sensitive = T;	/* hide this command */
2093       if (nntp_send_work (stream,"AUTHINFO PASS",pwd) == NNTPAUTHED)
2094 	ret = LONGT;		/* password OK */
2095       stream->sensitive = NIL;	/* unhide */
2096       if (ret) break;		/* OK if successful */
2097     default:			/* authentication failed */
2098       mm_log (stream->reply,WARN);
2099       if (trial == nntp_maxlogintrials)
2100 	mm_log ("Too many NNTP authentication failures",ERROR);
2101     }
2102 				/* user refused to give a password */
2103     else mm_log ("Login aborted",ERROR);
2104   }
2105   memset (pwd,0,MAILTMPLEN);	/* erase password */
2106 				/* get new extensions if needed */
2107   if (ret && flags) nntp_extensions (stream,(mb->secflag ? AU_SECURE : NIL) |
2108 				     (mb->authuser[0] ? AU_AUTHUSER : NIL));
2109   return ret;
2110 }
2111 
2112 /* Get challenge to authenticator in binary
2113  * Accepts: stream
2114  *	    pointer to returned size
2115  * Returns: challenge or NIL if not challenge
2116  */
2117 
nntp_challenge(void * s,unsigned long * len)2118 void *nntp_challenge (void *s,unsigned long *len)
2119 {
2120   char tmp[MAILTMPLEN];
2121   void *ret = NIL;
2122   SENDSTREAM *stream = (SENDSTREAM *) s;
2123   if ((stream->replycode == NNTPCHALLENGE) &&
2124       !(ret = rfc822_base64 ((unsigned char *) stream->reply + 4,
2125 			     strlen (stream->reply + 4),len))) {
2126     sprintf (tmp,"NNTP SERVER BUG (invalid challenge): %.80s",stream->reply+4);
2127     mm_log (tmp,ERROR);
2128   }
2129   return ret;
2130 }
2131 
2132 
2133 /* Send authenticator response in BASE64
2134  * Accepts: MAIL stream
2135  *	    string to send
2136  *	    length of string
2137  * Returns: T, always
2138  */
2139 
nntp_response(void * s,char * response,unsigned long size)2140 long nntp_response (void *s,char *response,unsigned long size)
2141 {
2142   SENDSTREAM *stream = (SENDSTREAM *) s;
2143   unsigned long i,j;
2144   char *t,*u;
2145   if (response) {		/* make CRLFless BASE64 string */
2146     if (size) {
2147       for (t = (char *) rfc822_binary ((void *) response,size,&i),u = t,j = 0;
2148 	   j < i; j++) if (t[j] > ' ') *u++ = t[j];
2149       *u = '\0';		/* tie off string */
2150       i = nntp_send_work (stream,t,NIL);
2151       fs_give ((void **) &t);
2152     }
2153     else i = nntp_send_work (stream,"",NIL);
2154   }
2155   else {			/* abort requested */
2156     i = nntp_send_work (stream,"*",NIL);
2157     stream->saslcancel = T;	/* mark protocol-requested SASL cancel */
2158   }
2159   return LONGT;
2160 }
2161 
2162 /* NNTP get reply
2163  * Accepts: SEND stream
2164  * Returns: reply code
2165  */
2166 
nntp_reply(SENDSTREAM * stream)2167 long nntp_reply (SENDSTREAM *stream)
2168 {
2169 				/* flush old reply */
2170   if (stream->reply) fs_give ((void **) &stream->reply);
2171   				/* get reply */
2172   if (!(stream->reply = net_getline (stream->netstream)))
2173     return nntp_fake (stream,"NNTP connection broken (response)");
2174   if (stream->debug) mm_dlog (stream->reply);
2175 				/* handle continuation by recursion */
2176   if (stream->reply[3] == '-') return nntp_reply (stream);
2177 				/* return response code */
2178   return stream->replycode = atol (stream->reply);
2179 }
2180 
2181 
2182 /* NNTP set fake error
2183  * Accepts: SEND stream
2184  *	    error text
2185  * Returns: error code
2186  */
2187 
nntp_fake(SENDSTREAM * stream,char * text)2188 long nntp_fake (SENDSTREAM *stream,char *text)
2189 {
2190   if (stream->netstream) {	/* close net connection if still open */
2191     net_close (stream->netstream);
2192     stream->netstream = NIL;
2193   }
2194 				/* flush any old reply */
2195   if (stream->reply) fs_give ((void **) &stream->reply);
2196   				/* set up pseudo-reply string */
2197   stream->reply = (char *) fs_get (20+strlen (text));
2198   sprintf (stream->reply,"%ld %s",NNTPSOFTFATAL,text);
2199   return NNTPSOFTFATAL;		/* return error code */
2200 }
2201 
2202 /* NNTP filter mail
2203  * Accepts: stream
2204  *	    string
2205  * Returns: T on success, NIL on failure
2206  */
2207 
nntp_soutr(void * stream,char * s)2208 long nntp_soutr (void *stream,char *s)
2209 {
2210   char c,*t;
2211 				/* "." on first line */
2212   if (s[0] == '.') net_soutr (stream,".");
2213 				/* find lines beginning with a "." */
2214   while (t = strstr (s,"\015\012.")) {
2215     c = *(t += 3);		/* remember next character after "." */
2216     *t = '\0';			/* tie off string */
2217 				/* output prefix */
2218     if (!net_soutr (stream,s)) return NIL;
2219     *t = c;			/* restore delimiter */
2220     s = t - 1;			/* push pointer up to the "." */
2221   }
2222 				/* output remainder of text */
2223   return *s ? net_soutr (stream,s) : T;
2224 }
2225