1 /* ========================================================================
2 * Copyright 1988-2007 University of Washington
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
11 * ========================================================================
15 * Program: Network News Transfer Protocol (NNTP) routines
17 * Author: Mark Crispin
18 * Networks and Distributed Computing
19 * Computing & Communications
20 * University of Washington
21 * Administration Building, AG-44
23 * Internet: MRC@CAC.Washington.EDU
25 * Date: 10 February 1992
26 * Last Edited: 6 September 2007
39 #define NNTPSSLPORT (long) 563 /* assigned SSL TCP contact port */
40 #define NNTPGREET (long) 200 /* NNTP successful greeting */
41 /* NNTP successful greeting w/o posting priv */
42 #define NNTPGREETNOPOST (long) 201
43 #define NNTPEXTOK (long) 202 /* NNTP extensions OK */
44 #define NNTPGOK (long) 211 /* NNTP group selection OK */
45 #define NNTPGLIST (long) 215 /* NNTP group list being returned */
46 #define NNTPARTICLE (long) 220 /* NNTP article file */
47 #define NNTPHEAD (long) 221 /* NNTP header text */
48 #define NNTPBODY (long) 222 /* NNTP body text */
49 #define NNTPOVER (long) 224 /* NNTP overview text */
50 #define NNTPOK (long) 240 /* NNTP OK code */
51 #define NNTPAUTHED (long) 281 /* NNTP successful authentication */
52 /* NNTP successful authentication with data */
53 #define NNTPAUTHEDDATA (long) 282
54 #define NNTPREADY (long) 340 /* NNTP ready for data */
55 #define NNTPWANTAUTH2 (long) 380/* NNTP authentication needed (old) */
56 #define NNTPWANTPASS (long) 381 /* NNTP password needed */
57 #define NNTPTLSSTART (long) 382 /* NNTP continue with TLS negotiation */
58 #define NNTPCHALLENGE (long) 383/* NNTP challenge, want response */
59 #define NNTPSOFTFATAL (long) 400/* NNTP soft fatal code */
60 #define NNTPWANTAUTH (long) 480 /* NNTP authentication needed */
61 #define NNTPBADCMD (long) 500 /* NNTP unrecognized command */
62 #define IDLETIMEOUT (long) 3 /* defined in NNTPEXT WG base draft */
65 /* NNTP I/O stream local data */
67 typedef struct nntp_local
{
68 SENDSTREAM
*nntpstream
; /* NNTP stream for I/O */
69 unsigned int dirty
: 1; /* disk copy of .newsrc needs updating */
70 unsigned int tlsflag
: 1; /* TLS session */
71 unsigned int tlssslv23
: 1; /* TLS using SSLv23 client method */
72 unsigned int notlsflag
: 1; /* TLS not used in session */
73 unsigned int sslflag
: 1; /* SSL session */
74 unsigned int novalidate
: 1; /* certificate not validated */
75 unsigned int xover
: 1; /* supports XOVER */
76 unsigned int xhdr
: 1; /* supports XHDR */
77 char *name
; /* remote newsgroup name */
78 char *user
; /* mailbox user */
79 char *newsrc
; /* newsrc file */
80 char *over_fmt
; /* overview format */
81 unsigned long msgno
; /* current text message number */
82 FILE *txt
; /* current text */
83 unsigned long txtsize
; /* current text size */
87 /* Convenient access to local data */
89 #define LOCAL ((NNTPLOCAL *) stream->local)
92 /* Convenient access to protocol-specific data */
94 #define NNTP stream->protocol.nntp
97 /* Convenient access to extensions */
99 #define EXTENSION LOCAL->nntpstream->protocol.nntp.ext
101 /* Function prototypes */
103 DRIVER
*nntp_valid (char *name
);
104 DRIVER
*nntp_isvalid (char *name
,char *mbx
);
105 void *nntp_parameters (long function
,void *value
);
106 void nntp_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
);
107 void nntp_list (MAILSTREAM
*stream
,char *ref
,char *pat
);
108 void nntp_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
);
109 long nntp_canonicalize (char *ref
,char *pat
,char *pattern
,char *wildmat
);
110 long nntp_subscribe (MAILSTREAM
*stream
,char *mailbox
);
111 long nntp_unsubscribe (MAILSTREAM
*stream
,char *mailbox
);
112 long nntp_create (MAILSTREAM
*stream
,char *mailbox
);
113 long nntp_delete (MAILSTREAM
*stream
,char *mailbox
);
114 long nntp_rename (MAILSTREAM
*stream
,char *old
,char *newname
);
115 long nntp_status (MAILSTREAM
*stream
,char *mbx
,long flags
);
116 long nntp_getmap (MAILSTREAM
*stream
,char *name
,
117 unsigned long first
,unsigned long last
,
118 unsigned long rnmsgs
,unsigned long nmsgs
,char *tmp
);
119 MAILSTREAM
*nntp_mopen (MAILSTREAM
*stream
);
120 void nntp_mclose (MAILSTREAM
*stream
,long options
);
121 void nntp_fetchfast (MAILSTREAM
*stream
,char *sequence
,long flags
);
122 void nntp_flags (MAILSTREAM
*stream
,char *sequence
,long flags
);
123 long nntp_overview (MAILSTREAM
*stream
,overview_t ofn
);
124 long nntp_parse_overview (OVERVIEW
*ov
,char *text
,MESSAGECACHE
*elt
);
125 long nntp_over (MAILSTREAM
*stream
,char *sequence
);
126 char *nntp_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *size
,
128 long nntp_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
);
129 FILE *nntp_article (MAILSTREAM
*stream
,char *msgid
,unsigned long *size
,
130 unsigned long *hsiz
);
131 void nntp_flagmsg (MAILSTREAM
*stream
,MESSAGECACHE
*elt
);
132 long nntp_search (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*pgm
,long flags
);
133 long nntp_search_msg (MAILSTREAM
*stream
,unsigned long msgno
,SEARCHPGM
*pgm
,
135 unsigned long *nntp_sort (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*spg
,
136 SORTPGM
*pgm
,long flags
);
137 SORTCACHE
**nntp_sort_loadcache (MAILSTREAM
*stream
,SORTPGM
*pgm
,
138 unsigned long start
,unsigned long last
,
140 THREADNODE
*nntp_thread (MAILSTREAM
*stream
,char *type
,char *charset
,
141 SEARCHPGM
*spg
,long flags
);
142 long nntp_ping (MAILSTREAM
*stream
);
143 void nntp_check (MAILSTREAM
*stream
);
144 long nntp_expunge (MAILSTREAM
*stream
,char *sequence
,long options
);
145 long nntp_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
);
146 long nntp_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
);
148 long nntp_extensions (SENDSTREAM
*stream
,long flags
);
149 long nntp_send (SENDSTREAM
*stream
,char *command
,char *args
);
150 long nntp_send_work (SENDSTREAM
*stream
,char *command
,char *args
);
151 long nntp_send_auth (SENDSTREAM
*stream
,long flags
);
152 long nntp_send_auth_work (SENDSTREAM
*stream
,NETMBX
*mb
,char *pwd
,long flags
);
153 void *nntp_challenge (void *s
,unsigned long *len
);
154 long nntp_response (void *s
,char *response
,unsigned long size
);
155 long nntp_reply (SENDSTREAM
*stream
);
156 long nntp_fake (SENDSTREAM
*stream
,char *text
);
157 long nntp_soutr (void *stream
,char *s
);
159 /* Driver dispatch used by MAIL */
161 DRIVER nntpdriver
= {
162 "nntp", /* driver name */
164 #ifdef INADEQUATE_MEMORY
167 DR_NEWS
|DR_READONLY
|DR_NOFAST
|DR_NAMESPACE
|DR_CRLF
|DR_RECYCLE
|DR_XPOINT
|
168 DR_NOINTDATE
|DR_NONEWMAIL
|DR_HALFOPEN
,
169 (DRIVER
*) NIL
, /* next driver */
170 nntp_valid
, /* mailbox is valid for us */
171 nntp_parameters
, /* manipulate parameters */
172 nntp_scan
, /* scan mailboxes */
173 nntp_list
, /* find mailboxes */
174 nntp_lsub
, /* find subscribed mailboxes */
175 nntp_subscribe
, /* subscribe to mailbox */
176 nntp_unsubscribe
, /* unsubscribe from mailbox */
177 nntp_create
, /* create mailbox */
178 nntp_delete
, /* delete mailbox */
179 nntp_rename
, /* rename mailbox */
180 nntp_status
, /* status of mailbox */
181 nntp_mopen
, /* open mailbox */
182 nntp_mclose
, /* close mailbox */
183 nntp_fetchfast
, /* fetch message "fast" attributes */
184 nntp_flags
, /* fetch message flags */
185 nntp_overview
, /* fetch overview */
186 NIL
, /* fetch message structure */
187 nntp_header
, /* fetch message header */
188 nntp_text
, /* fetch message text */
189 NIL
, /* fetch message */
190 NIL
, /* unique identifier */
191 NIL
, /* message number from UID */
192 NIL
, /* modify flags */
193 nntp_flagmsg
, /* per-message modify flags */
194 nntp_search
, /* search for message based on criteria */
195 nntp_sort
, /* sort messages */
196 nntp_thread
, /* thread messages */
197 nntp_ping
, /* ping mailbox to see if still alive */
198 nntp_check
, /* check for new messages */
199 nntp_expunge
, /* expunge deleted messages */
200 nntp_copy
, /* copy messages to another mailbox */
201 nntp_append
, /* append string message to mailbox */
202 NIL
/* garbage collect stream */
205 /* prototype stream */
206 MAILSTREAM nntpproto
= {&nntpdriver
};
209 /* driver parameters */
210 static unsigned long nntp_maxlogintrials
= MAXLOGINTRIALS
;
211 static long nntp_port
= 0;
212 static long nntp_sslport
= 0;
213 static unsigned long nntp_range
= 0;
214 static long nntp_hidepath
= 0;
216 /* NNTP validate mailbox
217 * Accepts: mailbox name
218 * Returns: our driver if name is valid, NIL otherwise
221 DRIVER
*nntp_valid (char *name
)
223 char tmp
[MAILTMPLEN
];
224 return nntp_isvalid (name
,tmp
);
228 /* NNTP validate mailbox work routine
229 * Accepts: mailbox name
230 * buffer for returned mailbox name
231 * Returns: our driver if name is valid, NIL otherwise
234 DRIVER
*nntp_isvalid (char *name
,char *mbx
)
237 if (!mail_valid_net_parse (name
,&mb
) || strcmp (mb
.service
,nntpdriver
.name
)||
238 mb
.anoflag
) return NIL
;
239 if (mb
.mailbox
[0] != '#') strcpy (mbx
,mb
.mailbox
);
240 /* namespace format name */
241 else if ((mb
.mailbox
[1] == 'n') && (mb
.mailbox
[2] == 'e') &&
242 (mb
.mailbox
[3] == 'w') && (mb
.mailbox
[4] == 's') &&
243 (mb
.mailbox
[5] == '.')) strcpy (mbx
,mb
.mailbox
+6);
244 else return NIL
; /* bogus name */
248 /* News manipulate driver parameters
249 * Accepts: function code
250 * function-dependent value
251 * Returns: function-dependent return value
254 void *nntp_parameters (long function
,void *value
)
256 switch ((int) function
) {
257 case SET_MAXLOGINTRIALS
:
258 nntp_maxlogintrials
= (unsigned long) value
;
260 case GET_MAXLOGINTRIALS
:
261 value
= (void *) nntp_maxlogintrials
;
264 nntp_port
= (long) value
;
267 value
= (void *) nntp_port
;
269 case SET_SSLNNTPPORT
:
270 nntp_sslport
= (long) value
;
272 case GET_SSLNNTPPORT
:
273 value
= (void *) nntp_sslport
;
276 nntp_range
= (unsigned long) value
;
279 value
= (void *) nntp_range
;
281 case SET_NNTPHIDEPATH
:
282 nntp_hidepath
= (long) value
;
284 case GET_NNTPHIDEPATH
:
285 value
= (void *) nntp_hidepath
;
289 value
= (void *) ((NNTPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->newsrc
;
291 case GET_IDLETIMEOUT
:
292 value
= (void *) IDLETIMEOUT
;
296 ((NNTPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->nntpstream
->debug
= T
;
300 ((NNTPLOCAL
*) ((MAILSTREAM
*) value
)->local
)->nntpstream
->debug
= NIL
;
303 value
= NIL
; /* error case */
309 /* NNTP mail scan mailboxes for string
310 * Accepts: mail stream
316 void nntp_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
)
318 char tmp
[MAILTMPLEN
];
319 if (nntp_canonicalize (ref
,pat
,tmp
,NIL
))
320 mm_log ("Scan not valid for NNTP mailboxes",ERROR
);
324 /* NNTP list newsgroups
325 * Accepts: mail stream
330 void nntp_list (MAILSTREAM
*stream
,char *ref
,char *pat
)
332 MAILSTREAM
*st
= stream
;
333 char *s
,*t
,*lcl
,pattern
[MAILTMPLEN
],name
[MAILTMPLEN
],wildmat
[MAILTMPLEN
];
334 int showuppers
= pat
[strlen (pat
) - 1] == '%';
336 if (nntp_canonicalize (ref
,"*",pattern
,NIL
)) {
337 /* tie off name at root */
338 if ((s
= strchr (pattern
,'}')) && (s
= strchr (s
+1,'.'))) *++s
= '\0';
339 else pattern
[0] = '\0';
340 mm_list (stream
,'.',pattern
,NIL
);
343 /* ask server for open newsgroups */
344 else if (nntp_canonicalize (ref
,pat
,pattern
,wildmat
) &&
345 ((stream
&& LOCAL
&& LOCAL
->nntpstream
) ||
346 (stream
= mail_open (NIL
,pattern
,OP_HALFOPEN
|OP_SILENT
))) &&
347 ((nntp_send (LOCAL
->nntpstream
,"LIST ACTIVE",
348 wildmat
[0] ? wildmat
: NIL
) == NNTPGLIST
) ||
349 (nntp_send (LOCAL
->nntpstream
,"LIST",NIL
) == NNTPGLIST
))) {
350 /* namespace format name? */
351 if (*(lcl
= strchr (strcpy (name
,pattern
),'}') + 1) == '#') lcl
+= 6;
352 /* process data until we see final dot */
353 while (s
= net_getline (LOCAL
->nntpstream
->netstream
)) {
354 if ((*s
== '.') && !s
[1]){/* end of text */
355 fs_give ((void **) &s
);
358 if (t
= strchr (s
,' ')) { /* tie off after newsgroup name */
360 strcpy (lcl
,s
); /* make full form of name */
361 /* report if match */
362 if (pmatch_full (name
,pattern
,'.')) mm_list (stream
,'.',name
,NIL
);
363 else while (showuppers
&& (t
= strrchr (lcl
,'.'))) {
364 *t
= '\0'; /* tie off the name */
365 if (pmatch_full (name
,pattern
,'.'))
366 mm_list (stream
,'.',name
,LATT_NOSELECT
);
369 fs_give ((void **) &s
); /* clean up */
371 if (stream
!= st
) mail_close (stream
);
375 /* NNTP list subscribed newsgroups
376 * Accepts: mail stream
381 void nntp_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
)
384 char *s
,mbx
[MAILTMPLEN
];
385 /* return data from newsrc */
386 if (nntp_canonicalize (ref
,pat
,mbx
,NIL
)) newsrc_lsub (stream
,mbx
);
387 if (*pat
== '{') { /* if remote pattern, must be NNTP */
388 if (!nntp_valid (pat
)) return;
389 ref
= NIL
; /* good NNTP pattern, punt reference */
391 /* if remote reference, must be valid NNTP */
392 if (ref
&& (*ref
== '{') && !nntp_valid (ref
)) return;
393 /* kludgy application of reference */
394 if (ref
&& *ref
) sprintf (mbx
,"%s%s",ref
,pat
);
395 else strcpy (mbx
,pat
);
397 if (s
= sm_read (&sdb
)) do if (nntp_valid (s
) && pmatch (s
,mbx
))
398 mm_lsub (stream
,NIL
,s
,NIL
);
399 while (s
= sm_read (&sdb
)); /* until no more subscriptions */
402 /* NNTP canonicalize newsgroup name
405 * returned single pattern
406 * returned wildmat pattern
407 * Returns: T on success, NIL on failure
410 long nntp_canonicalize (char *ref
,char *pat
,char *pattern
,char *wildmat
)
414 if (ref
&& *ref
) { /* have a reference */
415 if (!nntp_valid (ref
)) return NIL
;
416 strcpy (pattern
,ref
); /* copy reference to pattern */
417 /* # overrides mailbox field in reference */
418 if (*pat
== '#') strcpy (strchr (pattern
,'}') + 1,pat
);
419 /* pattern starts, reference ends, with . */
420 else if ((*pat
== '.') && (pattern
[strlen (pattern
) - 1] == '.'))
421 strcat (pattern
,pat
+ 1); /* append, omitting one of the period */
422 else strcat (pattern
,pat
); /* anything else is just appended */
424 else strcpy (pattern
,pat
); /* just have basic name */
425 if ((ret
= wildmat
? /* if valid and wildmat */
426 nntp_isvalid (pattern
,wildmat
) : nntp_valid (pattern
)) && wildmat
) {
427 /* don't return wildmat if specials present */
428 if (strpbrk (wildmat
,",?![\\]")) wildmat
[0] = '\0';
429 /* replace all % with * */
430 for (s
= wildmat
; s
= strchr (s
,'%'); *s
= '*');
432 return ret
? LONGT
: NIL
;
435 /* NNTP subscribe to mailbox
436 * Accepts: mail stream
437 * mailbox to add to subscription list
438 * Returns: T on success, NIL on failure
441 long nntp_subscribe (MAILSTREAM
*stream
,char *mailbox
)
443 char mbx
[MAILTMPLEN
];
444 return nntp_isvalid (mailbox
,mbx
) ? newsrc_update (stream
,mbx
,':') : NIL
;
448 /* NNTP unsubscribe to mailbox
449 * Accepts: mail stream
450 * mailbox to delete from subscription list
451 * Returns: T on success, NIL on failure
454 long nntp_unsubscribe (MAILSTREAM
*stream
,char *mailbox
)
456 char mbx
[MAILTMPLEN
];
457 return nntp_isvalid (mailbox
,mbx
) ? newsrc_update (stream
,mbx
,'!') : NIL
;
460 /* NNTP create mailbox
461 * Accepts: mail stream
462 * mailbox name to create
463 * Returns: T on success, NIL on failure
466 long nntp_create (MAILSTREAM
*stream
,char *mailbox
)
468 return NIL
; /* never valid for NNTP */
472 /* NNTP delete mailbox
473 * mailbox name to delete
474 * Returns: T on success, NIL on failure
477 long nntp_delete (MAILSTREAM
*stream
,char *mailbox
)
479 return NIL
; /* never valid for NNTP */
483 /* NNTP rename mailbox
484 * Accepts: mail stream
487 * Returns: T on success, NIL on failure
490 long nntp_rename (MAILSTREAM
*stream
,char *old
,char *newname
)
492 return NIL
; /* never valid for NNTP */
496 * Accepts: mail stream
499 * Returns: T on success, NIL on failure
502 long nntp_status (MAILSTREAM
*stream
,char *mbx
,long flags
)
506 unsigned long i
,j
,k
,rnmsgs
;
508 char *s
,*name
,*state
,tmp
[MAILTMPLEN
];
509 char *old
= (stream
&& !stream
->halfopen
) ? LOCAL
->name
: NIL
;
510 MAILSTREAM
*tstream
= NIL
;
511 if (!(mail_valid_net_parse (mbx
,&mb
) && !strcmp (mb
.service
,"nntp") &&
513 ((mb
.mailbox
[0] != '#') ||
514 ((mb
.mailbox
[1] == 'n') && (mb
.mailbox
[2] == 'e') &&
515 (mb
.mailbox
[3] == 'w') && (mb
.mailbox
[4] == 's') &&
516 (mb
.mailbox
[5] == '.'))))) {
517 sprintf (tmp
,"Invalid NNTP name %s",mbx
);
521 /* note mailbox name */
522 name
= (*mb
.mailbox
== '#') ? mb
.mailbox
+6 : mb
.mailbox
;
523 /* stream to reuse? */
524 if (!(stream
&& LOCAL
->nntpstream
&&
525 mail_usable_network_stream (stream
,mbx
)) &&
527 mail_open (NIL
,mbx
,OP_HALFOPEN
|OP_SILENT
|
528 ((flags
& SA_MULNEWSRC
) ? OP_MULNEWSRC
: NIL
))))
529 return NIL
; /* can't reuse or make a new one */
531 if (nntp_send (LOCAL
->nntpstream
,"GROUP",name
) == NNTPGOK
) {
532 status
.flags
= flags
; /* status validity flags */
533 k
= strtoul (LOCAL
->nntpstream
->reply
+ 4,&s
,10);
534 i
= strtoul (s
,&s
,10); /* first assigned UID */
535 /* next UID to be assigned */
536 status
.uidnext
= (j
= strtoul (s
,NIL
,10)) + 1;
537 /* maximum number of messages */
538 rnmsgs
= status
.messages
= (i
| j
) ? status
.uidnext
- i
: 0;
539 if (k
> status
.messages
) { /* check for absurdity */
540 sprintf (tmp
,"NNTP SERVER BUG (impossible message count): %lu > %lu",
544 /* restrict article range if needed */
545 if (nntp_range
&& (status
.messages
> nntp_range
)) {
546 i
= status
.uidnext
- (status
.messages
= nntp_range
);
547 if (k
> nntp_range
) k
= nntp_range
;
550 status
.recent
= status
.unseen
= 0;
551 if (!status
.messages
); /* empty case */
552 /* use server guesstimate in simple case */
553 else if (!(flags
& (SA_RECENT
| SA_UNSEEN
))) status
.messages
= k
;
555 /* have newsrc state? */
556 else if (state
= newsrc_state (stream
,name
)) {
557 /* yes, get the UID/sequence map */
558 if (nntp_getmap (stream
,name
,i
,status
.uidnext
- 1,rnmsgs
,
559 status
.messages
,tmp
)) {
560 /* calculate true count */
561 for (status
.messages
= 0;
562 (s
= net_getline (LOCAL
->nntpstream
->netstream
)) &&
564 /* only count if in range */
565 if (((k
= atol (s
)) >= i
) && (k
< status
.uidnext
)) {
566 newsrc_check_uid (state
,k
,&status
.recent
,&status
.unseen
);
569 fs_give ((void **) &s
);
571 if (s
) fs_give ((void **) &s
);
573 /* assume c-client/NNTP map is entire range */
574 else while (i
< status
.uidnext
)
575 newsrc_check_uid (state
,i
++,&status
.recent
,&status
.unseen
);
576 fs_give ((void **) &state
);
578 /* no .newsrc state, all messages new */
579 else status
.recent
= status
.unseen
= status
.messages
;
580 /* UID validity is a constant */
581 status
.uidvalidity
= stream
->uid_validity
;
582 /* pass status to main program */
583 mm_status (stream
,mbx
,&status
);
584 ret
= T
; /* succes */
586 /* flush temporary stream */
587 if (tstream
) mail_close (tstream
);
588 /* else reopen old newsgroup */
589 else if (old
&& nntp_send (LOCAL
->nntpstream
,"GROUP",old
) != NNTPGOK
) {
590 mm_log (LOCAL
->nntpstream
->reply
,ERROR
);
591 stream
->halfopen
= T
; /* go halfopen */
593 return ret
; /* success */
599 * first UID in map range
600 * last UID in map range
601 * reported total number of messages in newsgroup
602 * calculated number of messages in range
604 * Returns: T on success, NIL on failure
607 long nntp_getmap (MAILSTREAM
*stream
,char *name
,
608 unsigned long first
,unsigned long last
,
609 unsigned long rnmsgs
,unsigned long nmsgs
,char *tmp
)
611 short trylistgroup
= NIL
;
612 if (rnmsgs
> (nmsgs
* 8)) /* small subrange? */
613 trylistgroup
= T
; /* yes, can try LISTGROUP if [X]HDR fails */
614 else switch ((int) nntp_send (LOCAL
->nntpstream
,"LISTGROUP",name
)) {
615 case NNTPGOK
: /* got data */
617 default: /* else give up if server claims LISTGROUP */
618 if (EXTENSION
.listgroup
) return NIL
;
621 sprintf (tmp
,"%lu-%lu",first
,last
);
622 if (EXTENSION
.hdr
) /* have HDR extension? */
623 return (nntp_send (LOCAL
->nntpstream
,"HDR Date",tmp
) == NNTPHEAD
) ?
625 if (LOCAL
->xhdr
) /* try the experimental extension then */
626 switch ((int) nntp_send (LOCAL
->nntpstream
,"XHDR Date",tmp
)) {
627 case NNTPHEAD
: /* got an overview? */
629 case NNTPBADCMD
: /* unknown command? */
630 LOCAL
->xhdr
= NIL
; /* disable future XHDR attempts */
632 if (trylistgroup
&& /* no [X]HDR, maybe do LISTGROUP after all */
633 (nntp_send (LOCAL
->nntpstream
,"LISTGROUP",name
) == NNTPGOK
))
639 * Accepts: stream to open
640 * Returns: stream on success, NIL on failure
643 MAILSTREAM
*nntp_mopen (MAILSTREAM
*stream
)
645 unsigned long i
,j
,k
,nmsgs
,rnmsgs
;
646 char *s
,*mbx
,tmp
[MAILTMPLEN
];
649 char *newsrc
= (char *) mail_parameters (NIL
,GET_NEWSRC
,NIL
);
650 newsrcquery_t nq
= (newsrcquery_t
) mail_parameters (NIL
,GET_NEWSRCQUERY
,NIL
);
651 SENDSTREAM
*nstream
= NIL
;
652 /* return prototype for OP_PROTOTYPE call */
653 if (!stream
) return &nntpproto
;
654 mail_valid_net_parse (stream
->mailbox
,&mb
);
655 /* note mailbox anme */
656 mbx
= (*mb
.mailbox
== '#') ? mb
.mailbox
+6 : mb
.mailbox
;
657 if (LOCAL
) { /* recycle stream */
658 nstream
= LOCAL
->nntpstream
;/* remember NNTP protocol stream */
659 sprintf (tmp
,"Reusing connection to %s",net_host (nstream
->netstream
));
660 if (!stream
->silent
) mm_log (tmp
,(long) NIL
);
661 if (stream
->rdonly
) mb
.readonlyflag
= T
;
662 if (LOCAL
->tlsflag
) mb
.tlsflag
= T
;
663 if (LOCAL
->tlssslv23
) mb
.tlssslv23
= T
;
664 if (LOCAL
->notlsflag
) mb
.notlsflag
= T
;
665 if (LOCAL
->sslflag
) mb
.sslflag
= T
;
666 if (LOCAL
->novalidate
) mb
.novalidate
= T
;
667 if (LOCAL
->nntpstream
->loser
) mb
.loser
= T
;
668 if (stream
->secure
) mb
.secflag
= T
;
669 LOCAL
->nntpstream
= NIL
; /* keep nntp_mclose() from punting it */
670 nntp_mclose (stream
,NIL
); /* do close action */
671 stream
->dtb
= &nntpdriver
; /* reattach this driver */
674 if (mb
.dbgflag
) stream
->debug
= T
;
675 if (mb
.readonlyflag
) stream
->rdonly
= T
;
676 if (mb
.secflag
) stream
->secure
= T
;
677 mb
.trysslflag
= stream
->tryssl
= (mb
.trysslflag
|| stream
->tryssl
) ? T
: NIL
;
678 if (!nstream
) { /* open NNTP now if not already open */
680 hostlist
[0] = strcpy (tmp
,mb
.host
);
681 if (mb
.port
|| nntp_port
)
682 sprintf (tmp
+ strlen (tmp
),":%lu",mb
.port
? mb
.port
: nntp_port
);
683 if (mb
.tlsflag
) strcat (tmp
,"/tls");
684 if (mb
.tlssslv23
) strcat (tmp
,"/tls-sslv23");
685 if (mb
.notlsflag
) strcat (tmp
,"/notls");
686 if (mb
.sslflag
) strcat (tmp
,"/ssl");
687 if (mb
.novalidate
) strcat (tmp
,"/novalidate-cert");
688 if (mb
.loser
) strcat (tmp
,"/loser");
689 if (mb
.secflag
) strcat (tmp
,"/secure");
690 if (mb
.user
[0]) sprintf (tmp
+ strlen (tmp
),"/user=\"%s\"",mb
.user
);
692 if (!(nstream
= nntp_open (hostlist
,NOP_READONLY
|
693 (stream
->debug
? NOP_DEBUG
: NIL
)))) return NIL
;
696 /* always zero messages if halfopen */
697 if (stream
->halfopen
) i
= j
= k
= rnmsgs
= nmsgs
= 0;
698 /* otherwise open the newsgroup */
699 else if (nntp_send (nstream
,"GROUP",mbx
) == NNTPGOK
) {
700 k
= strtoul (nstream
->reply
+ 4,&s
,10);
701 i
= strtoul (s
,&s
,10);
702 stream
->uid_last
= j
= strtoul (s
,&s
,10);
703 rnmsgs
= nmsgs
= (i
| j
) ? 1 + j
- i
: 0;
704 if (k
> nmsgs
) { /* check for absurdity */
705 sprintf (tmp
,"NNTP SERVER BUG (impossible message count): %lu > %lu",
709 /* restrict article range if needed */
710 if (nntp_range
&& (nmsgs
> nntp_range
)) i
= 1 + j
- (nmsgs
= nntp_range
);
712 else { /* no such newsgroup */
713 mm_log (nstream
->reply
,ERROR
);
714 nntp_close (nstream
); /* punt stream */
717 /* instantiate local data */
718 stream
->local
= memset (fs_get (sizeof (NNTPLOCAL
)),0,sizeof (NNTPLOCAL
));
719 LOCAL
->nntpstream
= nstream
;
720 /* save state for future recycling */
721 if (mb
.tlsflag
) LOCAL
->tlsflag
= T
;
722 if (mb
.tlssslv23
) LOCAL
->tlssslv23
= T
;
723 if (mb
.notlsflag
) LOCAL
->notlsflag
= T
;
724 if (mb
.sslflag
) LOCAL
->sslflag
= T
;
725 if (mb
.novalidate
) LOCAL
->novalidate
= T
;
726 if (mb
.loser
) LOCAL
->nntpstream
->loser
= T
;
727 /* assume present until proven otherwise */
728 LOCAL
->xhdr
= LOCAL
->xover
= T
;
729 LOCAL
->name
= cpystr (mbx
); /* copy newsgroup name */
730 if (stream
->mulnewsrc
) { /* want to use multiple .newsrc files? */
732 s
= tmp
+ strlen (tmp
); /* end of string */
733 *s
++ = '-'; /* hyphen delimiter and host */
734 lcase (strcpy (s
,(long) mail_parameters (NIL
,GET_NEWSRCCANONHOST
,NIL
) ?
735 net_host (nstream
->netstream
) : mb
.host
));
736 LOCAL
->newsrc
= cpystr (nq
? (*nq
) (stream
,tmp
,newsrc
) : tmp
);
738 else LOCAL
->newsrc
= cpystr (newsrc
);
739 if (mb
.user
[0]) LOCAL
->user
= cpystr (mb
.user
);
740 stream
->sequence
++; /* bump sequence number */
741 stream
->rdonly
= stream
->perm_deleted
= T
;
742 /* UIDs are always valid */
743 stream
->uid_validity
= 0xbeefface;
744 sprintf (tmp
,"{%s:%lu/nntp",(long) mail_parameters (NIL
,GET_TRUSTDNS
,NIL
) ?
745 net_host (nstream
->netstream
) : mb
.host
,
746 net_port (nstream
->netstream
));
747 if (LOCAL
->tlsflag
) strcat (tmp
,"/tls");
748 if (LOCAL
->tlssslv23
) strcat (tmp
,"/tls-sslv23");
749 if (LOCAL
->notlsflag
) strcat (tmp
,"/notls");
750 if (LOCAL
->sslflag
) strcat (tmp
,"/ssl");
751 if (LOCAL
->novalidate
) strcat (tmp
,"/novalidate-cert");
752 if (LOCAL
->nntpstream
->loser
) strcat (tmp
,"/loser");
753 if (stream
->secure
) strcat (tmp
,"/secure");
754 if (stream
->rdonly
) strcat (tmp
,"/readonly");
755 if (LOCAL
->user
) sprintf (tmp
+ strlen (tmp
),"/user=\"%s\"",LOCAL
->user
);
756 if (stream
->halfopen
) strcat (tmp
,"}<no_mailbox>");
757 else sprintf (tmp
+ strlen (tmp
),"}#news.%s",mbx
);
758 fs_give ((void **) &stream
->mailbox
);
759 stream
->mailbox
= cpystr (tmp
);
761 if (EXTENSION
.over
&& /* get overview format if have OVER */
762 (nntp_send (LOCAL
->nntpstream
,"LIST","OVERVIEW.FMT") == NNTPGLIST
) &&
763 (f
= netmsg_slurp (LOCAL
->nntpstream
->netstream
,&k
,NIL
))) {
764 fread (LOCAL
->over_fmt
= (char *) fs_get ((size_t) k
+ 3),
765 (size_t) 1,(size_t) k
,f
);
766 LOCAL
->over_fmt
[k
] = '\0';
767 fclose (f
); /* flush temp file */
769 if (nmsgs
) { /* if any messages exist */
770 short silent
= stream
->silent
;
771 stream
->silent
= T
; /* don't notify main program yet */
772 mail_exists (stream
,nmsgs
); /* silently set the cache to the guesstimate */
773 /* get UID/sequence map, nuke holes */
774 if (nntp_getmap (stream
,mbx
,i
,j
,rnmsgs
,nmsgs
,tmp
)) {
775 for (nmsgs
= 0; /* calculate true count */
776 (s
= net_getline (nstream
->netstream
)) && strcmp (s
,"."); ) {
777 if ((k
= atol (s
)) > j
){/* discard too high article numbers */
778 sprintf (tmp
,"NNTP SERVER BUG (out of range article ID): %lu > %lu",
780 mm_notify (stream
,tmp
,NIL
);
781 stream
->unhealthy
= T
;
783 else if (k
>= i
) { /* silently ignore too-low article numbers */
784 /* guard against server returning extra msgs */
785 if (nmsgs
== stream
->nmsgs
) mail_exists (stream
,nmsgs
+1);
786 /* create elt for this message, set UID */
787 mail_elt (stream
,++nmsgs
)->private.uid
= k
;
789 fs_give ((void **) &s
);
791 if (s
) fs_give ((void **) &s
);
793 /* assume c-client/NNTP map is entire range */
794 else for (k
= 1; k
<= nmsgs
; k
++) mail_elt (stream
,k
)->private.uid
= i
++;
795 stream
->unhealthy
= NIL
; /* set healthy */
796 stream
->nmsgs
= 0; /* whack it back down */
797 stream
->silent
= silent
; /* restore old silent setting */
798 mail_exists (stream
,nmsgs
); /* notify upper level that messages exist */
799 /* read .newsrc entries */
800 mail_recent (stream
,newsrc_read (mbx
,stream
));
802 else { /* empty newsgroup or halfopen */
803 if (!(stream
->silent
|| stream
->halfopen
)) {
804 sprintf (tmp
,"Newsgroup %s is empty",mbx
);
807 mail_exists (stream
,(long) 0);
808 mail_recent (stream
,(long) 0);
810 return stream
; /* return stream to caller */
814 * Accepts: MAIL stream
818 void nntp_mclose (MAILSTREAM
*stream
,long options
)
822 if (LOCAL
) { /* only if a file is open */
823 nntp_check (stream
); /* dump final checkpoint */
824 if (LOCAL
->over_fmt
) fs_give ((void **) &LOCAL
->over_fmt
);
825 if (LOCAL
->name
) fs_give ((void **) &LOCAL
->name
);
826 if (LOCAL
->user
) fs_give ((void **) &LOCAL
->user
);
827 if (LOCAL
->newsrc
) fs_give ((void **) &LOCAL
->newsrc
);
828 if (LOCAL
->txt
) fclose (LOCAL
->txt
);
829 /* close NNTP connection */
830 if (LOCAL
->nntpstream
) nntp_close (LOCAL
->nntpstream
);
831 for (i
= 1; i
<= stream
->nmsgs
; i
++)
832 if ((elt
= mail_elt (stream
,i
))->private.spare
.ptr
)
833 fs_give ((void **) &elt
->private.spare
.ptr
);
834 /* nuke the local data */
835 fs_give ((void **) &stream
->local
);
836 stream
->dtb
= NIL
; /* log out the DTB */
840 /* NNTP fetch fast information
841 * Accepts: MAIL stream
844 * This is ugly and slow
847 void nntp_fetchfast (MAILSTREAM
*stream
,char *sequence
,long flags
)
852 if (stream
&& LOCAL
&& ((flags
& FT_UID
) ?
853 mail_uid_sequence (stream
,sequence
) :
854 mail_sequence (stream
,sequence
)))
855 for (i
= 1; i
<= stream
->nmsgs
; i
++) {
856 if ((elt
= mail_elt (stream
,i
))->sequence
&& (elt
->valid
= T
) &&
857 !(elt
->day
&& elt
->rfc822_size
)) {
858 ENVELOPE
**env
= NIL
;
860 if (!stream
->scache
) env
= &elt
->private.msg
.env
;
861 else if (stream
->msgno
== i
) env
= &stream
->env
;
863 if (!*env
|| !elt
->rfc822_size
) {
866 char *ht
= (*stream
->dtb
->header
) (stream
,i
,&hs
,NIL
);
867 /* need to make an envelope? */
868 if (!*env
) rfc822_parse_msg (env
,NIL
,ht
,hs
,NIL
,BADHOST
,
870 /* need message size too, ugh */
871 if (!elt
->rfc822_size
) {
872 (*stream
->dtb
->text
) (stream
,i
,&bs
,FT_PEEK
);
873 elt
->rfc822_size
= hs
+ SIZE (&bs
) - GETPOS (&bs
);
876 /* if need date, have date in envelope? */
877 if (!elt
->day
&& *env
&& (*env
)->date
)
878 mail_parse_date (elt
,(*env
)->date
);
879 /* sigh, fill in bogus default */
880 if (!elt
->day
) elt
->day
= elt
->month
= 1;
881 mail_free_envelope (&e
);
887 * Accepts: MAIL stream
892 void nntp_flags (MAILSTREAM
*stream
,char *sequence
,long flags
)
895 if ((flags
& FT_UID
) ? /* validate all elts */
896 mail_uid_sequence (stream
,sequence
) : mail_sequence (stream
,sequence
))
897 for (i
= 1; i
<= stream
->nmsgs
; i
++) mail_elt (stream
,i
)->valid
= T
;
900 /* NNTP fetch overview
901 * Accepts: MAIL stream, sequence bits set
902 * overview return function
903 * Returns: T if successful, NIL otherwise
906 long nntp_overview (MAILSTREAM
*stream
,overview_t ofn
)
908 unsigned long i
,j
,k
,uid
;
909 char c
,*s
,*t
,*v
,tmp
[MAILTMPLEN
];
912 if (!LOCAL
->nntpstream
->netstream
) return NIL
;
913 /* scan sequence to load cache */
914 for (i
= 1; i
<= stream
->nmsgs
; i
++)
915 /* have cached overview yet? */
916 if ((elt
= mail_elt (stream
,i
))->sequence
&& !elt
->private.spare
.ptr
) {
917 for (j
= i
+ 1; /* no, find end of cache gap range */
918 (j
<= stream
->nmsgs
) && (elt
= mail_elt (stream
,j
))->sequence
&&
919 !elt
->private.spare
.ptr
; j
++);
920 /* make NNTP range */
921 sprintf (tmp
,(i
== (j
- 1)) ? "%lu" : "%lu-%lu",mail_uid (stream
,i
),
922 mail_uid (stream
,j
- 1));
923 i
= j
; /* advance beyond gap */
924 /* ask server for overview data to cache */
925 if (nntp_over (stream
,tmp
)) {
926 while ((s
= net_getline (LOCAL
->nntpstream
->netstream
)) &&
928 /* death to embedded newlines */
929 for (t
= v
= s
; c
= *v
++;)
930 if ((c
!= '\012') && (c
!= '\015')) *t
++ = c
;
931 *t
++ = '\0'; /* tie off string in case it was shortened */
932 /* cache the overview if found its sequence */
933 if ((uid
= atol (s
)) && (k
= mail_msgno (stream
,uid
)) &&
934 (t
= strchr (s
,'\t'))) {
935 if ((elt
= mail_elt (stream
,k
))->private.spare
.ptr
)
936 fs_give ((void **) &elt
->private.spare
.ptr
);
937 elt
->private.spare
.ptr
= cpystr (t
+ 1);
939 else { /* shouldn't happen, snarl if it does */
940 sprintf (tmp
,"Server returned data for unknown UID %lu",uid
);
941 mm_notify (stream
,tmp
,WARN
);
942 stream
->unhealthy
= T
;
944 /* flush the overview */
945 fs_give ((void **) &s
);
947 stream
->unhealthy
= NIL
;/* set healthy */
948 /* flush the terminating dot */
949 if (s
) fs_give ((void **) &s
);
951 else i
= stream
->nmsgs
; /* OVER failed, punt cache load */
954 /* now scan sequence to return overviews */
955 if (ofn
) for (i
= 1; i
<= stream
->nmsgs
; i
++)
956 if ((elt
= mail_elt (stream
,i
))->sequence
) {
957 uid
= mail_uid (stream
,i
);/* UID for this message */
958 /* parse cached overview */
959 if (nntp_parse_overview (&ov
,s
= (char *) elt
->private.spare
.ptr
,elt
))
960 (*ofn
) (stream
,uid
,&ov
,i
);
961 else { /* parse failed */
962 (*ofn
) (stream
,uid
,NIL
,i
);
963 if (s
&& *s
) { /* unusable cached entry? */
964 sprintf (tmp
,"Unable to parse overview for UID %lu: %.500s",uid
,s
);
965 mm_notify (stream
,tmp
,WARN
);
966 stream
->unhealthy
= T
;
967 /* erase it from the cache */
968 fs_give ((void **) &s
);
970 stream
->unhealthy
= NIL
;/* set healthy */
971 /* insert empty cached text as necessary */
972 if (!s
) elt
->private.spare
.ptr
= cpystr ("");
974 /* clean up overview data */
975 if (ov
.from
) mail_free_address (&ov
.from
);
976 if (ov
.subject
) fs_give ((void **) &ov
.subject
);
981 /* Send OVER to NNTP server
982 * Accepts: mail stream
984 * Returns: T if success and overviews will follow, else NIL
987 long nntp_over (MAILSTREAM
*stream
,char *sequence
)
990 /* test for Netscape Collabra server */
991 if (EXTENSION
.over
&& LOCAL
->xover
&&
992 nntp_send (LOCAL
->nntpstream
,"OVER","0") == NNTPOVER
) {
993 /* "Netscape-Collabra/3.52 03615 NNTP" responds to the OVER command with
994 * a bogus "Subject:From:Date:Bytes:Lines" response followed by overviews
995 * which lack the Message-ID and References:. This violates the draft
996 * NNTP specification (draft-ietf-nntpext-base-18.txt as of this writing).
999 while ((s
= net_getline (LOCAL
->nntpstream
->netstream
)) && strcmp (s
,".")){
1000 if (!isdigit (*s
)) { /* is it that fetid piece of reptile dung? */
1001 EXTENSION
.over
= NIL
; /* sure smells like it */
1002 mm_log ("Working around Netscape Collabra bug",WARN
);
1004 fs_give ((void **) &s
); /* flush the overview */
1006 if (s
) fs_give ((void **) &s
);
1007 /* don't do this test again */
1008 if (EXTENSION
.over
) LOCAL
->xover
= NIL
;
1010 if (EXTENSION
.over
) /* have OVER extension? */
1011 return (nntp_send (LOCAL
->nntpstream
,"OVER",sequence
) == NNTPOVER
) ?
1013 if (LOCAL
->xover
) /* try the experiment extension then */
1014 switch ((int) nntp_send (LOCAL
->nntpstream
,"XOVER",sequence
)) {
1015 case NNTPOVER
: /* got an overview? */
1017 case NNTPBADCMD
: /* unknown command? */
1018 LOCAL
->xover
= NIL
; /* disable future XOVER attempts */
1023 /* Parse OVERVIEW struct from cached NNTP OVER response
1024 * Accepts: struct to load
1025 * cached OVER response
1027 * Returns: T if success, NIL if fail
1030 long nntp_parse_overview (OVERVIEW
*ov
,char *text
,MESSAGECACHE
*elt
)
1033 /* nothing in overview yet */
1034 memset ((void *) ov
,0,sizeof (OVERVIEW
));
1035 /* no cached data */
1036 if (!(text
&& *text
)) return NIL
;
1037 ov
->subject
= cpystr (text
); /* make hackable copy of overview */
1038 /* find end of Subject */
1039 if (t
= strchr (ov
->subject
,'\t')) {
1040 *t
++ = '\0'; /* tie off Subject, point to From */
1041 /* find end of From */
1042 if (ov
->date
= strchr (t
,'\t')) {
1043 *ov
->date
++ = '\0'; /* tie off From, point to Date */
1044 /* load internaldate too */
1045 if (!elt
->day
) mail_parse_date (elt
,ov
->date
);
1047 rfc822_parse_adrlist (&ov
->from
,t
,BADHOST
);
1048 /* find end of Date */
1049 if (ov
->message_id
= strchr (ov
->date
,'\t')) {
1050 /* tie off Date, point to Message-ID */
1051 *ov
->message_id
++ = '\0';
1052 /* find end of Message-ID */
1053 if (ov
->references
= strchr (ov
->message_id
,'\t')) {
1054 /* tie off Message-ID, point to References */
1055 *ov
->references
++ = '\0';
1056 /* fine end of References */
1057 if (t
= strchr (ov
->references
,'\t')) {
1058 *t
++ = '\0'; /* tie off References, point to octet size */
1059 /* parse size of message in octets */
1060 ov
->optional
.octets
= atol (t
);
1061 /* find end of size */
1062 if (t
= strchr (t
,'\t')) {
1063 /* parse size of message in lines */
1064 ov
->optional
.lines
= atol (++t
);
1066 if (ov
->optional
.xref
= strchr (t
,'\t'))
1067 *ov
->optional
.xref
++ = '\0';
1074 return ov
->references
? T
: NIL
;
1077 /* NNTP fetch header as text
1078 * Accepts: mail stream
1080 * pointer to return size
1082 * Returns: header text
1085 char *nntp_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *size
,
1088 char tmp
[MAILTMPLEN
];
1092 if ((flags
& FT_UID
) && !(msgno
= mail_msgno (stream
,msgno
))) return "";
1093 /* have header text? */
1094 if (!(elt
= mail_elt (stream
,msgno
))->private.msg
.header
.text
.data
) {
1095 sprintf (tmp
,"%lu",mail_uid (stream
,msgno
));
1096 /* get header text */
1097 switch (nntp_send (LOCAL
->nntpstream
,"HEAD",tmp
)) {
1099 if (f
= netmsg_slurp (LOCAL
->nntpstream
->netstream
,size
,NIL
)) {
1100 fread (elt
->private.msg
.header
.text
.data
=
1101 (unsigned char *) fs_get ((size_t) *size
+ 3),
1102 (size_t) 1,(size_t) *size
,f
);
1103 fclose (f
); /* flush temp file */
1104 /* tie off header with extra CRLF and NUL */
1105 elt
->private.msg
.header
.text
.data
[*size
] = '\015';
1106 elt
->private.msg
.header
.text
.data
[++*size
] = '\012';
1107 elt
->private.msg
.header
.text
.data
[++*size
] = '\0';
1108 elt
->private.msg
.header
.text
.size
= *size
;
1109 elt
->valid
= T
; /* make elt valid now */
1112 /* fall into default case */
1113 default: /* failed, mark as deleted and empty */
1114 elt
->valid
= elt
->deleted
= T
;
1115 case NNTPSOFTFATAL
: /* don't mark deleted if stream dead */
1116 *size
= elt
->private.msg
.header
.text
.size
= 0;
1120 /* just return size of text */
1121 else *size
= elt
->private.msg
.header
.text
.size
;
1122 return elt
->private.msg
.header
.text
.data
?
1123 (char *) elt
->private.msg
.header
.text
.data
: "";
1127 * Accepts: mail stream
1129 * pointer to stringstruct to initialize
1131 * Returns: T if successful, else NIL
1134 long nntp_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
)
1136 char tmp
[MAILTMPLEN
];
1138 INIT (bs
,mail_string
,(void *) "",0);
1139 if ((flags
& FT_UID
) && !(msgno
= mail_msgno (stream
,msgno
))) return NIL
;
1140 elt
= mail_elt (stream
,msgno
);
1141 /* different message, flush cache */
1142 if (LOCAL
->txt
&& (LOCAL
->msgno
!= msgno
)) {
1143 fclose (LOCAL
->txt
);
1146 LOCAL
->msgno
= msgno
; /* note cached message */
1147 if (!LOCAL
->txt
) { /* have file for this message? */
1148 sprintf (tmp
,"%lu",elt
->private.uid
);
1149 switch (nntp_send (LOCAL
->nntpstream
,"BODY",tmp
)) {
1151 if (LOCAL
->txt
= netmsg_slurp (LOCAL
->nntpstream
->netstream
,
1152 &LOCAL
->txtsize
,NIL
)) break;
1153 /* fall into default case */
1154 default: /* failed, mark as deleted */
1156 case NNTPSOFTFATAL
: /* don't mark deleted if stream dead */
1160 if (!(flags
& FT_PEEK
)) { /* mark seen if needed */
1162 mm_flags (stream
,elt
->msgno
);
1164 INIT (bs
,file_string
,(void *) LOCAL
->txt
,LOCAL
->txtsize
);
1168 /* NNTP fetch article from message ID (for news: URL support)
1169 * Accepts: mail stream
1171 * pointer to return total message size
1172 * pointer to return file size
1173 * Returns: FILE * to message if successful, else NIL
1176 FILE *nntp_article (MAILSTREAM
*stream
,char *msgid
,unsigned long *size
,
1177 unsigned long *hsiz
)
1179 return (nntp_send (LOCAL
->nntpstream
,"ARTICLE",msgid
) == NNTPARTICLE
) ?
1180 netmsg_slurp (LOCAL
->nntpstream
->netstream
,size
,hsiz
) : NIL
;
1184 /* NNTP per-message modify flag
1185 * Accepts: MAIL stream
1186 * message cache element
1189 void nntp_flagmsg (MAILSTREAM
*stream
,MESSAGECACHE
*elt
)
1191 if (!LOCAL
->dirty
) { /* only bother checking if not dirty yet */
1192 if (elt
->valid
) { /* if done, see if deleted changed */
1193 if (elt
->sequence
!= elt
->deleted
) LOCAL
->dirty
= T
;
1194 elt
->sequence
= T
; /* leave the sequence set */
1196 /* note current setting of deleted flag */
1197 else elt
->sequence
= elt
->deleted
;
1201 /* NNTP search messages
1202 * Accepts: mail stream
1206 * Returns: T on success, NIL on failure
1209 long nntp_search (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*pgm
,long flags
)
1215 /* make sure that charset is good */
1216 if (msg
= utf8_badcharset (charset
)) {
1217 MM_LOG (msg
,ERROR
); /* output error */
1218 fs_give ((void **) &msg
);
1221 utf8_searchpgm (pgm
,charset
);
1222 if (flags
& SO_OVERVIEW
) { /* only if specified to use overview */
1223 /* identify messages that will be searched */
1224 for (i
= 1; i
<= stream
->nmsgs
; ++i
)
1225 mail_elt (stream
,i
)->sequence
= nntp_search_msg (stream
,i
,pgm
,NIL
);
1226 nntp_overview (stream
,NIL
); /* load the overview cache */
1228 /* init in case no overview at cleanup */
1229 memset ((void *) &ov
,0,sizeof (OVERVIEW
));
1230 /* otherwise do default search */
1231 for (i
= 1; i
<= stream
->nmsgs
; ++i
) {
1232 if (((flags
& SO_OVERVIEW
) && ((elt
= mail_elt (stream
,i
))->sequence
) &&
1233 nntp_parse_overview (&ov
,(char *) elt
->private.spare
.ptr
,elt
)) ?
1234 nntp_search_msg (stream
,i
,pgm
,&ov
) :
1235 mail_search_msg (stream
,i
,NIL
,pgm
)) {
1236 if (flags
& SE_UID
) mm_searched (stream
,mail_uid (stream
,i
));
1237 else { /* mark as searched, notify mail program */
1238 mail_elt (stream
,i
)->searched
= T
;
1239 if (!stream
->silent
) mm_searched (stream
,i
);
1242 /* clean up overview data */
1243 if (ov
.from
) mail_free_address (&ov
.from
);
1244 if (ov
.subject
) fs_give ((void **) &ov
.subject
);
1249 /* NNTP search message
1250 * Accepts: MAIL stream
1253 * overview to search (NIL means preliminary pass)
1254 * Returns: T if found, NIL otherwise
1257 long nntp_search_msg (MAILSTREAM
*stream
,unsigned long msgno
,SEARCHPGM
*pgm
,
1261 unsigned long now
= (unsigned long) time (0);
1262 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1266 if (pgm
->msgno
|| pgm
->uid
) { /* message set searches */
1268 /* message sequences */
1269 if (set
= pgm
->msgno
) { /* must be inside this sequence */
1270 while (set
) { /* run down until find matching range */
1271 if (set
->last
? ((msgno
< set
->first
) || (msgno
> set
->last
)) :
1272 msgno
!= set
->first
) set
= set
->next
;
1275 if (!set
) return NIL
; /* not found within sequence */
1277 if (set
= pgm
->uid
) { /* must be inside this sequence */
1278 unsigned long uid
= mail_uid (stream
,msgno
);
1279 while (set
) { /* run down until find matching range */
1280 if (set
->last
? ((uid
< set
->first
) || (uid
> set
->last
)) :
1281 uid
!= set
->first
) set
= set
->next
;
1284 if (!set
) return NIL
; /* not found within sequence */
1288 /* Fast data searches */
1290 if ((pgm
->answered
&& !elt
->answered
) ||
1291 (pgm
->unanswered
&& elt
->answered
) ||
1292 (pgm
->deleted
&& !elt
->deleted
) ||
1293 (pgm
->undeleted
&& elt
->deleted
) ||
1294 (pgm
->draft
&& !elt
->draft
) ||
1295 (pgm
->undraft
&& elt
->draft
) ||
1296 (pgm
->flagged
&& !elt
->flagged
) ||
1297 (pgm
->unflagged
&& elt
->flagged
) ||
1298 (pgm
->recent
&& !elt
->recent
) ||
1299 (pgm
->old
&& elt
->recent
) ||
1300 (pgm
->seen
&& !elt
->seen
) ||
1301 (pgm
->unseen
&& elt
->seen
)) return NIL
;
1303 if ((pgm
->keyword
&& !mail_search_keyword (stream
,elt
,pgm
->keyword
,LONGT
)) ||
1304 (pgm
->unkeyword
&& mail_search_keyword (stream
,elt
,pgm
->unkeyword
,NIL
)))
1306 if (ov
) { /* only do this if real searching */
1309 if ((pgm
->larger
&& (ov
->optional
.octets
<= pgm
->larger
)) ||
1310 (pgm
->smaller
&& (ov
->optional
.octets
>= pgm
->smaller
))) return NIL
;
1312 if ((pgm
->sentbefore
|| pgm
->senton
|| pgm
->sentsince
||
1313 pgm
->before
|| pgm
->on
|| pgm
->since
) &&
1314 (!mail_parse_date (&delt
,ov
->date
) ||
1315 !(d
= mail_shortdate (delt
.year
,delt
.month
,delt
.day
)) ||
1316 (pgm
->sentbefore
&& (d
>= pgm
->sentbefore
)) ||
1317 (pgm
->senton
&& (d
!= pgm
->senton
)) ||
1318 (pgm
->sentsince
&& (d
< pgm
->sentsince
)) ||
1319 (pgm
->before
&& (d
>= pgm
->before
)) ||
1320 (pgm
->on
&& (d
!= pgm
->on
)) ||
1321 (pgm
->since
&& (d
< pgm
->since
)))) return NIL
;
1322 if (pgm
->older
|| pgm
->younger
) {
1323 unsigned long msgd
= mail_longdate (elt
);
1324 if (pgm
->older
&& msgd
> (now
- pgm
->older
)) return NIL
;
1325 if (pgm
->younger
&& msgd
< (now
- pgm
->younger
)) return NIL
;
1327 if ((pgm
->from
&& !mail_search_addr (ov
->from
,pgm
->from
)) ||
1328 (pgm
->subject
&& !mail_search_header_text (ov
->subject
,pgm
->subject
))||
1330 !mail_search_header_text (ov
->message_id
,pgm
->message_id
)) ||
1332 !mail_search_header_text (ov
->references
,pgm
->references
)))
1336 /* envelope searches */
1337 if (pgm
->bcc
|| pgm
->cc
|| pgm
->to
|| pgm
->return_path
|| pgm
->sender
||
1338 pgm
->reply_to
|| pgm
->in_reply_to
|| pgm
->newsgroups
||
1340 ENVELOPE
*env
= mail_fetchenvelope (stream
,msgno
);
1341 if (!env
) return NIL
; /* no envelope obtained */
1342 /* search headers */
1343 if ((pgm
->bcc
&& !mail_search_addr (env
->bcc
,pgm
->bcc
)) ||
1344 (pgm
->cc
&& !mail_search_addr (env
->cc
,pgm
->cc
)) ||
1345 (pgm
->to
&& !mail_search_addr (env
->to
,pgm
->to
)))
1347 /* These criteria are not supported by IMAP and have to be emulated */
1348 if ((pgm
->return_path
&&
1349 !mail_search_addr (env
->return_path
,pgm
->return_path
)) ||
1350 (pgm
->sender
&& !mail_search_addr (env
->sender
,pgm
->sender
)) ||
1351 (pgm
->reply_to
&& !mail_search_addr (env
->reply_to
,pgm
->reply_to
)) ||
1352 (pgm
->in_reply_to
&&
1353 !mail_search_header_text (env
->in_reply_to
,pgm
->in_reply_to
)) ||
1355 !mail_search_header_text (env
->newsgroups
,pgm
->newsgroups
)) ||
1356 (pgm
->followup_to
&&
1357 !mail_search_header_text (env
->followup_to
,pgm
->followup_to
)))
1361 /* search header lines */
1362 for (hdr
= pgm
->header
; hdr
; hdr
= hdr
->next
) {
1366 sth
.next
= stc
.next
= NIL
;/* only one at a time */
1367 sth
.text
.data
= hdr
->line
.data
;
1368 sth
.text
.size
= hdr
->line
.size
;
1369 /* get the header text */
1370 if ((t
= mail_fetch_header (stream
,msgno
,NIL
,&sth
,&s
.size
,
1371 FT_INTERNAL
| FT_PEEK
)) && strchr (t
,':')) {
1372 if (hdr
->text
.size
) { /* anything matches empty search string */
1373 /* non-empty, copy field data */
1374 s
.data
= (unsigned char *) fs_get (s
.size
+ 1);
1376 for (v
= (char *) s
.data
, e
= t
+ s
.size
; t
< e
;) switch (*t
) {
1377 default: /* non-continuation, skip leading field name */
1378 while ((t
< e
) && (*t
++ != ':'));
1379 if ((t
< e
) && (*t
== ':')) t
++;
1380 case '\t': case ' ': /* copy field data */
1381 while ((t
< e
) && (*t
!= '\015') && (*t
!= '\012')) *v
++ = *t
++;
1382 *v
++ = '\n'; /* tie off line */
1383 while (((*t
== '\015') || (*t
== '\012')) && (t
< e
)) t
++;
1385 /* calculate true size */
1386 s
.size
= v
- (char *) s
.data
;
1387 *v
= '\0'; /* tie off results */
1388 stc
.text
.data
= hdr
->text
.data
;
1389 stc
.text
.size
= hdr
->text
.size
;
1391 if (mail_search_header (&s
,&stc
)) fs_give ((void **) &s
.data
);
1392 else { /* search failed */
1393 fs_give ((void **) &s
.data
);
1398 else return NIL
; /* no matching header text */
1400 /* search strings */
1402 !mail_search_text (stream
,msgno
,NIL
,pgm
->text
,LONGT
))||
1403 (pgm
->body
&& !mail_search_text (stream
,msgno
,NIL
,pgm
->body
,NIL
)))
1406 /* logical conditions */
1407 for (or = pgm
->or; or; or = or->next
)
1408 if (!(nntp_search_msg (stream
,msgno
,or->first
,ov
) ||
1409 nntp_search_msg (stream
,msgno
,or->second
,ov
))) return NIL
;
1410 for (not = pgm
->not; not; not = not->next
)
1411 if (nntp_search_msg (stream
,msgno
,not->pgm
,ov
)) return NIL
;
1415 /* NNTP sort messages
1416 * Accepts: mail stream
1421 * Returns: vector of sorted message sequences or NIL if error
1424 unsigned long *nntp_sort (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*spg
,
1425 SORTPGM
*pgm
,long flags
)
1427 unsigned long i
,start
,last
;
1429 mailcache_t mailcache
= (mailcache_t
) mail_parameters (NIL
,GET_CACHE
,NIL
);
1430 unsigned long *ret
= NIL
;
1431 sortresults_t sr
= (sortresults_t
) mail_parameters (NIL
,GET_SORTRESULTS
,NIL
);
1432 if (spg
) { /* only if a search needs to be done */
1433 int silent
= stream
->silent
;
1434 stream
->silent
= T
; /* don't pass up mm_searched() events */
1435 /* search for messages */
1436 mail_search_full (stream
,charset
,spg
,NIL
);
1437 stream
->silent
= silent
; /* restore silence state */
1439 /* initialize progress counters */
1440 pgm
->nmsgs
= pgm
->progress
.cached
= 0;
1441 /* pass 1: count messages to sort */
1442 for (i
= 1,start
= last
= 0; i
<= stream
->nmsgs
; ++i
)
1443 if (mail_elt (stream
,i
)->searched
) {
1445 /* have this in the sortcache already? */
1446 if (!((SORTCACHE
*) (*mailcache
) (stream
,i
,CH_SORTCACHE
))->date
) {
1447 /* no, record as last message */
1448 last
= mail_uid (stream
,i
);
1449 /* and as first too if needed */
1450 if (!start
) start
= last
;
1453 if (pgm
->nmsgs
) { /* pass 2: load sort cache */
1454 sc
= nntp_sort_loadcache (stream
,pgm
,start
,last
,flags
);
1455 /* pass 3: sort messages */
1456 if (!pgm
->abort
) ret
= mail_sort_cache (stream
,pgm
,sc
,flags
);
1457 fs_give ((void **) &sc
); /* don't need sort vector any more */
1459 /* empty sort results */
1460 else ret
= (unsigned long *) memset (fs_get (sizeof (unsigned long)),0,
1461 sizeof (unsigned long));
1462 /* also return via callback if requested */
1463 if (sr
) (*sr
) (stream
,ret
,pgm
->nmsgs
);
1467 /* Mail load sortcache
1468 * Accepts: mail stream, already searched
1473 * Returns: vector of sortcache pointers matching search
1476 SORTCACHE
**nntp_sort_loadcache (MAILSTREAM
*stream
,SORTPGM
*pgm
,
1477 unsigned long start
,unsigned long last
,
1481 char c
,*s
,*t
,*v
,tmp
[MAILTMPLEN
];
1486 mailcache_t mailcache
= (mailcache_t
) mail_parameters (NIL
,GET_CACHE
,NIL
);
1487 /* verify that the sortpgm is OK */
1488 for (pg
= pgm
; pg
; pg
= pg
->next
) switch (pg
->function
) {
1489 case SORTARRIVAL
: /* sort by arrival date */
1490 case SORTSIZE
: /* sort by message size */
1491 case SORTDATE
: /* sort by date */
1492 case SORTFROM
: /* sort by first from */
1493 case SORTSUBJECT
: /* sort by subject */
1495 case SORTTO
: /* sort by first to */
1496 mm_notify (stream
,"[NNTPSORT] Can't do To-field sorting in NNTP",WARN
);
1498 case SORTCC
: /* sort by first cc */
1499 mm_notify (stream
,"[NNTPSORT] Can't do cc-field sorting in NNTP",WARN
);
1502 fatal ("Unknown sort function");
1505 if (start
) { /* messages need to be loaded in sortcache? */
1506 /* yes, build range */
1507 if (start
!= last
) sprintf (tmp
,"%lu-%lu",start
,last
);
1508 else sprintf (tmp
,"%lu",start
);
1509 /* get it from the NNTP server */
1510 if (!nntp_over (stream
,tmp
)) return mail_sort_loadcache (stream
,pgm
);
1511 while ((s
= net_getline (LOCAL
->nntpstream
->netstream
)) && strcmp (s
,".")){
1512 /* death to embedded newlines */
1513 for (t
= v
= s
; c
= *v
++;) if ((c
!= '\012') && (c
!= '\015')) *t
++ = c
;
1514 *t
++ = '\0'; /* tie off resulting string */
1515 /* parse OVER response */
1516 if ((i
= mail_msgno (stream
,atol (s
))) &&
1517 (t
= strchr (s
,'\t')) && (v
= strchr (++t
,'\t'))) {
1518 *v
++ = '\0'; /* tie off subject */
1519 /* put stripped subject in sortcache */
1520 r
= (SORTCACHE
*) (*mailcache
) (stream
,i
,CH_SORTCACHE
);
1521 r
->refwd
= mail_strip_subject (t
,&r
->subject
);
1522 if (t
= strchr (v
,'\t')) {
1523 *t
++ = '\0'; /* tie off from */
1524 if (adr
= rfc822_parse_address (&adr
,adr
,&v
,BADHOST
,0)) {
1525 r
->from
= adr
->mailbox
;
1527 mail_free_address (&adr
);
1529 if (v
= strchr (t
,'\t')) {
1530 *v
++ = '\0'; /* tie off date */
1531 if (mail_parse_date (&telt
,t
)) r
->date
= mail_longdate (&telt
);
1532 if ((v
= strchr (v
,'\t')) && (v
= strchr (++v
,'\t')))
1533 r
->size
= atol (++v
);
1537 fs_give ((void **) &s
);
1539 if (s
) fs_give ((void **) &s
);
1542 /* calculate size of sortcache index */
1543 i
= pgm
->nmsgs
* sizeof (SORTCACHE
*);
1544 /* instantiate the index */
1545 sc
= (SORTCACHE
**) memset (fs_get ((size_t) i
),0,(size_t) i
);
1546 /* see what needs to be loaded */
1547 for (i
= 1; !pgm
->abort
&& (i
<= stream
->nmsgs
); i
++)
1548 if ((mail_elt (stream
,i
))->searched
) {
1549 sc
[pgm
->progress
.cached
++] =
1550 r
= (SORTCACHE
*) (*mailcache
) (stream
,i
,CH_SORTCACHE
);
1551 r
->pgm
= pgm
; /* note sort program */
1552 r
->num
= (flags
& SE_UID
) ? mail_uid (stream
,i
) : i
;
1553 if (!r
->date
) r
->date
= r
->num
;
1554 if (!r
->arrival
) r
->arrival
= mail_uid (stream
,i
);
1555 if (!r
->size
) r
->size
= 1;
1556 if (!r
->from
) r
->from
= cpystr ("");
1557 if (!r
->to
) r
->to
= cpystr ("");
1558 if (!r
->cc
) r
->cc
= cpystr ("");
1559 if (!r
->subject
) r
->subject
= cpystr ("");
1565 /* NNTP thread messages
1566 * Accepts: mail stream
1571 * Returns: thread node tree
1574 THREADNODE
*nntp_thread (MAILSTREAM
*stream
,char *type
,char *charset
,
1575 SEARCHPGM
*spg
,long flags
)
1577 return mail_thread_msgs (stream
,type
,charset
,spg
,flags
,nntp_sort
);
1580 /* NNTP ping mailbox
1581 * Accepts: MAIL stream
1582 * Returns: T if stream alive, else NIL
1585 long nntp_ping (MAILSTREAM
*stream
)
1587 return (nntp_send (LOCAL
->nntpstream
,"STAT",NIL
) != NNTPSOFTFATAL
);
1591 /* NNTP check mailbox
1592 * Accepts: MAIL stream
1595 void nntp_check (MAILSTREAM
*stream
)
1597 /* never do if no updates */
1598 if (LOCAL
->dirty
) newsrc_write (LOCAL
->name
,stream
);
1603 /* NNTP expunge mailbox
1604 * Accepts: MAIL stream
1605 * sequence to expunge if non-NIL
1607 * Returns: T if success, NIL if failure
1610 long nntp_expunge (MAILSTREAM
*stream
,char *sequence
,long options
)
1612 if (!stream
->silent
) mm_log ("Expunge ignored on readonly mailbox",NIL
);
1616 /* NNTP copy message(s)
1617 * Accepts: MAIL stream
1619 * destination mailbox
1621 * Returns: T if copy successful, else NIL
1624 long nntp_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
)
1626 mailproxycopy_t pc
=
1627 (mailproxycopy_t
) mail_parameters (stream
,GET_MAILPROXYCOPY
,NIL
);
1628 if (pc
) return (*pc
) (stream
,sequence
,mailbox
,options
);
1629 mm_log ("Copy not valid for NNTP",ERROR
);
1634 /* NNTP append message from stringstruct
1635 * Accepts: MAIL stream
1636 * destination mailbox
1639 * Returns: T if append successful, else NIL
1642 long nntp_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
)
1644 mm_log ("Append not valid for NNTP",ERROR
);
1648 /* NNTP open connection
1649 * Accepts: network driver
1654 * Returns: SEND stream on success, NIL on failure
1657 SENDSTREAM
*nntp_open_full (NETDRIVER
*dv
,char **hostlist
,char *service
,
1658 unsigned long port
,long options
)
1660 SENDSTREAM
*stream
= NIL
;
1661 NETSTREAM
*netstream
= NIL
;
1663 char tmp
[MAILTMPLEN
];
1665 NETDRIVER
*ssld
= (NETDRIVER
*) mail_parameters (NIL
,GET_SSLDRIVER
,NIL
);
1666 sslstart_t stls
= (sslstart_t
) mail_parameters (NIL
,GET_SSLSTART
,NIL
);
1667 if (!(hostlist
&& *hostlist
)) mm_log ("Missing NNTP service host",ERROR
);
1668 else do { /* try to open connection */
1669 sprintf (tmp
,"{%.200s/%.20s}",*hostlist
,service
? service
: "nntp");
1670 if (!mail_valid_net_parse (tmp
,&mb
) || mb
.anoflag
) {
1671 sprintf (tmp
,"Invalid host specifier: %.80s",*hostlist
);
1674 else { /* light tryssl flag if requested */
1675 mb
.trysslflag
= (options
& NOP_TRYSSL
) ? T
: NIL
;
1677 if (mb
.port
) port
= mb
.port
;
1678 else if (!port
) port
= nntp_port
? nntp_port
: NNTPTCPPORT
;
1679 if (netstream
= /* try to open ordinary connection */
1680 net_open (&mb
,dv
,port
,
1681 (NETDRIVER
*) mail_parameters (NIL
,GET_SSLDRIVER
,NIL
),
1682 "*nntps",nntp_sslport
? nntp_sslport
: NNTPSSLPORT
)) {
1683 stream
= (SENDSTREAM
*) fs_get (sizeof (SENDSTREAM
));
1684 /* initialize stream */
1685 memset ((void *) stream
,0,sizeof (SENDSTREAM
));
1686 stream
->netstream
= netstream
;
1687 stream
->host
= cpystr ((long) mail_parameters (NIL
,GET_TRUSTDNS
,NIL
) ?
1688 net_host (netstream
) : mb
.host
);
1689 stream
->debug
= (mb
.dbgflag
|| (options
& NOP_DEBUG
)) ? T
: NIL
;
1690 if (mb
.loser
) stream
->loser
= T
;
1691 /* process greeting */
1692 switch ((int) nntp_reply (stream
)) {
1693 case NNTPGREET
: /* allow posting */
1695 mm_notify (NIL
,stream
->reply
+ 4,(long) NIL
);
1697 case NNTPGREETNOPOST
: /* posting not allowed, must be readonly */
1701 mm_log (stream
->reply
,ERROR
);
1702 stream
= nntp_close (stream
);
1707 } while (!stream
&& *++hostlist
);
1709 /* get extensions */
1710 if (stream
&& extok
)
1711 extok
= nntp_extensions (stream
,(mb
.secflag
? AU_SECURE
: NIL
) |
1712 (mb
.authuser
[0] ? AU_AUTHUSER
: NIL
));
1713 if (stream
&& !dv
&& stls
&& NNTP
.ext
.starttls
&&
1714 !mb
.sslflag
&& !mb
.notlsflag
&&
1715 (nntp_send_work (stream
,"STARTTLS",NNTP
.ext
.multidomain
? mb
.host
: NIL
)
1717 mb
.tlsflag
= T
; /* TLS OK, get into TLS at this end */
1718 stream
->netstream
->dtb
= ssld
;
1720 if (stream
->netstream
->stream
=
1721 (*stls
) (stream
->netstream
->stream
,mb
.host
,
1722 SSL_METHOD(mb
) | (mb
.novalidate
? NET_NOVALIDATECERT
:NIL
)))
1723 extok
= nntp_extensions (stream
,(mb
.secflag
? AU_SECURE
: NIL
) |
1724 (mb
.authuser
[0] ? AU_AUTHUSER
: NIL
));
1726 sprintf (tmp
,"Unable to negotiate TLS with this server: %.80s",mb
.host
);
1728 /* close without doing QUIT */
1729 if (stream
->netstream
) net_close (stream
->netstream
);
1730 stream
->netstream
= NIL
;
1731 stream
= nntp_close (stream
);
1734 else if (mb
.tlsflag
) { /* user specified /tls but can't do it */
1735 mm_log ("Unable to negotiate TLS with this server",ERROR
);
1738 if (stream
) { /* have a session? */
1739 if (mb
.user
[0]) { /* yes, have user name? */
1740 if ((long) mail_parameters (NIL
,GET_TRUSTDNS
,NIL
)) {
1741 /* remote name for authentication */
1742 strncpy (mb
.host
,(long) mail_parameters (NIL
,GET_SASLUSESPTRNAME
,NIL
) ?
1743 net_remotehost (netstream
) : net_host (netstream
),
1745 mb
.host
[NETMAXHOST
-1] = '\0';
1747 if (!nntp_send_auth_work (stream
,&mb
,tmp
,NIL
))
1748 stream
= nntp_close (stream
);
1750 /* authenticate if no-post and not readonly */
1751 else if (!(NNTP
.post
|| (options
& NOP_READONLY
) ||
1752 nntp_send_auth (stream
,NIL
))) stream
= nntp_close (stream
);
1755 /* in case server demands MODE READER */
1756 if (stream
) switch ((int) nntp_send_work (stream
,"MODE","READER")) {
1760 case NNTPGREETNOPOST
:
1763 case NNTPWANTAUTH
: /* server wants auth first, do so and retry */
1764 case NNTPWANTAUTH2
: /* remote name for authentication */
1765 if ((long) mail_parameters (NIL
,GET_TRUSTDNS
,NIL
)) {
1766 strncpy (mb
.host
,(long) mail_parameters (NIL
,GET_SASLUSESPTRNAME
,NIL
) ?
1767 net_remotehost (netstream
) : net_host (netstream
),NETMAXHOST
-1);
1768 mb
.host
[NETMAXHOST
-1] = '\0';
1770 if (nntp_send_auth_work (stream
,&mb
,tmp
,NIL
))
1771 switch ((int) nntp_send (stream
,"MODE","READER")) {
1775 case NNTPGREETNOPOST
:
1779 else stream
= nntp_close (stream
);
1782 if (stream
) { /* looks like we have a stream? */
1783 /* yes, make sure can post if not readonly */
1784 if (!(NNTP
.post
|| (options
& NOP_READONLY
))) stream
= nntp_close (stream
);
1785 else if (extok
) nntp_extensions (stream
,(mb
.secflag
? AU_SECURE
: NIL
) |
1786 (mb
.authuser
[0] ? AU_AUTHUSER
: NIL
));
1793 * authenticator flags
1794 * Returns: T on success, NIL on failure
1797 long nntp_extensions (SENDSTREAM
*stream
,long flags
)
1801 /* zap all old extensions */
1802 memset (&NNTP
.ext
,0,sizeof (NNTP
.ext
));
1803 if (stream
->loser
) return NIL
;/* nothing at all for losers */
1804 /* get server extensions */
1805 switch ((int) nntp_send_work (stream
,"LIST","EXTENSIONS")) {
1806 case NNTPEXTOK
: /* what NNTP base spec says */
1807 case NNTPGLIST
: /* some servers do this instead */
1809 default: /* no LIST EXTENSIONS on this server */
1812 NNTP
.ext
.ok
= T
; /* server offers extensions */
1813 while ((t
= net_getline (stream
->netstream
)) && (t
[1] || (*t
!= '.'))) {
1814 if (stream
->debug
) mm_dlog (t
);
1815 /* get optional capability arguments */
1816 if (args
= strchr (t
,' ')) *args
++ = '\0';
1817 if (!compare_cstring (t
,"LISTGROUP")) NNTP
.ext
.listgroup
= T
;
1818 else if (!compare_cstring (t
,"OVER")) NNTP
.ext
.over
= T
;
1819 else if (!compare_cstring (t
,"HDR")) NNTP
.ext
.hdr
= T
;
1820 else if (!compare_cstring (t
,"PAT")) NNTP
.ext
.pat
= T
;
1821 else if (!compare_cstring (t
,"STARTTLS")) NNTP
.ext
.starttls
= T
;
1822 else if (!compare_cstring (t
,"MULTIDOMAIN")) NNTP
.ext
.multidomain
= T
;
1824 else if (!compare_cstring (t
,"AUTHINFO") && args
) {
1826 for (args
= strtok_r (args
," ",&r
); args
; args
= strtok_r (NIL
," ",&r
)) {
1827 if (!compare_cstring (args
,"USER")) NNTP
.ext
.authuser
= T
;
1828 else if (((args
[0] == 'S') || (args
[0] == 's')) &&
1829 ((args
[1] == 'A') || (args
[1] == 'a')) &&
1830 ((args
[2] == 'S') || (args
[2] == 's')) &&
1831 ((args
[3] == 'L') || (args
[3] == 'l')) && (args
[4] == ':'))
1834 if (sasl
) { /* if SASL, look up authenticators */
1835 for (sasl
= strtok_r (sasl
,",",&r
); sasl
; sasl
= strtok_r (NIL
,",",&r
))
1836 if ((i
= mail_lookup_auth_name (sasl
,flags
)) &&
1837 (--i
< MAXAUTHENTICATORS
))
1838 NNTP
.ext
.sasl
|= (1 << i
);
1839 /* disable LOGIN if PLAIN also advertised */
1840 if ((i
= mail_lookup_auth_name ("PLAIN",NIL
)) &&
1841 (--i
< MAXAUTHENTICATORS
) && (NNTP
.ext
.sasl
& (1 << i
)) &&
1842 (i
= mail_lookup_auth_name ("LOGIN",NIL
)) &&
1843 (--i
< MAXAUTHENTICATORS
)) NNTP
.ext
.sasl
&= ~(1 << i
);
1846 fs_give ((void **) &t
);
1848 if (t
) { /* flush end of text indicator */
1849 if (stream
->debug
) mm_dlog (t
);
1850 fs_give ((void **) &t
);
1855 /* NNTP close connection
1856 * Accepts: SEND stream
1857 * Returns: NIL always
1860 SENDSTREAM
*nntp_close (SENDSTREAM
*stream
)
1862 if (stream
) { /* send "QUIT" */
1863 if (stream
->netstream
) nntp_send (stream
,"QUIT",NIL
);
1864 /* do close actions */
1865 if (stream
->netstream
) net_close (stream
->netstream
);
1866 if (stream
->host
) fs_give ((void **) &stream
->host
);
1867 if (stream
->reply
) fs_give ((void **) &stream
->reply
);
1868 fs_give ((void **) &stream
);/* flush the stream */
1873 /* NNTP deliver news
1874 * Accepts: SEND stream
1877 * Returns: T on success, NIL on failure
1880 long nntp_mail (SENDSTREAM
*stream
,ENVELOPE
*env
,BODY
*body
)
1884 char *s
,path
[MAILTMPLEN
],tmp
[SENDBUFLEN
+1];
1887 buf
.f
= nntp_soutr
; /* initialize buffer */
1888 buf
.s
= stream
->netstream
;
1889 buf
.end
= (buf
.beg
= buf
.cur
= tmp
) + SENDBUFLEN
;
1890 tmp
[SENDBUFLEN
] = '\0'; /* must have additional null guard byte */
1891 /* Gabba gabba hey, we need some brain damage to send netnews!!!
1893 * First, we give ourselves a frontal lobotomy, and put in some UUCP
1894 * syntax. It doesn't matter that it's completely bogus UUCP, and
1895 * that UUCP has nothing to do with anything we're doing. It's been
1896 * alleged that "Path: not-for-mail" is also acceptable, but we won't
1897 * make assumptions unless the user says so.
1899 * Second, we bop ourselves on the head with a ball-peen hammer. How
1900 * dare we be so presumptious as to insert a *comment* in a Date:
1901 * header line. Why, we were actually trying to be nice to a human
1902 * by giving a symbolic timezone (such as PST) in addition to a
1903 * numeric timezone (such as -0800). But the gods of news transport
1904 * will have none of this. Unix weenies, tried and true, rule!!!
1906 * Third, Netscape Collabra server doesn't give the NNTPWANTAUTH error
1907 * until after requesting and receiving the entire message. So we can't
1908 * call rely upon nntp_send() to do the auth retry.
1910 /* RFC-1036 requires this cretinism */
1911 sprintf (path
,"Path: %s!%s\015\012",net_localhost (stream
->netstream
),
1912 env
->sender
? env
->sender
->mailbox
:
1913 (env
->from
? env
->from
->mailbox
: "not-for-mail"));
1914 /* here's another cretinism */
1915 if (s
= strstr (env
->date
," (")) *s
= NIL
;
1916 do if ((ret
= nntp_send_work (stream
,"POST",NIL
)) == NNTPREADY
)
1917 /* output data, return success status */
1918 ret
= (net_soutr (stream
->netstream
,
1919 nntp_hidepath
? "Path: not-for-mail\015\012" : path
) &&
1920 rfc822_output_full (&buf
,env
,body
,T
)) ?
1921 nntp_send_work (stream
,".",NIL
) :
1922 nntp_fake (stream
,"NNTP connection broken (message text)");
1923 while (((ret
== NNTPWANTAUTH
) || (ret
== NNTPWANTAUTH2
)) &&
1924 nntp_send_auth (stream
,LONGT
));
1925 if (s
) *s
= ' '; /* put the comment in the date back */
1926 if (ret
== NNTPOK
) return LONGT
;
1927 else if (ret
< 400) { /* if not an error reply */
1928 sprintf (tmp
,"Unexpected NNTP posting reply code %ld",ret
);
1929 mm_log (tmp
,WARN
); /* so someone looks at this eventually */
1930 if ((ret
>= 200) && (ret
< 300)) return LONGT
;
1935 /* NNTP send command
1936 * Accepts: SEND stream
1938 * Returns: reply code
1941 long nntp_send (SENDSTREAM
*stream
,char *command
,char *args
)
1944 switch ((int) (ret
= nntp_send_work (stream
,command
,args
))) {
1945 case NNTPWANTAUTH
: /* authenticate and retry */
1947 if (nntp_send_auth (stream
,LONGT
))
1948 ret
= nntp_send_work (stream
,command
,args
);
1949 else { /* we're probably hosed, nuke the session */
1950 nntp_send (stream
,"QUIT",NIL
);
1951 /* close net connection */
1952 if (stream
->netstream
) net_close (stream
->netstream
);
1953 stream
->netstream
= NIL
;
1955 default: /* all others just return */
1962 /* NNTP send command worker routine
1963 * Accepts: SEND stream
1965 * Returns: reply code
1968 long nntp_send_work (SENDSTREAM
*stream
,char *command
,char *args
)
1971 char *s
= (char *) fs_get (strlen (command
) + (args
? strlen (args
) + 1 : 0)
1973 if (!stream
->netstream
) ret
= nntp_fake (stream
,"NNTP connection lost");
1974 else { /* build the complete command */
1975 if (args
) sprintf (s
,"%s %s",command
,args
);
1976 else strcpy (s
,command
);
1977 if (stream
->debug
) mail_dlog (s
,stream
->sensitive
);
1978 strcat (s
,"\015\012");
1979 /* send the command */
1980 ret
= net_soutr (stream
->netstream
,s
) ? nntp_reply (stream
) :
1981 nntp_fake (stream
,"NNTP connection broken (command)");
1983 fs_give ((void **) &s
);
1987 /* NNTP send authentication if needed
1988 * Accepts: SEND stream
1989 * flags (non-NIL to get new extensions)
1990 * Returns: T if need to redo command, NIL otherwise
1993 long nntp_send_auth (SENDSTREAM
*stream
,long flags
)
1996 char tmp
[MAILTMPLEN
];
1997 /* remote name for authentication */
1998 sprintf (tmp
,"{%.200s/nntp",(long) mail_parameters (NIL
,GET_TRUSTDNS
,NIL
) ?
1999 ((long) mail_parameters (NIL
,GET_SASLUSESPTRNAME
,NIL
) ?
2000 net_remotehost (stream
->netstream
) : net_host (stream
->netstream
)):
2002 if (stream
->netstream
->dtb
==
2003 (NETDRIVER
*) mail_parameters (NIL
,GET_SSLDRIVER
,NIL
))
2004 strcat (tmp
,"/ssl");
2005 strcat (tmp
,"}<none>");
2006 mail_valid_net_parse (tmp
,&mb
);
2007 return nntp_send_auth_work (stream
,&mb
,tmp
,flags
);
2010 /* NNTP send authentication worker routine
2011 * Accepts: SEND stream
2013 * scratch buffer of length MAILTMPLEN
2014 * flags (non-NIL to get new extensions)
2015 * Returns: T if authenticated, NIL otherwise
2018 long nntp_send_auth_work (SENDSTREAM
*stream
,NETMBX
*mb
,char *pwd
,long flags
)
2020 unsigned long trial
,auths
;
2021 char tmp
[MAILTMPLEN
],usr
[MAILTMPLEN
];
2025 /* try SASL first */
2026 for (auths
= NNTP
.ext
.sasl
, stream
->saslcancel
= NIL
;
2027 !ret
&& stream
->netstream
&& auths
&&
2028 (at
= mail_lookup_auth (find_rightmost_bit (&auths
) + 1)); ) {
2029 if (lsterr
) { /* previous authenticator failed? */
2030 sprintf (tmp
,"Retrying using %s authentication after %.80s",
2033 fs_give ((void **) &lsterr
);
2035 trial
= 0; /* initial trial count */
2036 tmp
[0] = '\0'; /* empty buffer */
2037 if (stream
->netstream
) do {
2039 sprintf (tmp
,"Retrying %s authentication after %.80s",at
->name
,lsterr
);
2041 fs_give ((void **) &lsterr
);
2043 stream
->saslcancel
= NIL
;
2044 if (nntp_send (stream
,"AUTHINFO SASL",at
->name
) == NNTPCHALLENGE
) {
2045 /* hide client authentication responses */
2046 if (!(at
->flags
& AU_SECURE
)) stream
->sensitive
= T
;
2047 if ((*at
->client
) (nntp_challenge
,nntp_response
,"nntp",mb
,stream
,
2049 if (stream
->replycode
== NNTPAUTHED
) ret
= LONGT
;
2050 /* if main program requested cancellation */
2051 else if (!trial
) mm_log ("NNTP Authentication cancelled",ERROR
);
2053 stream
->sensitive
= NIL
;/* unhide */
2055 /* remember response if error and no cancel */
2056 if (!ret
&& trial
) lsterr
= cpystr (stream
->reply
);
2057 } while (!ret
&& stream
->netstream
&& trial
&&
2058 (trial
< nntp_maxlogintrials
));
2061 if (lsterr
) { /* SAIL failed? */
2062 if (!stream
->saslcancel
) { /* don't do this if a cancel */
2063 sprintf (tmp
,"Can not authenticate to NNTP server: %.80s",lsterr
);
2066 fs_give ((void **) &lsterr
);
2068 else if (mb
->secflag
) /* no SASL, can't do /secure */
2069 mm_log ("Can't do secure authentication with this server",ERROR
);
2070 else if (mb
->authuser
[0]) /* or /authuser */
2071 mm_log ("Can't do /authuser with this server",ERROR
);
2072 /* Always try AUTHINFO USER, even if NNTP.ext.authuser isn't set. There
2073 * are servers that require it but don't return it as an extension.
2075 else for (trial
= 0, pwd
[0] = 'x';
2076 !ret
&& pwd
[0] && (trial
< nntp_maxlogintrials
) &&
2077 stream
->netstream
; ) {
2078 pwd
[0] = NIL
; /* get user name and password */
2079 mm_login (mb
,usr
,pwd
,trial
++);
2080 /* do the authentication */
2081 if (pwd
[0]) switch ((int) nntp_send_work (stream
,"AUTHINFO USER",usr
)) {
2082 case NNTPBADCMD
: /* give up if unrecognized command */
2083 mm_log (NNTP
.ext
.authuser
? stream
->reply
:
2084 "Can't do AUTHINFO USER to this server",ERROR
);
2085 trial
= nntp_maxlogintrials
;
2087 case NNTPAUTHED
: /* successful authentication */
2088 ret
= LONGT
; /* guess no password was needed */
2090 case NNTPWANTPASS
: /* wants password */
2091 stream
->sensitive
= T
; /* hide this command */
2092 if (nntp_send_work (stream
,"AUTHINFO PASS",pwd
) == NNTPAUTHED
)
2093 ret
= LONGT
; /* password OK */
2094 stream
->sensitive
= NIL
; /* unhide */
2095 if (ret
) break; /* OK if successful */
2096 default: /* authentication failed */
2097 mm_log (stream
->reply
,WARN
);
2098 if (trial
== nntp_maxlogintrials
)
2099 mm_log ("Too many NNTP authentication failures",ERROR
);
2101 /* user refused to give a password */
2102 else mm_log ("Login aborted",ERROR
);
2104 memset (pwd
,0,MAILTMPLEN
); /* erase password */
2105 /* get new extensions if needed */
2106 if (ret
&& flags
) nntp_extensions (stream
,(mb
->secflag
? AU_SECURE
: NIL
) |
2107 (mb
->authuser
[0] ? AU_AUTHUSER
: NIL
));
2111 /* Get challenge to authenticator in binary
2113 * pointer to returned size
2114 * Returns: challenge or NIL if not challenge
2117 void *nntp_challenge (void *s
,unsigned long *len
)
2119 char tmp
[MAILTMPLEN
];
2121 SENDSTREAM
*stream
= (SENDSTREAM
*) s
;
2122 if ((stream
->replycode
== NNTPCHALLENGE
) &&
2123 !(ret
= rfc822_base64 ((unsigned char *) stream
->reply
+ 4,
2124 strlen (stream
->reply
+ 4),len
))) {
2125 sprintf (tmp
,"NNTP SERVER BUG (invalid challenge): %.80s",stream
->reply
+4);
2132 /* Send authenticator response in BASE64
2133 * Accepts: MAIL stream
2136 * Returns: T, always
2139 long nntp_response (void *s
,char *response
,unsigned long size
)
2141 SENDSTREAM
*stream
= (SENDSTREAM
*) s
;
2144 if (response
) { /* make CRLFless BASE64 string */
2146 for (t
= (char *) rfc822_binary ((void *) response
,size
,&i
),u
= t
,j
= 0;
2147 j
< i
; j
++) if (t
[j
] > ' ') *u
++ = t
[j
];
2148 *u
= '\0'; /* tie off string */
2149 i
= nntp_send_work (stream
,t
,NIL
);
2150 fs_give ((void **) &t
);
2152 else i
= nntp_send_work (stream
,"",NIL
);
2154 else { /* abort requested */
2155 i
= nntp_send_work (stream
,"*",NIL
);
2156 stream
->saslcancel
= T
; /* mark protocol-requested SASL cancel */
2162 * Accepts: SEND stream
2163 * Returns: reply code
2166 long nntp_reply (SENDSTREAM
*stream
)
2168 /* flush old reply */
2169 if (stream
->reply
) fs_give ((void **) &stream
->reply
);
2171 if (!(stream
->reply
= net_getline (stream
->netstream
)))
2172 return nntp_fake (stream
,"NNTP connection broken (response)");
2173 if (stream
->debug
) mm_dlog (stream
->reply
);
2174 /* handle continuation by recursion */
2175 if (stream
->reply
[3] == '-') return nntp_reply (stream
);
2176 /* return response code */
2177 return stream
->replycode
= atol (stream
->reply
);
2181 /* NNTP set fake error
2182 * Accepts: SEND stream
2184 * Returns: error code
2187 long nntp_fake (SENDSTREAM
*stream
,char *text
)
2189 if (stream
->netstream
) { /* close net connection if still open */
2190 net_close (stream
->netstream
);
2191 stream
->netstream
= NIL
;
2193 /* flush any old reply */
2194 if (stream
->reply
) fs_give ((void **) &stream
->reply
);
2195 /* set up pseudo-reply string */
2196 stream
->reply
= (char *) fs_get (20+strlen (text
));
2197 sprintf (stream
->reply
,"%ld %s",NNTPSOFTFATAL
,text
);
2198 return NNTPSOFTFATAL
; /* return error code */
2204 * Returns: T on success, NIL on failure
2207 long nntp_soutr (void *stream
,char *s
)
2210 /* "." on first line */
2211 if (s
[0] == '.') net_soutr (stream
,".");
2212 /* find lines beginning with a "." */
2213 while (t
= strstr (s
,"\015\012.")) {
2214 c
= *(t
+= 3); /* remember next character after "." */
2215 *t
= '\0'; /* tie off string */
2217 if (!net_soutr (stream
,s
)) return NIL
;
2218 *t
= c
; /* restore delimiter */
2219 s
= t
- 1; /* push pointer up to the "." */
2221 /* output remainder of text */
2222 return *s
? net_soutr (stream
,s
) : T
;