1 /* ========================================================================
2 * Copyright 2008-2011 Mark Crispin
3 * ========================================================================
7 * Program: Network News Transfer Protocol (NNTP) routines
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
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 */
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
,
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
,
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
,
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 */
166 #ifdef INADEQUATE_MEMORY
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
)
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 */
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
;
262 case GET_MAXLOGINTRIALS
:
263 value
= (void *) nntp_maxlogintrials
;
266 nntp_port
= (long) value
;
269 value
= (void *) nntp_port
;
271 case SET_SSLNNTPPORT
:
272 nntp_sslport
= (long) value
;
274 case GET_SSLNNTPPORT
:
275 value
= (void *) nntp_sslport
;
278 nntp_range
= (unsigned long) value
;
281 value
= (void *) nntp_range
;
283 case SET_NNTPHIDEPATH
:
284 nntp_hidepath
= (long) value
;
286 case GET_NNTPHIDEPATH
:
287 value
= (void *) nntp_hidepath
;
291 value
= (void *) ((NNTPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->newsrc
;
293 case GET_IDLETIMEOUT
:
294 value
= (void *) IDLETIMEOUT
;
298 ((NNTPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->nntpstream
->debug
= T
;
302 ((NNTPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->nntpstream
->debug
= NIL
;
305 value
= NIL
; /* error case */
311 /* NNTP mail scan mailboxes for string
312 * Accepts: mail stream
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
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] == '%';
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
);
360 if ((t
= strchr (s
,' ')) != NULL
) { /* tie off after newsgroup name */
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
383 void nntp_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
)
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
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
)
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
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 */
499 * Accepts: mail stream
502 * Returns: T on success, NIL on failure
505 long nntp_status (MAILSTREAM
*stream
,char *mbx
,long flags
)
509 unsigned long i
,j
,k
,rnmsgs
;
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") &&
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
);
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
)) &&
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",
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
;
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
)) &&
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
);
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 */
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
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 */
620 default: /* else give up if server claims LISTGROUP */
621 if (EXTENSION
.listgroup
) return NIL
;
624 sprintf (tmp
,"%lu-%lu",first
,last
);
625 if (EXTENSION
.hdr
) /* have HDR extension? */
626 return (nntp_send (LOCAL
->nntpstream
,"HDR Date",tmp
) == NNTPHEAD
) ?
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? */
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
))
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
];
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 */
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 */
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
);
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 */
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",
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 */
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? */
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",
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
);
827 mail_exists (stream
,(long) 0);
828 mail_recent (stream
,(long) 0);
830 return stream
; /* return stream to caller */
834 * Accepts: MAIL stream
838 void nntp_mclose (MAILSTREAM
*stream
,long options
)
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
864 * This is ugly and slow
867 void nntp_fetchfast (MAILSTREAM
*stream
,char *sequence
,long flags
)
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
;
880 if (!stream
->scache
) env
= &elt
->private.msg
.env
;
881 else if (stream
->msgno
== i
) env
= &stream
->env
;
883 if (!*env
|| !elt
->rfc822_size
) {
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
,
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
);
907 * Accepts: MAIL stream
912 void nntp_flags (MAILSTREAM
*stream
,char *sequence
,long flags
)
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
];
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
)) &&
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
);
1001 /* Send OVER to NNTP server
1002 * Accepts: mail stream
1004 * Returns: T if success and overviews will follow, else NIL
1007 long nntp_over (MAILSTREAM
*stream
,char *sequence
)
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).
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
) ?
1033 if (LOCAL
->xover
) /* try the experiment extension then */
1034 switch ((int) nntp_send (LOCAL
->nntpstream
,"XOVER",sequence
)) {
1035 case NNTPOVER
: /* got an overview? */
1037 case NNTPBADCMD
: /* unknown command? */
1038 LOCAL
->xover
= NIL
; /* disable future XOVER attempts */
1043 /* Parse OVERVIEW struct from cached NNTP OVER response
1044 * Accepts: struct to load
1045 * cached OVER response
1047 * Returns: T if success, NIL if fail
1050 long nntp_parse_overview (OVERVIEW
*ov
,char *text
,MESSAGECACHE
*elt
)
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
);
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
);
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
1100 * pointer to return size
1102 * Returns: header text
1105 char *nntp_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *size
,
1108 char tmp
[MAILTMPLEN
];
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
)) {
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 */
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;
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
: "";
1147 * Accepts: mail stream
1149 * pointer to stringstruct to initialize
1151 * Returns: T if successful, else NIL
1154 long nntp_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
)
1156 char tmp
[MAILTMPLEN
];
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
);
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
)) {
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 */
1176 case NNTPSOFTFATAL
: /* don't mark deleted if stream dead */
1180 if (!(flags
& FT_PEEK
)) { /* mark seen if needed */
1182 mm_flags (stream
,elt
->msgno
);
1184 INIT (bs
,file_string
,(void *) LOCAL
->txt
,LOCAL
->txtsize
);
1188 /* NNTP fetch article from message ID (for news: URL support)
1189 * Accepts: mail stream
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
1226 * Returns: T on success, NIL on failure
1229 long nntp_search (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*pgm
,long flags
)
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
);
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
);
1269 /* NNTP search message
1270 * Accepts: MAIL stream
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
,
1281 unsigned long now
= (unsigned long) time (0);
1282 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1286 if (pgm
->msgno
|| pgm
->uid
) { /* message set searches */
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
;
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
;
1304 if (!set
) return NIL
; /* not found within sequence */
1308 /* Fast data searches */
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
;
1323 if ((pgm
->keyword
&& !mail_search_keyword (stream
,elt
,pgm
->keyword
,LONGT
)) ||
1324 (pgm
->unkeyword
&& mail_search_keyword (stream
,elt
,pgm
->unkeyword
,NIL
)))
1326 if (ov
) { /* only do this if real searching */
1329 if ((pgm
->larger
&& (ov
->optional
.octets
<= pgm
->larger
)) ||
1330 (pgm
->smaller
&& (ov
->optional
.octets
>= pgm
->smaller
))) return NIL
;
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
))||
1350 !mail_search_header_text (ov
->message_id
,pgm
->message_id
)) ||
1352 !mail_search_header_text (ov
->references
,pgm
->references
)))
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
||
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
)))
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
)) ||
1375 !mail_search_header_text (env
->newsgroups
,pgm
->newsgroups
)) ||
1376 (pgm
->followup_to
&&
1377 !mail_search_header_text (env
->followup_to
,pgm
->followup_to
)))
1381 /* search header lines */
1382 for (hdr
= pgm
->header
; hdr
; hdr
= hdr
->next
) {
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);
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
;
1411 if (mail_search_header (&s
,&stc
)) fs_give ((void **) &s
.data
);
1412 else { /* search failed */
1413 fs_give ((void **) &s
.data
);
1418 else return NIL
; /* no matching header text */
1420 /* search strings */
1422 !mail_search_text (stream
,msgno
,NIL
,pgm
->text
,LONGT
))||
1423 (pgm
->body
&& !mail_search_text (stream
,msgno
,NIL
,pgm
->body
,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
;
1435 /* NNTP sort messages
1436 * Accepts: mail stream
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
;
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
) {
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
);
1487 /* Mail load sortcache
1488 * Accepts: mail stream, already searched
1493 * Returns: vector of sortcache pointers matching search
1496 SORTCACHE
**nntp_sort_loadcache (MAILSTREAM
*stream
,SORTPGM
*pgm
,
1497 unsigned long start
,unsigned long last
,
1501 char c
,*s
,*t
,*v
,tmp
[MAILTMPLEN
];
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 */
1515 case SORTTO
: /* sort by first to */
1516 mm_notify (stream
,"[NNTPSORT] Can't do To-field sorting in NNTP",WARN
);
1518 case SORTCC
: /* sort by first cc */
1519 mm_notify (stream
,"[NNTPSORT] Can't do cc-field sorting in NNTP",WARN
);
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
;
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 ("");
1585 /* NNTP thread messages
1586 * Accepts: mail stream
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
);
1623 /* NNTP expunge mailbox
1624 * Accepts: MAIL stream
1625 * sequence to expunge if non-NIL
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
);
1636 /* NNTP copy message(s)
1637 * Accepts: MAIL stream
1639 * destination mailbox
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
);
1654 /* NNTP append message from stringstruct
1655 * Accepts: MAIL stream
1656 * destination mailbox
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
);
1668 /* NNTP open connection
1669 * Accepts: network driver
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
;
1683 char tmp
[MAILTMPLEN
];
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
);
1694 else { /* light tryssl flag if requested */
1695 mb
.trysslflag
= (options
& NOP_TRYSSL
) ? T
: NIL
;
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 */
1715 mm_notify (NIL
,stream
->reply
+ 4,(long) NIL
);
1717 case NNTPGREETNOPOST
: /* posting not allowed, must be readonly */
1721 mm_log (stream
->reply
,ERROR
);
1722 stream
= nntp_close (stream
);
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
)
1737 mb
.tlsflag
= T
; /* TLS OK, get into TLS at this end */
1738 stream
->netstream
->dtb
= ssld
;
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
));
1746 sprintf (tmp
,"Unable to negotiate TLS with this server: %.80s",mb
.host
);
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
);
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
),
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")) {
1781 case NNTPGREETNOPOST
:
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")) {
1796 case NNTPGREETNOPOST
:
1800 else stream
= nntp_close (stream
);
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
));
1814 * authenticator flags
1815 * Returns: T on success, NIL on failure
1818 long nntp_extensions (SENDSTREAM
*stream
,long flags
)
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 */
1830 default: /* no LIST EXTENSIONS on this server */
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
) {
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] == ':'))
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
);
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 */
1894 /* NNTP deliver news
1895 * Accepts: SEND stream
1898 * Returns: T on success, NIL on failure
1901 long nntp_mail (SENDSTREAM
*stream
,ENVELOPE
*env
,BODY
*body
)
1905 char *s
,path
[MAILTMPLEN
],tmp
[SENDBUFLEN
+1];
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
;
1956 /* NNTP send command
1957 * Accepts: SEND stream
1959 * Returns: reply code
1962 long nntp_send (SENDSTREAM
*stream
,char *command
,char *args
)
1965 switch ((int) (ret
= nntp_send_work (stream
,command
,args
))) {
1966 case NNTPWANTAUTH
: /* authenticate and retry */
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 */
1983 /* NNTP send command worker routine
1984 * Accepts: SEND stream
1986 * Returns: reply code
1989 long nntp_send_work (SENDSTREAM
*stream
,char *command
,char *args
)
1992 char *s
= (char *) fs_get (strlen (command
) + (args
? strlen (args
) + 1 : 0)
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
);
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
)
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
)):
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
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
];
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",
2054 fs_give ((void **) &lsterr
);
2056 trial
= 0; /* initial trial count */
2057 tmp
[0] = '\0'; /* empty buffer */
2058 if (stream
->netstream
) do {
2060 sprintf (tmp
,"Retrying %s authentication after %.80s",at
->name
,lsterr
);
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
,
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
);
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
;
2108 case NNTPAUTHED
: /* successful authentication */
2109 ret
= LONGT
; /* guess no password was needed */
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
));
2132 /* Get challenge to authenticator in binary
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
];
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);
2153 /* Send authenticator response in BASE64
2154 * Accepts: MAIL stream
2157 * Returns: T, always
2160 long nntp_response (void *s
,char *response
,unsigned long size
)
2162 SENDSTREAM
*stream
= (SENDSTREAM
*) s
;
2165 if (response
) { /* make CRLFless BASE64 string */
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 */
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
);
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
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 */
2225 * Returns: T on success, NIL on failure
2228 long nntp_soutr (void *stream
,char *s
)
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 */
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
;