1
/* ========================================================================
2
* Copyright 1988-2006 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 December 2006
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",(int) 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)
1214
if (charset && *charset && /* convert if charset not US-ASCII or UTF-8 */
1215
!(((charset[0] == 'U') || (charset[0] == 'u')) &&
1216
((((charset[1] == 'S') || (charset[1] == 's')) &&
1217
(charset[2] == '-') &&
1218
((charset[3] == 'A') || (charset[3] == 'a')) &&
1219
((charset[4] == 'S') || (charset[4] == 's')) &&
1220
((charset[5] == 'C') || (charset[5] == 'c')) &&
1221
((charset[6] == 'I') || (charset[6] == 'i')) &&
1222
((charset[7] == 'I') || (charset[7] == 'i')) && !charset[8]) ||
1223
(((charset[1] == 'T') || (charset[1] == 't')) &&
1224
((charset[2] == 'F') || (charset[2] == 'f')) &&
1225
(charset[3] == '-') && (charset[4] == '8') && !charset[5])))) {
1226
if (utf8_text (NIL,charset,NIL,T)) utf8_searchpgm (pgm,charset);
1227
else return NIL; /* charset unknown */
1229
if (flags & SO_OVERVIEW) { /* only if specified to use overview */
1230
/* identify messages that will be searched */
1231
for (i = 1; i <= stream->nmsgs; ++i)
1232
mail_elt (stream,i)->sequence = nntp_search_msg (stream,i,pgm,NIL);
1233
nntp_overview (stream,NIL); /* load the overview cache */
1235
/* init in case no overview at cleanup */
1236
memset ((void *) &ov,0,sizeof (OVERVIEW));
1237
/* otherwise do default search */
1238
for (i = 1; i <= stream->nmsgs; ++i) {
1239
if (((flags & SO_OVERVIEW) && ((elt = mail_elt (stream,i))->sequence) &&
1240
nntp_parse_overview (&ov,(char *) elt->private.spare.ptr,elt)) ?
1241
nntp_search_msg (stream,i,pgm,&ov) :
1242
mail_search_msg (stream,i,NIL,pgm)) {
1243
if (flags & SE_UID) mm_searched (stream,mail_uid (stream,i));
1244
else { /* mark as searched, notify mail program */
1245
mail_elt (stream,i)->searched = T;
1246
if (!stream->silent) mm_searched (stream,i);
1249
/* clean up overview data */
1250
if (ov.from) mail_free_address (&ov.from);
1251
if (ov.subject) fs_give ((void **) &ov.subject);
1256
/* NNTP search message
1257
* Accepts: MAIL stream
1260
* overview to search (NIL means preliminary pass)
1261
* Returns: T if found, NIL otherwise
1264
long nntp_search_msg (MAILSTREAM *stream,unsigned long msgno,SEARCHPGM *pgm,
1268
MESSAGECACHE *elt = mail_elt (stream,msgno);
1272
if (pgm->msgno || pgm->uid) { /* message set searches */
1274
/* message sequences */
1275
if (set = pgm->msgno) { /* must be inside this sequence */
1276
while (set) { /* run down until find matching range */
1277
if (set->last ? ((msgno < set->first) || (msgno > set->last)) :
1278
msgno != set->first) set = set->next;
1281
if (!set) return NIL; /* not found within sequence */
1283
if (set = pgm->uid) { /* must be inside this sequence */
1284
unsigned long uid = mail_uid (stream,msgno);
1285
while (set) { /* run down until find matching range */
1286
if (set->last ? ((uid < set->first) || (uid > set->last)) :
1287
uid != set->first) set = set->next;
1290
if (!set) return NIL; /* not found within sequence */
1294
/* Fast data searches */
1296
if ((pgm->answered && !elt->answered) ||
1297
(pgm->unanswered && elt->answered) ||
1298
(pgm->deleted && !elt->deleted) ||
1299
(pgm->undeleted && elt->deleted) ||
1300
(pgm->draft && !elt->draft) ||
1301
(pgm->undraft && elt->draft) ||
1302
(pgm->flagged && !elt->flagged) ||
1303
(pgm->unflagged && elt->flagged) ||
1304
(pgm->recent && !elt->recent) ||
1305
(pgm->old && elt->recent) ||
1306
(pgm->seen && !elt->seen) ||
1307
(pgm->unseen && elt->seen)) return NIL;
1309
if ((pgm->keyword && !mail_search_keyword (stream,elt,pgm->keyword,LONGT)) ||
1310
(pgm->unkeyword && mail_search_keyword (stream,elt,pgm->unkeyword,NIL)))
1312
if (ov) { /* only do this if real searching */
1315
if ((pgm->larger && (ov->optional.octets <= pgm->larger)) ||
1316
(pgm->smaller && (ov->optional.octets >= pgm->smaller))) return NIL;
1318
if ((pgm->sentbefore || pgm->senton || pgm->sentsince ||
1319
(pgm->before || pgm->on || pgm->since)) &&
1320
(!mail_parse_date (&delt,ov->date) ||
1321
!(d = mail_shortdate (delt.year,delt.month,delt.day)) ||
1322
(pgm->sentbefore && (d >= pgm->sentbefore)) ||
1323
(pgm->senton && (d != pgm->senton)) ||
1324
(pgm->sentsince && (d < pgm->sentsince)) ||
1325
(pgm->before && (d >= pgm->before)) ||
1326
(pgm->on && (d != pgm->on)) ||
1327
(pgm->since && (d < pgm->since)))) return NIL;
1328
if ((pgm->from && !mail_search_addr (ov->from,pgm->from)) ||
1329
(pgm->subject && !mail_search_header_text (ov->subject,pgm->subject))||
1331
!mail_search_header_text (ov->message_id,pgm->message_id)) ||
1333
!mail_search_header_text (ov->references,pgm->references)))
1337
/* envelope searches */
1338
if (pgm->bcc || pgm->cc || pgm->to || pgm->return_path || pgm->sender ||
1339
pgm->reply_to || pgm->in_reply_to || pgm->newsgroups ||
1341
ENVELOPE *env = mail_fetchenvelope (stream,msgno);
1342
if (!env) return NIL; /* no envelope obtained */
1343
/* search headers */
1344
if ((pgm->bcc && !mail_search_addr (env->bcc,pgm->bcc)) ||
1345
(pgm->cc && !mail_search_addr (env->cc,pgm->cc)) ||
1346
(pgm->to && !mail_search_addr (env->to,pgm->to)))
1348
/* These criteria are not supported by IMAP and have to be emulated */
1349
if ((pgm->return_path &&
1350
!mail_search_addr (env->return_path,pgm->return_path)) ||
1351
(pgm->sender && !mail_search_addr (env->sender,pgm->sender)) ||
1352
(pgm->reply_to && !mail_search_addr (env->reply_to,pgm->reply_to)) ||
1353
(pgm->in_reply_to &&
1354
!mail_search_header_text (env->in_reply_to,pgm->in_reply_to)) ||
1356
!mail_search_header_text (env->newsgroups,pgm->newsgroups)) ||
1357
(pgm->followup_to &&
1358
!mail_search_header_text (env->followup_to,pgm->followup_to)))
1362
/* search header lines */
1363
for (hdr = pgm->header; hdr; hdr = hdr->next) {
1367
sth.next = stc.next = NIL;/* only one at a time */
1368
sth.text.data = hdr->line.data;
1369
sth.text.size = hdr->line.size;
1370
/* get the header text */
1371
if ((t = mail_fetch_header (stream,msgno,NIL,&sth,&s.size,
1372
FT_INTERNAL | FT_PEEK)) && strchr (t,':')) {
1373
if (hdr->text.size) { /* anything matches empty search string */
1374
/* non-empty, copy field data */
1375
s.data = (unsigned char *) fs_get (s.size + 1);
1377
for (v = (char *) s.data, e = t + s.size; t < e;) switch (*t) {
1378
default: /* non-continuation, skip leading field name */
1379
while ((t < e) && (*t++ != ':'));
1380
if ((t < e) && (*t == ':')) t++;
1381
case '\t': case ' ': /* copy field data */
1382
while ((t < e) && (*t != '\015') && (*t != '\012')) *v++ = *t++;
1383
*v++ = '\n'; /* tie off line */
1384
while (((*t == '\015') || (*t == '\012')) && (t < e)) t++;
1386
/* calculate true size */
1387
s.size = v - (char *) s.data;
1388
*v = '\0'; /* tie off results */
1389
stc.text.data = hdr->text.data;
1390
stc.text.size = hdr->text.size;
1392
if (mail_search_header (&s,&stc)) fs_give ((void **) &s.data);
1393
else { /* search failed */
1394
fs_give ((void **) &s.data);
1399
else return NIL; /* no matching header text */
1401
/* search strings */
1403
!mail_search_text (stream,msgno,NIL,pgm->text,LONGT))||
1404
(pgm->body && !mail_search_text (stream,msgno,NIL,pgm->body,NIL)))
1407
/* logical conditions */
1408
for (or = pgm->or; or; or = or->next)
1409
if (!(nntp_search_msg (stream,msgno,or->first,ov) ||
1410
nntp_search_msg (stream,msgno,or->second,ov))) return NIL;
1411
for (not = pgm->not; not; not = not->next)
1412
if (nntp_search_msg (stream,msgno,not->pgm,ov)) return NIL;
1416
/* NNTP sort messages
1417
* Accepts: mail stream
1422
* Returns: vector of sorted message sequences or NIL if error
1425
unsigned long *nntp_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
1426
SORTPGM *pgm,long flags)
1428
unsigned long i,start,last;
1430
mailcache_t mailcache = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
1431
unsigned long *ret = NIL;
1432
sortresults_t sr = (sortresults_t) mail_parameters (NIL,GET_SORTRESULTS,NIL);
1433
if (spg) { /* only if a search needs to be done */
1434
int silent = stream->silent;
1435
stream->silent = T; /* don't pass up mm_searched() events */
1436
/* search for messages */
1437
mail_search_full (stream,charset,spg,NIL);
1438
stream->silent = silent; /* restore silence state */
1440
/* initialize progress counters */
1441
pgm->nmsgs = pgm->progress.cached = 0;
1442
/* pass 1: count messages to sort */
1443
for (i = 1,start = last = 0; i <= stream->nmsgs; ++i)
1444
if (mail_elt (stream,i)->searched) {
1446
/* have this in the sortcache already? */
1447
if (!((SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE))->date) {
1448
/* no, record as last message */
1449
last = mail_uid (stream,i);
1450
/* and as first too if needed */
1451
if (!start) start = last;
1454
if (pgm->nmsgs) { /* pass 2: load sort cache */
1455
sc = nntp_sort_loadcache (stream,pgm,start,last,flags);
1456
/* pass 3: sort messages */
1457
if (!pgm->abort) ret = mail_sort_cache (stream,pgm,sc,flags);
1458
fs_give ((void **) &sc); /* don't need sort vector any more */
1460
/* empty sort results */
1461
else ret = (unsigned long *) memset (fs_get (sizeof (unsigned long)),0,
1462
sizeof (unsigned long));
1463
/* also return via callback if requested */
1464
if (sr) (*sr) (stream,ret,pgm->nmsgs);
1468
/* Mail load sortcache
1469
* Accepts: mail stream, already searched
1474
* Returns: vector of sortcache pointers matching search
1477
SORTCACHE **nntp_sort_loadcache (MAILSTREAM *stream,SORTPGM *pgm,
1478
unsigned long start,unsigned long last,
1482
char c,*s,*t,*v,tmp[MAILTMPLEN];
1487
mailcache_t mailcache = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
1488
/* verify that the sortpgm is OK */
1489
for (pg = pgm; pg; pg = pg->next) switch (pg->function) {
1490
case SORTARRIVAL: /* sort by arrival date */
1491
case SORTSIZE: /* sort by message size */
1492
case SORTDATE: /* sort by date */
1493
case SORTFROM: /* sort by first from */
1494
case SORTSUBJECT: /* sort by subject */
1496
case SORTTO: /* sort by first to */
1497
mm_notify (stream,"[NNTPSORT] Can't do To-field sorting in NNTP",WARN);
1499
case SORTCC: /* sort by first cc */
1500
mm_notify (stream,"[NNTPSORT] Can't do cc-field sorting in NNTP",WARN);
1503
fatal ("Unknown sort function");
1506
if (start) { /* messages need to be loaded in sortcache? */
1507
/* yes, build range */
1508
if (start != last) sprintf (tmp,"%lu-%lu",start,last);
1509
else sprintf (tmp,"%lu",start);
1510
/* get it from the NNTP server */
1511
if (!nntp_over (stream,tmp)) return mail_sort_loadcache (stream,pgm);
1512
while ((s = net_getline (LOCAL->nntpstream->netstream)) && strcmp (s,".")){
1513
/* death to embedded newlines */
1514
for (t = v = s; c = *v++;) if ((c != '\012') && (c != '\015')) *t++ = c;
1515
*t++ = '\0'; /* tie off resulting string */
1516
/* parse OVER response */
1517
if ((i = mail_msgno (stream,atol (s))) &&
1518
(t = strchr (s,'\t')) && (v = strchr (++t,'\t'))) {
1519
*v++ = '\0'; /* tie off subject */
1520
/* put stripped subject in sortcache */
1521
r = (SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE);
1522
r->refwd = mail_strip_subject (t,&r->subject);
1523
if (t = strchr (v,'\t')) {
1524
*t++ = '\0'; /* tie off from */
1525
if (adr = rfc822_parse_address (&adr,adr,&v,BADHOST,0)) {
1526
r->from = adr->mailbox;
1528
mail_free_address (&adr);
1530
if (v = strchr (t,'\t')) {
1531
*v++ = '\0'; /* tie off date */
1532
if (mail_parse_date (&telt,t)) r->date = mail_longdate (&telt);
1533
if ((v = strchr (v,'\t')) && (v = strchr (++v,'\t')))
1534
r->size = atol (++v);
1538
fs_give ((void **) &s);
1540
if (s) fs_give ((void **) &s);
1543
/* calculate size of sortcache index */
1544
i = pgm->nmsgs * sizeof (SORTCACHE *);
1545
/* instantiate the index */
1546
sc = (SORTCACHE **) memset (fs_get ((size_t) i),0,(size_t) i);
1547
/* see what needs to be loaded */
1548
for (i = 1; !pgm->abort && (i <= stream->nmsgs); i++)
1549
if ((mail_elt (stream,i))->searched) {
1550
sc[pgm->progress.cached++] =
1551
r = (SORTCACHE *) (*mailcache) (stream,i,CH_SORTCACHE);
1552
r->pgm = pgm; /* note sort program */
1553
r->num = (flags & SE_UID) ? mail_uid (stream,i) : i;
1554
if (!r->date) r->date = r->num;
1555
if (!r->arrival) r->arrival = mail_uid (stream,i);
1556
if (!r->size) r->size = 1;
1557
if (!r->from) r->from = cpystr ("");
1558
if (!r->to) r->to = cpystr ("");
1559
if (!r->cc) r->cc = cpystr ("");
1560
if (!r->subject) r->subject = cpystr ("");
1566
/* NNTP thread messages
1567
* Accepts: mail stream
1572
* Returns: thread node tree
1575
THREADNODE *nntp_thread (MAILSTREAM *stream,char *type,char *charset,
1576
SEARCHPGM *spg,long flags)
1578
return mail_thread_msgs (stream,type,charset,spg,flags,nntp_sort);
1581
/* NNTP ping mailbox
1582
* Accepts: MAIL stream
1583
* Returns: T if stream alive, else NIL
1586
long nntp_ping (MAILSTREAM *stream)
1588
return (nntp_send (LOCAL->nntpstream,"STAT",NIL) != NNTPSOFTFATAL);
1592
/* NNTP check mailbox
1593
* Accepts: MAIL stream
1596
void nntp_check (MAILSTREAM *stream)
1598
/* never do if no updates */
1599
if (LOCAL->dirty) newsrc_write (LOCAL->name,stream);
1604
/* NNTP expunge mailbox
1605
* Accepts: MAIL stream
1606
* sequence to expunge if non-NIL
1608
* Returns: T if success, NIL if failure
1611
long nntp_expunge (MAILSTREAM *stream,char *sequence,long options)
1613
if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
1617
/* NNTP copy message(s)
1618
* Accepts: MAIL stream
1620
* destination mailbox
1622
* Returns: T if copy successful, else NIL
1625
long nntp_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
1627
mailproxycopy_t pc =
1628
(mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
1629
if (pc) return (*pc) (stream,sequence,mailbox,options);
1630
mm_log ("Copy not valid for NNTP",ERROR);
1635
/* NNTP append message from stringstruct
1636
* Accepts: MAIL stream
1637
* destination mailbox
1640
* Returns: T if append successful, else NIL
1643
long nntp_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
1645
mm_log ("Append not valid for NNTP",ERROR);
1649
/* NNTP open connection
1650
* Accepts: network driver
1655
* Returns: SEND stream on success, NIL on failure
1658
SENDSTREAM *nntp_open_full (NETDRIVER *dv,char **hostlist,char *service,
1659
unsigned long port,long options)
1661
SENDSTREAM *stream = NIL;
1662
NETSTREAM *netstream = NIL;
1664
char tmp[MAILTMPLEN];
1666
NETDRIVER *ssld = (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL);
1667
sslstart_t stls = (sslstart_t) mail_parameters (NIL,GET_SSLSTART,NIL);
1668
if (!(hostlist && *hostlist)) mm_log ("Missing NNTP service host",ERROR);
1669
else do { /* try to open connection */
1670
sprintf (tmp,"{%.200s/%.20s}",*hostlist,service ? service : "nntp");
1671
if (!mail_valid_net_parse (tmp,&mb) || mb.anoflag) {
1672
sprintf (tmp,"Invalid host specifier: %.80s",*hostlist);
1675
else { /* light tryssl flag if requested */
1676
mb.trysslflag = (options & NOP_TRYSSL) ? T : NIL;
1678
if (mb.port) port = mb.port;
1679
else if (!port) port = nntp_port ? nntp_port : NNTPTCPPORT;
1680
if (netstream = /* try to open ordinary connection */
1681
net_open (&mb,dv,port,
1682
(NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL),
1683
"*nntps",nntp_sslport ? nntp_sslport : NNTPSSLPORT)) {
1684
stream = (SENDSTREAM *) fs_get (sizeof (SENDSTREAM));
1685
/* initialize stream */
1686
memset ((void *) stream,0,sizeof (SENDSTREAM));
1687
stream->netstream = netstream;
1688
stream->host = cpystr ((int) mail_parameters (NIL,GET_TRUSTDNS,NIL) ?
1689
net_host (netstream) : mb.host);
1690
stream->debug = (mb.dbgflag || (options & NOP_DEBUG)) ? T : NIL;
1691
if (mb.loser) stream->loser = T;
1692
/* process greeting */
1693
switch ((int) nntp_reply (stream)) {
1694
case NNTPGREET: /* allow posting */
1696
mm_notify (NIL,stream->reply + 4,(long) NIL);
1698
case NNTPGREETNOPOST: /* posting not allowed, must be readonly */
1702
mm_log (stream->reply,ERROR);
1703
stream = nntp_close (stream);
1708
} while (!stream && *++hostlist);
1710
/* get extensions */
1711
if (stream && extok)
1712
extok = nntp_extensions (stream,(mb.secflag ? AU_SECURE : NIL) |
1713
(mb.authuser[0] ? AU_AUTHUSER : NIL));
1714
if (stream && !dv && stls && NNTP.ext.starttls &&
1715
!mb.sslflag && !mb.notlsflag &&
1716
(nntp_send_work (stream,"STARTTLS",NNTP.ext.multidomain ? mb.host : NIL)
1718
mb.tlsflag = T; /* TLS OK, get into TLS at this end */
1719
stream->netstream->dtb = ssld;
1721
if (stream->netstream->stream =
1722
(*stls) (stream->netstream->stream,mb.host,
1723
(mb.tlssslv23 ? NIL : NET_TLSCLIENT) |
1724
(mb.novalidate ? NET_NOVALIDATECERT:NIL)))
1725
extok = nntp_extensions (stream,(mb.secflag ? AU_SECURE : NIL) |
1726
(mb.authuser[0] ? AU_AUTHUSER : NIL));
1728
sprintf (tmp,"Unable to negotiate TLS with this server: %.80s",mb.host);
1730
/* close without doing QUIT */
1731
if (stream->netstream) net_close (stream->netstream);
1732
stream->netstream = NIL;
1733
stream = nntp_close (stream);
1736
else if (mb.tlsflag) { /* user specified /tls but can't do it */
1737
mm_log ("Unable to negotiate TLS with this server",ERROR);
1740
if (stream) { /* have a session? */
1741
if (mb.user[0]) { /* yes, have user name? */
1742
if ((int) mail_parameters (NIL,GET_TRUSTDNS,NIL)) {
1743
/* remote name for authentication */
1744
strncpy (mb.host,(int) mail_parameters (NIL,GET_SASLUSESPTRNAME,NIL) ?
1745
net_remotehost (netstream) : net_host (netstream),
1747
mb.host[NETMAXHOST-1] = '\0';
1749
if (!nntp_send_auth_work (stream,&mb,tmp,NIL))
1750
stream = nntp_close (stream);
1752
/* authenticate if no-post and not readonly */
1753
else if (!(NNTP.post || (options & NOP_READONLY) ||
1754
nntp_send_auth (stream,NIL))) stream = nntp_close (stream);
1757
/* in case server demands MODE READER */
1758
if (stream) switch ((int) nntp_send_work (stream,"MODE","READER")) {
1762
case NNTPGREETNOPOST:
1765
case NNTPWANTAUTH: /* server wants auth first, do so and retry */
1766
case NNTPWANTAUTH2: /* remote name for authentication */
1767
if ((int) mail_parameters (NIL,GET_TRUSTDNS,NIL)) {
1768
strncpy (mb.host,(int) mail_parameters (NIL,GET_SASLUSESPTRNAME,NIL) ?
1769
net_remotehost (netstream) : net_host (netstream),NETMAXHOST-1);
1770
mb.host[NETMAXHOST-1] = '\0';
1772
if (nntp_send_auth_work (stream,&mb,tmp,NIL))
1773
switch ((int) nntp_send (stream,"MODE","READER")) {
1777
case NNTPGREETNOPOST:
1781
else stream = nntp_close (stream);
1784
if (stream) { /* looks like we have a stream? */
1785
/* yes, make sure can post if not readonly */
1786
if (!(NNTP.post || (options & NOP_READONLY))) stream = nntp_close (stream);
1787
else if (extok) nntp_extensions (stream,(mb.secflag ? AU_SECURE : NIL) |
1788
(mb.authuser[0] ? AU_AUTHUSER : NIL));
1795
* authenticator flags
1796
* Returns: T on success, NIL on failure
1799
long nntp_extensions (SENDSTREAM *stream,long flags)
1803
/* zap all old extensions */
1804
memset (&NNTP.ext,0,sizeof (NNTP.ext));
1805
if (stream->loser) return NIL;/* nothing at all for losers */
1806
/* get server extensions */
1807
switch ((int) nntp_send_work (stream,"LIST","EXTENSIONS")) {
1808
case NNTPEXTOK: /* what NNTP base spec says */
1809
case NNTPGLIST: /* some servers do this instead */
1811
default: /* no LIST EXTENSIONS on this server */
1814
NNTP.ext.ok = T; /* server offers extensions */
1815
while ((t = net_getline (stream->netstream)) && (t[1] || (*t != '.'))) {
1816
if (stream->debug) mm_dlog (t);
1817
/* get optional capability arguments */
1818
if (args = strchr (t,' ')) *args++ = '\0';
1819
if (!compare_cstring (t,"LISTGROUP")) NNTP.ext.listgroup = T;
1820
else if (!compare_cstring (t,"OVER")) NNTP.ext.over = T;
1821
else if (!compare_cstring (t,"HDR")) NNTP.ext.hdr = T;
1822
else if (!compare_cstring (t,"PAT")) NNTP.ext.pat = T;
1823
else if (!compare_cstring (t,"STARTTLS")) NNTP.ext.starttls = T;
1824
else if (!compare_cstring (t,"MULTIDOMAIN")) NNTP.ext.multidomain = T;
1826
else if (!compare_cstring (t,"AUTHINFO") && args) {
1828
for (args = strtok (args," "); args; args = strtok (NIL," ")) {
1829
if (!compare_cstring (args,"USER")) NNTP.ext.authuser = T;
1830
else if (((args[0] == 'S') || (args[0] == 's')) &&
1831
((args[1] == 'A') || (args[1] == 'a')) &&
1832
((args[2] == 'S') || (args[2] == 's')) &&
1833
((args[3] == 'L') || (args[3] == 'l')) && (args[4] == ':'))
1836
if (sasl) { /* if SASL, look up authenticators */
1837
for (sasl = strtok (sasl,","); sasl; sasl = strtok (NIL,","))
1838
if ((i = mail_lookup_auth_name (sasl,flags)) &&
1839
(--i < MAXAUTHENTICATORS))
1840
NNTP.ext.sasl |= (1 << i);
1841
/* disable LOGIN if PLAIN also advertised */
1842
if ((i = mail_lookup_auth_name ("PLAIN",NIL)) &&
1843
(--i < MAXAUTHENTICATORS) && (NNTP.ext.sasl & (1 << i)) &&
1844
(i = mail_lookup_auth_name ("LOGIN",NIL)) &&
1845
(--i < MAXAUTHENTICATORS)) NNTP.ext.sasl &= ~(1 << i);
1848
fs_give ((void **) &t);
1850
if (t) { /* flush end of text indicator */
1851
if (stream->debug) mm_dlog (t);
1852
fs_give ((void **) &t);
1857
/* NNTP close connection
1858
* Accepts: SEND stream
1859
* Returns: NIL always
1862
SENDSTREAM *nntp_close (SENDSTREAM *stream)
1864
if (stream) { /* send "QUIT" */
1865
if (stream->netstream) nntp_send (stream,"QUIT",NIL);
1866
/* do close actions */
1867
if (stream->netstream) net_close (stream->netstream);
1868
if (stream->host) fs_give ((void **) &stream->host);
1869
if (stream->reply) fs_give ((void **) &stream->reply);
1870
fs_give ((void **) &stream);/* flush the stream */
1875
/* NNTP deliver news
1876
* Accepts: SEND stream
1879
* Returns: T on success, NIL on failure
1882
long nntp_mail (SENDSTREAM *stream,ENVELOPE *env,BODY *body)
1886
char *s,path[MAILTMPLEN],tmp[SENDBUFLEN+1];
1889
buf.f = nntp_soutr; /* initialize buffer */
1890
buf.s = stream->netstream;
1891
buf.end = (buf.beg = buf.cur = tmp) + SENDBUFLEN;
1892
tmp[SENDBUFLEN] = '\0'; /* must have additional null guard byte */
1893
/* Gabba gabba hey, we need some brain damage to send netnews!!!
1895
* First, we give ourselves a frontal lobotomy, and put in some UUCP
1896
* syntax. It doesn't matter that it's completely bogus UUCP, and
1897
* that UUCP has nothing to do with anything we're doing. It's been
1898
* alleged that "Path: not-for-mail" is also acceptable, but we won't
1899
* make assumptions unless the user says so.
1901
* Second, we bop ourselves on the head with a ball-peen hammer. How
1902
* dare we be so presumptious as to insert a *comment* in a Date:
1903
* header line. Why, we were actually trying to be nice to a human
1904
* by giving a symbolic timezone (such as PST) in addition to a
1905
* numeric timezone (such as -0800). But the gods of news transport
1906
* will have none of this. Unix weenies, tried and true, rule!!!
1908
* Third, Netscape Collabra server doesn't give the NNTPWANTAUTH error
1909
* until after requesting and receiving the entire message. So we can't
1910
* call rely upon nntp_send() to do the auth retry.
1912
/* RFC-1036 requires this cretinism */
1913
sprintf (path,"Path: %s!%s\015\012",net_localhost (stream->netstream),
1914
env->sender ? env->sender->mailbox :
1915
(env->from ? env->from->mailbox : "not-for-mail"));
1916
/* here's another cretinism */
1917
if (s = strstr (env->date," (")) *s = NIL;
1918
do if ((ret = nntp_send_work (stream,"POST",NIL)) == NNTPREADY)
1919
/* output data, return success status */
1920
ret = (net_soutr (stream->netstream,
1921
nntp_hidepath ? "Path: not-for-mail\015\012" : path) &&
1922
rfc822_output_full (&buf,env,body,T)) ?
1923
nntp_send_work (stream,".",NIL) :
1924
nntp_fake (stream,"NNTP connection broken (message text)");
1925
while (((ret == NNTPWANTAUTH) || (ret == NNTPWANTAUTH2)) &&
1926
nntp_send_auth (stream,LONGT));
1927
if (s) *s = ' '; /* put the comment in the date back */
1928
if (ret == NNTPOK) return LONGT;
1929
else if (ret < 400) { /* if not an error reply */
1930
sprintf (tmp,"Unexpected NNTP posting reply code %ld",ret);
1931
mm_log (tmp,WARN); /* so someone looks at this eventually */
1932
if ((ret >= 200) && (ret < 300)) return LONGT;
1937
/* NNTP send command
1938
* Accepts: SEND stream
1940
* Returns: reply code
1943
long nntp_send (SENDSTREAM *stream,char *command,char *args)
1946
switch ((int) (ret = nntp_send_work (stream,command,args))) {
1947
case NNTPWANTAUTH: /* authenticate and retry */
1949
if (nntp_send_auth (stream,LONGT))
1950
ret = nntp_send_work (stream,command,args);
1951
else { /* we're probably hosed, nuke the session */
1952
nntp_send (stream,"QUIT",NIL);
1953
/* close net connection */
1954
if (stream->netstream) net_close (stream->netstream);
1955
stream->netstream = NIL;
1957
default: /* all others just return */
1964
/* NNTP send command worker routine
1965
* Accepts: SEND stream
1967
* Returns: reply code
1970
long nntp_send_work (SENDSTREAM *stream,char *command,char *args)
1973
char *s = (char *) fs_get (strlen (command) + (args ? strlen (args) + 1 : 0)
1975
if (!stream->netstream) ret = nntp_fake (stream,"NNTP connection lost");
1976
else { /* build the complete command */
1977
if (args) sprintf (s,"%s %s",command,args);
1978
else strcpy (s,command);
1979
if (stream->debug) mail_dlog (s,stream->sensitive);
1980
strcat (s,"\015\012");
1981
/* send the command */
1982
ret = net_soutr (stream->netstream,s) ? nntp_reply (stream) :
1983
nntp_fake (stream,"NNTP connection broken (command)");
1985
fs_give ((void **) &s);
1989
/* NNTP send authentication if needed
1990
* Accepts: SEND stream
1991
* flags (non-NIL to get new extensions)
1992
* Returns: T if need to redo command, NIL otherwise
1995
long nntp_send_auth (SENDSTREAM *stream,long flags)
1998
char tmp[MAILTMPLEN];
1999
/* remote name for authentication */
2000
sprintf (tmp,"{%.200s/nntp",(int) mail_parameters (NIL,GET_TRUSTDNS,NIL) ?
2001
((int) mail_parameters (NIL,GET_SASLUSESPTRNAME,NIL) ?
2002
net_remotehost (stream->netstream) : net_host (stream->netstream)):
2004
if (stream->netstream->dtb ==
2005
(NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL))
2006
strcat (tmp,"/ssl");
2007
strcat (tmp,"}<none>");
2008
mail_valid_net_parse (tmp,&mb);
2009
return nntp_send_auth_work (stream,&mb,tmp,flags);
2012
/* NNTP send authentication worker routine
2013
* Accepts: SEND stream
2015
* scratch buffer of length MAILTMPLEN
2016
* flags (non-NIL to get new extensions)
2017
* Returns: T if authenticated, NIL otherwise
2020
long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags)
2022
unsigned long trial,auths;
2023
char tmp[MAILTMPLEN],usr[MAILTMPLEN];
2027
/* try SASL first */
2028
for (auths = NNTP.ext.sasl, stream->saslcancel = NIL;
2029
!ret && stream->netstream && auths &&
2030
(at = mail_lookup_auth (find_rightmost_bit (&auths) + 1)); ) {
2031
if (lsterr) { /* previous authenticator failed? */
2032
sprintf (tmp,"Retrying using %s authentication after %.80s",
2035
fs_give ((void **) &lsterr);
2037
trial = 0; /* initial trial count */
2038
tmp[0] = '\0'; /* empty buffer */
2039
if (stream->netstream) do {
2041
sprintf (tmp,"Retrying %s authentication after %.80s",at->name,lsterr);
2043
fs_give ((void **) &lsterr);
2045
stream->saslcancel = NIL;
2046
if (nntp_send (stream,"AUTHINFO SASL",at->name)) {
2047
/* hide client authentication responses */
2048
if (!(at->flags & AU_SECURE)) stream->sensitive = T;
2049
if ((*at->client) (nntp_challenge,nntp_response,"nntp",mb,stream,
2051
if (stream->replycode == NNTPAUTHED) ret = LONGT;
2052
/* if main program requested cancellation */
2053
else if (!trial) mm_log ("NNTP Authentication cancelled",ERROR);
2055
stream->sensitive = NIL;/* unhide */
2057
/* remember response if error and no cancel */
2058
if (!ret && trial) lsterr = cpystr (stream->reply);
2059
} while (!ret && stream->netstream && trial &&
2060
(trial < nntp_maxlogintrials));
2063
if (lsterr) { /* SAIL failed? */
2064
if (!stream->saslcancel) { /* don't do this if a cancel */
2065
sprintf (tmp,"Can not authenticate to NNTP server: %.80s",lsterr);
2068
fs_give ((void **) &lsterr);
2070
else if (mb->secflag) /* no SASL, can't do /secure */
2071
mm_log ("Can't do secure authentication with this server",ERROR);
2072
else if (mb->authuser[0]) /* or /authuser */
2073
mm_log ("Can't do /authuser with this server",ERROR);
2074
/* Always try AUTHINFO USER, even if NNTP.ext.authuser isn't set. There
2075
* are servers that require it but don't return it as an extension.
2077
else for (trial = 0, pwd[0] = 'x';
2078
!ret && pwd[0] && (trial < nntp_maxlogintrials) &&
2079
stream->netstream; ) {
2080
pwd[0] = NIL; /* get user name and password */
2081
mm_login (mb,usr,pwd,trial++);
2082
/* do the authentication */
2083
if (pwd[0]) switch ((int) nntp_send_work (stream,"AUTHINFO USER",usr)) {
2084
case NNTPBADCMD: /* give up if unrecognized command */
2085
mm_log (NNTP.ext.authuser ? stream->reply :
2086
"Can't do AUTHINFO USER to this server",ERROR);
2087
trial = nntp_maxlogintrials;
2089
case NNTPAUTHED: /* successful authentication */
2090
ret = LONGT; /* guess no password was needed */
2092
case NNTPWANTPASS: /* wants password */
2093
stream->sensitive = T; /* hide this command */
2094
if (nntp_send_work (stream,"AUTHINFO PASS",pwd) == NNTPAUTHED)
2095
ret = LONGT; /* password OK */
2096
stream->sensitive = NIL; /* unhide */
2097
if (ret) break; /* OK if successful */
2098
default: /* authentication failed */
2099
mm_log (stream->reply,WARN);
2100
if (trial == nntp_maxlogintrials)
2101
mm_log ("Too many NNTP authentication failures",ERROR);
2103
/* user refused to give a password */
2104
else mm_log ("Login aborted",ERROR);
2106
memset (pwd,0,MAILTMPLEN); /* erase password */
2107
/* get new extensions if needed */
2108
if (ret && flags) nntp_extensions (stream,(mb->secflag ? AU_SECURE : NIL) |
2109
(mb->authuser[0] ? AU_AUTHUSER : NIL));
2113
/* Get challenge to authenticator in binary
2115
* pointer to returned size
2116
* Returns: challenge or NIL if not challenge
2119
void *nntp_challenge (void *s,unsigned long *len)
2121
char tmp[MAILTMPLEN];
2123
SENDSTREAM *stream = (SENDSTREAM *) s;
2124
if ((stream->replycode == NNTPCHALLENGE) &&
2125
!(ret = rfc822_base64 ((unsigned char *) stream->reply + 4,
2126
strlen (stream->reply + 4),len))) {
2127
sprintf (tmp,"NNTP SERVER BUG (invalid challenge): %.80s",stream->reply+4);
2134
/* Send authenticator response in BASE64
2135
* Accepts: MAIL stream
2138
* Returns: T, always
2141
long nntp_response (void *s,char *response,unsigned long size)
2143
SENDSTREAM *stream = (SENDSTREAM *) s;
2146
if (response) { /* make CRLFless BASE64 string */
2148
for (t = (char *) rfc822_binary ((void *) response,size,&i),u = t,j = 0;
2149
j < i; j++) if (t[j] > ' ') *u++ = t[j];
2150
*u = '\0'; /* tie off string */
2151
i = nntp_send_work (stream,t,NIL);
2152
fs_give ((void **) &t);
2154
else i = nntp_send_work (stream,"",NIL);
2156
else { /* abort requested */
2157
i = nntp_send_work (stream,"*",NIL);
2158
stream->saslcancel = T; /* mark protocol-requested SASL cancel */
2164
* Accepts: SEND stream
2165
* Returns: reply code
2168
long nntp_reply (SENDSTREAM *stream)
2170
/* flush old reply */
2171
if (stream->reply) fs_give ((void **) &stream->reply);
2173
if (!(stream->reply = net_getline (stream->netstream)))
2174
return nntp_fake (stream,"NNTP connection broken (response)");
2175
if (stream->debug) mm_dlog (stream->reply);
2176
/* handle continuation by recursion */
2177
if (stream->reply[3] == '-') return nntp_reply (stream);
2178
/* return response code */
2179
return stream->replycode = atol (stream->reply);
2183
/* NNTP set fake error
2184
* Accepts: SEND stream
2186
* Returns: error code
2189
long nntp_fake (SENDSTREAM *stream,char *text)
2191
if (stream->netstream) { /* close net connection if still open */
2192
net_close (stream->netstream);
2193
stream->netstream = NIL;
2195
/* flush any old reply */
2196
if (stream->reply) fs_give ((void **) &stream->reply);
2197
/* set up pseudo-reply string */
2198
stream->reply = (char *) fs_get (20+strlen (text));
2199
sprintf (stream->reply,"%ld %s",NNTPSOFTFATAL,text);
2200
return NNTPSOFTFATAL; /* return error code */
2206
* Returns: T on success, NIL on failure
2209
long nntp_soutr (void *stream,char *s)
2212
/* "." on first line */
2213
if (s[0] == '.') net_soutr (stream,".");
2214
/* find lines beginning with a "." */
2215
while (t = strstr (s,"\015\012.")) {
2216
c = *(t += 3); /* remember next character after "." */
2217
*t = '\0'; /* tie off string */
2219
if (!net_soutr (stream,s)) return NIL;
2220
*t = c; /* restore delimiter */
2221
s = t - 1; /* push pointer up to the "." */
2223
/* output remainder of text */
2224
return *s ? net_soutr (stream,s) : T;