rsalz@bbn.com (Rich Salz) (04/21/88)
Submitted-by: Phil Lapsley <phil@ucbvax.berkeley.edu> Posting-number: Volume 14, Issue 54 Archive-name: nntp1.5/part08 #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh <file", e.g.. If this archive is complete, you # will see the following message at the end: # "End of archive 8 (of 9)." # Contents: ./support/nntp_awk.ucbvax ./xmit/nntpxmit.c # Wrapped by rsalz@fig.bbn.com on Tue Apr 19 18:16:49 1988 PATH=/bin:/usr/bin:/usr/ucb ; export PATH if test -f './support/nntp_awk.ucbvax' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'./support/nntp_awk.ucbvax'\" else echo shar: Extracting \"'./support/nntp_awk.ucbvax'\" \(12577 characters\) sed "s/^X//" >'./support/nntp_awk.ucbvax' <<'END_OF_FILE' X# an awk script X# an NNTP log summary report generator X# X# NOTE: for systems that are not as yet using the new 4.3 BSD syslog X# (and therefore have nntp messages lumped with everything else), it X# would be best to invoke this script thusly: X# X# egrep nntp syslog.old | awk -f nntp_awk > report_of_the_week X# X# because this script will include in the report all messages in the log X# that it does not recognize (on the assumption that they are errors to X# be dealt with by a human). X# X# Erik E. Fair <fair@ucbarpa.berkeley.edu> X# May 17, 1986 - Norwegian Independence Day X# X# Recognize some new things - February 22, 1987 X# Erik E. Fair <fair@ucbarpa.berkeley.edu> X# X# fix "xmt is not an array" bug - March 11, 1987 X# Change Elapsed/CPU fields to break out time values, HH:MM:SS X# Erik E. Fair <fair@ucbarpa.berkeley.edu> X# X# Add reporting for newnews commands - August 27, 1987 X# Erik E. Fair <fair@ucbarpa.berkeley.edu> X# X# Add nntpxmit connection attempt counting/reporting - December 7, 1987 X# Erik E. Fair <fair@ucbarpa.berkeley.edu> X# BEGIN{ X readers = 0; X transmit = 0; X receive = 0; X polled = 0; X} X### Skip stderr reports from rnews X{ X n = split($6, path, "/"); X if (path[n] == "rnews:") next; X n = split($7, path, "/"); X if (path[n] == "rnews") next; X host = $6; X} X$7 == "group" { X readers = 1; X ng[$8]++; X next; X} X$7 == "ihave" { X receive = 1; X rec[host]++; X if ($9 == "accepted") { X rec_accept[host]++; X if ($10 == "failed") rec_failed[host]++; X } else if ($9 == "rejected") rec_refuse[host]++; X next; X} X# this is from version 1.4 of nntpd X$7 == "ihave_stats" { X receive = 1; X rec[host] += $9 + $11 + $13; X rec_accept[host] += $9; X rec_refuse[host] += $11; X rec_failed[host] += $13; X next; X} X$7 == "connect" { X systems[host]++; X next; X} X# nntpxmit connection errors X# Ooooh! I *wish* awk had N dimensional arrays, X# so I wouldn't have to throw away the error message here! X$7 == "hello:" { X conn[host]++; X if ($8 == "Connection" && $9 == "refused") X rmt_fail[host]++; X else X open_fail[host]++; X next; X} X# we'll get stats from this, don't count conn[] X$7 == "xfer:" { X open_fail[host]++; X# since these are expected to be few in number, we still print X# the exact error (no "next;" statement here). X} X$7 == "greeted" { X conn[host]++; X rmt_fail[host]++; X next; X} X$7 == "host" && $8 == "unknown" { X conn[host]++; X ns_fail[host]++; X next; X} X# nntpd connection abort - all "broken pipe" right now X$7 == "disconnect:" { next } X# syslogd shit X$7 == "repeated" { next } X# inews shit X$11 == "spooled" { next } X$7 == "exit" { X if ($8 > 0) readers = 1; X articles[host] += $8; X groups[host] += $10; X next; X} X$7 == "xmit" { X xmt_cpu[host] += $9 + $11; X xmt_ela[host] += $13; X next; X} X$7 == "times" { X cpu[host] += $9 + $11; X ela[host] += $13; X next; X} X$7 == "stats" { X transmit = 1; X conn[host]++; X xmt[host] += $8; X xmt_accept[host] += $10; X xmt_refuse[host] += $12; X xmt_failed[host] += $14; X next; X} X# X# For the Nth time, I wish awk had two dimensional associative X# arrays. I assume that the last request is the same as all the X# others in this section of logfile. X# X$7 == "newnews" { X polled = 1; X poll[host] ++; X poll_asked[host] = $8; X next; X} X$7 == "newnews_stats" { X poll_offered[host] += $9; X poll_took[host] += $11; X next; X} X$7 == "post" { X readers = 1; X post[host]++; X next; X} X$7 == "timeout" { X timeout[host]++; X timeouts = 1; X next; X} X$7 == "unrecognized" { X unknown[host] = 1; X curious = 1; X} X### Print anything that we don't recognize in the report X{ X print; X} END{ X printf("\n"); X############################################################################### X### Article Exchange With Peers (other servers) Statistics ### X############################################################################### X if (transmit || receive || polled) X printf("NNTP peer article transfers\n\n"); X X if (polled) for(s in poll) servers[s]++; X if (receive) for(s in rec) servers[s]++; X if (transmit) for(s in xmt) servers[s]++; X X if (receive) { X printf("Article Reception (they contact us)\n"); X printf("System Offered Took Toss Fail Toss Elapsed CPU Pct\n"); X for(s in rec) { X X nrec += rec[s]; X nrec_accept += rec_accept[s]; X nrec_refuse += rec_refuse[s]; X nrec_failed += rec_failed[s]; X nrec_cpu += cpu[s]; X nrec_ela += ela[s]; X X they_offered = rec[s]; X if (they_offered == 0) they_offered = 1; X we_toss = (rec_refuse[s] / they_offered) * 100 + 0.5; X X e_hours = ela[s] / 3600; X e_sec = ela[s] % 3600; X e_min = e_sec / 60; X e_sec %= 60; X X c_hours = cpu[s] / 3600; X c_sec = cpu[s] % 3600; X c_min = c_sec / 60; X c_sec %= 60; X X tmp = ela[s]; X if (tmp == 0) tmp = 1; X pct = ((cpu[s] / tmp) * 100.0 + 0.5); X X printf("%-25s %5d %5d %5d %5d %3d%% %3d:%02d:%02d %3d:%02d:%02d %3d%%\n", s, rec[s], rec_accept[s], rec_refuse[s], rec_failed[s], we_toss, e_hours, e_min, e_sec, c_hours, c_min, c_sec, pct); X } X X e_hours = nrec_ela / 3600; X e_sec = nrec_ela % 3600; X e_min = e_sec / 60; X e_sec %= 60; X X c_hours = nrec_cpu / 3600; X c_sec = nrec_cpu % 3600; X c_min = c_sec / 60; X c_sec %= 60; X X they_offered = nrec; X if (they_offered == 0) they_offered = 1; X we_toss = (nrec_refuse / they_offered) * 100 + 0.5; X X if (nrec_ela == 0) nrec_ela = 1; X pct = ((nrec_cpu / nrec_ela) * 100.0 + 0.5); X X printf("\n%-25s %5d %5d %5d %5d %3d%% %3d:%02d:%02d %3d:%02d:%02d %3d%%\n\n", "TOTALS", nrec, nrec_accept, nrec_refuse, nrec_failed, we_toss, e_hours, e_min, e_sec, c_hours, c_min, c_sec, pct); X } X X############################################################################### X if (polled) { X printf("Article Transmission (they poll us)\n"); X printf("System Conn Offrd Took Elapsed CPU Pct Groups\n"); X npoll = 0; X npoll_offered = 0; X npoll_took = 0; X npoll_cpu = 0; X npoll_ela = 0; X X for(s in poll) { X npoll += poll[s]; X npoll_offered += poll_offered[s]; X npoll_took += poll_took[s]; X X if (rec[s]) { X printf("%-25s %5d %5d %5d (see Article Reception) %s\n", s, poll[s], poll_offered[s], poll_took[s], poll_asked[s]); X } else { X npoll_ela += ela[s]; X npoll_cpu += cpu[s]; X X e_hours = ela[s] / 3600; X e_sec = ela[s] % 3600; X e_min = e_sec / 60; X e_sec %= 60; X X c_hours = cpu[s] / 3600; X c_sec = cpu[s] % 3600; X c_min = c_sec / 60; X c_sec %= 60; X X tmp = ela[s]; X if (tmp == 0) tmp = 1; X pct = ((cpu[s] / tmp) * 100.0 + 0.5); X X printf("%-25s %5d %5d %5d %3d:%02d:%02d %3d:%02d:%02d %3d%% %s\n", s, poll[s], poll_offered[s], poll_took[s], e_hours, e_min, e_sec, c_hours, c_min, c_sec, pct, poll_asked[s]); X } X } X printf("\n%-25s %5d %5d %5d", "TOTALS", npoll, npoll_offered, npoll_took); X if (npoll_ela > 0 && npoll_cpu > 0) { X X e_hours = npoll_ela / 3600; X e_sec = npoll_ela % 3600; X e_min = e_sec / 60; X e_sec %= 60; X X c_hours = npoll_cpu / 3600; X c_sec = npoll_cpu % 3600; X c_min = c_sec / 60; X c_sec %= 60; X X tmp = npoll_ela; X if (tmp == 0) tmp = 1; X pct = ((npoll_cpu / tmp) * 100.0 + 0.5); X X printf(" %3d:%02d:%02d %3d:%02d:%02d %3d%%\n\n", e_hours, e_min, e_sec, c_hours, c_min, c_sec, pct); X } else X printf("\n\n"); X } X X############################################################################### X if (transmit) { X printf("Article Transmission (we contact them)\n"); X printf("System Offrd Took Toss Fail Pct Elapsed CPU Pct\n"); X for(s in xmt) { X we_offered = xmt[s]; X if (we_offered == 0) we_offered = 1; X they_toss = (xmt_refuse[s] / we_offered) * 100 + 0.5; X X e_hours = xmt_ela[s] / 3600; X e_sec = xmt_ela[s] % 3600; X e_min = e_sec / 60; X e_sec %= 60; X X c_hours = xmt_cpu[s] / 3600; X c_sec = xmt_cpu[s] % 3600; X c_min = c_sec / 60; X c_sec %= 60; X X elapsed = xmt_ela[s]; X if (elapsed == 0) elapsed = 1; X pct = ((xmt_cpu[s] / elapsed) * 100.0 + 0.5); X X printf("%-25s %5d %5d %5d %5d %3d%% %3d:%02d:%02d %3d:%02d:%02d %3d%%\n", s, xmt[s], xmt_accept[s], xmt_refuse[s], xmt_failed[s], they_toss, e_hours, e_min, e_sec, c_hours, c_min, c_sec, pct); X X nxmt += xmt[s]; X nxmt_accept += xmt_accept[s]; X nxmt_refuse += xmt_refuse[s]; X nxmt_failed += xmt_failed[s]; X nxmt_ela += xmt_ela[s]; X nxmt_cpu += xmt_cpu[s]; X } X X we_offered = nxmt; X if (we_offered == 0) we_offered = 1; X they_toss = (nxmt_refuse / we_offered) * 100 + 0.5; X X e_hours = nxmt_ela / 3600; X e_sec = nxmt_ela % 3600; X e_min = e_sec / 60; X e_sec %= 60; X X c_hours = nxmt_cpu / 3600; X c_sec = nxmt_cpu % 3600; X c_min = c_sec / 60; X c_sec %= 60; X X if (nxmt_ela == 0) nxmt_ela = 1; X pct = ((nxmt_cpu / nxmt_ela) * 100.0 + 0.5); X X printf("\n%-25s %5d %5d %5d %5d %3d%% %3d:%02d:%02d %3d:%02d:%02d %3d%%\n\n", "TOTALS", nxmt, nxmt_accept, nxmt_refuse, nxmt_failed, they_toss, e_hours, e_min, e_sec, c_hours, c_min, c_sec, pct); X X printf("Transmission Connection Attempts ------errors-------\n"); X printf("System Conn OK NS Net Rmt Pct\n"); X for(s in xmt) { X tot = conn[s]; X if (tot == 0) tot = 1; X errs = rmt_fail[s] + ns_fail[s] + open_fail[s]; X ok = (conn[s] - errs); X printf("%-25s %5d %5d %5d %5d %5d %3d%%\n", s, conn[s], ok, ns_fail[s], open_fail[s], rmt_fail[s], (100.0 * errs / tot + 0.5)); X ct_tot += conn[s]; X ct_ok += ok; X ct_ns += ns_fail[s]; X ct_net += open_fail[s]; X ct_rmt += rmt_fail[s]; X } X tot = ct_tot; X if (tot == 0) tot = 1; X errs = ct_ns + ct_net + ct_rmt; X printf("\n%-25s %5d %5d %5d %5d %5d %3d%%\n\n", "TOTALS", ct_tot, ct_ok, ct_ns, ct_net, ct_rmt, (100.0 * errs / tot + 0.5)); X } X X############################################################################### X### Article Readership Statistics ### X############################################################################### X X if (readers) { X printf("NNTP readership statistics\n"); X printf("System Conn Articles Groups Post Elapsed CPU Pct\n"); X for(s in systems) { X### X### servers are different animals; they don't belong in this part of the report X### X if (servers[s] > 0 && groups[s] == 0 && articles[s] == 0) X continue; X### X### report the curious server pokers elsewhere X### X if (groups[s] == 0 && articles[s] == 0 && post[s] == 0) { X unknown[s] += systems[s]; X curious = 1; X continue; X } X X nconn += systems[s]; X nart += articles[s]; X ngrp += groups[s]; X npost += post[s]; X ncpu += cpu[s]; X nela += ela[s]; X X e_hours = ela[s] / 3600; X e_sec = ela[s] % 3600; X e_min = e_sec / 60; X e_sec %= 60; X X c_hours = cpu[s] / 3600; X c_sec = cpu[s] % 3600; X c_min = c_sec / 60; X c_sec %= 60; X X elapsed = ela[s]; X if (elapsed == 0) elapsed = 1; X pct = ((cpu[s] / elapsed) * 100 + 0.5); X X printf("%-25s %5d %8d %6d %4d %3d:%02d:%02d %3d:%02d:%02d %3d%%\n", s, systems[s], articles[s], groups[s], post[s], e_hours, e_min, e_sec, c_hours, c_min, c_sec, pct); X } X X e_hours = nela / 3600; X e_sec = nela % 3600; X e_min = e_sec / 60; X e_sec %= 60; X X c_hours = ncpu / 3600; X c_sec = ncpu % 3600; X c_min = c_sec / 60; X c_sec %= 60; X X if (nela == 0) nela = 1; X pct = ((ncpu / nela) * 100 + 0.5); X X printf("\n%-25s %5d %8d %6d %4d %3d:%02d:%02d %3d:%02d:%02d %3d%%\n\n", "TOTALS", nconn, nart, ngrp, npost, e_hours, e_min, e_sec, c_hours, c_min, c_sec, pct); X } X X############################################################################### X if (curious) { X printf("Unknown NNTP server explorers\nSystem Conn\n"); X for(s in unknown) { X printf("%-25s %5d\n", s, unknown[s]); X } X printf("\n"); X } X############################################################################### X if (timeouts) { X printf("Server timeouts\n"); X for(s in timeout) { X printf("%-25s %5d\n", s, timeout[s]); X } X printf("\n"); X } X############################################################################### X if (readers) { X for(g in ng) { X x = length(g); X if (x > max) max = x; X X i = index(g, "."); X if (i > 0) top = substr(g, 1, i - 1); X else top = g; X category[top] += ng[g]; X } X fmt = sprintf("%%-%ds %%5d\n", max); X X printf("Newsgroup Request Counts (by category)\n"); X for(g in category) printf(fmt, g, category[g]); X X printf("\nNewsgroup Request Counts (by newsgroup)\n"); X for(g in ng) printf(fmt, g, ng[g]); X printf("\n"); X } X} END_OF_FILE if test 12577 -ne `wc -c <'./support/nntp_awk.ucbvax'`; then echo shar: \"'./support/nntp_awk.ucbvax'\" unpacked with wrong size! fi # end of './support/nntp_awk.ucbvax' fi if test -f './xmit/nntpxmit.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'./xmit/nntpxmit.c'\" else echo shar: Extracting \"'./xmit/nntpxmit.c'\" \(26902 characters\) sed "s/^X//" >'./xmit/nntpxmit.c' <<'END_OF_FILE' X/* nntpxmit - transmit netnews articles across the internet with nntp X** X** This program is for transmitting netnews articles between sites X** that offer the NNTP service, internet style. There are two forms X** of article transmission that can be used in this environment, since X** the communication is interactive (and relatively more immediate, X** when compared to batched file transfer protocols, like UUCP). They X** are: active send (I have `x', do you want it?) and polling (what X** have you gotten lately?). X** X** A C T I V E S E N D X** X** Sites on the UUCP network generally use active send, without asking X** in advance (that is, unless you got an article from your neighbor, X** or their site is listed in the Path: header already, you assume X** they don't have it and send it along). There is an ihave/sendme X** protocol for doing active send over batched links, but I claim that X** it won't work well because of the high latency between queueing X** and actual transfer that UUCP links typically have. That is, you'll X** still end up with a high rate of duplicate articles being sent over X** that type of link. X** X** With NNTP-based IHAVE, the update window in which another site can X** give the remote the article you just offered him is the time between X** the remote telling you it doesn't have the article, and your X** completed transfer of the article (pretty small). In practice, we X** still get duplicates, but generally from two problems: synchronized X** transmission of an article from two different neighbors (this can X** only be fixed by putting inews(1) into nntpd), and by articles X** being accepting during an expire(1) run (expire locks out inews X** processing while it is running, and articles collect until expire X** is done; since accepted article message-ids aren't added to X** the history file until expire is done, several clients can offer X** you the same article, and you'll accept all the copies offered you. X** When rnews gets run after expire, it will reject the duplicates). X** X** P O L L I N G X** X** Polling presents some article and distribution security problems, X** because the server has no contol over what a transmission client X** will ask for, and it must therefore control what it tells a client X** in response to a query. X** X** Articles that appear in local newsgroup hierarchies, or appear in X** the generally distributed USENET newsgroups with local distributions X** have to be filtered out from the list of message-IDs that the server X** gives to a client in response to a NEWNEWS query, or filtered when X** the server fetches the articles off the disk in response to an X** ARTICLE command (and therefore has complete access to the required X** information). Otherwise, distributions will leak. X** X** The other problem with polling is that a good client should keep track X** of when it last successfully polled a server, so that it doesn't force X** h server to dump its entire history file across the network, and this X** involves more file locking and manipulations routines. X** X** nntpxmit only implements active send, for now. X** X** Erik E. Fair <fair@ucbarpa.berkeley.edu>, Dec 4, 1987 X*/ X X#include "nntpxmit.h" X#include <stdio.h> X#include <errno.h> X#include <ctype.h> X#include <sys/types.h> X#include <sys/time.h> X#ifdef BSD4_2 X#include <sys/resource.h> X#else X#include <sys/times.h> extern time_t time(); X#endif BSD4_2 X#include <sys/file.h> X#include <fcntl.h> X#include <signal.h> X#ifdef USG X#include "sysexits.h" X#else X#include <sysexits.h> X#endif X#ifdef SYSLOG X#include <syslog.h> X#endif SYSLOG X#include "nntp.h" X#include "llist.h" X X#define MAXFNAME BUFSIZ /* maximum filename size - big enough? */ X#define FCLOSE(fp) (void) fclose(fp); (fp) = (FILE *)NULL X FILE *getfp(); char *errmsg(); void requeue(); void catchsig(); void restsig(); void logstats(); void log(); int interrupted(); X X/* X** Globals that certain things need. X** X** Various subroutines want the program name to report errors. X** The queue file, queue file pointer and current article name are X** there to write out the state of the queue file from a signal handler X** (that is, the list of unsent and (possibly) failed articles) so X** that when next we try sending to a given remote site, we don't send X** stuff we've already sent. X*/ char *Pname; /* this program's invocation name */ char *Host; /* current remote host */ char *Qfile; /* current queue file we're operating on */ FILE *Qfp; /* the (FILE *) for above */ char Article[MAXFNAME]; /* current article filename */ X X/* X** Some flags, toggled by arguments X*/ X#define TOGGLE(boolean) (boolean) = !(boolean) char Debug = FALSE; char Report_Stats = TRUE; char ReQueue_Fails = TRUE; X char *USAGE = "USAGE: nntpxmit [-d][-s][-r][-T][-F][-D] hostname|hostname:file [...]"; char *Fmt = "%s: %s\n"; char *E_fopen = "fopen(%s, \"%s\"): %s"; char *E_unlk = "unlink(%s): %s"; X#ifdef USELOG char *NNTPlog = USELOG; /* yet another external log file */ FILE *Logfp = (FILE *)NULL; X#endif USELOG X ll_t FailedArticles; /* list of failed articles */ X struct { X u_long offered; X u_long accepted; X u_long rejected; X u_long failed; X} Stats = {0L, 0L, 0L, 0L}; X double Tbegin, Tend; /* transfer timestamps */ X extern int errno; extern int strncmp(); extern char *rindex(); extern char *index(); extern char *mktemp(); extern char *strcpy(); X X#ifdef USG void bzero(s, l) register caddr_t s; register int l; X{ X while(l-- > 0) *s++ = 0; X} X#endif USG X main(ac, av) int ac; char *av[]; X{ X register int i; X int transport = T_IP_TCP; /* default is IP/TCP */ X int isQfile = TRUE; /* file arg is a Queue file */ X#ifdef USELOG X char *amode = "a"; X#endif USELOG X#ifdef BSD4_2 X struct timeval tod; X struct timezone tz; X X (void) gettimeofday(&tod, &tz); X Tbegin = tod.tv_sec + (double)tod.tv_usec/1000000.; X#else X Tbegin = (double) time((time_t *)NULL); X#endif BSD4_2 X X Pname = ((Pname = rindex(av[0],'/')) ? Pname + 1 : av[0]); X X if (ac < 2) { X fprintf(stderr, Fmt, Pname, USAGE); X exit(EX_USAGE); X } X X#ifdef SYSLOG X /* 4.2 BSD openlog has only two args */ X#ifdef LOG_LOCAL7 X (void) openlog(Pname, LOG_PID, LOG_LOCAL7); X#else X (void) openlog(Pname, LOG_PID); X#endif LOG_LOCAL_7 X#endif SYSLOG X#ifdef USELOG X if ((Logfp = fopen(NNTPlog, amode)) == (FILE *)NULL) { X char buf[BUFSIZ]; X X sprintf(buf, E_fopen, NNTPlog, amode, errmsg(errno)); X log(L_NOTICE, buf); X } X#endif USELOG X X for(i = 1; i < ac; i++) { X if (av[i][0] == '-') { X switch(av[i][1]) { X case 'T': X transport = T_IP_TCP; X break; X case 'D': X transport = T_DECNET; X break; X case 'F': X transport = T_FD; X break; X case 's': X TOGGLE(Report_Stats); X break; X case 'd': X TOGGLE(Debug); X break; X case 'r': X TOGGLE(ReQueue_Fails); X break; X case 'a': X isQfile = FALSE; X break; X default: X fprintf(stderr, "%s: no such option: -%c\n", X Pname, av[i][1]); X fprintf(stderr, Fmt, Pname, USAGE); X exit(EX_USAGE); X } X continue; X } X X /* X ** OK, it wasn't an option, therefore it must be a X ** hostname, filename pair. X ** X ** If the user typed host::file, then it's DECNET, X ** whether they remembered the "-D" option or not. X */ X Host = av[i]; X if ((Qfile = index(Host, ':')) != (char *)NULL) { X if (Qfile[1] == ':') { X transport = T_DECNET; X *Qfile++ = '\0'; X } else if (transport != T_FD) X transport = T_IP_TCP; X *Qfile++ = '\0'; X } else X Qfile = Host; X X bzero((caddr_t)&Stats, sizeof(Stats)); X if (isQfile) { X if (sendnews(Host, transport, Qfile, isQfile) && Report_Stats) { X logstats(); X } X } else { X /* one-shot */ X (void) strcpy(Article, Qfile); X exit(sendnews(Host, transport, Qfile, isQfile) ? EX_OK : EX_TEMPFAIL); X } X } X exit(EX_OK); X} X X/* X** Calculate how much time we've used, X** and report that (and the transfer statistics). X** X*/ void logstats() X{ X static double ouser = 0.0, osys = 0.0; X double user, sys; X char buf[BUFSIZ]; X#ifdef USELOG X#ifdef BSD4_2 X extern time_t time(); X#endif BSD4_2 X time_t tstamp; X char *tp; X extern char *ctime(); X#endif USELOG X#ifdef BSD4_2 X struct rusage self, kids; X struct timeval tod; X struct timezone tzdummy; X X (void) getrusage(RUSAGE_SELF, &self); X (void) getrusage(RUSAGE_CHILDREN, &kids); X (void) gettimeofday(&tod, &tzdummy); X X Tend = tod.tv_sec + (double)tod.tv_usec/1000000.; X X user = self.ru_utime.tv_sec + kids.ru_utime.tv_sec + X (double) self.ru_utime.tv_usec/1000000. + X (double) kids.ru_utime.tv_usec/1000000.; X X sys = self.ru_stime.tv_sec + kids.ru_stime.tv_sec + X (double) self.ru_stime.tv_usec/1000000. + X (double) kids.ru_stime.tv_usec/1000000.; X#else X#define HZ 60.0 /* typical system clock ticks - param.h */ X struct tms cpu; X X (void) times(&cpu); X X Tend = (double) time((time_t *)NULL); X user = (double)(cpu.tms_utime + cpu.tms_cutime) / HZ; X sys = (double)(cpu.tms_stime + cpu.tms_cstime) / HZ; X#endif BSD4_2 X sprintf(buf, X "%s stats %lu offered %lu accepted %lu rejected %lu failed", X Host, Stats.offered, Stats.accepted, Stats.rejected, X Stats.failed); X log(L_INFO, buf); X#ifdef USELOG X if (Logfp != (FILE *)NULL) { X (void) time(&tstamp); X tp = ctime(&tstamp); X tp[19] = '\0'; X fprintf(Logfp, Fmt, &tp[4], buf); X } X#endif USELOG X X sprintf(buf, "%s xmit user %.1f system %.1f elapsed %.1f", X Host, (user - ouser), (sys - osys), (Tend - Tbegin)); X log(L_INFO, buf); X#ifdef USELOG X if (Logfp != (FILE *)NULL) { X fprintf(Logfp, Fmt, &tp[4], buf); X (void) fflush(Logfp); X } X#endif USELOG X X /* reset reference point */ X Tbegin = Tend; X ouser = user; X osys = sys; X} X X/* X** Given a hostname to connect to, and a file of filenames (which contain X** netnews articles), send those articles to the named host using NNTP. X** X** Return code behavior is different depending upon isQfile. X** X** TRUE - return TRUE if we contacted the remote and started X** transferring news - this is to decide whether to X** record CPU and transfer statistics. X** X** FALSE - a one-shot file transfer - return TRUE or FALSE depending X** upon whether we successfully transferred the one article. X*/ sendnews(host, transport, file, isQfile) char *host, *file; int transport, isQfile; X{ X register FILE *fp; X#ifdef FTRUNCATE X char *mode = "r+"; /* so we can use ftruncate() */ X#else X char *mode = "r"; X#endif FTRUNCATE X X if ((Qfp = fopen(file, mode)) == (FILE *)NULL) { X char buf[BUFSIZ]; X X sprintf(buf, E_fopen, file, mode, errmsg(errno)); X log(L_WARNING, buf); X return(FALSE); X } X X /* X ** interlock with other copies of this process. X ** non-blocking. X */ X if (isQfile) { X if (!lockfd(fileno(Qfp), file, DONT_BLOCK)) { X FCLOSE(Qfp); X return(FALSE); X } X } X X /* X ** Open a connection to the remote server X */ X if (hello(host, transport) == FAIL) { X FCLOSE(Qfp); X return(FALSE); X } X X if (isQfile) { X /* X ** We're sending a batch queue: X ** open article X ** get message-ID X ** send "IHAVE <message-ID>" to remote X ** read their reply X ** send article if appropriate X ** iterate to end of queue file X */ X catchsig(interrupted); X X while((fp = getfp(Qfp, Article, sizeof(Article))) != (FILE *)NULL) { X if (!sendarticle(host, fp)) { X (void) fclose(fp); X requeue(Article); X Article[0] = '\0'; X cleanup(); X goodbye(DONT_WAIT); X restsig(); X return(TRUE); X } X (void) fclose(fp); X } X X cleanup(); X goodbye(WAIT); X restsig(); X return(TRUE); X } else { X /* X ** Qfp is a netnews article - this is a one-shot X ** operation, exit code dependent upon remote's X ** acceptance of the article X */ X register int retcode; X X retcode = sendarticle(host, Qfp); X FCLOSE(Qfp); X goodbye(retcode ? WAIT : DONT_WAIT); X return(retcode && Stats.accepted == 1 && Stats.failed == 0); X } X} X X/* X** Perform one transfer operation: X** Give IHAVE command X** Wait for reply, and send article if they ask for it X** Wait for transfer confirmation, and requeue the article X** if they drop it. X** Watch all network I/O for errors, return FALSE if X** the connection fails and we have to cleanup. X*/ sendarticle(host, fp) char *host; FILE *fp; X{ X register int code; X char buf[BUFSIZ]; X char *e_xfer = "%s xfer: %s"; X X switch(code = ihave(fp)) { X case CONT_XFER: X /* X ** They want it. Give it to 'em. X */ X if (!sendfile(fp)) { X sprintf(buf, e_xfer, host, errmsg(errno)); X log(L_NOTICE, buf); X Stats.failed++; X return(FALSE); X } X /* X ** Did the article transfer OK? X ** Stay tuned to this same socket to find out! X */ X if ((code = readreply(buf, sizeof(buf))) != OK_XFERED) { X Stats.failed++; X if (code < 0) { X if (errno > 0) { X sprintf(buf, e_xfer, host, errmsg(errno)); X log(L_NOTICE, buf); X } else { X char errbuf[BUFSIZ]; X X sprintf(errbuf, e_xfer, host, buf); X log(L_NOTICE, errbuf); X } X return(FALSE); X } X if (ReQueue_Fails && code != ERR_XFERRJCT) { X requeue(Article); X Article[0] = '\0'; X } X } X break; X case ERR_GOTIT: X /* they don't want it */ X break; X default: X if (code < 0) { X if (errno > 0) { X sprintf(buf, e_xfer, host, errmsg(errno)); X log(L_NOTICE, buf); X } else { X sprintf(buf, e_xfer, host, "ihave"); X log(L_NOTICE, buf); X } X } else { X sprintf(buf, "%s improper response to IHAVE: %d while offering %s", host, code, Article); X log(L_WARNING, buf); X } X return(FALSE); X } X return(TRUE); X} X char * errmsg(code) int code; X{ X extern int sys_nerr; X extern char *sys_errlist[]; X static char ebuf[6+5+1]; X X if (code > sys_nerr || code < 0) { X (void) sprintf(ebuf, "Error %d", code); X return ebuf; X } else X return sys_errlist[code]; X} X X/* X** strip leading and trailing spaces X*/ char * sp_strip(s) register char *s; X{ X register char *cp; X X if (s == NULL) X return(NULL); X X if (*s == '\0') X return(s); X X cp = &s[strlen(s) - 1]; X while(cp > s && isspace(*cp)) X cp--; X X *++cp = '\0'; /* zap trailing spaces */ X X for(cp = s; *cp && isspace(*cp); cp++) X continue; X X return(cp); /* return pointer to first non-space */ X} X X/* X** convert `s' to lower case X*/ char * lcase(s) register char *s; X{ X register char *cp; X X if (s == (char *)NULL) X return(s); X X for(cp = s; *cp != '\0'; cp++) X if (isupper(*cp)) X *cp = tolower(*cp); X return(s); X} X X/* X** Get the message-id header field data with a minimum of fuss. X*/ char * getmsgid(fp) FILE *fp; X{ X static char buf[BUFSIZ]; X static char *msgid = "message-id"; X register char *cp, *cp2; X X while(fgets(buf, sizeof(buf), fp) != (char *)NULL) { X switch(buf[0]) { X case '\n': X return((char *)NULL); /* EOH, we failed */ X case 'M': X case 'm': X if ((cp = index(buf, ':')) == (char *)NULL) X continue; X *cp++ = '\0'; X if (strncmp(lcase(buf), msgid, sizeof(*msgid)) == 0) { X /* dump extraneous trash - umass.bitnet */ X /* hope nobody quotes an '>' in a msgid */ X if ((cp2 = index(cp, '>')) != (char *)NULL) X *++cp2 = '\0'; X return(sp_strip(cp)); X } X break; X } X } X return((char *)NULL); /* EOF, we failed */ X} X X#ifdef notdef /* nobody obeys the triply damned protocol anyway! */ X/* X** Special characters, see RFC822, appendix D. X*/ isspecial(c) char c; X{ X char *specials = "()<>@,;:\\\".[]"; X X return(index(specials, c) != (char *)NULL ? TRUE : FALSE); X} X X/* X** Check on the validity of an RFC822 message-id X** X** By The Book, RFC822 Appendix D. X** msg-id = "<" addr-spec ">" X** addr-spec = local-part "@" domain X** local-part = word *("." word) X** word = atom / quoted-string X** domain = sub-domain *("." sub-domain) X** sub-domain = domain-ref / domain-literal X** domain-ref = atom X** domain-literal = "[" *(dtext / quoted-pair) "]" X** X** NOTE: close reading of the RFC822 spec indicates that a fully X** qualified domain name (i.e. one with at least one dot) is X** NOT required in the domain part of the addr-spec. However, X** I've decided to be an asshole and require them, since we'll X** all die a slow death later on if I don't at this juncture. X** To disable, if you disagree with me, see the last return X** statement. - Erik E. Fair <fair@ucbarpa.berkeley.edu> X** May 30, 1986 X*/ msgid_ok(id) register char *id; X{ X register Langle = FALSE; X register Rangle = FALSE; X register local_part = FALSE; X register at = FALSE; X register dot = FALSE; X X /* skip up to the opening angle bracket */ X if (id == (char *)NULL || (id = index(id, '<')) == (char *)NULL) X return(FALSE); /* don't waste my time! */ X X for(; *id != '\0'; id++) { X switch(*id) { X case '<': X if (Langle) return(FALSE); X Langle = local_part = TRUE; X break; X case '>': X if (Rangle || !Langle || !at) return(FALSE); X else Rangle = TRUE; X break; X case '@': /* should be a domain spec */ X at = TRUE; X local_part = FALSE; X break; X case '.': X dot = at; X break; X case '\\': X /* X ** quoted pair; this disallows NULs, but how X ** many mailers would die if someone used one? X */ X if (!local_part || (*++id) == '\0') return(FALSE); X break; X case '"': X /* X ** quoted string X */ X if (!local_part) return(FALSE); X do { X switch(*++id) { X case '\\': X if ((*++id) == '\0') return(FALSE); X break; X case '\r': X return(FALSE); X } X } while(*id != '\0' && *id != '"'); X break; X case '[': X /* X ** domain literal X */ X if (local_part) return(FALSE); X do { X switch(*++id) { X case '\\': X if ((*++id) == '\0') return(FALSE); X break; X case '\r': X return(FALSE); X } X } while(*id != '\0' && *id != ']'); X break; X default: X if (!isascii(*id) || iscntrl(*id) || isspace(*id) || isspecial(*id)) X return(FALSE); /* quit immediately */ X break; X } X } X return(at && dot && Langle && Rangle); X} X#else notdef X X/* X** Simpleton's check for message ID syntax. X** A concession to the realities of the ARPA Internet. X*/ msgid_ok(s) register char *s; X{ X register char c; X register in_msgid = FALSE; X X if (s == (char *)NULL) X return(FALSE); X X while((c = *s++) != '\0') { X if (!isascii(c) || iscntrl(c) || isspace(c)) X return(FALSE); X switch(c) { X case '<': X in_msgid = TRUE; X break; X case '>': X return(in_msgid); X } X } X return(FALSE); X} X#endif notdef X X/* X** Read the header of a netnews article, snatch the message-id therefrom, X** and ask the remote if they have that one already. X*/ ihave(fp) FILE *fp; X{ X register int code; X register char *id; X char buf[BUFSIZ]; X X if ((id = getmsgid(fp)) == (char *)NULL || *id == '\0') { X /* X ** something botched locally with the article X ** so we don't send it, but we don't break off X ** communications with the remote either. X */ X sprintf(buf, "%s: message-id missing!", Article); X log(L_DEBUG, buf); X return(ERR_GOTIT); X } X X if (!msgid_ok(id)) { X sprintf(buf, "%s: message-id syntax error: %s", Article, id); X log(L_DEBUG, buf); X return(ERR_GOTIT); X } X X sprintf(buf, "IHAVE %s", id); X Stats.offered++; X X switch(code = converse(buf, sizeof(buf))) { X case CONT_XFER: X Stats.accepted++; X rewind(fp); X return(code); X case ERR_GOTIT: X Stats.rejected++; X return(code); X default: X return(code); X } X} X X/* X** Given that fp points to an open file containing filenames, X** open and return a file pointer to the next filename in the file. X** Don't you love indirection? X** X** Returns a valid FILE pointer or NULL if end of file. X*/ FILE * getfp(fp, filename, fnlen) register FILE *fp; char *filename; register int fnlen; X{ X register FILE *newfp = (FILE *)NULL; X register char *cp; X char *mode = "r"; X X while(newfp == (FILE *)NULL) { X if (fgets(filename, fnlen, fp) == (char *)NULL) X return((FILE *)NULL); /* EOF, tell caller */ X X filename[fnlen - 1] = '\0'; /* make sure */ X X /* if fgets() ever forgets the '\n', we're fucked */ X if (*(cp = &filename[strlen(filename) - 1]) == '\n') X *cp = '\0'; X X if (filename[0] == '\0') X continue; X X if ((newfp = fopen(filename, mode)) == (FILE *)NULL) { X /* X ** The only permissible error is `file non-existant' X ** anything else indicates something is seriously X ** wrong, and we should go away to let the shell X ** script clean up. X */ X if (errno != ENOENT) { X char buf[BUFSIZ]; X X sprintf(buf, E_fopen, filename, mode, errmsg(errno)); X log(L_WARNING, buf); X goodbye(DONT_WAIT); X exit(EX_OSERR); X } X } X } X return(newfp); X} X X/* X** OK, clean up any mess and requeue failed articles X*/ cleanup() X{ X dprintf(stderr, "%s: cleanup()\n", Pname); X if (Qfp == (FILE *)NULL || Qfile == (char *)NULL) X return; X X if ((ReQueue_Fails && Stats.failed > 0) || !feof(Qfp)) { X rewrite(); X } else { X /* X ** Nothing to clean up after, reset stuff and X ** nuke the queue file. X */ X requeue((char *)NULL); X if (feof(Qfp)) { X dprintf(stderr, "%s: unlink(%s)\n", Pname, Qfile); X if (unlink(Qfile) < 0) { X char buf[BUFSIZ]; X X sprintf(buf, E_unlk, Qfile, errmsg(errno)); X log(L_WARNING, buf); X } X } X FCLOSE(Qfp); X } X} X X/* X** Add an article file name to an allocated linked list, X** so that we can rewrite it back to the queue file later. X** Calling this with a NULL pointer resets the internal pointer. X*/ void requeue(article) char *article; X{ X static ll_t *lp = &FailedArticles; X X if (article == (char *)NULL) { X dprintf(stderr, "%s: requeue(): reset\n", Pname); X goto reset; /* this is for our static pointer */ X } X X if (*article == '\0') X return; X X dprintf(stderr, "%s: requeue(%s)\n", Pname, article); X if ((lp = l_alloc(lp, article, strlen(article) + 1)) == (ll_t *)NULL) { X fprintf(stderr, "%s: requeue(%s) failed, dumping fail list\n", X Pname, article); X /* X ** Wow! Did you know that this could blow the stack X ** if we recurse too deeply? I sure didn't! X */ reset: X l_free(&FailedArticles); X lp = &FailedArticles; X } X} X X/* X** Note that if I'm not running as "news" or "usenet" (or whatever X** account is supposed to own netnews), the resultant file will be the X** wrong ownership, permissions, etc. X*/ rewrite() X{ X register ll_t *lp; X register FILE *tmpfp; X register int nart = 0; X char *mode = "w+"; X char *template = "/tmp/nntpxmitXXXXXX"; X char buf[BUFSIZ]; X static char *tempfile = (char *)NULL; X X dprintf(stderr, "%s: rewrite(%s)\n", Pname, Qfile); X X if (tempfile == (char *)NULL) /* should only need this once */ X tempfile = mktemp(template); X X if ((tmpfp = fopen(tempfile, mode)) == (FILE *)NULL) { X sprintf(buf, E_fopen, tempfile, mode, errmsg(errno)); X log(L_WARNING, buf); X FCLOSE(Qfp); X return; X } X X /* X ** Requeue the rest of the queue file first, X ** so that failed articles (if any) go to the end X ** of the new file. X */ X if (!feof(Qfp)) { X dprintf(stderr, "%s: copying the unused portion of %s to %s\n", X Pname, Qfile, tempfile); X while(fgets(buf, sizeof(buf), Qfp) != (char *)NULL) X (void) fputs(buf, tmpfp); X } X X /* X ** Here we write out the filenames of articles which X ** failed at the remote end. X */ X dprintf(stderr, "%s: writing failed article filenames to %s\n", X Pname, tempfile); X L_LOOP(lp, FailedArticles) { X fprintf(tmpfp, "%s\n", lp->l_item); X nart++; X } X dprintf(stderr, "%s: wrote %d article filenames to %s\n", X Pname, nart, tempfile); X X (void) fflush(tmpfp); X /* X ** If writing the temp file failed (maybe /tmp is full?) X ** back out and leave the queue file exactly as it is. X */ X if (ferror(tmpfp)) { X sprintf(buf, "rewrite(): copy to %s failed", tempfile); X log(L_WARNING, buf); X (void) fclose(tmpfp); X FCLOSE(Qfp); X if (unlink(tempfile) < 0) { X sprintf(buf, E_unlk, tempfile, errmsg(errno)); X log(L_WARNING, buf); X } X requeue((char *)NULL); /* reset */ X return; X } X X rewind(tmpfp); X#ifdef FTRUNCATE X rewind(Qfp); X if (ftruncate(fileno(Qfp), (off_t)0) < 0) { X sprintf(buf, "ftruncate(%s, 0): %s", Qfile, errmsg(errno)); X log(L_WARNING, buf); X FCLOSE(Qfp); X (void) fclose(tmpfp); X if (unlink(tempfile) < 0) { X sprintf(buf, E_unlk, tempfile, errmsg(errno)); X log(L_WARNING, buf); X } X requeue((char *)NULL); /* reset */ X return; X } X#else X FCLOSE(Qfp); /* we just nuked our lock here (lockfd) */ X if ((Qfp = fopen(Qfile, mode)) == (FILE *)NULL) { X sprintf(buf, E_fopen, Qfile, mode, errmsg(errno)); X log(L_WARNING, buf); X (void) fclose(tmpfp); X if (unlink(tempfile) < 0) { X sprintf(buf, E_unlk, tempfile, errmsg(errno)); X log(L_WARNING, buf); X } X requeue((char *)NULL); /* reset */ X return; X } X /* Try to get our lock back (but continue whether we do or not) */ X (void) lockfd(fileno(Qfp), Qfile, DONT_BLOCK); X#endif FTRUNCATE X X dprintf(stderr, "%s: copying %s back to %s\n", Pname, tempfile, Qfile); X while(fgets(buf, sizeof(buf), tmpfp) != (char *)NULL) X (void) fputs(buf, Qfp); X X (void) fflush(Qfp); X if (ferror(Qfp)) { X sprintf(buf, "rewrite(): copy to %s failed", Qfile); X log(L_WARNING, buf); X } X (void) fclose(tmpfp); X FCLOSE(Qfp); X if (unlink(tempfile) < 0) { X sprintf(buf, E_unlk, tempfile, errmsg(errno)); X log(L_WARNING, buf); X } X requeue((char *)NULL); /* reset */ X dprintf(stderr, "%s: rewrite(%s): done\n", Pname, Qfile); X return; X} X X/* X** Signal stuff X** X** There's probably too much stuff to do in this signal X** handler, but we're going to exit anyway... X*/ interrupted(sig) int sig; X{ X char buf[BUFSIZ]; X X#ifndef RELSIG X catchsig(SIG_IGN); /* for System V - hope we're quick enough */ X#endif RELSIG X sprintf(buf, "%s signal %d", Host, sig); X log(L_NOTICE, buf); X requeue(Article); X cleanup(); X if (Report_Stats) X logstats(); X goodbye(DONT_WAIT); X exit(EX_TEMPFAIL); X} X struct { X int signo; X ifunp state; X} SigList[] = { X {SIGHUP}, X {SIGINT}, X {SIGQUIT}, X {SIGTERM}, X {NULL} X}; X void catchsig(handler) ifunp handler; X{ X register int i; X X if (handler != SIG_IGN) { X for(i = 0; SigList[i].signo != NULL; i++) { X SigList[i].state = signal(SigList[i].signo, handler); X } X } else { X for(i = 0; SigList[i].signo != NULL; i++) { X (void) signal(SigList[i].signo, handler); X } X } X} X void restsig() X{ X register int i; X X for(i = 0; SigList[i].signo != NULL; i++) { X if (SigList[i].state != (ifunp)(-1)) X (void) signal(SigList[i].signo, SigList[i].state); X } X} X X/* X** log stuff X*/ void log(importance, error) int importance; char *error; X{ X FILE *report = (importance == L_INFO ? stdout : stderr); X X fprintf(report, Fmt, Pname, error); X#ifdef SYSLOG X switch(importance) { X case L_INFO: importance = LOG_INFO; break; X case L_DEBUG: importance = LOG_DEBUG; break; X case L_NOTICE: importance = LOG_NOTICE; break; X case L_WARNING: importance = LOG_WARNING; break; X default: importance = LOG_DEBUG; break; X } X syslog(importance, error); X#endif SYSLOG X} X X/* X** Lock a file descriptor X** X** NOTE: if the appropriate system calls are unavailable, X** this subroutine is a no-op. X*/ lockfd(fd, file, non_blocking) int fd, non_blocking; char *file; /* just for error reporting */ X{ X char buf[BUFSIZ]; X#ifdef USG X#ifdef F_TLOCK X if (lockf(fd, (non_blocking ? F_TLOCK : F_LOCK), 0) < 0) { X if (errno != EACCES) { X sprintf(buf, "lockf(%s): %s\n", file, errmsg(errno)); X log(L_WARNING, buf); X } X return(FALSE); X } X#endif F_TLOCK X#else X#ifdef LOCK_EX X if (flock(fd, LOCK_EX|(non_blocking ? LOCK_NB : 0)) < 0) { X if (errno != EWOULDBLOCK) { X sprintf(buf, "flock(%s): %s\n", file, errmsg(errno)); X log(L_WARNING, buf); X } X return(FALSE); X } X#endif LOCK_EX X#endif USG X return(TRUE); X} END_OF_FILE if test 26902 -ne `wc -c <'./xmit/nntpxmit.c'`; then echo shar: \"'./xmit/nntpxmit.c'\" unpacked with wrong size! fi # end of './xmit/nntpxmit.c' fi echo shar: End of archive 8 \(of 9\). cp /dev/null ark8isdone MISSING="" for I in 1 2 3 4 5 6 7 8 9 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have unpacked all 9 archives. rm -f ark[1-9]isdone ark[1-9][0-9]isdone else echo You still need to unpack the following archives: echo " " ${MISSING} fi ## End of shell archive. exit 0 -- Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.