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