1 /* nntp.c
2 */
3 /* This software is copyrighted as detailed in the LICENSE file. */
4 
5 
6 #include "EXTERN.h"
7 #include "common.h"
8 #include "list.h"
9 #include "util.h"
10 #include "util2.h"
11 #include "init.h"
12 #include "trn.h"
13 #include "hash.h"
14 #include "ngdata.h"
15 #include "nntpclient.h"
16 #include "datasrc.h"
17 #include "INTERN.h"
18 #include "nntp.h"
19 #include "nntp.ih"
20 #include "EXTERN.h"
21 #include "rcln.h"
22 #include "cache.h"
23 #include "bits.h"
24 #include "head.h"
25 #include "term.h"
26 #include "final.h"
27 #include "artio.h"
28 #include "rcstuff.h"
29 
30 #ifdef SUPPORT_NNTP
31 
32 int
nntp_list(type,arg,len)33 nntp_list(type, arg, len)
34 char* type;
35 char* arg;
36 int len;
37 {
38     int ret;
39 #ifdef DEBUG /*$$*/
40     if (len && (debug & 1) && strcaseEQ(type,"active"))
41 	return -1;
42 #endif
43     if (len)
44 	sprintf(ser_line, "LIST %s %.*s", type, len, arg);
45     else if (strcaseEQ(type,"active"))
46 	strcpy(ser_line, "LIST");
47     else
48 	sprintf(ser_line, "LIST %s", type);
49     if (nntp_command(ser_line) <= 0)
50 	return -2;
51     if ((ret = nntp_check()) <= 0)
52 	return ret? ret : -1;
53     if (!len)
54 	return 1;
55     if ((ret = nntp_gets(ser_line, sizeof ser_line)) < 0)
56 	return ret;
57 #if defined(DEBUG) && defined(FLUSH)
58     if (debug & DEB_NNTP)
59 	printf("<%s\n", ser_line) FLUSH;
60 #endif
61     if (nntp_at_list_end(ser_line))
62 	return 0;
63     return 1;
64 }
65 
66 void
nntp_finish_list()67 nntp_finish_list()
68 {
69     int ret;
70     do {
71 	while ((ret = nntp_gets(ser_line, sizeof ser_line)) == 0) {
72 	    /* A line w/o a newline is too long to be the end of the
73 	    ** list, so grab the rest of this line and try again. */
74 	    while ((ret = nntp_gets(ser_line, sizeof ser_line)) == 0)
75 		;
76 	    if (ret < 0)
77 		return;
78 	}
79     } while (ret > 0 && !nntp_at_list_end(ser_line));
80 }
81 
82 /* try to access the specified group */
83 
84 int
nntp_group(group,gp)85 nntp_group(group, gp)
86 char* group;
87 NGDATA* gp;
88 {
89     sprintf(ser_line, "GROUP %s", group);
90     if (nntp_command(ser_line) <= 0)
91 	return -2;
92     switch (nntp_check()) {
93       case -2:
94 	return -2;
95       case -1:
96       case 0: {
97 	int ser_int = atoi(ser_line);
98 	if (ser_int != NNTP_NOSUCHGROUP_VAL
99 	 && ser_int != NNTP_SYNTAX_VAL) {
100 	    if (ser_int != NNTP_AUTH_NEEDED_VAL && ser_int != NNTP_ACCESS_VAL
101 	     && ser_int != NNTP_AUTH_REJECT_VAL) {
102 		fprintf(stderr, "\nServer's response to GROUP %s:\n%s\n",
103 			group, ser_line);
104 		return -1;
105 	    }
106 	}
107 	return 0;
108       }
109     }
110     if (gp) {
111 	long count, first, last;
112 
113 	(void) sscanf(ser_line,"%*d%ld%ld%ld",&count,&first,&last);
114 	/* NNTP mangles the high/low values when no articles are present. */
115 	if (!count)
116 	    gp->abs1st = gp->ngmax+1;
117 	else {
118 	    gp->abs1st = (ART_NUM)first;
119 	    gp->ngmax = (ART_NUM)last;
120 	}
121     }
122     return 1;
123 }
124 
125 /* check on an article's existence */
126 
127 int
nntp_stat(artnum)128 nntp_stat(artnum)
129 ART_NUM artnum;
130 {
131     sprintf(ser_line, "STAT %ld", (long)artnum);
132     if (nntp_command(ser_line) <= 0)
133 	return -2;
134     return nntp_check();
135 }
136 
137 /* check on an article's existence by its message id */
138 
139 ART_NUM
nntp_stat_id(msgid)140 nntp_stat_id(msgid)
141 char* msgid;
142 {
143     long artnum;
144 
145     sprintf(ser_line, "STAT %s", msgid);
146     if (nntp_command(ser_line) <= 0)
147 	return -2;
148     artnum = nntp_check();
149     if (artnum > 0 && sscanf(ser_line, "%*d%ld", &artnum) != 1)
150 	artnum = 0;
151     return (ART_NUM)artnum;
152 }
153 
154 ART_NUM
nntp_next_art()155 nntp_next_art()
156 {
157     long artnum;
158 
159     if (nntp_command("NEXT") <= 0)
160 	return -2;
161     artnum = nntp_check();
162     if (artnum > 0 && sscanf(ser_line, "%*d %ld", &artnum) != 1)
163 	artnum = 0;
164     return (ART_NUM)artnum;
165 }
166 
167 /* prepare to get the header */
168 
169 int
nntp_header(artnum)170 nntp_header(artnum)
171 ART_NUM artnum;
172 {
173     sprintf(ser_line, "HEAD %ld", (long)artnum);
174     if (nntp_command(ser_line) <= 0)
175 	return -2;
176     return nntp_check();
177 }
178 
179 /* copy the body of an article to a temporary file */
180 
181 void
nntp_body(artnum)182 nntp_body(artnum)
183 ART_NUM artnum;
184 {
185     char* artname;
186 
187     artname = nntp_artname(artnum, FALSE); /* Is it already in a tmp file? */
188     if (artname) {
189 	if (body_pos >= 0)
190 	    nntp_finishbody(FB_DISCARD);
191 	artfp = fopen(artname,"r");
192 	if (artfp && fstat(fileno(artfp),&filestat) == 0)
193 	    body_end = filestat.st_size;
194 	return;
195     }
196 
197     artname = nntp_artname(artnum, TRUE);   /* Allocate a tmp file */
198     if (!(artfp = fopen(artname, "w+"))) {
199 	fprintf(stderr, "\nUnable to write temporary file: '%s'.\n",
200 		artname);
201 	finalize(1); /*$$*/
202     }
203 #ifndef MSDOS
204     chmod(artname, 0600);
205 #endif
206     /*artio_setbuf(artfp);$$*/
207     if (parsed_art == artnum)
208 	sprintf(ser_line, "BODY %ld", (long)artnum);
209     else
210 	sprintf(ser_line, "ARTICLE %ld", (long)artnum);
211     if (nntp_command(ser_line) <= 0)
212 	finalize(1); /*$$*/
213     switch (nntp_check()) {
214       case -2:
215       case -1:
216 	finalize(1); /*$$*/
217       case 0:
218 	fclose(artfp);
219 	artfp = NULL;
220 	errno = ENOENT;			/* Simulate file-not-found */
221 	return;
222     }
223     body_pos = 0;
224     if (parsed_art == artnum) {
225 	fwrite(headbuf, 1, strlen(headbuf), artfp);
226 	htype[PAST_HEADER].minpos = body_end = (ART_POS)ftell(artfp);
227     }
228     else {
229 	char b[NNTP_STRLEN];
230 	ART_POS prev_pos = body_end = 0;
231 	while (nntp_copybody(b, sizeof b, body_end+1) > 0) {
232 	    if (*b == '\n' && body_end - prev_pos < sizeof b)
233 		break;
234 	    prev_pos = body_end;
235 	}
236     }
237     fseek(artfp, 0L, 0);
238     nntplink.flags &= ~NNTP_NEW_CMD_OK;
239 }
240 
241 long
nntp_artsize()242 nntp_artsize()
243 {
244     return body_pos < 0 ? body_end : -1;
245 }
246 
247 static int
nntp_copybody(s,limit,pos)248 nntp_copybody(s, limit, pos)
249 char* s;
250 int limit;
251 ART_POS pos;
252 {
253     int len;
254     bool had_nl = TRUE;
255     int found_nl;
256 
257     while (pos > body_end || !had_nl) {
258 	found_nl = nntp_gets(s, limit);
259 	if (found_nl < 0)
260 	    strcpy(s,"."); /*$$*/
261 	if (had_nl) {
262 	    if (nntp_at_list_end(s)) {
263 		fseek(artfp, (long)body_pos, 0);
264 		body_pos = -1;
265 		return 0;
266 	    }
267 	    if (s[0] == '.')
268 		safecpy(s,s+1,limit);
269 	}
270 	len = strlen(s);
271 	if (found_nl)
272 	    strcpy(s+len, "\n");
273 	fputs(s, artfp);
274 	body_end = ftell(artfp);
275 	had_nl = found_nl;
276     }
277     return 1;
278 }
279 
280 int
nntp_finishbody(bmode)281 nntp_finishbody(bmode)
282 int bmode;
283 {
284     char b[NNTP_STRLEN];
285     if (body_pos < 0)
286 	return 0;
287     if (bmode == FB_DISCARD) {
288 	/*printf("Discarding the rest of the article...\n") FLUSH; $$*/
289 #if 0
290 	/* Implement this if flushing the data becomes possible */
291 	nntp_artname(openart, -1); /* Or something... */
292 	openart = 0;	/* Since we didn't finish the art, forget its number */
293 #endif
294     }
295     else
296     if (bmode == FB_OUTPUT) {
297 #ifdef VERBOSE
298 	IF(verbose)
299 	    printf("Receiving the rest of the article..."), fflush(stdout);
300 	ELSE
301 #endif
302 #ifdef TERSE
303 	    printf("Receiving..."), fflush(stdout);
304 #endif
305     }
306     if (body_end != body_pos)
307 	fseek(artfp, (long)body_end, 0);
308     if (bmode != FB_BACKGROUND)
309 	nntp_copybody(b, sizeof b, (ART_POS)0x7fffffffL);
310     else {
311 	while (nntp_copybody(b, sizeof b, body_end+1)) {
312 	    if (input_pending())
313 		break;
314 	}
315 	if (body_pos >= 0)
316 	    fseek(artfp, (long)body_pos, 0);
317     }
318     if (bmode == FB_OUTPUT)
319 	erase_line(0);	/* erase the prompt */
320     return 1;
321 }
322 
323 int
nntp_seekart(pos)324 nntp_seekart(pos)
325 ART_POS pos;
326 {
327     if (body_pos >= 0) {
328 	if (body_end < pos) {
329 	    char b[NNTP_STRLEN];
330 	    fseek(artfp, (long)body_end, 0);
331 	    nntp_copybody(b, sizeof b, pos);
332 	    if (body_pos >= 0)
333 		body_pos = pos;
334 	}
335 	else
336 	    body_pos = pos;
337     }
338     return fseek(artfp, (long)pos, 0);
339 }
340 
341 ART_POS
nntp_tellart()342 nntp_tellart()
343 {
344     return body_pos < 0 ? (ART_POS)ftell(artfp) : body_pos;
345 }
346 
347 char*
nntp_readart(s,limit)348 nntp_readart(s, limit)
349 char* s;
350 int limit;
351 {
352     if (body_pos >= 0) {
353 	if (body_pos == body_end) {
354 	    if (nntp_copybody(s, limit, body_pos+1) <= 0)
355 		return NULL;
356 	    if (body_end - body_pos < limit) {
357 		body_pos = body_end;
358 		return s;
359 	    }
360 	    fseek(artfp, (long)body_pos, 0);
361 	}
362 	s = fgets(s, limit, artfp);
363 	body_pos = ftell(artfp);
364 	if (body_pos == body_end)
365 	    fseek(artfp, (long)body_pos, 0);  /* Prepare for coming write */
366 	return s;
367     }
368     return fgets(s, limit, artfp);
369 }
370 
371 /* This is a 1-relative list */
372 static int maxdays[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
373 
374 time_t
nntp_time()375 nntp_time()
376 {
377     char* s;
378     int year, month, day, hh, mm;
379     time_t ss;
380 
381     if (nntp_command("DATE") <= 0)
382 	return -2;
383     if (nntp_check() <= 0)
384 	return time((time_t*)NULL);
385 
386     s = rindex(ser_line, ' ') + 1;
387     month = (s[4] - '0') * 10 + (s[5] - '0');
388     day = (s[6] - '0') * 10 + (s[7] - '0');
389     hh = (s[8] - '0') * 10 + (s[9] - '0');
390     mm = (s[10] - '0') * 10 + (s[11] - '0');
391     ss = (s[12] - '0') * 10 + (s[13] - '0');
392     s[4] = '\0';
393     year = atoi(s);
394 
395     /* This simple algorithm will be valid until the year 2100 */
396     if (year % 4)
397 	maxdays[2] = 28;
398     else
399 	maxdays[2] = 29;
400     if (month < 1 || month > 12 || day < 1 || day > maxdays[month]
401      || hh < 0 || hh > 23 || mm < 0 || mm > 59
402      || ss < 0 || ss > 59)
403 	return time((time_t*)NULL);
404 
405     for (month--; month; month--)
406 	day += maxdays[month];
407 
408     ss = ((((year-1970) * 365 + (year-1969)/4 + day - 1) * 24L + hh) * 60
409 	  + mm) * 60 + ss;
410 
411     return ss;
412 }
413 
414 int
nntp_newgroups(t)415 nntp_newgroups(t)
416 time_t t;
417 {
418     struct tm *ts;
419 
420     ts = gmtime(&t);
421     sprintf(ser_line, "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT",
422 	ts->tm_year % 100, ts->tm_mon+1, ts->tm_mday,
423 	ts->tm_hour, ts->tm_min, ts->tm_sec);
424     if (nntp_command(ser_line) <= 0)
425 	return -2;
426     return nntp_check();
427 }
428 
429 int
nntp_artnums()430 nntp_artnums()
431 {
432     if (datasrc->flags & DF_NOLISTGROUP)
433 	return 0;
434     if (nntp_command("LISTGROUP") <= 0)
435 	return -2;
436     if (nntp_check() <= 0) {
437 	datasrc->flags |= DF_NOLISTGROUP;
438 	return 0;
439     }
440     return 1;
441 }
442 
443 #if 0
444 int
445 nntp_rover()
446 {
447     if (datasrc->flags & DF_NOXROVER)
448 	return 0;
449     if (nntp_command("XROVER 1-") <= 0)
450 	return -2;
451     if (nntp_check() <= 0) {
452 	datasrc->flags |= DF_NOXROVER;
453 	return 0;
454     }
455     return 1;
456 }
457 #endif
458 
459 ART_NUM
nntp_find_real_art(after)460 nntp_find_real_art(after)
461 ART_NUM after;
462 {
463     ART_NUM an;
464 
465     if (last_cached > after || last_cached < absfirst
466      || nntp_stat(last_cached) <= 0) {
467 	if (nntp_stat_id("") > after)
468 	    return 0;
469     }
470 
471     while ((an = nntp_next_art()) > 0) {
472 	if (an > after)
473 	    return an;
474 	if (after - an > 10)
475 	    break;
476     }
477 
478     return 0;
479 }
480 
481 char*
nntp_artname(artnum,allocate)482 nntp_artname(artnum, allocate)
483 ART_NUM artnum;
484 bool_int allocate;
485 {
486     static ART_NUM artnums[MAX_NNTP_ARTICLES];
487     static time_t artages[MAX_NNTP_ARTICLES];
488     time_t now, lowage;
489     int i, j;
490 
491     if (!artnum) {
492 	for (i = 0; i < MAX_NNTP_ARTICLES; i++) {
493 	    artnums[i] = 0;
494 	    artages[i] = 0;
495 	}
496 	return NULL;
497     }
498 
499     now = time((time_t*)NULL);
500 
501     for (i = j = 0, lowage = now; i < MAX_NNTP_ARTICLES; i++) {
502 	if (artnums[i] == artnum) {
503 	    artages[i] = now;
504 	    return nntp_tmpname(i);
505 	}
506 	if (artages[i] <= lowage)
507 	    lowage = artages[j = i];
508     }
509 
510     if (allocate) {
511 	artnums[j] = artnum;
512 	artages[j] = now;
513 	return nntp_tmpname(j);
514     }
515 
516     return NULL;
517 }
518 
519 char*
nntp_tmpname(ndx)520 nntp_tmpname(ndx)
521 int ndx;
522 {
523     static char artname[20];
524     sprintf(artname,"rrn.%ld.%d",our_pid,ndx);
525     return artname;
526 }
527 
528 int
nntp_handle_nested_lists()529 nntp_handle_nested_lists()
530 {
531     if (strcaseEQ(last_command,"quit"))
532 	return 0; /*$$ flush data needed? */
533     if (nntp_finishbody(FB_DISCARD))
534 	return 1;
535     fprintf(stderr,"Programming error! Nested NNTP calls detected.\n");
536     return -1;
537 }
538 
539 int
nntp_handle_timeout()540 nntp_handle_timeout()
541 {
542     static bool handling_timeout = FALSE;
543     char last_command_save[NNTP_STRLEN];
544 
545     if (strcaseEQ(last_command,"quit"))
546 	return 0;
547     if (handling_timeout)
548 	return -1;
549     handling_timeout = TRUE;
550     strcpy(last_command_save, last_command);
551     nntp_close(FALSE);
552     datasrc->nntplink = nntplink;
553     if (nntp_connect(datasrc->newsid, 0) <= 0)
554 	return -2;
555     datasrc->nntplink = nntplink;
556     if (in_ng && nntp_group(ngname, (NGDATA*)NULL) <= 0)
557 	return -2;
558     if (nntp_command(last_command_save) <= 0)
559 	return -1;
560     strcpy(last_command, last_command_save); /*$$ Is this really needed? */
561     handling_timeout = FALSE;
562     return 1;
563 }
564 
565 void
nntp_server_died(dp)566 nntp_server_died(dp)
567 DATASRC* dp;
568 {
569     MULTIRC* mp = multirc;
570     close_datasrc(dp);
571     dp->flags |= DF_UNAVAILABLE;
572     unuse_multirc(mp);
573     if (!use_multirc(mp))
574 	multirc = NULL;
575     fprintf(stderr,"\n%s\n", ser_line);
576     get_anything();
577 }
578 
579 /* nntp_readcheck -- get a line of text from the server, interpreting
580 ** it as a status message for a binary command.  Call this once
581 ** before calling nntp_read() for the actual data transfer.
582 */
583 #ifdef SUPPORT_XTHREAD
584 long
nntp_readcheck()585 nntp_readcheck()
586 {
587     /* try to get the status line and the status code */
588     switch (nntp_check()) {
589       case -2:
590 	return -2;
591       case -1:
592       case 0:
593 	return rawbytes = -1;
594     }
595 
596     /* try to get the number of bytes being transfered */
597     if (sscanf(ser_line, "%*d%ld", &rawbytes) != 1)
598 	return rawbytes = -1;
599     return rawbytes;
600 }
601 #endif
602 
603 /* nntp_read -- read data from the server in binary format.  This call must
604 ** be preceeded by an appropriate binary command and an nntp_readcheck call.
605 */
606 #ifdef SUPPORT_XTHREAD
607 long
nntp_read(buf,n)608 nntp_read(buf, n)
609 char* buf;
610 long n;
611 {
612     /* if no bytes to read, then just return EOF */
613     if (rawbytes < 0)
614 	return 0;
615 
616 #ifdef HAS_SIGHOLD
617     sighold(SIGINT);
618 #endif
619 
620     /* try to read some data from the server */
621     if (rawbytes) {
622 	n = fread(buf, 1, n > rawbytes ? rawbytes : n, nntplink.rd_fp);
623 	rawbytes -= n;
624     } else
625 	n = 0;
626 
627     /* if no more left, then fetch the end-of-command signature */
628     if (!rawbytes) {
629 	char buf[5];	/* "\r\n.\r\n" */
630 
631 	fread(buf, 1, 5, nntplink.rd_fp);
632 	rawbytes = -1;
633     }
634 #ifdef HAS_SIGHOLD
635     sigrelse(SIGINT);
636 #endif
637     return n;
638 }
639 #endif /* SUPPORT_XTHREAD */
640 
641 #endif /* SUPPORT_NNTP */
642