1 /* ========================================================================
2 * Copyright 2008-2011 Mark Crispin
3 * Copyright 2019 Eduardo Chappa
4 * ========================================================================
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
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 */
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
,
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
,
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
,
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 *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 */
167 #ifdef INADEQUATE_MEMORY
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 */
208 /* prototype stream */
209 MAILSTREAM nntpproto
= {&nntpdriver
};
212 /* driver parameters */
213 static unsigned long nntp_maxlogintrials
= MAXLOGINTRIALS
;
214 static long nntp_port
= 0;
215 static long nntp_sslport
= 0;
216 static unsigned long nntp_range
= 0;
217 static long nntp_hidepath
= 0;
219 /* NNTP validate mailbox
220 * Accepts: mailbox name
221 * Returns: our driver if name is valid, NIL otherwise
224 DRIVER
*nntp_valid (char *name
)
226 char tmp
[MAILTMPLEN
];
227 return nntp_isvalid (name
,tmp
);
231 /* NNTP validate mailbox work routine
232 * Accepts: mailbox name
233 * buffer for returned mailbox name
234 * Returns: our driver if name is valid, NIL otherwise
237 DRIVER
*nntp_isvalid (char *name
,char *mbx
)
240 if (!mail_valid_net_parse (name
,&mb
) || strcmp (mb
.service
,nntpdriver
.name
)||
241 mb
.anoflag
) return NIL
;
242 if (mb
.mailbox
[0] != '#') strcpy (mbx
,mb
.mailbox
);
243 /* namespace format name */
244 else if ((mb
.mailbox
[1] == 'n') && (mb
.mailbox
[2] == 'e') &&
245 (mb
.mailbox
[3] == 'w') && (mb
.mailbox
[4] == 's') &&
246 (mb
.mailbox
[5] == '.')) strcpy (mbx
,mb
.mailbox
+6);
247 else return NIL
; /* bogus name */
251 /* News manipulate driver parameters
252 * Accepts: function code
253 * function-dependent value
254 * Returns: function-dependent return value
257 void *nntp_parameters (long function
,void *value
)
259 switch ((int) function
) {
260 case SET_MAXLOGINTRIALS
:
261 nntp_maxlogintrials
= (unsigned long) value
;
263 case GET_MAXLOGINTRIALS
:
264 value
= (void *) nntp_maxlogintrials
;
267 nntp_port
= (long) value
;
270 value
= (void *) nntp_port
;
272 case SET_SSLNNTPPORT
:
273 nntp_sslport
= (long) value
;
275 case GET_SSLNNTPPORT
:
276 value
= (void *) nntp_sslport
;
279 nntp_range
= (unsigned long) value
;
282 value
= (void *) nntp_range
;
284 case SET_NNTPHIDEPATH
:
285 nntp_hidepath
= (long) value
;
287 case GET_NNTPHIDEPATH
:
288 value
= (void *) nntp_hidepath
;
292 value
= (void *) ((NNTPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->newsrc
;
294 case GET_IDLETIMEOUT
:
295 value
= (void *) IDLETIMEOUT
;
299 ((NNTPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->nntpstream
->debug
= T
;
303 ((NNTPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->nntpstream
->debug
= NIL
;
306 value
= NIL
; /* error case */
312 /* NNTP mail scan mailboxes for string
313 * Accepts: mail stream
319 void nntp_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
)
321 char tmp
[MAILTMPLEN
];
322 if (nntp_canonicalize (ref
,pat
,tmp
,NIL
))
323 mm_log ("Scan not valid for NNTP mailboxes",ERROR
);
327 /* NNTP list newsgroups
328 * Accepts: mail stream
333 void nntp_list (MAILSTREAM
*stream
,char *ref
,char *pat
)
335 MAILSTREAM
*st
= stream
;
336 char *s
,*t
,*lcl
,pattern
[MAILTMPLEN
],name
[MAILTMPLEN
],wildmat
[MAILTMPLEN
];
337 int showuppers
= pat
[strlen (pat
) - 1] == '%';
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 /* namespace format name? */
354 if (*(lcl
= strchr (strcpy (name
,pattern
),'}') + 1) == '#') lcl
+= 6;
355 /* process data until we see final dot */
356 while ((s
= net_getline (LOCAL
->nntpstream
->netstream
)) != NULL
) {
357 if ((*s
== '.') && !s
[1]){/* end of text */
358 fs_give ((void **) &s
);
361 if ((t
= strchr (s
,' ')) != NULL
) { /* tie off after newsgroup name */
363 strcpy (lcl
,s
); /* make full form of name */
364 /* report if match */
365 if (pmatch_full (name
,pattern
,'.')) mm_list (stream
,'.',name
,NIL
);
366 else while (showuppers
&& (t
= strrchr (lcl
,'.'))) {
367 *t
= '\0'; /* tie off the name */
368 if (pmatch_full (name
,pattern
,'.'))
369 mm_list (stream
,'.',name
,LATT_NOSELECT
);
372 fs_give ((void **) &s
); /* clean up */
374 if (stream
!= st
) mail_close (stream
);
378 /* NNTP list subscribed newsgroups
379 * Accepts: mail stream
384 void nntp_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
)
387 char *s
,mbx
[MAILTMPLEN
],tmp
[MAILTMPLEN
];
388 /* return data from newsrc */
389 if (nntp_canonicalize (ref
,pat
,mbx
,NIL
)) newsrc_lsub (stream
,mbx
);
390 if (*pat
== '{') { /* if remote pattern, must be NNTP */
391 if (!nntp_valid (pat
)) return;
392 ref
= NIL
; /* good NNTP pattern, punt reference */
394 /* if remote reference, must be valid NNTP */
395 if (ref
&& (*ref
== '{') && !nntp_valid (ref
)) return;
396 /* kludgy application of reference */
397 if (ref
&& *ref
) sprintf (mbx
,"%s%s",ref
,pat
);
398 else strcpy (mbx
,pat
);
400 if ((s
= sm_read (tmp
,&sdb
)) != NULL
) do if (nntp_valid (s
) && pmatch (s
,mbx
))
401 mm_lsub (stream
,NIL
,s
,NIL
);
402 /* until no more subscriptions */
403 while ((s
= sm_read (tmp
,&sdb
)) != NULL
);
406 /* NNTP canonicalize newsgroup name
409 * returned single pattern
410 * returned wildmat pattern
411 * Returns: T on success, NIL on failure
414 long nntp_canonicalize (char *ref
,char *pat
,char *pattern
,char *wildmat
)
418 if (ref
&& *ref
) { /* have a reference */
419 if (!nntp_valid (ref
)) return NIL
;
420 strcpy (pattern
,ref
); /* copy reference to pattern */
421 /* # overrides mailbox field in reference */
422 if (*pat
== '#') strcpy (strchr (pattern
,'}') + 1,pat
);
423 /* pattern starts, reference ends, with . */
424 else if ((*pat
== '.') && (pattern
[strlen (pattern
) - 1] == '.'))
425 strcat (pattern
,pat
+ 1); /* append, omitting one of the period */
426 else strcat (pattern
,pat
); /* anything else is just appended */
428 else strcpy (pattern
,pat
); /* just have basic name */
429 if ((ret
= wildmat
? /* if valid and wildmat */
430 nntp_isvalid (pattern
,wildmat
) : nntp_valid (pattern
)) && wildmat
) {
431 /* don't return wildmat if specials present */
432 if (strpbrk (wildmat
,",?![\\]")) wildmat
[0] = '\0';
433 /* replace all % with * */
434 for (s
= wildmat
; (s
= strchr (s
,'%')) != NULL
; *s
= '*');
436 return ret
? LONGT
: NIL
;
439 /* NNTP subscribe to mailbox
440 * Accepts: mail stream
441 * mailbox to add to subscription list
442 * Returns: T on success, NIL on failure
445 long nntp_subscribe (MAILSTREAM
*stream
,char *mailbox
)
447 char mbx
[MAILTMPLEN
];
448 return nntp_isvalid (mailbox
,mbx
) ? newsrc_update (stream
,mbx
,':') : NIL
;
452 /* NNTP unsubscribe to mailbox
453 * Accepts: mail stream
454 * mailbox to delete from subscription list
455 * Returns: T on success, NIL on failure
458 long nntp_unsubscribe (MAILSTREAM
*stream
,char *mailbox
)
460 char mbx
[MAILTMPLEN
];
461 return nntp_isvalid (mailbox
,mbx
) ? newsrc_update (stream
,mbx
,'!') : NIL
;
464 /* NNTP create mailbox
465 * Accepts: mail stream
466 * mailbox name to create
467 * Returns: T on success, NIL on failure
470 long nntp_create (MAILSTREAM
*stream
,char *mailbox
)
472 return NIL
; /* never valid for NNTP */
476 /* NNTP delete mailbox
477 * mailbox name to delete
478 * Returns: T on success, NIL on failure
481 long nntp_delete (MAILSTREAM
*stream
,char *mailbox
)
483 return NIL
; /* never valid for NNTP */
487 /* NNTP rename mailbox
488 * Accepts: mail stream
491 * Returns: T on success, NIL on failure
494 long nntp_rename (MAILSTREAM
*stream
,char *old
,char *newname
)
496 return NIL
; /* never valid for NNTP */
500 * Accepts: mail stream
503 * Returns: T on success, NIL on failure
506 long nntp_status (MAILSTREAM
*stream
,char *mbx
,long flags
)
510 unsigned long i
,j
,k
,rnmsgs
;
512 char *s
,*name
,*state
,tmp
[MAILTMPLEN
];
513 char *old
= (stream
&& !stream
->halfopen
) ? LOCAL
->name
: NIL
;
514 MAILSTREAM
*tstream
= NIL
;
515 if (!(mail_valid_net_parse (mbx
,&mb
) && !strcmp (mb
.service
,"nntp") &&
517 ((mb
.mailbox
[0] != '#') ||
518 ((mb
.mailbox
[1] == 'n') && (mb
.mailbox
[2] == 'e') &&
519 (mb
.mailbox
[3] == 'w') && (mb
.mailbox
[4] == 's') &&
520 (mb
.mailbox
[5] == '.'))))) {
521 sprintf (tmp
,"Invalid NNTP name %s",mbx
);
525 /* note mailbox name */
526 name
= (*mb
.mailbox
== '#') ? mb
.mailbox
+6 : mb
.mailbox
;
527 /* stream to reuse? */
528 if (!(stream
&& LOCAL
->nntpstream
&&
529 mail_usable_network_stream (stream
,mbx
)) &&
531 mail_open (NIL
,mbx
,OP_HALFOPEN
|OP_SILENT
|
532 ((flags
& SA_MULNEWSRC
) ? OP_MULNEWSRC
: NIL
))))
533 return NIL
; /* can't reuse or make a new one */
535 if (nntp_send (LOCAL
->nntpstream
,"GROUP",name
) == NNTPGOK
) {
536 status
.flags
= flags
; /* status validity flags */
537 k
= strtoul (LOCAL
->nntpstream
->reply
+ 4,&s
,10);
538 i
= strtoul (s
,&s
,10); /* first assigned UID */
539 /* next UID to be assigned */
540 status
.uidnext
= (j
= strtoul (s
,NIL
,10)) + 1;
541 /* maximum number of messages */
542 rnmsgs
= status
.messages
= (i
| j
) ? status
.uidnext
- i
: 0;
543 if (k
> status
.messages
) { /* check for absurdity */
544 sprintf (tmp
,"NNTP SERVER BUG (impossible message count): %lu > %lu",
548 /* restrict article range if needed */
549 if (nntp_range
&& (status
.messages
> nntp_range
)) {
550 i
= status
.uidnext
- (status
.messages
= nntp_range
);
551 if (k
> nntp_range
) k
= nntp_range
;
554 status
.recent
= status
.unseen
= 0;
555 if (!status
.messages
); /* empty case */
556 /* use server guesstimate in simple case */
557 else if (!(flags
& (SA_RECENT
| SA_UNSEEN
))) status
.messages
= k
;
559 /* have newsrc state? */
560 else if ((state
= newsrc_state (stream
,name
)) != NULL
) {
561 /* yes, get the UID/sequence map */
562 if (nntp_getmap (stream
,name
,i
,status
.uidnext
- 1,rnmsgs
,
563 status
.messages
,tmp
)) {
564 /* calculate true count */
565 for (status
.messages
= 0;
566 (s
= net_getline (LOCAL
->nntpstream
->netstream
)) &&
568 /* only count if in range */
569 if (((k
= atol (s
)) >= i
) && (k
< status
.uidnext
)) {
570 newsrc_check_uid (state
,k
,&status
.recent
,&status
.unseen
);
573 fs_give ((void **) &s
);
575 if (s
) fs_give ((void **) &s
);
577 /* assume c-client/NNTP map is entire range */
578 else while (i
< status
.uidnext
)
579 newsrc_check_uid (state
,i
++,&status
.recent
,&status
.unseen
);
580 fs_give ((void **) &state
);
582 /* no .newsrc state, all messages new */
583 else status
.recent
= status
.unseen
= status
.messages
;
584 /* UID validity is a constant */
585 status
.uidvalidity
= stream
->uid_validity
;
586 /* pass status to main program */
587 mm_status (stream
,mbx
,&status
);
588 ret
= T
; /* succes */
590 /* flush temporary stream */
591 if (tstream
) mail_close (tstream
);
592 /* else reopen old newsgroup */
593 else if (old
&& nntp_send (LOCAL
->nntpstream
,"GROUP",old
) != NNTPGOK
) {
594 mm_log (LOCAL
->nntpstream
->reply
,ERROR
);
595 stream
->halfopen
= T
; /* go halfopen */
597 return ret
; /* success */
603 * first UID in map range
604 * last UID in map range
605 * reported total number of messages in newsgroup
606 * calculated number of messages in range
608 * Returns: T on success, NIL on failure
611 long nntp_getmap (MAILSTREAM
*stream
,char *name
,
612 unsigned long first
,unsigned long last
,
613 unsigned long rnmsgs
,unsigned long nmsgs
,char *tmp
)
615 short trylistgroup
= NIL
;
616 if (rnmsgs
> (nmsgs
* 8)) /* small subrange? */
617 trylistgroup
= T
; /* yes, can try LISTGROUP if [X]HDR fails */
618 else switch ((int) nntp_send (LOCAL
->nntpstream
,"LISTGROUP",name
)) {
619 case NNTPGOK
: /* got data */
621 default: /* else give up if server claims LISTGROUP */
622 if (EXTENSION
.listgroup
) return NIL
;
625 sprintf (tmp
,"%lu-%lu",first
,last
);
626 if (EXTENSION
.hdr
) /* have HDR extension? */
627 return (nntp_send (LOCAL
->nntpstream
,"HDR Date",tmp
) == NNTPHEAD
) ?
629 if (LOCAL
->xhdr
) /* try the experimental extension then */
630 switch ((int) nntp_send (LOCAL
->nntpstream
,"XHDR Date",tmp
)) {
631 case NNTPHEAD
: /* got an overview? */
633 case NNTPBADCMD
: /* unknown command? */
634 LOCAL
->xhdr
= NIL
; /* disable future XHDR attempts */
636 if (trylistgroup
&& /* no [X]HDR, maybe do LISTGROUP after all */
637 (nntp_send (LOCAL
->nntpstream
,"LISTGROUP",name
) == NNTPGOK
))
643 * Accepts: stream to open
644 * Returns: stream on success, NIL on failure
647 MAILSTREAM
*nntp_mopen (MAILSTREAM
*stream
)
649 unsigned long i
,j
,k
,nmsgs
,rnmsgs
;
650 char *s
,*mbx
,tmp
[MAILTMPLEN
];
653 char *newsrc
= (char *) mail_parameters (NIL
,GET_NEWSRC
,NIL
);
654 newsrcquery_t nq
= (newsrcquery_t
) mail_parameters (NIL
,GET_NEWSRCQUERY
,NIL
);
655 SENDSTREAM
*nstream
= NIL
;
656 /* return prototype for OP_PROTOTYPE call */
657 if (!stream
) return &nntpproto
;
658 mail_valid_net_parse (stream
->mailbox
,&mb
);
659 /* note mailbox anme */
660 mbx
= (*mb
.mailbox
== '#') ? mb
.mailbox
+6 : mb
.mailbox
;
661 if (LOCAL
) { /* recycle stream */
662 nstream
= LOCAL
->nntpstream
;/* remember NNTP protocol stream */
663 sprintf (tmp
,"Reusing connection to %s",net_host (nstream
->netstream
));
664 if (!stream
->silent
) mm_log (tmp
,(long) NIL
);
665 if (stream
->rdonly
) mb
.readonlyflag
= T
;
666 if (LOCAL
->tlsflag
) mb
.tlsflag
= T
;
667 if (LOCAL
->tlssslv23
) mb
.tlssslv23
= T
;
668 if (LOCAL
->notlsflag
) mb
.notlsflag
= T
;
669 if (LOCAL
->sslflag
) mb
.sslflag
= T
;
670 if (LOCAL
->tls1
) mb
.tls1
= T
;
671 if (LOCAL
->tls1_1
) mb
.tls1_1
= T
;
672 if (LOCAL
->tls1_2
) mb
.tls1_2
= T
;
673 if (LOCAL
->tls1_3
) mb
.tls1_3
= T
;
674 if (LOCAL
->novalidate
) mb
.novalidate
= T
;
675 if (LOCAL
->nntpstream
->loser
) mb
.loser
= T
;
676 if (stream
->secure
) mb
.secflag
= T
;
677 LOCAL
->nntpstream
= NIL
; /* keep nntp_mclose() from punting it */
678 nntp_mclose (stream
,NIL
); /* do close action */
679 stream
->dtb
= &nntpdriver
; /* reattach this driver */
682 if (mb
.dbgflag
) stream
->debug
= T
;
683 if (mb
.readonlyflag
) stream
->rdonly
= T
;
684 if (mb
.secflag
) stream
->secure
= T
;
685 mb
.trysslflag
= stream
->tryssl
= (mb
.trysslflag
|| stream
->tryssl
) ? T
: NIL
;
686 if (!nstream
) { /* open NNTP now if not already open */
688 hostlist
[0] = strcpy (tmp
,mb
.host
);
689 if (mb
.port
|| nntp_port
)
690 sprintf (tmp
+ strlen (tmp
),":%lu",mb
.port
? mb
.port
: nntp_port
);
691 if (mb
.tlsflag
) strcat (tmp
,"/tls");
692 if (mb
.tlssslv23
) strcat (tmp
,"/tls-sslv23");
693 if (mb
.notlsflag
) strcat (tmp
,"/notls");
694 if (mb
.sslflag
) strcat (tmp
,"/ssl");
695 if (mb
.tls1
) strcat (tmp
,"/tls1");
696 if (mb
.tls1_1
) strcat (tmp
,"/tls1_1");
697 if (mb
.tls1_2
) strcat (tmp
,"/tls1_2");
698 if (mb
.tls1_3
) strcat (tmp
,"/tls1_3");
699 if (mb
.novalidate
) strcat (tmp
,"/novalidate-cert");
700 if (mb
.loser
) strcat (tmp
,"/loser");
701 if (mb
.secflag
) strcat (tmp
,"/secure");
702 if (mb
.user
[0]) sprintf (tmp
+ strlen (tmp
),"/user=\"%s\"",mb
.user
);
704 if (!(nstream
= nntp_open (hostlist
,NOP_READONLY
|
705 (stream
->debug
? NOP_DEBUG
: NIL
)))) return NIL
;
708 if(!nstream
->netstream
){
709 mm_log (nstream
->reply
,ERROR
);
710 nntp_close (nstream
); /* punt stream */
713 /* always zero messages if halfopen */
714 if (stream
->halfopen
) i
= j
= k
= rnmsgs
= nmsgs
= 0;
715 /* otherwise open the newsgroup */
716 else if (nntp_send (nstream
,"GROUP",mbx
) == NNTPGOK
) {
717 k
= strtoul (nstream
->reply
+ 4,&s
,10);
718 i
= strtoul (s
,&s
,10);
719 stream
->uid_last
= j
= strtoul (s
,&s
,10);
720 rnmsgs
= nmsgs
= (i
| j
) ? 1 + j
- i
: 0;
721 if (k
> nmsgs
) { /* check for absurdity */
722 sprintf (tmp
,"NNTP SERVER BUG (impossible message count): %lu > %lu",
726 /* restrict article range if needed */
727 if (nntp_range
&& (nmsgs
> nntp_range
)) i
= 1 + j
- (nmsgs
= nntp_range
);
729 else { /* no such newsgroup */
730 mm_log (nstream
->reply
,ERROR
);
731 nntp_close (nstream
); /* punt stream */
734 /* instantiate local data */
735 stream
->local
= memset (fs_get (sizeof (NNTPLOCAL
)),0,sizeof (NNTPLOCAL
));
736 LOCAL
->nntpstream
= nstream
;
737 /* save state for future recycling */
738 if (mb
.tlsflag
) LOCAL
->tlsflag
= T
;
739 if (mb
.tlssslv23
) LOCAL
->tlssslv23
= T
;
740 if (mb
.notlsflag
) LOCAL
->notlsflag
= T
;
741 if (mb
.sslflag
) LOCAL
->sslflag
= T
;
742 if (mb
.novalidate
) LOCAL
->novalidate
= T
;
743 if (mb
.loser
) LOCAL
->nntpstream
->loser
= T
;
744 /* assume present until proven otherwise */
745 LOCAL
->xhdr
= LOCAL
->xover
= T
;
746 LOCAL
->name
= cpystr (mbx
); /* copy newsgroup name */
747 if (stream
->mulnewsrc
) { /* want to use multiple .newsrc files? */
749 s
= tmp
+ strlen (tmp
); /* end of string */
750 *s
++ = '-'; /* hyphen delimiter and host */
751 lcase (strcpy (s
,(long) mail_parameters (NIL
,GET_NEWSRCCANONHOST
,NIL
) ?
752 net_host (nstream
->netstream
) : mb
.host
));
753 LOCAL
->newsrc
= cpystr (nq
? (*nq
) (stream
,tmp
,newsrc
) : tmp
);
755 else LOCAL
->newsrc
= cpystr (newsrc
);
756 if (mb
.user
[0]) LOCAL
->user
= cpystr (mb
.user
);
757 stream
->sequence
++; /* bump sequence number */
758 stream
->rdonly
= stream
->perm_deleted
= T
;
759 /* UIDs are always valid */
760 stream
->uid_validity
= 0xbeefface;
761 sprintf (tmp
,"{%s:%lu/nntp",(long) mail_parameters (NIL
,GET_TRUSTDNS
,NIL
) ?
762 net_host (nstream
->netstream
) : mb
.host
,
763 net_port (nstream
->netstream
));
764 if (LOCAL
->tlsflag
) strcat (tmp
,"/tls");
765 if (LOCAL
->tlssslv23
) strcat (tmp
,"/tls-sslv23");
766 if (LOCAL
->notlsflag
) strcat (tmp
,"/notls");
767 if (LOCAL
->sslflag
) strcat (tmp
,"/ssl");
768 if (LOCAL
->tls1
) strcat (tmp
,"/tls1");
769 if (LOCAL
->tls1_1
) strcat (tmp
,"/tls1_1");
770 if (LOCAL
->tls1_2
) strcat (tmp
,"/tls1_2");
771 if (LOCAL
->tls1_3
) strcat (tmp
,"/tls1_3");
772 if (LOCAL
->novalidate
) strcat (tmp
,"/novalidate-cert");
773 if (LOCAL
->nntpstream
->loser
) strcat (tmp
,"/loser");
774 if (stream
->secure
) strcat (tmp
,"/secure");
775 if (stream
->rdonly
) strcat (tmp
,"/readonly");
776 if (LOCAL
->user
) sprintf (tmp
+ strlen (tmp
),"/user=\"%s\"",LOCAL
->user
);
777 if (stream
->halfopen
) strcat (tmp
,"}<no_mailbox>");
778 else sprintf (tmp
+ strlen (tmp
),"}#news.%s",mbx
);
779 fs_give ((void **) &stream
->mailbox
);
780 stream
->mailbox
= cpystr (tmp
);
782 if (EXTENSION
.over
&& /* get overview format if have OVER */
783 (nntp_send (LOCAL
->nntpstream
,"LIST","OVERVIEW.FMT") == NNTPGLIST
) &&
784 (f
= netmsg_slurp (LOCAL
->nntpstream
->netstream
,&k
,NIL
))) {
785 fread (LOCAL
->over_fmt
= (char *) fs_get ((size_t) k
+ 3),
786 (size_t) 1,(size_t) k
,f
);
787 LOCAL
->over_fmt
[k
] = '\0';
788 fclose (f
); /* flush temp file */
790 if (nmsgs
) { /* if any messages exist */
791 short silent
= stream
->silent
;
792 stream
->silent
= T
; /* don't notify main program yet */
793 mail_exists (stream
,nmsgs
); /* silently set the cache to the guesstimate */
794 /* get UID/sequence map, nuke holes */
795 if (nntp_getmap (stream
,mbx
,i
,j
,rnmsgs
,nmsgs
,tmp
)) {
796 for (nmsgs
= 0; /* calculate true count */
797 (s
= net_getline (nstream
->netstream
)) && strcmp (s
,"."); ) {
798 if ((k
= atol (s
)) > j
){/* discard too high article numbers */
799 sprintf (tmp
,"NNTP SERVER BUG (out of range article ID): %lu > %lu",
801 mm_notify (stream
,tmp
,NIL
);
802 stream
->unhealthy
= T
;
804 else if (k
>= i
) { /* silently ignore too-low article numbers */
805 /* guard against server returning extra msgs */
806 if (nmsgs
== stream
->nmsgs
) mail_exists (stream
,nmsgs
+1);
807 /* create elt for this message, set UID */
808 mail_elt (stream
,++nmsgs
)->private.uid
= k
;
810 fs_give ((void **) &s
);
812 if (s
) fs_give ((void **) &s
);
814 /* assume c-client/NNTP map is entire range */
815 else for (k
= 1; k
<= nmsgs
; k
++) mail_elt (stream
,k
)->private.uid
= i
++;
816 stream
->unhealthy
= NIL
; /* set healthy */
817 stream
->nmsgs
= 0; /* whack it back down */
818 stream
->silent
= silent
; /* restore old silent setting */
819 mail_exists (stream
,nmsgs
); /* notify upper level that messages exist */
820 /* read .newsrc entries */
821 mail_recent (stream
,newsrc_read (mbx
,stream
));
823 else { /* empty newsgroup or halfopen */
824 if (!(stream
->silent
|| stream
->halfopen
)) {
825 sprintf (tmp
,"Newsgroup %s is empty",mbx
);
828 mail_exists (stream
,(long) 0);
829 mail_recent (stream
,(long) 0);
831 return stream
; /* return stream to caller */
835 * Accepts: MAIL stream
839 void nntp_mclose (MAILSTREAM
*stream
,long options
)
843 if (LOCAL
) { /* only if a file is open */
844 nntp_check (stream
); /* dump final checkpoint */
845 if (LOCAL
->over_fmt
) fs_give ((void **) &LOCAL
->over_fmt
);
846 if (LOCAL
->name
) fs_give ((void **) &LOCAL
->name
);
847 if (LOCAL
->user
) fs_give ((void **) &LOCAL
->user
);
848 if (LOCAL
->newsrc
) fs_give ((void **) &LOCAL
->newsrc
);
849 if (LOCAL
->txt
) fclose (LOCAL
->txt
);
850 /* close NNTP connection */
851 if (LOCAL
->nntpstream
) nntp_close (LOCAL
->nntpstream
);
852 for (i
= 1; i
<= stream
->nmsgs
; i
++)
853 if ((elt
= mail_elt (stream
,i
))->private.spare
.ptr
)
854 fs_give ((void **) &elt
->private.spare
.ptr
);
855 /* nuke the local data */
856 fs_give ((void **) &stream
->local
);
857 stream
->dtb
= NIL
; /* log out the DTB */
861 /* NNTP fetch fast information
862 * Accepts: MAIL stream
865 * This is ugly and slow
868 void nntp_fetchfast (MAILSTREAM
*stream
,char *sequence
,long flags
)
873 if (stream
&& LOCAL
&& ((flags
& FT_UID
) ?
874 mail_uid_sequence (stream
,sequence
) :
875 mail_sequence (stream
,sequence
)))
876 for (i
= 1; i
<= stream
->nmsgs
; i
++) {
877 if ((elt
= mail_elt (stream
,i
))->sequence
&& (elt
->valid
= T
) &&
878 !(elt
->day
&& elt
->rfc822_size
)) {
879 ENVELOPE
**env
= NIL
;
881 if (!stream
->scache
) env
= &elt
->private.msg
.env
;
882 else if (stream
->msgno
== i
) env
= &stream
->env
;
884 if (!*env
|| !elt
->rfc822_size
) {
887 char *ht
= (*stream
->dtb
->header
) (stream
,i
,&hs
,NIL
);
888 /* need to make an envelope? */
889 if (!*env
) rfc822_parse_msg (env
,NIL
,ht
,hs
,NIL
,BADHOST
,
891 /* need message size too, ugh */
892 if (!elt
->rfc822_size
) {
893 (*stream
->dtb
->text
) (stream
,i
,&bs
,FT_PEEK
);
894 elt
->rfc822_size
= hs
+ SIZE (&bs
) - GETPOS (&bs
);
897 /* if need date, have date in envelope? */
898 if (!elt
->day
&& *env
&& (*env
)->date
)
899 mail_parse_date (elt
,(*env
)->date
);
900 /* sigh, fill in bogus default */
901 if (!elt
->day
) elt
->day
= elt
->month
= 1;
902 mail_free_envelope (&e
);
908 * Accepts: MAIL stream
913 void nntp_flags (MAILSTREAM
*stream
,char *sequence
,long flags
)
916 if ((flags
& FT_UID
) ? /* validate all elts */
917 mail_uid_sequence (stream
,sequence
) : mail_sequence (stream
,sequence
))
918 for (i
= 1; i
<= stream
->nmsgs
; i
++) mail_elt (stream
,i
)->valid
= T
;
921 /* NNTP fetch overview
922 * Accepts: MAIL stream, sequence bits set
923 * overview return function
924 * Returns: T if successful, NIL otherwise
927 long nntp_overview (MAILSTREAM
*stream
,overview_t ofn
)
929 unsigned long i
,j
,k
,uid
;
930 char c
,*s
,*t
,*v
,tmp
[MAILTMPLEN
];
933 if (!LOCAL
->nntpstream
->netstream
) return NIL
;
934 /* scan sequence to load cache */
935 for (i
= 1; i
<= stream
->nmsgs
; i
++)
936 /* have cached overview yet? */
937 if ((elt
= mail_elt (stream
,i
))->sequence
&& !elt
->private.spare
.ptr
) {
938 for (j
= i
+ 1; /* no, find end of cache gap range */
939 (j
<= stream
->nmsgs
) && (elt
= mail_elt (stream
,j
))->sequence
&&
940 !elt
->private.spare
.ptr
; j
++);
941 /* make NNTP range */
942 if(i
== (j
- 1)) sprintf (tmp
, "%lu", mail_uid (stream
,i
));
943 else sprintf (tmp
, "%lu-%lu",mail_uid (stream
,i
), mail_uid (stream
,j
- 1));
944 i
= j
; /* advance beyond gap */
945 /* ask server for overview data to cache */
946 if (nntp_over (stream
,tmp
)) {
947 while ((s
= net_getline (LOCAL
->nntpstream
->netstream
)) &&
949 /* death to embedded newlines */
950 for (t
= v
= s
; (c
= *v
++) != '\0';)
951 if ((c
!= '\012') && (c
!= '\015')) *t
++ = c
;
952 *t
++ = '\0'; /* tie off string in case it was shortened */
953 /* cache the overview if found its sequence */
954 if ((uid
= atol (s
)) && (k
= mail_msgno (stream
,uid
)) &&
955 (t
= strchr (s
,'\t'))) {
956 if ((elt
= mail_elt (stream
,k
))->private.spare
.ptr
)
957 fs_give ((void **) &elt
->private.spare
.ptr
);
958 elt
->private.spare
.ptr
= cpystr (t
+ 1);
960 else { /* shouldn't happen, snarl if it does */
961 sprintf (tmp
,"Server returned data for unknown UID %lu",uid
);
962 mm_notify (stream
,tmp
,WARN
);
963 stream
->unhealthy
= T
;
965 /* flush the overview */
966 fs_give ((void **) &s
);
968 stream
->unhealthy
= NIL
;/* set healthy */
969 /* flush the terminating dot */
970 if (s
) fs_give ((void **) &s
);
972 else i
= stream
->nmsgs
; /* OVER failed, punt cache load */
975 /* now scan sequence to return overviews */
976 if (ofn
) for (i
= 1; i
<= stream
->nmsgs
; i
++)
977 if ((elt
= mail_elt (stream
,i
))->sequence
) {
978 uid
= mail_uid (stream
,i
);/* UID for this message */
979 /* parse cached overview */
980 if (nntp_parse_overview (&ov
,s
= (char *) elt
->private.spare
.ptr
,elt
))
981 (*ofn
) (stream
,uid
,&ov
,i
);
982 else { /* parse failed */
983 (*ofn
) (stream
,uid
,NIL
,i
);
984 if (s
&& *s
) { /* unusable cached entry? */
985 sprintf (tmp
,"Unable to parse overview for UID %lu: %.500s",uid
,s
);
986 mm_notify (stream
,tmp
,WARN
);
987 stream
->unhealthy
= T
;
988 /* erase it from the cache */
989 fs_give ((void **) &s
);
991 stream
->unhealthy
= NIL
;/* set healthy */
992 /* insert empty cached text as necessary */
993 if (!s
) elt
->private.spare
.ptr
= cpystr ("");
995 /* clean up overview data */
996 if (ov
.from
) mail_free_address (&ov
.from
);
997 if (ov
.subject
) fs_give ((void **) &ov
.subject
);
1002 /* Send OVER to NNTP server
1003 * Accepts: mail stream
1005 * Returns: T if success and overviews will follow, else NIL
1008 long nntp_over (MAILSTREAM
*stream
,char *sequence
)
1011 /* test for Netscape Collabra server */
1012 if (EXTENSION
.over
&& LOCAL
->xover
&&
1013 nntp_send (LOCAL
->nntpstream
,"OVER","0") == NNTPOVER
) {
1014 /* "Netscape-Collabra/3.52 03615 NNTP" responds to the OVER command with
1015 * a bogus "Subject:From:Date:Bytes:Lines" response followed by overviews
1016 * which lack the Message-ID and References:. This violates the draft
1017 * NNTP specification (draft-ietf-nntpext-base-18.txt as of this writing).
1020 while ((s
= net_getline (LOCAL
->nntpstream
->netstream
)) && strcmp (s
,".")){
1021 if (!isdigit (*s
)) { /* is it that fetid piece of reptile dung? */
1022 EXTENSION
.over
= NIL
; /* sure smells like it */
1023 mm_log ("Working around Netscape Collabra bug",WARN
);
1025 fs_give ((void **) &s
); /* flush the overview */
1027 if (s
) fs_give ((void **) &s
);
1028 /* don't do this test again */
1029 if (EXTENSION
.over
) LOCAL
->xover
= NIL
;
1031 if (EXTENSION
.over
) /* have OVER extension? */
1032 return (nntp_send (LOCAL
->nntpstream
,"OVER",sequence
) == NNTPOVER
) ?
1034 if (LOCAL
->xover
) /* try the experiment extension then */
1035 switch ((int) nntp_send (LOCAL
->nntpstream
,"XOVER",sequence
)) {
1036 case NNTPOVER
: /* got an overview? */
1038 case NNTPBADCMD
: /* unknown command? */
1039 LOCAL
->xover
= NIL
; /* disable future XOVER attempts */
1044 /* Parse OVERVIEW struct from cached NNTP OVER response
1045 * Accepts: struct to load
1046 * cached OVER response
1048 * Returns: T if success, NIL if fail
1051 long nntp_parse_overview (OVERVIEW
*ov
,char *text
,MESSAGECACHE
*elt
)
1054 /* nothing in overview yet */
1055 memset ((void *) ov
,0,sizeof (OVERVIEW
));
1056 /* no cached data */
1057 if (!(text
&& *text
)) return NIL
;
1058 ov
->subject
= cpystr (text
); /* make hackable copy of overview */
1059 /* find end of Subject */
1060 if ((t
= strchr (ov
->subject
,'\t')) != NULL
) {
1061 *t
++ = '\0'; /* tie off Subject, point to From */
1062 /* find end of From */
1063 if ((ov
->date
= strchr (t
,'\t')) != NULL
) {
1064 *ov
->date
++ = '\0'; /* tie off From, point to Date */
1065 /* load internaldate too */
1066 if (!elt
->day
) mail_parse_date (elt
,ov
->date
);
1068 rfc822_parse_adrlist (&ov
->from
,t
,BADHOST
);
1069 /* find end of Date */
1070 if ((ov
->message_id
= strchr (ov
->date
,'\t')) != NULL
) {
1071 /* tie off Date, point to Message-ID */
1072 *ov
->message_id
++ = '\0';
1073 /* find end of Message-ID */
1074 if ((ov
->references
= strchr (ov
->message_id
,'\t')) != NULL
) {
1075 /* tie off Message-ID, point to References */
1076 *ov
->references
++ = '\0';
1077 /* fine end of References */
1078 if ((t
= strchr (ov
->references
,'\t')) != NULL
) {
1079 *t
++ = '\0'; /* tie off References, point to octet size */
1080 /* parse size of message in octets */
1081 ov
->optional
.octets
= atol (t
);
1082 /* find end of size */
1083 if ((t
= strchr (t
,'\t')) != NULL
) {
1084 /* parse size of message in lines */
1085 ov
->optional
.lines
= atol (++t
);
1087 if ((ov
->optional
.xref
= strchr (t
,'\t')) != NULL
)
1088 *ov
->optional
.xref
++ = '\0';
1095 return ov
->references
? T
: NIL
;
1098 /* NNTP fetch header as text
1099 * Accepts: mail stream
1101 * pointer to return size
1103 * Returns: header text
1106 char *nntp_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *size
,
1109 char tmp
[MAILTMPLEN
];
1113 if ((flags
& FT_UID
) && !(msgno
= mail_msgno (stream
,msgno
))) return "";
1114 /* have header text? */
1115 if (!(elt
= mail_elt (stream
,msgno
))->private.msg
.header
.text
.data
) {
1116 sprintf (tmp
,"%lu",mail_uid (stream
,msgno
));
1117 /* get header text */
1118 switch (nntp_send (LOCAL
->nntpstream
,"HEAD",tmp
)) {
1120 if ((f
= netmsg_slurp (LOCAL
->nntpstream
->netstream
,size
,NIL
)) != NULL
) {
1121 fread (elt
->private.msg
.header
.text
.data
=
1122 (unsigned char *) fs_get ((size_t) *size
+ 3),
1123 (size_t) 1,(size_t) *size
,f
);
1124 fclose (f
); /* flush temp file */
1125 /* tie off header with extra CRLF and NUL */
1126 elt
->private.msg
.header
.text
.data
[*size
] = '\015';
1127 elt
->private.msg
.header
.text
.data
[++*size
] = '\012';
1128 elt
->private.msg
.header
.text
.data
[++*size
] = '\0';
1129 elt
->private.msg
.header
.text
.size
= *size
;
1130 elt
->valid
= T
; /* make elt valid now */
1133 /* fall into default case */
1134 default: /* failed, mark as deleted and empty */
1135 elt
->valid
= elt
->deleted
= T
;
1136 case NNTPSOFTFATAL
: /* don't mark deleted if stream dead */
1137 *size
= elt
->private.msg
.header
.text
.size
= 0;
1141 /* just return size of text */
1142 else *size
= elt
->private.msg
.header
.text
.size
;
1143 return elt
->private.msg
.header
.text
.data
?
1144 (char *) elt
->private.msg
.header
.text
.data
: "";
1148 * Accepts: mail stream
1150 * pointer to stringstruct to initialize
1152 * Returns: T if successful, else NIL
1155 long nntp_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
)
1157 char tmp
[MAILTMPLEN
];
1159 INIT (bs
,mail_string
,(void *) "",0);
1160 if ((flags
& FT_UID
) && !(msgno
= mail_msgno (stream
,msgno
))) return NIL
;
1161 elt
= mail_elt (stream
,msgno
);
1162 /* different message, flush cache */
1163 if (LOCAL
->txt
&& (LOCAL
->msgno
!= msgno
)) {
1164 fclose (LOCAL
->txt
);
1167 LOCAL
->msgno
= msgno
; /* note cached message */
1168 if (!LOCAL
->txt
) { /* have file for this message? */
1169 sprintf (tmp
,"%lu",elt
->private.uid
);
1170 switch (nntp_send (LOCAL
->nntpstream
,"BODY",tmp
)) {
1172 if ((LOCAL
->txt
= netmsg_slurp (LOCAL
->nntpstream
->netstream
,
1173 &LOCAL
->txtsize
,NIL
)) != NULL
) break;
1174 /* fall into default case */
1175 default: /* failed, mark as deleted */
1177 case NNTPSOFTFATAL
: /* don't mark deleted if stream dead */
1181 if (!(flags
& FT_PEEK
)) { /* mark seen if needed */
1183 mm_flags (stream
,elt
->msgno
);
1185 INIT (bs
,file_string
,(void *) LOCAL
->txt
,LOCAL
->txtsize
);
1189 /* NNTP fetch article from message ID (for news: URL support)
1190 * Accepts: mail stream
1192 * pointer to return total message size
1193 * pointer to return file size
1194 * Returns: FILE * to message if successful, else NIL
1197 FILE *nntp_article (MAILSTREAM
*stream
,char *msgid
,unsigned long *size
,
1198 unsigned long *hsiz
)
1200 return (nntp_send (LOCAL
->nntpstream
,"ARTICLE",msgid
) == NNTPARTICLE
) ?
1201 netmsg_slurp (LOCAL
->nntpstream
->netstream
,size
,hsiz
) : NIL
;
1205 /* NNTP per-message modify flag
1206 * Accepts: MAIL stream
1207 * message cache element
1210 void nntp_flagmsg (MAILSTREAM
*stream
,MESSAGECACHE
*elt
)
1212 if (!LOCAL
->dirty
) { /* only bother checking if not dirty yet */
1213 if (elt
->valid
) { /* if done, see if deleted changed */
1214 if (elt
->sequence
!= elt
->deleted
) LOCAL
->dirty
= T
;
1215 elt
->sequence
= T
; /* leave the sequence set */
1217 /* note current setting of deleted flag */
1218 else elt
->sequence
= elt
->deleted
;
1222 /* NNTP search messages
1223 * Accepts: mail stream
1227 * Returns: T on success, NIL on failure
1230 long nntp_search (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*pgm
,long flags
)
1236 /* make sure that charset is good */
1237 if ((msg
= utf8_badcharset (charset
)) != NULL
) {
1238 MM_LOG (msg
,ERROR
); /* output error */
1239 fs_give ((void **) &msg
);
1242 utf8_searchpgm (pgm
,charset
);
1243 if (flags
& SO_OVERVIEW
) { /* only if specified to use overview */
1244 /* identify messages that will be searched */
1245 for (i
= 1; i
<= stream
->nmsgs
; ++i
)
1246 mail_elt (stream
,i
)->sequence
= nntp_search_msg (stream
,i
,pgm
,NIL
);
1247 nntp_overview (stream
,NIL
); /* load the overview cache */
1249 /* init in case no overview at cleanup */
1250 memset ((void *) &ov
,0,sizeof (OVERVIEW
));
1251 /* otherwise do default search */
1252 for (i
= 1; i
<= stream
->nmsgs
; ++i
) {
1253 if (((flags
& SO_OVERVIEW
) && ((elt
= mail_elt (stream
,i
))->sequence
) &&
1254 nntp_parse_overview (&ov
,(char *) elt
->private.spare
.ptr
,elt
)) ?
1255 nntp_search_msg (stream
,i
,pgm
,&ov
) :
1256 mail_search_msg (stream
,i
,NIL
,pgm
)) {
1257 if (flags
& SE_UID
) mm_searched (stream
,mail_uid (stream
,i
));
1258 else { /* mark as searched, notify mail program */
1259 mail_elt (stream
,i
)->searched
= T
;
1260 if (!stream
->silent
) mm_searched (stream
,i
);
1263 /* clean up overview data */
1264 if (ov
.from
) mail_free_address (&ov
.from
);
1265 if (ov
.subject
) fs_give ((void **) &ov
.subject
);
1270 /* NNTP search message
1271 * Accepts: MAIL stream
1274 * overview to search (NIL means preliminary pass)
1275 * Returns: T if found, NIL otherwise
1278 long nntp_search_msg (MAILSTREAM
*stream
,unsigned long msgno
,SEARCHPGM
*pgm
,
1282 unsigned long now
= (unsigned long) time (0);
1283 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1287 if (pgm
->msgno
|| pgm
->uid
) { /* message set searches */
1289 /* message sequences */
1290 if ((set
= pgm
->msgno
) != NULL
) { /* must be inside this sequence */
1291 while (set
) { /* run down until find matching range */
1292 if (set
->last
? ((msgno
< set
->first
) || (msgno
> set
->last
)) :
1293 msgno
!= set
->first
) set
= set
->next
;
1296 if (!set
) return NIL
; /* not found within sequence */
1298 if ((set
= pgm
->uid
) != NULL
) { /* must be inside this sequence */
1299 unsigned long uid
= mail_uid (stream
,msgno
);
1300 while (set
) { /* run down until find matching range */
1301 if (set
->last
? ((uid
< set
->first
) || (uid
> set
->last
)) :
1302 uid
!= set
->first
) set
= set
->next
;
1305 if (!set
) return NIL
; /* not found within sequence */
1309 /* Fast data searches */
1311 if ((pgm
->answered
&& !elt
->answered
) ||
1312 (pgm
->unanswered
&& elt
->answered
) ||
1313 (pgm
->deleted
&& !elt
->deleted
) ||
1314 (pgm
->undeleted
&& elt
->deleted
) ||
1315 (pgm
->draft
&& !elt
->draft
) ||
1316 (pgm
->undraft
&& elt
->draft
) ||
1317 (pgm
->flagged
&& !elt
->flagged
) ||
1318 (pgm
->unflagged
&& elt
->flagged
) ||
1319 (pgm
->recent
&& !elt
->recent
) ||
1320 (pgm
->old
&& elt
->recent
) ||
1321 (pgm
->seen
&& !elt
->seen
) ||
1322 (pgm
->unseen
&& elt
->seen
)) return NIL
;
1324 if ((pgm
->keyword
&& !mail_search_keyword (stream
,elt
,pgm
->keyword
,LONGT
)) ||
1325 (pgm
->unkeyword
&& mail_search_keyword (stream
,elt
,pgm
->unkeyword
,NIL
)))
1327 if (ov
) { /* only do this if real searching */
1330 if ((pgm
->larger
&& (ov
->optional
.octets
<= pgm
->larger
)) ||
1331 (pgm
->smaller
&& (ov
->optional
.octets
>= pgm
->smaller
))) return NIL
;
1333 if ((pgm
->sentbefore
|| pgm
->senton
|| pgm
->sentsince
||
1334 pgm
->before
|| pgm
->on
|| pgm
->since
) &&
1335 (!mail_parse_date (&delt
,ov
->date
) ||
1336 !(d
= mail_shortdate (delt
.year
,delt
.month
,delt
.day
)) ||
1337 (pgm
->sentbefore
&& (d
>= pgm
->sentbefore
)) ||
1338 (pgm
->senton
&& (d
!= pgm
->senton
)) ||
1339 (pgm
->sentsince
&& (d
< pgm
->sentsince
)) ||
1340 (pgm
->before
&& (d
>= pgm
->before
)) ||
1341 (pgm
->on
&& (d
!= pgm
->on
)) ||
1342 (pgm
->since
&& (d
< pgm
->since
)))) return NIL
;
1343 if (pgm
->older
|| pgm
->younger
) {
1344 unsigned long msgd
= mail_longdate (elt
);
1345 if (pgm
->older
&& msgd
> (now
- pgm
->older
)) return NIL
;
1346 if (pgm
->younger
&& msgd
< (now
- pgm
->younger
)) return NIL
;
1348 if ((pgm
->from
&& !mail_search_addr (ov
->from
,pgm
->from
)) ||
1349 (pgm
->subject
&& !mail_search_header_text (ov
->subject
,pgm
->subject
))||
1351 !mail_search_header_text (ov
->message_id
,pgm
->message_id
)) ||
1353 !mail_search_header_text (ov
->references
,pgm
->references
)))
1357 /* envelope searches */
1358 if (pgm
->bcc
|| pgm
->cc
|| pgm
->to
|| pgm
->return_path
|| pgm
->sender
||
1359 pgm
->reply_to
|| pgm
->in_reply_to
|| pgm
->newsgroups
||
1361 ENVELOPE
*env
= mail_fetchenvelope (stream
,msgno
);
1362 if (!env
) return NIL
; /* no envelope obtained */
1363 /* search headers */
1364 if ((pgm
->bcc
&& !mail_search_addr (env
->bcc
,pgm
->bcc
)) ||
1365 (pgm
->cc
&& !mail_search_addr (env
->cc
,pgm
->cc
)) ||
1366 (pgm
->to
&& !mail_search_addr (env
->to
,pgm
->to
)))
1368 /* These criteria are not supported by IMAP and have to be emulated */
1369 if ((pgm
->return_path
&&
1370 !mail_search_addr (env
->return_path
,pgm
->return_path
)) ||
1371 (pgm
->sender
&& !mail_search_addr (env
->sender
,pgm
->sender
)) ||
1372 (pgm
->reply_to
&& !mail_search_addr (env
->reply_to
,pgm
->reply_to
)) ||
1373 (pgm
->in_reply_to
&&
1374 !mail_search_header_text (env
->in_reply_to
,pgm
->in_reply_to
)) ||
1376 !mail_search_header_text (env
->newsgroups
,pgm
->newsgroups
)) ||
1377 (pgm
->followup_to
&&
1378 !mail_search_header_text (env
->followup_to
,pgm
->followup_to
)))
1382 /* search header lines */
1383 for (hdr
= pgm
->header
; hdr
; hdr
= hdr
->next
) {
1387 sth
.next
= stc
.next
= NIL
;/* only one at a time */
1388 sth
.text
.data
= hdr
->line
.data
;
1389 sth
.text
.size
= hdr
->line
.size
;
1390 /* get the header text */
1391 if ((t
= mail_fetch_header (stream
,msgno
,NIL
,&sth
,&s
.size
,
1392 FT_INTERNAL
| FT_PEEK
)) && strchr (t
,':')) {
1393 if (hdr
->text
.size
) { /* anything matches empty search string */
1394 /* non-empty, copy field data */
1395 s
.data
= (unsigned char *) fs_get (s
.size
+ 1);
1397 for (v
= (char *) s
.data
, e
= t
+ s
.size
; t
< e
;) switch (*t
) {
1398 default: /* non-continuation, skip leading field name */
1399 while ((t
< e
) && (*t
++ != ':'));
1400 if ((t
< e
) && (*t
== ':')) t
++;
1401 case '\t': case ' ': /* copy field data */
1402 while ((t
< e
) && (*t
!= '\015') && (*t
!= '\012')) *v
++ = *t
++;
1403 *v
++ = '\n'; /* tie off line */
1404 while (((*t
== '\015') || (*t
== '\012')) && (t
< e
)) t
++;
1406 /* calculate true size */
1407 s
.size
= v
- (char *) s
.data
;
1408 *v
= '\0'; /* tie off results */
1409 stc
.text
.data
= hdr
->text
.data
;
1410 stc
.text
.size
= hdr
->text
.size
;
1412 if (mail_search_header (&s
,&stc
)) fs_give ((void **) &s
.data
);
1413 else { /* search failed */
1414 fs_give ((void **) &s
.data
);
1419 else return NIL
; /* no matching header text */
1421 /* search strings */
1423 !mail_search_text (stream
,msgno
,NIL
,pgm
->text
,LONGT
))||
1424 (pgm
->body
&& !mail_search_text (stream
,msgno
,NIL
,pgm
->body
,NIL
)))
1427 /* logical conditions */
1428 for (or = pgm
->or; or; or = or->next
)
1429 if (!(nntp_search_msg (stream
,msgno
,or->first
,ov
) ||
1430 nntp_search_msg (stream
,msgno
,or->second
,ov
))) return NIL
;
1431 for (not = pgm
->not; not; not = not->next
)
1432 if (nntp_search_msg (stream
,msgno
,not->pgm
,ov
)) return NIL
;
1436 /* NNTP sort messages
1437 * Accepts: mail stream
1442 * Returns: vector of sorted message sequences or NIL if error
1445 unsigned long *nntp_sort (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*spg
,
1446 SORTPGM
*pgm
,long flags
)
1448 unsigned long i
,start
,last
;
1450 mailcache_t mailcache
= (mailcache_t
) mail_parameters (NIL
,GET_CACHE
,NIL
);
1451 unsigned long *ret
= NIL
;
1452 sortresults_t sr
= (sortresults_t
) mail_parameters (NIL
,GET_SORTRESULTS
,NIL
);
1453 if (spg
) { /* only if a search needs to be done */
1454 int silent
= stream
->silent
;
1455 stream
->silent
= T
; /* don't pass up mm_searched() events */
1456 /* search for messages */
1457 mail_search_full (stream
,charset
,spg
,NIL
);
1458 stream
->silent
= silent
; /* restore silence state */
1460 /* initialize progress counters */
1461 pgm
->nmsgs
= pgm
->progress
.cached
= 0;
1462 /* pass 1: count messages to sort */
1463 for (i
= 1,start
= last
= 0; i
<= stream
->nmsgs
; ++i
)
1464 if (mail_elt (stream
,i
)->searched
) {
1466 /* have this in the sortcache already? */
1467 if (!((SORTCACHE
*) (*mailcache
) (stream
,i
,CH_SORTCACHE
))->date
) {
1468 /* no, record as last message */
1469 last
= mail_uid (stream
,i
);
1470 /* and as first too if needed */
1471 if (!start
) start
= last
;
1474 if (pgm
->nmsgs
) { /* pass 2: load sort cache */
1475 sc
= nntp_sort_loadcache (stream
,pgm
,start
,last
,flags
);
1476 /* pass 3: sort messages */
1477 if (!pgm
->abort
) ret
= mail_sort_cache (stream
,pgm
,sc
,flags
);
1478 fs_give ((void **) &sc
); /* don't need sort vector any more */
1480 /* empty sort results */
1481 else ret
= (unsigned long *) memset (fs_get (sizeof (unsigned long)),0,
1482 sizeof (unsigned long));
1483 /* also return via callback if requested */
1484 if (sr
) (*sr
) (stream
,ret
,pgm
->nmsgs
);
1488 /* Mail load sortcache
1489 * Accepts: mail stream, already searched
1494 * Returns: vector of sortcache pointers matching search
1497 SORTCACHE
**nntp_sort_loadcache (MAILSTREAM
*stream
,SORTPGM
*pgm
,
1498 unsigned long start
,unsigned long last
,
1502 char c
,*s
,*t
,*v
,tmp
[MAILTMPLEN
];
1507 mailcache_t mailcache
= (mailcache_t
) mail_parameters (NIL
,GET_CACHE
,NIL
);
1508 /* verify that the sortpgm is OK */
1509 for (pg
= pgm
; pg
; pg
= pg
->next
) switch (pg
->function
) {
1510 case SORTARRIVAL
: /* sort by arrival date */
1511 case SORTSIZE
: /* sort by message size */
1512 case SORTDATE
: /* sort by date */
1513 case SORTFROM
: /* sort by first from */
1514 case SORTSUBJECT
: /* sort by subject */
1516 case SORTTO
: /* sort by first to */
1517 mm_notify (stream
,"[NNTPSORT] Can't do To-field sorting in NNTP",WARN
);
1519 case SORTCC
: /* sort by first cc */
1520 mm_notify (stream
,"[NNTPSORT] Can't do cc-field sorting in NNTP",WARN
);
1523 fatal ("Unknown sort function");
1526 if (start
) { /* messages need to be loaded in sortcache? */
1527 /* yes, build range */
1528 if (start
!= last
) sprintf (tmp
,"%lu-%lu",start
,last
);
1529 else sprintf (tmp
,"%lu",start
);
1530 /* get it from the NNTP server */
1531 if (!nntp_over (stream
,tmp
)) return mail_sort_loadcache (stream
,pgm
);
1532 while ((s
= net_getline (LOCAL
->nntpstream
->netstream
)) && strcmp (s
,".")){
1533 /* death to embedded newlines */
1534 for (t
= v
= s
; (c
= *v
++) != '\0';) if ((c
!= '\012') && (c
!= '\015')) *t
++ = c
;
1535 *t
++ = '\0'; /* tie off resulting string */
1536 /* parse OVER response */
1537 if ((i
= mail_msgno (stream
,atol (s
))) &&
1538 (t
= strchr (s
,'\t')) && (v
= strchr (++t
,'\t'))) {
1539 *v
++ = '\0'; /* tie off subject */
1540 /* put stripped subject in sortcache */
1541 r
= (SORTCACHE
*) (*mailcache
) (stream
,i
,CH_SORTCACHE
);
1542 r
->refwd
= mail_strip_subject (t
,&r
->subject
);
1543 if ((t
= strchr (v
,'\t')) != NULL
) {
1544 *t
++ = '\0'; /* tie off from */
1545 if ((adr
= rfc822_parse_address (&adr
,adr
,&v
,BADHOST
,0)) != NULL
) {
1546 r
->from
= adr
->mailbox
;
1548 mail_free_address (&adr
);
1550 if ((v
= strchr (t
,'\t')) != NULL
) {
1551 *v
++ = '\0'; /* tie off date */
1552 if (mail_parse_date (&telt
,t
)) r
->date
= mail_longdate (&telt
);
1553 if ((v
= strchr (v
,'\t')) && (v
= strchr (++v
,'\t')))
1554 r
->size
= atol (++v
);
1558 fs_give ((void **) &s
);
1560 if (s
) fs_give ((void **) &s
);
1563 /* calculate size of sortcache index */
1564 i
= pgm
->nmsgs
* sizeof (SORTCACHE
*);
1565 /* instantiate the index */
1566 sc
= (SORTCACHE
**) memset (fs_get ((size_t) i
),0,(size_t) i
);
1567 /* see what needs to be loaded */
1568 for (i
= 1; !pgm
->abort
&& (i
<= stream
->nmsgs
); i
++)
1569 if ((mail_elt (stream
,i
))->searched
) {
1570 sc
[pgm
->progress
.cached
++] =
1571 r
= (SORTCACHE
*) (*mailcache
) (stream
,i
,CH_SORTCACHE
);
1572 r
->pgm
= pgm
; /* note sort program */
1573 r
->num
= (flags
& SE_UID
) ? mail_uid (stream
,i
) : i
;
1574 if (!r
->date
) r
->date
= r
->num
;
1575 if (!r
->arrival
) r
->arrival
= mail_uid (stream
,i
);
1576 if (!r
->size
) r
->size
= 1;
1577 if (!r
->from
) r
->from
= cpystr ("");
1578 if (!r
->to
) r
->to
= cpystr ("");
1579 if (!r
->cc
) r
->cc
= cpystr ("");
1580 if (!r
->subject
) r
->subject
= cpystr ("");
1586 /* NNTP thread messages
1587 * Accepts: mail stream
1592 * Returns: thread node tree
1595 THREADNODE
*nntp_thread (MAILSTREAM
*stream
,char *type
,char *charset
,
1596 SEARCHPGM
*spg
,long flags
)
1598 return mail_thread_msgs (stream
,type
,charset
,spg
,flags
,nntp_sort
);
1601 /* NNTP ping mailbox
1602 * Accepts: MAIL stream
1603 * Returns: T if stream alive, else NIL
1606 long nntp_ping (MAILSTREAM
*stream
)
1608 return (nntp_send (LOCAL
->nntpstream
,"STAT",NIL
) != NNTPSOFTFATAL
);
1612 /* NNTP check mailbox
1613 * Accepts: MAIL stream
1616 void nntp_check (MAILSTREAM
*stream
)
1618 /* never do if no updates */
1619 if (LOCAL
->dirty
) newsrc_write (LOCAL
->name
,stream
);
1624 /* NNTP expunge mailbox
1625 * Accepts: MAIL stream
1626 * sequence to expunge if non-NIL
1628 * Returns: T if success, NIL if failure
1631 long nntp_expunge (MAILSTREAM
*stream
,char *sequence
,long options
)
1633 if (!stream
->silent
) mm_log ("Expunge ignored on readonly mailbox",NIL
);
1637 /* NNTP copy message(s)
1638 * Accepts: MAIL stream
1640 * destination mailbox
1642 * Returns: T if copy successful, else NIL
1645 long nntp_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
)
1647 mailproxycopy_t pc
=
1648 (mailproxycopy_t
) mail_parameters (stream
,GET_MAILPROXYCOPY
,NIL
);
1649 if (pc
) return (*pc
) (stream
,sequence
,mailbox
,options
);
1650 mm_log ("Copy not valid for NNTP",ERROR
);
1655 /* NNTP append message from stringstruct
1656 * Accepts: MAIL stream
1657 * destination mailbox
1660 * Returns: T if append successful, else NIL
1663 long nntp_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
)
1665 mm_log ("Append not valid for NNTP",ERROR
);
1669 /* NNTP open connection
1670 * Accepts: network driver
1675 * Returns: SEND stream on success, NIL on failure
1678 SENDSTREAM
*nntp_open_full (NETDRIVER
*dv
,char **hostlist
,char *service
,
1679 unsigned long port
,long options
)
1681 SENDSTREAM
*stream
= NIL
;
1682 NETSTREAM
*netstream
= NIL
;
1684 char tmp
[MAILTMPLEN
];
1686 NETDRIVER
*ssld
= (NETDRIVER
*) mail_parameters (NIL
,GET_SSLDRIVER
,NIL
);
1687 sslstart_t stls
= (sslstart_t
) mail_parameters (NIL
,GET_SSLSTART
,NIL
);
1688 if (!(hostlist
&& *hostlist
)) mm_log ("Missing NNTP service host",ERROR
);
1689 else do { /* try to open connection */
1690 sprintf (tmp
,"{%.200s/%.20s}",*hostlist
,service
? service
: "nntp");
1691 if (!mail_valid_net_parse (tmp
,&mb
) || mb
.anoflag
) {
1692 sprintf (tmp
,"Invalid host specifier: %.80s",*hostlist
);
1695 else { /* light tryssl flag if requested */
1696 mb
.trysslflag
= (options
& NOP_TRYSSL
) ? T
: NIL
;
1698 if (mb
.port
) port
= mb
.port
;
1699 else if (!port
) port
= nntp_port
? nntp_port
: NNTPTCPPORT
;
1700 if ((netstream
= /* try to open ordinary connection */
1701 net_open (&mb
,dv
,port
,
1702 (NETDRIVER
*) mail_parameters (NIL
,GET_SSLDRIVER
,NIL
),
1703 "*nntps",nntp_sslport
? nntp_sslport
: NNTPSSLPORT
)) != NULL
) {
1704 stream
= (SENDSTREAM
*) fs_get (sizeof (SENDSTREAM
));
1705 /* initialize stream */
1706 memset ((void *) stream
,0,sizeof (SENDSTREAM
));
1707 stream
->netstream
= netstream
;
1708 stream
->host
= cpystr ((long) mail_parameters (NIL
,GET_TRUSTDNS
,NIL
) ?
1709 net_host (netstream
) : mb
.host
);
1710 stream
->debug
= (mb
.dbgflag
|| (options
& NOP_DEBUG
)) ? T
: NIL
;
1711 if (mb
.loser
) stream
->loser
= T
;
1712 /* process greeting */
1713 switch ((int) nntp_reply (stream
)) {
1714 case NNTPGREET
: /* allow posting */
1716 mm_notify (NIL
,stream
->reply
+ 4,(long) NIL
);
1718 case NNTPGREETNOPOST
: /* posting not allowed, must be readonly */
1722 mm_log (stream
->reply
,ERROR
);
1723 stream
= nntp_close (stream
);
1728 } while (!stream
&& *++hostlist
);
1730 /* get extensions */
1731 if (stream
&& extok
)
1732 extok
= nntp_extensions (stream
,(mb
.secflag
? AU_SECURE
: NIL
) |
1733 (mb
.authuser
[0] ? AU_AUTHUSER
: NIL
));
1734 if (stream
&& !dv
&& stls
&& NNTP
.ext
.starttls
&&
1735 !mb
.sslflag
&& !mb
.notlsflag
&&
1736 (nntp_send_work (stream
,"STARTTLS",NNTP
.ext
.multidomain
? mb
.host
: NIL
)
1738 mb
.tlsflag
= T
; /* TLS OK, get into TLS at this end */
1739 stream
->netstream
->dtb
= ssld
;
1741 if ((stream
->netstream
->stream
=
1742 (*stls
) (stream
->netstream
->stream
,mb
.host
,
1743 SSL_MTHD(mb
) | (mb
.novalidate
? NET_NOVALIDATECERT
:NIL
))) != NULL
)
1744 extok
= nntp_extensions (stream
,(mb
.secflag
? AU_SECURE
: NIL
) |
1745 (mb
.authuser
[0] ? AU_AUTHUSER
: NIL
));
1747 sprintf (tmp
,"Unable to negotiate TLS with this server: %.80s",mb
.host
);
1749 /* close without doing QUIT */
1750 if (stream
->netstream
) net_close (stream
->netstream
);
1751 stream
->netstream
= NIL
;
1752 stream
= nntp_close (stream
);
1755 else if (mb
.tlsflag
) { /* user specified /tls but can't do it */
1756 mm_log ("Unable to negotiate TLS with this server",ERROR
);
1759 if(stream
&& !stream
->netstream
) stream
= nntp_close(stream
);
1760 if (stream
) { /* have a session? */
1761 if (mb
.user
[0]) { /* yes, have user name? */
1762 if ((long) mail_parameters (NIL
,GET_TRUSTDNS
,NIL
)) {
1763 /* remote name for authentication */
1764 strncpy (mb
.host
,(long) mail_parameters (NIL
,GET_SASLUSESPTRNAME
,NIL
) ?
1765 net_remotehost (netstream
) : net_host (netstream
),
1767 mb
.host
[NETMAXHOST
-1] = '\0';
1769 if (!nntp_send_auth_work (stream
,&mb
,tmp
,NIL
))
1770 stream
= nntp_close (stream
);
1772 /* authenticate if no-post and not readonly */
1773 else if (!(NNTP
.post
|| (options
& NOP_READONLY
) ||
1774 nntp_send_auth (stream
,NIL
))) stream
= nntp_close (stream
);
1777 /* in case server demands MODE READER */
1778 if (stream
) switch ((int) nntp_send_work (stream
,"MODE","READER")) {
1782 case NNTPGREETNOPOST
:
1785 case NNTPWANTAUTH
: /* server wants auth first, do so and retry */
1786 case NNTPWANTAUTH2
: /* remote name for authentication */
1787 if ((long) mail_parameters (NIL
,GET_TRUSTDNS
,NIL
)) {
1788 strncpy (mb
.host
,(long) mail_parameters (NIL
,GET_SASLUSESPTRNAME
,NIL
) ?
1789 net_remotehost (netstream
) : net_host (netstream
),NETMAXHOST
-1);
1790 mb
.host
[NETMAXHOST
-1] = '\0';
1792 if (nntp_send_auth_work (stream
,&mb
,tmp
,NIL
))
1793 switch ((int) nntp_send (stream
,"MODE","READER")) {
1797 case NNTPGREETNOPOST
:
1801 else stream
= nntp_close (stream
);
1804 if (stream
) { /* looks like we have a stream? */
1805 /* yes, make sure can post if not readonly */
1806 if (!(NNTP
.post
|| (options
& NOP_READONLY
))) stream
= nntp_close (stream
);
1807 else if (extok
) nntp_extensions (stream
,(mb
.secflag
? AU_SECURE
: NIL
) |
1808 (mb
.authuser
[0] ? AU_AUTHUSER
: NIL
));
1810 /* check one last time that we have a netstream before returning
1811 * a stream that does not have it. Otherwise, nntp_mail will fail
1812 * trying to dereference a null pointer.
1814 if(stream
&& !stream
->netstream
) stream
= nntp_close(stream
);
1820 * authenticator flags
1821 * Returns: T on success, NIL on failure
1824 long nntp_extensions (SENDSTREAM
*stream
,long flags
)
1828 /* zap all old extensions */
1829 memset (&NNTP
.ext
,0,sizeof (NNTP
.ext
));
1830 if (stream
->loser
) return NIL
;/* nothing at all for losers */
1831 /* get server extensions */
1832 switch ((int) nntp_send_work (stream
,"LIST","EXTENSIONS")) {
1833 case NNTPEXTOK
: /* what NNTP base spec says */
1834 case NNTPGLIST
: /* some servers do this instead */
1836 default: /* no LIST EXTENSIONS on this server */
1839 NNTP
.ext
.ok
= T
; /* server offers extensions */
1840 while ((t
= net_getline (stream
->netstream
)) && (t
[1] || (*t
!= '.'))) {
1841 if (stream
->debug
) mm_dlog (t
);
1842 /* get optional capability arguments */
1843 if ((args
= strchr (t
,' ')) != NULL
) *args
++ = '\0';
1844 if (!compare_cstring (t
,"LISTGROUP")) NNTP
.ext
.listgroup
= T
;
1845 else if (!compare_cstring (t
,"OVER")) NNTP
.ext
.over
= T
;
1846 else if (!compare_cstring (t
,"HDR")) NNTP
.ext
.hdr
= T
;
1847 else if (!compare_cstring (t
,"PAT")) NNTP
.ext
.pat
= T
;
1848 else if (!compare_cstring (t
,"STARTTLS")) NNTP
.ext
.starttls
= T
;
1849 else if (!compare_cstring (t
,"MULTIDOMAIN")) NNTP
.ext
.multidomain
= T
;
1851 else if (!compare_cstring (t
,"AUTHINFO") && args
) {
1853 for (args
= strtok_r (args
," ",&r
); args
; args
= strtok_r (NIL
," ",&r
)) {
1854 if (!compare_cstring (args
,"USER")) NNTP
.ext
.authuser
= T
;
1855 else if (((args
[0] == 'S') || (args
[0] == 's')) &&
1856 ((args
[1] == 'A') || (args
[1] == 'a')) &&
1857 ((args
[2] == 'S') || (args
[2] == 's')) &&
1858 ((args
[3] == 'L') || (args
[3] == 'l')) && (args
[4] == ':'))
1861 if (sasl
) { /* if SASL, look up authenticators */
1862 for (sasl
= strtok_r (sasl
,",",&r
); sasl
; sasl
= strtok_r (NIL
,",",&r
))
1863 if ((i
= mail_lookup_auth_name (sasl
,flags
)) &&
1864 (--i
< MAXAUTHENTICATORS
))
1865 NNTP
.ext
.sasl
|= (1 << i
);
1866 /* disable LOGIN if PLAIN also advertised */
1867 if ((i
= mail_lookup_auth_name ("PLAIN",NIL
)) &&
1868 (--i
< MAXAUTHENTICATORS
) && (NNTP
.ext
.sasl
& (1 << i
)) &&
1869 (i
= mail_lookup_auth_name ("LOGIN",NIL
)) &&
1870 (--i
< MAXAUTHENTICATORS
)) NNTP
.ext
.sasl
&= ~(1 << i
);
1873 fs_give ((void **) &t
);
1875 if (t
) { /* flush end of text indicator */
1876 if (stream
->debug
) mm_dlog (t
);
1877 fs_give ((void **) &t
);
1882 /* NNTP close connection
1883 * Accepts: SEND stream
1884 * Returns: NIL always
1887 SENDSTREAM
*nntp_close (SENDSTREAM
*stream
)
1889 if (stream
) { /* send "QUIT" */
1890 if (stream
->netstream
) nntp_send (stream
,"QUIT",NIL
);
1891 /* do close actions */
1892 if (stream
->netstream
) net_close (stream
->netstream
);
1893 if (stream
->host
) fs_give ((void **) &stream
->host
);
1894 if (stream
->reply
) fs_give ((void **) &stream
->reply
);
1895 fs_give ((void **) &stream
);/* flush the stream */
1900 /* NNTP deliver news
1901 * Accepts: SEND stream
1904 * Returns: T on success, NIL on failure
1907 long nntp_mail (SENDSTREAM
*stream
,ENVELOPE
*env
,BODY
*body
)
1911 char *s
,path
[MAILTMPLEN
],tmp
[SENDBUFLEN
+1];
1914 buf
.f
= nntp_soutr
; /* initialize buffer */
1915 buf
.s
= stream
->netstream
;
1916 buf
.end
= (buf
.beg
= buf
.cur
= tmp
) + SENDBUFLEN
;
1917 tmp
[SENDBUFLEN
] = '\0'; /* must have additional null guard byte */
1918 /* Gabba gabba hey, we need some brain damage to send netnews!!!
1920 * First, we give ourselves a frontal lobotomy, and put in some UUCP
1921 * syntax. It doesn't matter that it's completely bogus UUCP, and
1922 * that UUCP has nothing to do with anything we're doing. It's been
1923 * alleged that "Path: not-for-mail" is also acceptable, but we won't
1924 * make assumptions unless the user says so.
1926 * Second, we bop ourselves on the head with a ball-peen hammer. How
1927 * dare we be so presumptious as to insert a *comment* in a Date:
1928 * header line. Why, we were actually trying to be nice to a human
1929 * by giving a symbolic timezone (such as PST) in addition to a
1930 * numeric timezone (such as -0800). But the gods of news transport
1931 * will have none of this. Unix weenies, tried and true, rule!!!
1933 * Third, Netscape Collabra server doesn't give the NNTPWANTAUTH error
1934 * until after requesting and receiving the entire message. So we can't
1935 * call rely upon nntp_send() to do the auth retry.
1937 /* RFC-1036 requires this cretinism */
1938 sprintf (path
,"Path: %s!%s\015\012",net_localhost (stream
->netstream
),
1939 env
->sender
? env
->sender
->mailbox
:
1940 (env
->from
? env
->from
->mailbox
: "not-for-mail"));
1941 /* here's another cretinism */
1942 if ((s
= strstr (env
->date
," (")) != NULL
) *s
= NIL
;
1943 do if ((ret
= nntp_send_work (stream
,"POST",NIL
)) == NNTPREADY
)
1944 /* output data, return success status */
1945 ret
= (net_soutr (stream
->netstream
,
1946 nntp_hidepath
? "Path: not-for-mail\015\012" : path
) &&
1947 rfc822_output_full (&buf
,env
,body
,T
)) ?
1948 nntp_send_work (stream
,".",NIL
) :
1949 nntp_fake (stream
,"NNTP connection broken (message text)");
1950 while (((ret
== NNTPWANTAUTH
) || (ret
== NNTPWANTAUTH2
)) &&
1951 nntp_send_auth (stream
,LONGT
));
1952 if (s
) *s
= ' '; /* put the comment in the date back */
1953 if (ret
== NNTPOK
) return LONGT
;
1954 else if (ret
< 400) { /* if not an error reply */
1955 sprintf (tmp
,"Unexpected NNTP posting reply code %ld",ret
);
1956 mm_log (tmp
,WARN
); /* so someone looks at this eventually */
1957 if ((ret
>= 200) && (ret
< 300)) return LONGT
;
1962 /* NNTP send command
1963 * Accepts: SEND stream
1965 * Returns: reply code
1968 long nntp_send (SENDSTREAM
*stream
,char *command
,char *args
)
1971 switch ((int) (ret
= nntp_send_work (stream
,command
,args
))) {
1972 case NNTPWANTAUTH
: /* authenticate and retry */
1974 if (nntp_send_auth (stream
,LONGT
))
1975 ret
= nntp_send_work (stream
,command
,args
);
1976 else { /* we're probably hosed, nuke the session */
1977 nntp_send (stream
,"QUIT",NIL
);
1978 /* close net connection */
1979 if (stream
->netstream
) net_close (stream
->netstream
);
1980 stream
->netstream
= NIL
;
1982 default: /* all others just return */
1989 /* NNTP send command worker routine
1990 * Accepts: SEND stream
1992 * Returns: reply code
1995 long nntp_send_work (SENDSTREAM
*stream
,char *command
,char *args
)
1998 char *s
= (char *) fs_get (strlen (command
) + (args
? strlen (args
) + 1 : 0)
2000 if (!stream
->netstream
) ret
= nntp_fake (stream
,"NNTP connection lost");
2001 else { /* build the complete command */
2002 if (args
) sprintf (s
,"%s %s",command
,args
);
2003 else strcpy (s
,command
);
2004 if (stream
->debug
) mail_dlog (s
,stream
->sensitive
);
2005 strcat (s
,"\015\012");
2006 /* send the command */
2007 ret
= net_soutr (stream
->netstream
,s
) ? nntp_reply (stream
) :
2008 nntp_fake (stream
,"NNTP connection broken (command)");
2010 fs_give ((void **) &s
);
2014 /* NNTP send authentication if needed
2015 * Accepts: SEND stream
2016 * flags (non-NIL to get new extensions)
2017 * Returns: T if need to redo command, NIL otherwise
2020 long nntp_send_auth (SENDSTREAM
*stream
,long flags
)
2023 char tmp
[MAILTMPLEN
];
2024 /* remote name for authentication */
2025 sprintf (tmp
,"{%.200s/nntp",(long) mail_parameters (NIL
,GET_TRUSTDNS
,NIL
) ?
2026 ((long) mail_parameters (NIL
,GET_SASLUSESPTRNAME
,NIL
) ?
2027 net_remotehost (stream
->netstream
) : net_host (stream
->netstream
)):
2029 if (stream
->netstream
->dtb
==
2030 (NETDRIVER
*) mail_parameters (NIL
,GET_SSLDRIVER
,NIL
))
2031 strcat (tmp
,"/ssl");
2032 strcat (tmp
,"}<none>");
2033 mail_valid_net_parse (tmp
,&mb
);
2034 return nntp_send_auth_work (stream
,&mb
,tmp
,flags
);
2037 /* NNTP send authentication worker routine
2038 * Accepts: SEND stream
2040 * scratch buffer of length MAILTMPLEN
2041 * flags (non-NIL to get new extensions)
2042 * Returns: T if authenticated, NIL otherwise
2045 long nntp_send_auth_work (SENDSTREAM
*stream
,NETMBX
*mb
,char *pwd
,long flags
)
2047 unsigned long trial
,auths
;
2048 char tmp
[MAILTMPLEN
],usr
[MAILTMPLEN
], *pwd2
= NIL
;
2052 /* try SASL first */
2053 for (auths
= NNTP
.ext
.sasl
, stream
->saslcancel
= NIL
;
2054 !ret
&& stream
->netstream
&& auths
&&
2055 (at
= mail_lookup_auth (find_rightmost_bit (&auths
) + 1)); ) {
2056 if (lsterr
) { /* previous authenticator failed? */
2057 sprintf (tmp
,"Retrying using %s authentication after %.80s",
2060 fs_give ((void **) &lsterr
);
2062 trial
= 0; /* initial trial count */
2063 tmp
[0] = '\0'; /* empty buffer */
2064 if (stream
->netstream
) do {
2066 sprintf (tmp
,"Retrying %s authentication after %.80s",at
->name
,lsterr
);
2068 fs_give ((void **) &lsterr
);
2070 stream
->saslcancel
= NIL
;
2071 if (nntp_send (stream
,"AUTHINFO SASL",at
->name
) == NNTPCHALLENGE
) {
2072 /* hide client authentication responses */
2073 if (!(at
->flags
& AU_SECURE
)) stream
->sensitive
= T
;
2074 if ((*at
->client
) (nntp_challenge
,nntp_response
,"nntp",mb
,stream
,
2076 if (stream
->replycode
== NNTPAUTHED
) ret
= LONGT
;
2077 /* if main program requested cancellation */
2078 else if (!trial
) mm_log ("NNTP Authentication cancelled",ERROR
);
2080 stream
->sensitive
= NIL
;/* unhide */
2082 /* remember response if error and no cancel */
2083 if (!ret
&& trial
) lsterr
= cpystr (stream
->reply
);
2084 } while (!ret
&& stream
->netstream
&& trial
&&
2085 (trial
< nntp_maxlogintrials
));
2088 if (lsterr
) { /* SAIL failed? */
2089 if (!stream
->saslcancel
) { /* don't do this if a cancel */
2090 sprintf (tmp
,"Can not authenticate to NNTP server: %.80s",lsterr
);
2093 fs_give ((void **) &lsterr
);
2095 else if (mb
->secflag
) /* no SASL, can't do /secure */
2096 mm_log ("Can't do secure authentication with this server",ERROR
);
2097 else if (mb
->authuser
[0]) /* or /authuser */
2098 mm_log ("Can't do /authuser with this server",ERROR
);
2099 /* Always try AUTHINFO USER, even if NNTP.ext.authuser isn't set. There
2100 * are servers that require it but don't return it as an extension.
2102 else for (trial
= 0, pwd
[0] = 'x';
2103 !ret
&& pwd
[0] && (trial
< nntp_maxlogintrials
) &&
2104 stream
->netstream
; ) {
2105 mm_login (mb
,usr
, &pwd2
,trial
++);
2106 pwd
[0] = pwd2
? pwd2
[0] : '\0';
2107 /* do the authentication */
2108 if (pwd2
&& *pwd2
) switch ((int) nntp_send_work (stream
,"AUTHINFO USER",usr
)) {
2109 case NNTPBADCMD
: /* give up if unrecognized command */
2110 mm_log (NNTP
.ext
.authuser
? stream
->reply
:
2111 "Can't do AUTHINFO USER to this server",ERROR
);
2112 trial
= nntp_maxlogintrials
;
2114 case NNTPAUTHED
: /* successful authentication */
2115 ret
= LONGT
; /* guess no password was needed */
2117 case NNTPWANTPASS
: /* wants password */
2118 stream
->sensitive
= T
; /* hide this command */
2119 if (nntp_send_work (stream
,"AUTHINFO PASS",pwd2
) == NNTPAUTHED
)
2120 ret
= LONGT
; /* password OK */
2121 stream
->sensitive
= NIL
; /* unhide */
2122 if (ret
) break; /* OK if successful */
2123 default: /* authentication failed */
2124 mm_log (stream
->reply
,WARN
);
2125 if (trial
== nntp_maxlogintrials
)
2126 mm_log ("Too many NNTP authentication failures",ERROR
);
2128 /* user refused to give a password */
2129 else mm_log ("Login aborted",ERROR
);
2131 memset (pwd
,0,MAILTMPLEN
); /* erase password */
2132 /* get new extensions if needed */
2133 if (ret
&& flags
) nntp_extensions (stream
,(mb
->secflag
? AU_SECURE
: NIL
) |
2134 (mb
->authuser
[0] ? AU_AUTHUSER
: NIL
));
2138 /* Get challenge to authenticator in binary
2140 * pointer to returned size
2141 * Returns: challenge or NIL if not challenge
2144 void *nntp_challenge (void *s
,unsigned long *len
)
2146 char tmp
[MAILTMPLEN
];
2148 SENDSTREAM
*stream
= (SENDSTREAM
*) s
;
2149 if ((stream
->replycode
== NNTPCHALLENGE
) &&
2150 !(ret
= rfc822_base64 ((unsigned char *) stream
->reply
+ 4,
2151 strlen (stream
->reply
+ 4),len
))) {
2152 sprintf (tmp
,"NNTP SERVER BUG (invalid challenge): %.80s",stream
->reply
+4);
2159 /* Send authenticator response in BASE64
2160 * Accepts: MAIL stream
2163 * Returns: T, always
2166 long nntp_response (void *s
,char *response
,unsigned long size
)
2168 SENDSTREAM
*stream
= (SENDSTREAM
*) s
;
2171 if (response
) { /* make CRLFless BASE64 string */
2173 for (t
= (char *) rfc822_binary ((void *) response
,size
,&i
),u
= t
,j
= 0;
2174 j
< i
; j
++) if (t
[j
] > ' ') *u
++ = t
[j
];
2175 *u
= '\0'; /* tie off string */
2176 i
= nntp_send_work (stream
,t
,NIL
);
2177 fs_give ((void **) &t
);
2179 else i
= nntp_send_work (stream
,"",NIL
);
2181 else { /* abort requested */
2182 i
= nntp_send_work (stream
,"*",NIL
);
2183 stream
->saslcancel
= T
; /* mark protocol-requested SASL cancel */
2189 * Accepts: SEND stream
2190 * Returns: reply code
2193 long nntp_reply (SENDSTREAM
*stream
)
2195 /* flush old reply */
2196 if (stream
->reply
) fs_give ((void **) &stream
->reply
);
2198 if (!(stream
->reply
= net_getline (stream
->netstream
)))
2199 return nntp_fake (stream
,"NNTP connection broken (response)");
2200 if (stream
->debug
) mm_dlog (stream
->reply
);
2201 /* handle continuation by recursion */
2202 if (stream
->reply
[3] == '-') return nntp_reply (stream
);
2203 /* return response code */
2204 return stream
->replycode
= atol (stream
->reply
);
2208 /* NNTP set fake error
2209 * Accepts: SEND stream
2211 * Returns: error code
2214 long nntp_fake (SENDSTREAM
*stream
,char *text
)
2216 if (stream
->netstream
) { /* close net connection if still open */
2217 net_close (stream
->netstream
);
2218 stream
->netstream
= NIL
;
2220 /* flush any old reply */
2221 if (stream
->reply
) fs_give ((void **) &stream
->reply
);
2222 /* set up pseudo-reply string */
2223 stream
->reply
= (char *) fs_get (20+strlen (text
));
2224 sprintf (stream
->reply
,"%ld %s",NNTPSOFTFATAL
,text
);
2225 return NNTPSOFTFATAL
; /* return error code */
2231 * Returns: T on success, NIL on failure
2234 long nntp_soutr (void *stream
,char *s
)
2237 /* "." on first line */
2238 if (s
[0] == '.') net_soutr (stream
,".");
2239 /* find lines beginning with a "." */
2240 while ((t
= strstr (s
,"\015\012.")) != NULL
) {
2241 c
= *(t
+= 3); /* remember next character after "." */
2242 *t
= '\0'; /* tie off string */
2244 if (!net_soutr (stream
,s
)) return NIL
;
2245 *t
= c
; /* restore delimiter */
2246 s
= t
- 1; /* push pointer up to the "." */
2248 /* output remainder of text */
2249 return *s
? net_soutr (stream
,s
) : T
;