1: /* nntpxmit - transmit netnews articles across the internet with nntp 2: ** 3: ** This program is for transmitting netnews articles between sites 4: ** that offer the NNTP service, internet style. There are two forms 5: ** of article transmission that can be used in this environment, since 6: ** the communication is interactive (and relatively more immediate, 7: ** when compared to batched file transfer protocols, like UUCP). They 8: ** are: active send (I have `x', do you want it?) and polling (what 9: ** have you gotten lately?). 10: ** 11: ** A C T I V E S E N D 12: ** 13: ** Sites on the UUCP network generally use active send, without asking 14: ** in advance (that is, unless you got an article from your neighbor, 15: ** or their site is listed in the Path: header already, you assume 16: ** they don't have it and send it along). There is an ihave/sendme 17: ** protocol for doing active send over batched links, but I claim that 18: ** it won't work well because of the high latency between queueing 19: ** and actual transfer that UUCP links typically have. That is, you'll 20: ** still end up with a high rate of duplicate articles being sent over 21: ** that type of link. 22: ** 23: ** With NNTP-based IHAVE, the update window in which another site can 24: ** give the remote the article you just offered him is the time between 25: ** the remote telling you it doesn't have the article, and your 26: ** completed transfer of the article (pretty small). In practice, we 27: ** still get duplicates, but generally from two problems: synchronized 28: ** transmission of an article from two different neighbors (this can 29: ** only be fixed by putting inews(1) into nntpd), and by articles 30: ** being accepting during an expire(1) run (expire locks out inews 31: ** processing while it is running, and articles collect until expire 32: ** is done; since accepted article message-ids aren't added to 33: ** the history file until expire is done, several clients can offer 34: ** you the same article, and you'll accept all the copies offered you. 35: ** When rnews gets run after expire, it will reject the duplicates). 36: ** 37: ** P O L L I N G 38: ** 39: ** Polling presents some article and distribution security problems, 40: ** because the server has no contol over what a transmission client 41: ** will ask for, and it must therefore control what it tells a client 42: ** in response to a query. 43: ** 44: ** Articles that appear in local newsgroup hierarchies, or appear in 45: ** the generally distributed USENET newsgroups with local distributions 46: ** have to be filtered out from the list of message-IDs that the server 47: ** gives to a client in response to a NEWNEWS query, or filtered when 48: ** the server fetches the articles off the disk in response to an 49: ** ARTICLE command (and therefore has complete access to the required 50: ** information). Otherwise, distributions will leak. 51: ** 52: ** The other problem with polling is that a good client should keep track 53: ** of when it last successfully polled a server, so that it doesn't force 54: ** h server to dump its entire history file across the network, and this 55: ** involves more file locking and manipulations routines. 56: ** 57: ** nntpxmit only implements active send, for now. 58: ** 59: ** Erik E. Fair <fair@ucbarpa.berkeley.edu>, Dec 4, 1987 60: */ 61: 62: #include "nntpxmit.h" 63: #include <stdio.h> 64: #include <errno.h> 65: #include <ctype.h> 66: #include <sys/types.h> 67: #include <sys/time.h> 68: #ifdef BSD4_2 69: #include <sys/resource.h> 70: #else 71: #include <sys/times.h> 72: extern time_t time(); 73: #endif BSD4_2 74: #include <sys/file.h> 75: #include <fcntl.h> 76: #include <signal.h> 77: #ifdef USG 78: #include "sysexits.h" 79: #else 80: #include <sysexits.h> 81: #endif 82: #ifdef SYSLOG 83: #include <syslog.h> 84: #endif SYSLOG 85: #include "nntp.h" 86: #include "llist.h" 87: 88: #define MAXFNAME BUFSIZ /* maximum filename size - big enough? */ 89: #define FCLOSE(fp) (void) fclose(fp); (fp) = (FILE *)NULL 90: 91: FILE *getfp(); 92: char *errmsg(); 93: void requeue(); 94: void catchsig(); 95: void restsig(); 96: void logstats(); 97: void log(); 98: int interrupted(); 99: 100: /* 101: ** Globals that certain things need. 102: ** 103: ** Various subroutines want the program name to report errors. 104: ** The queue file, queue file pointer and current article name are 105: ** there to write out the state of the queue file from a signal handler 106: ** (that is, the list of unsent and (possibly) failed articles) so 107: ** that when next we try sending to a given remote site, we don't send 108: ** stuff we've already sent. 109: */ 110: char *Pname; /* this program's invocation name */ 111: char *Host; /* current remote host */ 112: char *Qfile; /* current queue file we're operating on */ 113: FILE *Qfp; /* the (FILE *) for above */ 114: char Article[MAXFNAME]; /* current article filename */ 115: 116: /* 117: ** Some flags, toggled by arguments 118: */ 119: #define TOGGLE(boolean) (boolean) = !(boolean) 120: char Debug = FALSE; 121: char Report_Stats = TRUE; 122: char ReQueue_Fails = TRUE; 123: 124: char *USAGE = "USAGE: nntpxmit [-d][-s][-r][-T][-F][-D] hostname|hostname:file [...]"; 125: char *Fmt = "%s: %s\n"; 126: char *E_fopen = "fopen(%s, \"%s\"): %s"; 127: char *E_unlk = "unlink(%s): %s"; 128: #ifdef USELOG 129: char *NNTPlog = USELOG; /* yet another external log file */ 130: FILE *Logfp = (FILE *)NULL; 131: #endif USELOG 132: 133: ll_t FailedArticles; /* list of failed articles */ 134: 135: struct { 136: u_long offered; 137: u_long accepted; 138: u_long rejected; 139: u_long failed; 140: } Stats = {0L, 0L, 0L, 0L}; 141: 142: double Tbegin, Tend; /* transfer timestamps */ 143: 144: extern int errno; 145: extern int strncmp(); 146: extern char *rindex(); 147: extern char *index(); 148: extern char *mktemp(); 149: extern char *strcpy(); 150: 151: #ifdef USG 152: void 153: bzero(s, l) 154: register caddr_t s; 155: register int l; 156: { 157: while(l-- > 0) *s++ = 0; 158: } 159: #endif USG 160: 161: main(ac, av) 162: int ac; 163: char *av[]; 164: { 165: register int i; 166: int transport = T_IP_TCP; /* default is IP/TCP */ 167: int isQfile = TRUE; /* file arg is a Queue file */ 168: #ifdef USELOG 169: char *amode = "a"; 170: #endif USELOG 171: #ifdef BSD4_2 172: struct timeval tod; 173: struct timezone tz; 174: 175: (void) gettimeofday(&tod, &tz); 176: Tbegin = tod.tv_sec + (double)tod.tv_usec/1000000.; 177: #else 178: Tbegin = (double) time((time_t *)NULL); 179: #endif BSD4_2 180: 181: Pname = ((Pname = rindex(av[0],'/')) ? Pname + 1 : av[0]); 182: 183: if (ac < 2) { 184: fprintf(stderr, Fmt, Pname, USAGE); 185: exit(EX_USAGE); 186: } 187: 188: #ifdef SYSLOG 189: /* 4.2 BSD openlog has only two args */ 190: #ifdef LOG_LOCAL7 191: (void) openlog(Pname, LOG_PID, LOG_LOCAL7); 192: #else 193: (void) openlog(Pname, LOG_PID); 194: #endif LOG_LOCAL_7 195: #endif SYSLOG 196: #ifdef USELOG 197: if ((Logfp = fopen(NNTPlog, amode)) == (FILE *)NULL) { 198: char buf[BUFSIZ]; 199: 200: sprintf(buf, E_fopen, NNTPlog, amode, errmsg(errno)); 201: log(L_NOTICE, buf); 202: } 203: #endif USELOG 204: 205: for(i = 1; i < ac; i++) { 206: if (av[i][0] == '-') { 207: switch(av[i][1]) { 208: case 'T': 209: transport = T_IP_TCP; 210: break; 211: case 'D': 212: transport = T_DECNET; 213: break; 214: case 'F': 215: transport = T_FD; 216: break; 217: case 's': 218: TOGGLE(Report_Stats); 219: break; 220: case 'd': 221: TOGGLE(Debug); 222: break; 223: case 'r': 224: TOGGLE(ReQueue_Fails); 225: break; 226: case 'a': 227: isQfile = FALSE; 228: break; 229: default: 230: fprintf(stderr, "%s: no such option: -%c\n", 231: Pname, av[i][1]); 232: fprintf(stderr, Fmt, Pname, USAGE); 233: exit(EX_USAGE); 234: } 235: continue; 236: } 237: 238: /* 239: ** OK, it wasn't an option, therefore it must be a 240: ** hostname, filename pair. 241: ** 242: ** If the user typed host::file, then it's DECNET, 243: ** whether they remembered the "-D" option or not. 244: */ 245: Host = av[i]; 246: if ((Qfile = index(Host, ':')) != (char *)NULL) { 247: if (Qfile[1] == ':') { 248: transport = T_DECNET; 249: *Qfile++ = '\0'; 250: } else if (transport != T_FD) 251: transport = T_IP_TCP; 252: *Qfile++ = '\0'; 253: } else 254: Qfile = Host; 255: 256: bzero((caddr_t)&Stats, sizeof(Stats)); 257: if (isQfile) { 258: if (sendnews(Host, transport, Qfile, isQfile) && Report_Stats) { 259: logstats(); 260: } 261: } else { 262: /* one-shot */ 263: (void) strcpy(Article, Qfile); 264: exit(sendnews(Host, transport, Qfile, isQfile) ? EX_OK : EX_TEMPFAIL); 265: } 266: } 267: exit(EX_OK); 268: } 269: 270: /* 271: ** Calculate how much time we've used, 272: ** and report that (and the transfer statistics). 273: ** 274: */ 275: void 276: logstats() 277: { 278: static double ouser = 0.0, osys = 0.0; 279: double user, sys; 280: char buf[BUFSIZ]; 281: #ifdef USELOG 282: #ifdef BSD4_2 283: extern time_t time(); 284: #endif BSD4_2 285: time_t tstamp; 286: char *tp; 287: extern char *ctime(); 288: #endif USELOG 289: #ifdef BSD4_2 290: struct rusage self, kids; 291: struct timeval tod; 292: struct timezone tzdummy; 293: 294: (void) getrusage(RUSAGE_SELF, &self); 295: (void) getrusage(RUSAGE_CHILDREN, &kids); 296: (void) gettimeofday(&tod, &tzdummy); 297: 298: Tend = tod.tv_sec + (double)tod.tv_usec/1000000.; 299: 300: user = self.ru_utime.tv_sec + kids.ru_utime.tv_sec + 301: (double) self.ru_utime.tv_usec/1000000. + 302: (double) kids.ru_utime.tv_usec/1000000.; 303: 304: sys = self.ru_stime.tv_sec + kids.ru_stime.tv_sec + 305: (double) self.ru_stime.tv_usec/1000000. + 306: (double) kids.ru_stime.tv_usec/1000000.; 307: #else 308: #define HZ 60.0 /* typical system clock ticks - param.h */ 309: struct tms cpu; 310: 311: (void) times(&cpu); 312: 313: Tend = (double) time((time_t *)NULL); 314: user = (double)(cpu.tms_utime + cpu.tms_cutime) / HZ; 315: sys = (double)(cpu.tms_stime + cpu.tms_cstime) / HZ; 316: #endif BSD4_2 317: sprintf(buf, 318: "%s stats %lu offered %lu accepted %lu rejected %lu failed", 319: Host, Stats.offered, Stats.accepted, Stats.rejected, 320: Stats.failed); 321: log(L_INFO, buf); 322: #ifdef USELOG 323: if (Logfp != (FILE *)NULL) { 324: (void) time(&tstamp); 325: tp = ctime(&tstamp); 326: tp[19] = '\0'; 327: fprintf(Logfp, Fmt, &tp[4], buf); 328: } 329: #endif USELOG 330: 331: sprintf(buf, "%s xmit user %.1f system %.1f elapsed %.1f", 332: Host, (user - ouser), (sys - osys), (Tend - Tbegin)); 333: log(L_INFO, buf); 334: #ifdef USELOG 335: if (Logfp != (FILE *)NULL) { 336: fprintf(Logfp, Fmt, &tp[4], buf); 337: (void) fflush(Logfp); 338: } 339: #endif USELOG 340: 341: /* reset reference point */ 342: Tbegin = Tend; 343: ouser = user; 344: osys = sys; 345: } 346: 347: /* 348: ** Given a hostname to connect to, and a file of filenames (which contain 349: ** netnews articles), send those articles to the named host using NNTP. 350: ** 351: ** Return code behavior is different depending upon isQfile. 352: ** 353: ** TRUE - return TRUE if we contacted the remote and started 354: ** transferring news - this is to decide whether to 355: ** record CPU and transfer statistics. 356: ** 357: ** FALSE - a one-shot file transfer - return TRUE or FALSE depending 358: ** upon whether we successfully transferred the one article. 359: */ 360: sendnews(host, transport, file, isQfile) 361: char *host, *file; 362: int transport, isQfile; 363: { 364: register FILE *fp; 365: #ifdef FTRUNCATE 366: char *mode = "r+"; /* so we can use ftruncate() */ 367: #else 368: char *mode = "r"; 369: #endif FTRUNCATE 370: 371: if ((Qfp = fopen(file, mode)) == (FILE *)NULL) { 372: char buf[BUFSIZ]; 373: 374: sprintf(buf, E_fopen, file, mode, errmsg(errno)); 375: log(L_WARNING, buf); 376: return(FALSE); 377: } 378: 379: /* 380: ** interlock with other copies of this process. 381: ** non-blocking. 382: */ 383: if (isQfile) { 384: if (!lockfd(fileno(Qfp), file, DONT_BLOCK)) { 385: FCLOSE(Qfp); 386: return(FALSE); 387: } 388: } 389: 390: /* 391: ** Open a connection to the remote server 392: */ 393: if (hello(host, transport) == FAIL) { 394: FCLOSE(Qfp); 395: return(FALSE); 396: } 397: 398: if (isQfile) { 399: /* 400: ** We're sending a batch queue: 401: ** open article 402: ** get message-ID 403: ** send "IHAVE <message-ID>" to remote 404: ** read their reply 405: ** send article if appropriate 406: ** iterate to end of queue file 407: */ 408: catchsig(interrupted); 409: 410: while((fp = getfp(Qfp, Article, sizeof(Article))) != (FILE *)NULL) { 411: if (!sendarticle(host, fp)) { 412: (void) fclose(fp); 413: requeue(Article); 414: Article[0] = '\0'; 415: cleanup(); 416: goodbye(DONT_WAIT); 417: restsig(); 418: return(TRUE); 419: } 420: (void) fclose(fp); 421: } 422: 423: cleanup(); 424: goodbye(WAIT); 425: restsig(); 426: return(TRUE); 427: } else { 428: /* 429: ** Qfp is a netnews article - this is a one-shot 430: ** operation, exit code dependent upon remote's 431: ** acceptance of the article 432: */ 433: register int retcode; 434: 435: retcode = sendarticle(host, Qfp); 436: FCLOSE(Qfp); 437: goodbye(retcode ? WAIT : DONT_WAIT); 438: return(retcode && Stats.accepted == 1 && Stats.failed == 0); 439: } 440: } 441: 442: /* 443: ** Perform one transfer operation: 444: ** Give IHAVE command 445: ** Wait for reply, and send article if they ask for it 446: ** Wait for transfer confirmation, and requeue the article 447: ** if they drop it. 448: ** Watch all network I/O for errors, return FALSE if 449: ** the connection fails and we have to cleanup. 450: */ 451: sendarticle(host, fp) 452: char *host; 453: FILE *fp; 454: { 455: register int code; 456: char buf[BUFSIZ]; 457: char *e_xfer = "%s xfer: %s"; 458: 459: switch(code = ihave(fp)) { 460: case CONT_XFER: 461: /* 462: ** They want it. Give it to 'em. 463: */ 464: if (!sendfile(fp)) { 465: sprintf(buf, e_xfer, host, errmsg(errno)); 466: log(L_NOTICE, buf); 467: Stats.failed++; 468: return(FALSE); 469: } 470: /* 471: ** Did the article transfer OK? 472: ** Stay tuned to this same socket to find out! 473: */ 474: if ((code = readreply(buf, sizeof(buf))) != OK_XFERED) { 475: Stats.failed++; 476: if (code < 0) { 477: if (errno > 0) { 478: sprintf(buf, e_xfer, host, errmsg(errno)); 479: log(L_NOTICE, buf); 480: } else { 481: char errbuf[BUFSIZ]; 482: 483: sprintf(errbuf, e_xfer, host, buf); 484: log(L_NOTICE, errbuf); 485: } 486: return(FALSE); 487: } 488: if (ReQueue_Fails && code != ERR_XFERRJCT) { 489: requeue(Article); 490: Article[0] = '\0'; 491: } 492: } 493: break; 494: case ERR_GOTIT: 495: /* they don't want it */ 496: break; 497: default: 498: if (code < 0) { 499: if (errno > 0) { 500: sprintf(buf, e_xfer, host, errmsg(errno)); 501: log(L_NOTICE, buf); 502: } else { 503: sprintf(buf, e_xfer, host, "ihave"); 504: log(L_NOTICE, buf); 505: } 506: } else { 507: sprintf(buf, "%s improper response to IHAVE: %d while offering %s", host, code, Article); 508: log(L_WARNING, buf); 509: } 510: return(FALSE); 511: } 512: return(TRUE); 513: } 514: 515: char * 516: errmsg(code) 517: int code; 518: { 519: return(strerror(code)); 520: } 521: 522: /* 523: ** strip leading and trailing spaces 524: */ 525: char * 526: sp_strip(s) 527: register char *s; 528: { 529: register char *cp; 530: 531: if (s == NULL) 532: return(NULL); 533: 534: if (*s == '\0') 535: return(s); 536: 537: cp = &s[strlen(s) - 1]; 538: while(cp > s && isspace(*cp)) 539: cp--; 540: 541: *++cp = '\0'; /* zap trailing spaces */ 542: 543: for(cp = s; *cp && isspace(*cp); cp++) 544: continue; 545: 546: return(cp); /* return pointer to first non-space */ 547: } 548: 549: /* 550: ** convert `s' to lower case 551: */ 552: char * 553: lcase(s) 554: register char *s; 555: { 556: register char *cp; 557: 558: if (s == (char *)NULL) 559: return(s); 560: 561: for(cp = s; *cp != '\0'; cp++) 562: if (isupper(*cp)) 563: *cp = tolower(*cp); 564: return(s); 565: } 566: 567: /* 568: ** Get the message-id header field data with a minimum of fuss. 569: */ 570: char * 571: getmsgid(fp) 572: FILE *fp; 573: { 574: static char buf[BUFSIZ]; 575: static char *msgid = "message-id"; 576: register char *cp, *cp2; 577: 578: while(fgets(buf, sizeof(buf), fp) != (char *)NULL) { 579: switch(buf[0]) { 580: case '\n': 581: return((char *)NULL); /* EOH, we failed */ 582: case 'M': 583: case 'm': 584: if ((cp = index(buf, ':')) == (char *)NULL) 585: continue; 586: *cp++ = '\0'; 587: if (strncmp(lcase(buf), msgid, sizeof(*msgid)) == 0) { 588: /* dump extraneous trash - umass.bitnet */ 589: /* hope nobody quotes an '>' in a msgid */ 590: if ((cp2 = index(cp, '>')) != (char *)NULL) 591: *++cp2 = '\0'; 592: return(sp_strip(cp)); 593: } 594: break; 595: } 596: } 597: return((char *)NULL); /* EOF, we failed */ 598: } 599: 600: #ifdef notdef /* nobody obeys the triply damned protocol anyway! */ 601: /* 602: ** Special characters, see RFC822, appendix D. 603: */ 604: isspecial(c) 605: char c; 606: { 607: char *specials = "()<>@,;:\\\".[]"; 608: 609: return(index(specials, c) != (char *)NULL ? TRUE : FALSE); 610: } 611: 612: /* 613: ** Check on the validity of an RFC822 message-id 614: ** 615: ** By The Book, RFC822 Appendix D. 616: ** msg-id = "<" addr-spec ">" 617: ** addr-spec = local-part "@" domain 618: ** local-part = word *("." word) 619: ** word = atom / quoted-string 620: ** domain = sub-domain *("." sub-domain) 621: ** sub-domain = domain-ref / domain-literal 622: ** domain-ref = atom 623: ** domain-literal = "[" *(dtext / quoted-pair) "]" 624: ** 625: ** NOTE: close reading of the RFC822 spec indicates that a fully 626: ** qualified domain name (i.e. one with at least one dot) is 627: ** NOT required in the domain part of the addr-spec. However, 628: ** I've decided to be an asshole and require them, since we'll 629: ** all die a slow death later on if I don't at this juncture. 630: ** To disable, if you disagree with me, see the last return 631: ** statement. - Erik E. Fair <fair@ucbarpa.berkeley.edu> 632: ** May 30, 1986 633: */ 634: msgid_ok(id) 635: register char *id; 636: { 637: register Langle = FALSE; 638: register Rangle = FALSE; 639: register local_part = FALSE; 640: register at = FALSE; 641: register dot = FALSE; 642: 643: /* skip up to the opening angle bracket */ 644: if (id == (char *)NULL || (id = index(id, '<')) == (char *)NULL) 645: return(FALSE); /* don't waste my time! */ 646: 647: for(; *id != '\0'; id++) { 648: switch(*id) { 649: case '<': 650: if (Langle) return(FALSE); 651: Langle = local_part = TRUE; 652: break; 653: case '>': 654: if (Rangle || !Langle || !at) return(FALSE); 655: else Rangle = TRUE; 656: break; 657: case '@': /* should be a domain spec */ 658: at = TRUE; 659: local_part = FALSE; 660: break; 661: case '.': 662: dot = at; 663: break; 664: case '\\': 665: /* 666: ** quoted pair; this disallows NULs, but how 667: ** many mailers would die if someone used one? 668: */ 669: if (!local_part || (*++id) == '\0') return(FALSE); 670: break; 671: case '"': 672: /* 673: ** quoted string 674: */ 675: if (!local_part) return(FALSE); 676: do { 677: switch(*++id) { 678: case '\\': 679: if ((*++id) == '\0') return(FALSE); 680: break; 681: case '\r': 682: return(FALSE); 683: } 684: } while(*id != '\0' && *id != '"'); 685: break; 686: case '[': 687: /* 688: ** domain literal 689: */ 690: if (local_part) return(FALSE); 691: do { 692: switch(*++id) { 693: case '\\': 694: if ((*++id) == '\0') return(FALSE); 695: break; 696: case '\r': 697: return(FALSE); 698: } 699: } while(*id != '\0' && *id != ']'); 700: break; 701: default: 702: if (!isascii(*id) || iscntrl(*id) || isspace(*id) || isspecial(*id)) 703: return(FALSE); /* quit immediately */ 704: break; 705: } 706: } 707: return(at && dot && Langle && Rangle); 708: } 709: #else notdef 710: 711: /* 712: ** Simpleton's check for message ID syntax. 713: ** A concession to the realities of the ARPA Internet. 714: */ 715: msgid_ok(s) 716: register char *s; 717: { 718: register char c; 719: register in_msgid = FALSE; 720: 721: if (s == (char *)NULL) 722: return(FALSE); 723: 724: while((c = *s++) != '\0') { 725: if (!isascii(c) || iscntrl(c) || isspace(c)) 726: return(FALSE); 727: switch(c) { 728: case '<': 729: in_msgid = TRUE; 730: break; 731: case '>': 732: return(in_msgid); 733: } 734: } 735: return(FALSE); 736: } 737: #endif notdef 738: 739: /* 740: ** Read the header of a netnews article, snatch the message-id therefrom, 741: ** and ask the remote if they have that one already. 742: */ 743: ihave(fp) 744: FILE *fp; 745: { 746: register int code; 747: register char *id; 748: char buf[BUFSIZ]; 749: 750: if ((id = getmsgid(fp)) == (char *)NULL || *id == '\0') { 751: /* 752: ** something botched locally with the article 753: ** so we don't send it, but we don't break off 754: ** communications with the remote either. 755: */ 756: sprintf(buf, "%s: message-id missing!", Article); 757: log(L_DEBUG, buf); 758: return(ERR_GOTIT); 759: } 760: 761: if (!msgid_ok(id)) { 762: sprintf(buf, "%s: message-id syntax error: %s", Article, id); 763: log(L_DEBUG, buf); 764: return(ERR_GOTIT); 765: } 766: 767: sprintf(buf, "IHAVE %s", id); 768: Stats.offered++; 769: 770: switch(code = converse(buf, sizeof(buf))) { 771: case CONT_XFER: 772: Stats.accepted++; 773: rewind(fp); 774: return(code); 775: case ERR_GOTIT: 776: Stats.rejected++; 777: return(code); 778: default: 779: return(code); 780: } 781: } 782: 783: /* 784: ** Given that fp points to an open file containing filenames, 785: ** open and return a file pointer to the next filename in the file. 786: ** Don't you love indirection? 787: ** 788: ** Returns a valid FILE pointer or NULL if end of file. 789: */ 790: FILE * 791: getfp(fp, filename, fnlen) 792: register FILE *fp; 793: char *filename; 794: register int fnlen; 795: { 796: register FILE *newfp = (FILE *)NULL; 797: register char *cp; 798: char *mode = "r"; 799: 800: while(newfp == (FILE *)NULL) { 801: if (fgets(filename, fnlen, fp) == (char *)NULL) 802: return((FILE *)NULL); /* EOF, tell caller */ 803: 804: filename[fnlen - 1] = '\0'; /* make sure */ 805: 806: /* if fgets() ever forgets the '\n', we're fucked */ 807: if (*(cp = &filename[strlen(filename) - 1]) == '\n') 808: *cp = '\0'; 809: 810: if (filename[0] == '\0') 811: continue; 812: 813: if ((newfp = fopen(filename, mode)) == (FILE *)NULL) { 814: /* 815: ** The only permissible error is `file non-existant' 816: ** anything else indicates something is seriously 817: ** wrong, and we should go away to let the shell 818: ** script clean up. 819: */ 820: if (errno != ENOENT) { 821: char buf[BUFSIZ]; 822: 823: sprintf(buf, E_fopen, filename, mode, errmsg(errno)); 824: log(L_WARNING, buf); 825: goodbye(DONT_WAIT); 826: exit(EX_OSERR); 827: } 828: } 829: } 830: return(newfp); 831: } 832: 833: /* 834: ** OK, clean up any mess and requeue failed articles 835: */ 836: cleanup() 837: { 838: dprintf(stderr, "%s: cleanup()\n", Pname); 839: if (Qfp == (FILE *)NULL || Qfile == (char *)NULL) 840: return; 841: 842: if ((ReQueue_Fails && Stats.failed > 0) || !feof(Qfp)) { 843: rewrite(); 844: } else { 845: /* 846: ** Nothing to clean up after, reset stuff and 847: ** nuke the queue file. 848: */ 849: requeue((char *)NULL); 850: if (feof(Qfp)) { 851: dprintf(stderr, "%s: unlink(%s)\n", Pname, Qfile); 852: if (unlink(Qfile) < 0) { 853: char buf[BUFSIZ]; 854: 855: sprintf(buf, E_unlk, Qfile, errmsg(errno)); 856: log(L_WARNING, buf); 857: } 858: } 859: FCLOSE(Qfp); 860: } 861: } 862: 863: /* 864: ** Add an article file name to an allocated linked list, 865: ** so that we can rewrite it back to the queue file later. 866: ** Calling this with a NULL pointer resets the internal pointer. 867: */ 868: void 869: requeue(article) 870: char *article; 871: { 872: static ll_t *lp = &FailedArticles; 873: 874: if (article == (char *)NULL) { 875: dprintf(stderr, "%s: requeue(): reset\n", Pname); 876: goto reset; /* this is for our static pointer */ 877: } 878: 879: if (*article == '\0') 880: return; 881: 882: dprintf(stderr, "%s: requeue(%s)\n", Pname, article); 883: if ((lp = l_alloc(lp, article, strlen(article) + 1)) == (ll_t *)NULL) { 884: fprintf(stderr, "%s: requeue(%s) failed, dumping fail list\n", 885: Pname, article); 886: /* 887: ** Wow! Did you know that this could blow the stack 888: ** if we recurse too deeply? I sure didn't! 889: */ 890: reset: 891: l_free(&FailedArticles); 892: lp = &FailedArticles; 893: } 894: } 895: 896: /* 897: ** Note that if I'm not running as "news" or "usenet" (or whatever 898: ** account is supposed to own netnews), the resultant file will be the 899: ** wrong ownership, permissions, etc. 900: */ 901: rewrite() 902: { 903: register ll_t *lp; 904: register FILE *tmpfp; 905: register int nart = 0; 906: char *mode = "w+"; 907: char *template = "/tmp/nntpxmitXXXXXX"; 908: char buf[BUFSIZ]; 909: static char *tempfile = (char *)NULL; 910: 911: dprintf(stderr, "%s: rewrite(%s)\n", Pname, Qfile); 912: 913: if (tempfile == (char *)NULL) /* should only need this once */ 914: tempfile = mktemp(template); 915: 916: if ((tmpfp = fopen(tempfile, mode)) == (FILE *)NULL) { 917: sprintf(buf, E_fopen, tempfile, mode, errmsg(errno)); 918: log(L_WARNING, buf); 919: FCLOSE(Qfp); 920: return; 921: } 922: 923: /* 924: ** Requeue the rest of the queue file first, 925: ** so that failed articles (if any) go to the end 926: ** of the new file. 927: */ 928: if (!feof(Qfp)) { 929: dprintf(stderr, "%s: copying the unused portion of %s to %s\n", 930: Pname, Qfile, tempfile); 931: while(fgets(buf, sizeof(buf), Qfp) != (char *)NULL) 932: (void) fputs(buf, tmpfp); 933: } 934: 935: /* 936: ** Here we write out the filenames of articles which 937: ** failed at the remote end. 938: */ 939: dprintf(stderr, "%s: writing failed article filenames to %s\n", 940: Pname, tempfile); 941: L_LOOP(lp, FailedArticles) { 942: fprintf(tmpfp, "%s\n", lp->l_item); 943: nart++; 944: } 945: dprintf(stderr, "%s: wrote %d article filenames to %s\n", 946: Pname, nart, tempfile); 947: 948: (void) fflush(tmpfp); 949: /* 950: ** If writing the temp file failed (maybe /tmp is full?) 951: ** back out and leave the queue file exactly as it is. 952: */ 953: if (ferror(tmpfp)) { 954: sprintf(buf, "rewrite(): copy to %s failed", tempfile); 955: log(L_WARNING, buf); 956: (void) fclose(tmpfp); 957: FCLOSE(Qfp); 958: if (unlink(tempfile) < 0) { 959: sprintf(buf, E_unlk, tempfile, errmsg(errno)); 960: log(L_WARNING, buf); 961: } 962: requeue((char *)NULL); /* reset */ 963: return; 964: } 965: 966: rewind(tmpfp); 967: #ifdef FTRUNCATE 968: rewind(Qfp); 969: if (ftruncate(fileno(Qfp), (off_t)0) < 0) { 970: sprintf(buf, "ftruncate(%s, 0): %s", Qfile, errmsg(errno)); 971: log(L_WARNING, buf); 972: FCLOSE(Qfp); 973: (void) fclose(tmpfp); 974: if (unlink(tempfile) < 0) { 975: sprintf(buf, E_unlk, tempfile, errmsg(errno)); 976: log(L_WARNING, buf); 977: } 978: requeue((char *)NULL); /* reset */ 979: return; 980: } 981: #else 982: FCLOSE(Qfp); /* we just nuked our lock here (lockfd) */ 983: if ((Qfp = fopen(Qfile, mode)) == (FILE *)NULL) { 984: sprintf(buf, E_fopen, Qfile, mode, errmsg(errno)); 985: log(L_WARNING, buf); 986: (void) fclose(tmpfp); 987: if (unlink(tempfile) < 0) { 988: sprintf(buf, E_unlk, tempfile, errmsg(errno)); 989: log(L_WARNING, buf); 990: } 991: requeue((char *)NULL); /* reset */ 992: return; 993: } 994: /* Try to get our lock back (but continue whether we do or not) */ 995: (void) lockfd(fileno(Qfp), Qfile, DONT_BLOCK); 996: #endif FTRUNCATE 997: 998: dprintf(stderr, "%s: copying %s back to %s\n", Pname, tempfile, Qfile); 999: while(fgets(buf, sizeof(buf), tmpfp) != (char *)NULL) 1000: (void) fputs(buf, Qfp); 1001: 1002: (void) fflush(Qfp); 1003: if (ferror(Qfp)) { 1004: sprintf(buf, "rewrite(): copy to %s failed", Qfile); 1005: log(L_WARNING, buf); 1006: } 1007: (void) fclose(tmpfp); 1008: FCLOSE(Qfp); 1009: if (unlink(tempfile) < 0) { 1010: sprintf(buf, E_unlk, tempfile, errmsg(errno)); 1011: log(L_WARNING, buf); 1012: } 1013: requeue((char *)NULL); /* reset */ 1014: dprintf(stderr, "%s: rewrite(%s): done\n", Pname, Qfile); 1015: return; 1016: } 1017: 1018: /* 1019: ** Signal stuff 1020: ** 1021: ** There's probably too much stuff to do in this signal 1022: ** handler, but we're going to exit anyway... 1023: */ 1024: interrupted(sig) 1025: int sig; 1026: { 1027: char buf[BUFSIZ]; 1028: 1029: #ifndef RELSIG 1030: catchsig(SIG_IGN); /* for System V - hope we're quick enough */ 1031: #endif RELSIG 1032: sprintf(buf, "%s signal %d", Host, sig); 1033: log(L_NOTICE, buf); 1034: requeue(Article); 1035: cleanup(); 1036: if (Report_Stats) 1037: logstats(); 1038: goodbye(DONT_WAIT); 1039: exit(EX_TEMPFAIL); 1040: } 1041: 1042: struct { 1043: int signo; 1044: ifunp state; 1045: } SigList[] = { 1046: {SIGHUP}, 1047: {SIGINT}, 1048: {SIGQUIT}, 1049: {SIGTERM}, 1050: {NULL} 1051: }; 1052: 1053: void 1054: catchsig(handler) 1055: ifunp handler; 1056: { 1057: register int i; 1058: 1059: if (handler != SIG_IGN) { 1060: for(i = 0; SigList[i].signo != NULL; i++) { 1061: SigList[i].state = signal(SigList[i].signo, handler); 1062: } 1063: } else { 1064: for(i = 0; SigList[i].signo != NULL; i++) { 1065: (void) signal(SigList[i].signo, handler); 1066: } 1067: } 1068: } 1069: 1070: void 1071: restsig() 1072: { 1073: register int i; 1074: 1075: for(i = 0; SigList[i].signo != NULL; i++) { 1076: if (SigList[i].state != (ifunp)(-1)) 1077: (void) signal(SigList[i].signo, SigList[i].state); 1078: } 1079: } 1080: 1081: /* 1082: ** log stuff 1083: */ 1084: void 1085: log(importance, error) 1086: int importance; 1087: char *error; 1088: { 1089: FILE *report = (importance == L_INFO ? stdout : stderr); 1090: 1091: fprintf(report, Fmt, Pname, error); 1092: #ifdef SYSLOG 1093: switch(importance) { 1094: case L_INFO: importance = LOG_INFO; break; 1095: case L_DEBUG: importance = LOG_DEBUG; break; 1096: case L_NOTICE: importance = LOG_NOTICE; break; 1097: case L_WARNING: importance = LOG_WARNING; break; 1098: default: importance = LOG_DEBUG; break; 1099: } 1100: syslog(importance, error); 1101: #endif SYSLOG 1102: } 1103: 1104: /* 1105: ** Lock a file descriptor 1106: ** 1107: ** NOTE: if the appropriate system calls are unavailable, 1108: ** this subroutine is a no-op. 1109: */ 1110: lockfd(fd, file, non_blocking) 1111: int fd, non_blocking; 1112: char *file; /* just for error reporting */ 1113: { 1114: char buf[BUFSIZ]; 1115: #ifdef USG 1116: #ifdef F_TLOCK 1117: if (lockf(fd, (non_blocking ? F_TLOCK : F_LOCK), 0) < 0) { 1118: if (errno != EACCES) { 1119: sprintf(buf, "lockf(%s): %s\n", file, errmsg(errno)); 1120: log(L_WARNING, buf); 1121: } 1122: return(FALSE); 1123: } 1124: #endif F_TLOCK 1125: #else 1126: #ifdef LOCK_EX 1127: if (flock(fd, LOCK_EX|(non_blocking ? LOCK_NB : 0)) < 0) { 1128: if (errno != EWOULDBLOCK) { 1129: sprintf(buf, "flock(%s): %s\n", file, errmsg(errno)); 1130: log(L_WARNING, buf); 1131: } 1132: return(FALSE); 1133: } 1134: #endif LOCK_EX 1135: #endif USG 1136: return(TRUE); 1137: }