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