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