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