ka@hou3c.UUCP (Kenneth Almquist) (12/08/83)
# This article contains the source for xfernews. This article it in "shar" # format. Feed it into sh (not csh). # # Xfernews is a set of programs designed to transfer USENET articles using # uucp. It requires no modifications to uucp. Look at the NROFFME file for # more information. cat > NROFFME <<\! .. No marco packages are required to nroff this file. .hy .de p .sp .ti +5 .. .de Np 'bp 'sp 5 .ns .. .wh 61 Np .de h .sp 2 .ne 4 .nr h +1 \\nh)\ \ \\$1 .p .. .sp 6 .ce \f3XFERNEWS\f1 .sp 2 .h "What is xfernews?" Xfernews is a package of software for transporting news, and optionally mail, between machines. It is designed to be efficient, reliable, and to run on top of vanilla uucp. .p The memo is divided into five sections. Section 2 documents the protocal used by xfernews. Section 3 gives an overview of how the xfernews software works. Section 4 and 5 discuss the compilation and installation of xfernews. Finally, section 6 talks about error messages. .h "The xfernews protocal" The two news transport methods described in the .ul USENET Interchange Standard are based upon remote execution and mail, respectively. Xfernews is based upon file transfer, which is handled better by uucp. .p Assuming that two systems communicate using the xfernews protocal, each systems has an input directory which the other system sends files to. Each system periodicly checks its input directory and processes any files which it may find there. The name of the file identifies its contents. The first character of the name is the type; a list of types is given below. The next 9 characters contain the value in decimal returned by time(2) when the file was queued for transfer. This should be used by the receiving system to process news in the same order that it was queued. The final character of the filename is a letter chosen to make the file name unique. .p There are three file types currently defined. Type 'n' files contain news articles. Type 'm' files contain mail. The use of this protocal for mail is optional, but is recommended for links which carry large amounts of mail. The first line of the file contains the three characters "To " followed by the destination of the mail. The rest of the file contains the letter. Type 'a' files are acknowledgement files. An acknowledgement file contains a list of files received by the system which sent the acknowledgement file. If a system fails to acknowledge a file, the file should be resent. .h "The Xfernews Software" This gives an overview of the implementation of the xfernews protocal for use with uucp. Three programs are provided. Qnews queues news for transmission to another system. Sendnews sends the news which has been queued up to another system. Recvnews processes news files sent from another system. .p For each system talked to using xfernews, there is a spool directory. The contents of this directory are: .de l .sp .ti 0 .ta 16 \\$1 \c .ta 8,16,24,32,40,48,56,64,72,80 .. .in 16 .l in The input directory used by the remote system (see section 2). .l out News to be sent to the remote system is placed here by queuenews. .l sent When sendnews sends news to the remote system, it moves it from the out directory to the sent directory. The news remains in the sent directory until the remote system acknowledges it. .l ackfile This file contains a list of input files which have been processed. Sendnews sends the contents of this file to the remote system as an acknowledgement file. .l lastack This file contains the time of the last file acknowledged by the remote system. It is used to avoid resending files which haven't been acknowledged because the remote system is down. .l resentflag When sendnews resends some news, it creates this file. The next time sendnews is invoked, it will not resned any news in order to give the remote system time to acknowledge the files already resent. .l bad When a file is found in one of the directories "in", "out", or "sent" which cannot be processed, it is moved to this directory and you are informed of the fact by mail. .in 0 .h "Compiling the Xfernews Software" Compiling the xfernews software is simple: all you have to do is to type "make". However, you will probably want to modify some compile time parameters first. .p If you are not running System 3 or System 5, you should remove the "#define USG 1" line from common.h. This will get you code which should run using Version 6 system calls. The version of the library routines provided with 4.1 BSD should work with this code. If you have 4.2 BSD, the directory format is different; eliminate the 4.2 compatability routines in dir.c and dir.h, and use the real routines provided by Berkeley. .p There are several constants defined in common.h that you may want to change: .in 16 .l RNEWS is the path name of the rnews program used for processing news. Be warned that no path search will be performed. Furthermore, the rnews program cannot be a shell procedure. The default is "/usr/bin/rnews". .l RMAIL is the path name of a program for processing mail. The default is "/bin/rmail". .l UUCP is the path name of the uucp command. The default is "/usr/bin/uucp". .l MAILCMD is the command passed to popen to inform the system administrator of problems. The default is "mail\ usenet". .l RECVLOCK is the name of the lock file used to prevent two copies of recvnews from running simultaneously. The default is "/tmp/recv.lock". .l NETNEWS is the numeric user id which is to be used by sendnews and recvnews when they are invoked as root. If they are not invoked as root then this has no effect. .l DESTLEN is the maximum length of a mail destination. .l MAXARGS is the maximum number of files which can be processed by a given invocation of sendnews or recvnews. .l MINACK specifies the minimum number of files needing to be acknowledged before an acknowledgement file will be sent. Setting this to zero causes the systems to keep trading acknowledgements even when the link is idle. (Each acknowledgement has to be acked.) Note that any pending acknowledgements are always sent if a connection has to be established to transfer news or mail anyway. MINACK is specified as the number of bytes; since each file currently takes 12 bytes, divide by 12 to get the number of files. .in 0 .h " Installing Xfernews" Once xfernews is compiled, you can set up links to other machines using the xfernews protocal. .p The first step is to create xfernews spool directories for the systems you want to talk to. The shell procedure mkspool creates an xfernews spool directory. .p A handy convention is to name the spool directories after the systems you talk to. Thus if you talk to spanky, you might use /usr/spool/spanky as the spool directory. If you think you might want to move the spool directory to a different file system at some point, set the login directory for "netnews" to /usr/spool, and have spanky send news to myname!~netnews/spanky/in. .p Recvnews is invoked as "recvnews directory...". Each directory is the name of an xfernews spool directory. It is recommended that xfernews be invoked from cron quite frequently, say once every 10 or 15 minutes, so that news will be processed as quickly as possible. .p Sendnews is invoked as "sendnews [\ -r\ ] directory to". Directory is the name of an xfernews spool directory. To is the name of the input directory on the remote system in uucp format, e. g. spanky!/usr/spool/spanky/in. The -r option is passed directly to uucp; it tells uucp not to start up the uucp daemon. .p It is recommended that two systems using xfernews invoke sendnews simultaneously. The only problem with doing this is that if uucp is invoked simultaneously on the two machines, each machine may place a call to the other. If this proves to be a problem, it can be avoided by invoking sendnews slightly earlier on one machine that the other. That way, the machine on which xfernews is invoked first will have a chance to complete a call to the second machine before uucp is invoked on the second machine. Uucp on second machine will then observe that it is already talking to the first machine and so that it doesn't have to call the first machine. The problem can also be avoided by calling sendnews with the -r option on one machine. .p Once the connection is set up, you can begin feeding news into it using the qnews program. For a normal interface with netnews, place the entry "qnews directory/out" in to fourth field to the sys file for the system you wish to talk to. Directory should be replaced by the name of the spool directory for the system you wish to talk to. This will cause qnews to read its standard input and copy it to a file in the directory specified as its argument. .p In version 2.10 of netnews, it is possible to reference the name of the article as it is stored in the netnews spool directory, thereby allowing the article to be linked into the spool directory rather than being copied there. To use this feature, the netnews spool directory and the xfernews spool directory must be in the same file system. Add the U flag to the third field of the entry in the sys file, and in the fourth field say: "qnews directory/out %s" The %s will be replaced by the name of the article in the spool directory. .p If the cost of a phone connection is very high, or you are having problems with mail being lost, you may want to transfer mail as well as news using xfernews. You will probably have to modify your mailer code. The basic idea is you first figure out how your mail system transfers mail using uux. It will invoke uux by saying something like: .nf sprintf(cmd, "uux - %s!rnews \\(%s\\)", system, dest) ; fp = popen(cmd, "w") ; .fi Assuming spanky is the system you want to send mail to using xfernews, change this to: .nf if (strcmp(system, "spanky") == 0) { sprintf(cmd, "qnews -tm %s /usr/spool/spanky", dest) ; fp = popen(cmd, "w") ; if (fp != NULL) fprintf(fp, "To %s\\n", dest) ; } else { sprintf(cmd, "uux - %s!rnews \\(%s\\)", system, dest) ; fp = popen(cmd, "w") ; } .fi The -t option to qnews specifies the type of file to be created; in this case 'm' or mail. .h "Administration of xfernews" When an error occurs in the xfernews package, you will be informed by mail. It is important that the mail command work; try invoking sendnews without any arguments and see if an error message is mailed to you. Most error messages refer to errors which "can't happen" (i. e. the problem is either a bug in the package or an error in installation). You may have to grep through the code or contact the author to identify these. You are also informed when the RNEWS or RMAIL programs exit with non-zero status. When one of these programs fails, the mail or news is still acknowledged and the file is linked into the directory "bad" where you can fix the problem manually. The exit status of the program and any error messages generated by the program are included in in the message. Sometimes the problem is transient, so just running rnews again will fix the problem. If you run 2.10, you may want to inews to exit with an error indication when an unknown newsgroup is received. This way you can fix the problem and resubmit the article. The following version of the routine getapproval (in inews.c) does the trick, at least for the beta release: .ec ~ .nf getapproval(ng) char *ng; { char buf[128] ; sprintf(buf, "inews: unrecognized newsgroup %s\n", ng) ; log(buf) ; printf("%s", buf) ; xxit(4) ; } .fi .ec \ Files in the directories "in", "out", and "sent" with unrecognized names will also be moved to the directory "bad". If rnews dies with a core dump, the core file will be left in "in", and the next invocation of recvnews will move it to "bad". ! cat > common.h <<\! #define USG 1 #include <stdio.h> #include <sys/types.h> #include "dir.h" #include <sys/stat.h> #include <signal.h> #include <errno.h> #ifdef USG #include <fcntl.h> #endif #define FNLEN 15 /* max file name length (including nul) */ #define PATHLEN 100 /* max path name length */ #define DESTLEN 256 /* max length of mail destination */ #define MAXARGS 200 /* max number of args to uucp */ #define SETIN 01 /* flag to run: reset stdin */ #define RNEWS "/usr/bin/rnews" #define RMAIL "/bin/rmail" #define UUCP "/usr/bin/uucp" #define MAILCMD "/bin/mail usenet" #define RECVLOCK "/usr/tmp/recv.lock" #define MINACK (10 * 12) #define NETNEWS 72 /* netnews user id */ #ifndef USG #define strchr index #endif /* routine to determine if a process exists */ #define procexists(pid) (kill(pid, 0) >= 0 || errno == EPERM) long time(), atol() ; char *strcpy(), *strchr() ; char *malloc() ; FILE *popen() ; int comp() ; extern int errno ; ! cat > dir.c <<\! #include <sys/types.h> #include <sys/param.h> #include "dir.h" /* * close a directory. */ void closedir(dirp) register DIR *dirp; { close(dirp->dd_fd); dirp->dd_fd = -1; dirp->dd_loc = 0; free(dirp); } /* * open a directory. */ DIR * opendir(name) char *name; { register DIR *dirp; register int fd; if ((fd = open(name, 0)) == -1) return NULL; if ((dirp = (DIR *)malloc(sizeof(DIR))) == NULL) { close (fd); return NULL; } dirp->dd_fd = fd; dirp->dd_loc = 0; return dirp; } /* * read an old style directory entry and present it as a new one */ #define ODIRSIZ 14 struct olddirect { ino_t od_ino; char od_name[ODIRSIZ]; }; /* * get next entry in a directory. */ struct direct * readdir(dirp) register DIR *dirp; { register struct olddirect *dp; static struct direct dir; for (;;) { if (dirp->dd_loc == 0) { dirp->dd_size = read(dirp->dd_fd, dirp->dd_buf, DIRBLKSIZ); if (dirp->dd_size <= 0) return NULL; } if (dirp->dd_loc >= dirp->dd_size) { dirp->dd_loc = 0; continue; } dp = (struct olddirect *)(dirp->dd_buf + dirp->dd_loc); dirp->dd_loc += sizeof(struct olddirect); if (dp->od_ino == 0) continue; dir.d_ino = dp->od_ino; strncpy(dir.d_name, dp->od_name, ODIRSIZ); dir.d_name[ODIRSIZ] = '\0'; /* insure null termination */ dir.d_namlen = strlen(dir.d_name); dir.d_reclen = DIRBLKSIZ; return (&dir); } } ! cat > dir.h <<\! /* dir.h 4.4 82/07/25 */ /* * A directory consists of some number of blocks of DIRBLKSIZ * bytes, where DIRBLKSIZ is chosen such that it can be transferred * to disk in a single atomic operation (e.g. 512 bytes on most machines). * * Each DIRBLKSIZ byte block contains some number of directory entry * structures, which are of variable length. Each directory entry has * a struct direct at the front of it, containing its inode number, * the length of the entry, and the length of the name contained in * the entry. These are followed by the name padded to a 4 byte boundary * with null bytes. All names are guaranteed null terminated. * The maximum length of a name in a directory is MAXNAMLEN. * * The macro DIRSIZ(dp) gives the amount of space required to represent * a directory entry. Free space in a directory is represented by * entries which have dp->d_reclen >= DIRSIZ(dp). All DIRBLKSIZ bytes * in a directory block are claimed by the directory entries. This * usually results in the last entry in a directory having a large * dp->d_reclen. When entries are deleted from a directory, the * space is returned to the previous entry in the same directory * block by increasing its dp->d_reclen. If the first entry of * a directory block is free, then its dp->d_ino is set to 0. * Entries other than the first in a directory do not normally have * dp->d_ino set to 0. */ #define DIRBLKSIZ 512 #define MAXNAMLEN 255 struct direct { long d_ino; /* inode number of entry */ short d_reclen; /* length of this record */ short d_namlen; /* length of string in d_name */ char d_name[MAXNAMLEN + 1]; /* name must be no longer than this */ }; /* * The DIRSIZ macro gives the minimum record length which will hold * the directory entry. This requires the amount of space in struct direct * without the d_name field, plus enough space for the name with a terminating * null byte (dp->d_namlen+1), rounded up to a 4 byte boundary. */ #undef DIRSIZ #define DIRSIZ(dp) \ ((sizeof (struct direct) - (MAXNAMLEN+1)) + (((dp)->d_namlen+1 + 3) &~ 3)) #ifndef KERNEL /* * Definitions for library routines operating on directories. */ typedef struct _dirdesc { int dd_fd; long dd_loc; long dd_size; char dd_buf[DIRBLKSIZ]; } DIR; #ifndef NULL #define NULL 0 #endif extern DIR *opendir(); extern struct direct *readdir(); extern long telldir(); extern void seekdir(); #define rewinddir(dirp) seekdir((dirp), (long)0) extern void closedir(); #endif KERNEL ! cat > makefile <<\! all: recvnews sendnews qnews recvnews: recvnews.o dir.o $(CC) -o $@ recvnews.o dir.o sendnews: sendnews.o dir.o $(CC) -o $@ sendnews.o dir.o qnews: qnews.c common.h $(CC) -o $@ $(CFLAGS) qnews.c sendnews.o recvnews.o: common.h ! cat > mkspool <<\! # Create an xfernews spool directory if test "$1" = "" then echo usage: mkspool directory-name exit 1 fi if mkdir $1 then mkdir $1/in mkdir $1/sent mkdir $1/out mkdir $1/bad echo 0 > $1/lastack fi ! chmod +x mkspool cat > qnews.c <<\! #include "common.h" char *directory ; char *file ; main(argc, argv) char **argv ; { long t ; char *from, *lastc ; char to[PATHLEN] ; char **ap ; char prefix ; int fd ; prefix = 'n' ; ap = argv + 1 ; if (ap[0][0] == '-' && ap[0][1] == 't') { if ((prefix = ap[0][2]) == '\0') usage: fatal("usage: qnews [ -tc ] directory [ file ]") ; ap++ ; } if ((directory = *ap++) == NULL) goto usage ; from = *ap ; if (from != NULL && strcmp(from, "%s") == 0) from = NULL ; time(&t) ; sprintf(to, "%s/%c%lda", directory, prefix, t) ; lastc = to + strlen(to) - 1 ; signal(SIGTERM, SIG_IGN) ; for (;;) { if (from != NULL) fd = link(from, to) ; else { #ifdef USG fd = open(to, O_WRONLY | O_CREAT | O_EXCL, 0444) ; #else fd = creat(to, 0444) ; #endif } if (fd >= 0) break ; if (errno != EEXIST && errno != EPERM || *lastc == 'z') fatal("can't create %s", to) ; *lastc += 1 ; } if (from == NULL) { char buf[BUFSIZ] ; int count ; file = to ; while ((count = read(0, buf, BUFSIZ)) > 0) { if (write(fd, buf, count) != count) { fatal("write error") ; } } if (count < 0) { fatal("read error") ; } } exit(0) ; } /* * Fatal error. * Print error message and send mail to administrator. */ fatal(fmt, a1, a2, a3, a4) char *fmt ; { static int reentered = 0 ; if (reentered) { fprintf(stderr, "fatal entered recursively\n") ; fprintf(stderr, fmt, a1, a2, a3, a4) ; if (file != NULL) unlink(file) ; exit(3) ; } reentered = 1 ; msg(fmt, a1, a2, a3, a4) ; if (file != NULL) if (unlink(file) < 0) msg("unlink failed: %s", file) ; exit(2) ; } /* * Send mail to administrator. */ msg(fmt, a1, a2, a3, a4) char *fmt ; { FILE *fp ; int fd ; int e = errno ; fprintf(stderr, fmt, a1, a2, a3, a4) ; putc('\n', stderr) ; /* be sure stdin is open; otherwise popen won't work */ if ((fd = open("/dev/null", 0)) > 0) close(fd) ; if ((fp = popen(MAILCMD, "w")) == NULL) fatal("popen failed") ; fputs("Subject: error in qnews\n\n", fp) ; fprintf(fp, fmt, a1, a2, a3, a4) ; if (directory != NULL) { fputs("\nprocessing ", fp) ; fputs(directory, fp) ; } putc('\n', fp) ; fprintf(fp, "errno = %d\n", e) ; if (pclose(fp) != 0) fatal("msg failed") ; } ! cat > recvnews.c <<\! #define RECVNEWS 1 #include "common.h" #ifdef USG #include <setjmp.h> #define setexit() setjmp(nextdir) #define reset() longjmp(nextdir, 1) jmp_buf nextdir ; /* label to jump to on major error */ #endif struct arglist { int nargs ; char *arg[MAXARGS] ; } ; char *directory ; /* directory currently being processed */ int errflag ; /* set if any errors */ char lockfile[] = RECVLOCK ; main(argc, argv) char **argv ; { char **ap ; nice(10) ; setuid(NETNEWS) ; /* in case invoked as root by cron */ if (setlock(lockfile) == 0) { printf("recvnews locked\n") ; exit(0) ; } ap = argv + 1 ; setexit() ; while (*ap != NULL) { directory = *ap++ ; if (chdir(directory) < 0) fatal("directory nonexistant") ; inputnews() ; } if (unlink(lockfile) < 0) msg("can't unlink lock") ; exit(errflag) ; } inputnews() { struct arglist in ; DIR *dp ; struct direct *d ; int oflow ; int i ; in.nargs = 0 ; oflow = 0 ; if (chdir("in") < 0) fatal("in missing") ; dp = opendir(".") ; if (dp == NULL) fatal("no .") ; while ((d = readdir(dp)) != NULL) { if (d->d_name[0] != '.') { if (in.nargs < MAXARGS) addarg(d->d_name, &in) ; else oflow++ ; } } if (oflow > 0) msg("%d articles not processed", oflow) ; closedir(dp) ; if (in.nargs == 0) goto out ; qsort((char *)in.arg, in.nargs, sizeof(char *), comp) ; sleep(5) ; /* in case any files half written */ for (i = 0 ; i < in.nargs ; i++) { procfile(in.arg[i]) ; free(in.arg[i]) ; } out: if (chdir("..") < 0) fatal("can't chdir ..") ; } procfile(name) char *name ; { FILE *fp ; int rc ; if (badname(name)) { msg("bad input file name %s", name) ; movebad(name) ; return ; } if ((fp = fopen(name, "r")) == NULL) { msg("unreadable file %s", name) ; movebad(name) ; return ; } switch (name[0]) { case 'a': rc = procack(name, fp) ; break ; case 'n': rc = procnews(name, fp) ; break ; case 'm': rc = procmail(name, fp) ; break ; default: fatal("can't happen %s", name) ; break ; } if (rc < 0) movebad(name) ; else if (unlink(name) < 0) msg("can't unlink %s", name) ; fclose(fp) ; if ((fp = fopen("../ackfile", "a")) == NULL) fatal("Can't open ackfile") ; fprintf(fp, "%s\n", name) ; fclose(fp) ; } procnews(name, fp) char *name ; FILE *fp ; { char *arg[2] ; arg[0] = RNEWS, arg[1] = NULL ; return chkrun(arg, name, fp) ; } procmail(name, fp) char *name ; FILE *fp ; { char *arg[4] ; char buf[DESTLEN] ; char *p ; setbuf(fp, NULL) ; /* turn off buffering */ if (fgets(buf, DESTLEN, fp) == NULL) { msg("%s: empty file", name) ; return -1 ; } if (strncmp(buf, "To ", 3) != 0) { msg("corrupted mail %s", name) ; return -1 ; } if ((p = strchr(buf, '\n')) == NULL) { msg("destination too long, file %s", name) ; return -1 ; } *p = '\0' ; arg[0] = RMAIL, arg[1] = buf + 3, arg[2] = NULL ; return chkrun(arg, name, fp) ; } procack(name, fp) char *name ; FILE *fp ; { char line[FNLEN+2] ; char *p ; if (chdir("../sent") < 0) fatal("no sent dir") ; while (fgets(line, FNLEN + 2, fp) != NULL) { if ((p = strchr(line, '\n')) == NULL) { msg("line too long, file %s", name) ; bad: if (chdir("../in") < 0) fatal("return to in") ; return -1 ; } *p = '\0' ; if (badname(line)) { msg("bad file %s acked in %s", line, name) ; goto bad ; } if (unlink(line) < 0) printf("Can't unlink %s/in/%s, ack file %s\n", directory, line, name) ; } if (chdir("../in") < 0) fatal("return to in") ; if ((fp = fopen("../lastack", "w")) == NULL) fatal("can't open lastack") ; fprintf(fp, "%.9s\n", line + 1) ; fclose(fp) ; return 0 ; } badname(fname) char *fname ; { register c ; if ((c = *fname++) != 'a' && c != 'n' && c != 'm') return -1 ; if ((c = *fname++) < '0' || c > '9') return -1 ; return 0 ; } movebad(fname) char *fname ; { char bad[PATHLEN] ; sprintf(bad, "../bad/%s", fname) ; unlink(bad) ; if (link(fname, bad) < 0) fatal("link to bad failed for %s", fname) ; if (unlink(fname) < 0) fatal("unlink bad file %s failed", fname) ; } comp(a, b) char **a, **b ; { return strcmp(*a, *b) ; } addarg(fname, argl) struct arglist *argl ; char *fname ; { char *p ; if (argl->nargs >= MAXARGS) fatal("too many articles") ; if (fname == NULL) p = NULL ; else { if ((p = malloc(strlen(fname) + 1)) == NULL) fatal("out of space") ; strcpy(p, fname) ; } argl->arg[argl->nargs++] = p ; } /* * Run a program, informing the system administrator if it fails. */ chkrun(arg, name, fp) char *arg[] ; char *name ; FILE *fp ; { char *p ; int outfd ; int rc ; FILE *mailfp ; static char outfile[24] ; if (outfile[0] == '\0') sprintf(outfile, "/tmp/recvnews%d", getpid()) ; if ((outfd = creat(outfile, 0666)) < 0) fatal("Can't create %s", outfile) ; rc = run(arg, fileno(fp), outfd) ; close(outfd) ; if (rc != 0) { if ((mailfp = popen(MAILCMD, "w")) == NULL) fatal("Can't popen MAILCMD") ; fprintf(mailfp, "Subject: error in recvnews\n\n") ; if ((rc & 0177) == 0) { fprintf(mailfp, "exit status %d from %s", rc >> 8, arg[0]) ; } else { fprintf(mailfp, "%s died with signal %d", arg[0], rc & 0177) ; if (rc & 0200) fprintf(mailfp, " - core dumped") ; } fprintf(mailfp, "\nfile %s/bad/%s\n", directory, name) ; if ((fp = fopen(outfile, "r")) == NULL) fprintf(mailfp, "Can't open %s\n", outfile) ; else { fprintf(mailfp, "Output of program:\n") ; while ((rc = getc(fp)) != EOF) putc(rc, mailfp) ; fclose(fp) ; } pclose(mailfp) ; if (unlink(outfile) < 0) msg("can't unlink %s", outfile) ; return -1 ; } if (unlink(outfile) < 0) msg("can't unlink %s", outfile) ; return 0 ; } run(args, in, out) char *args[] ; int in, out ; { int pid ; int status ; int i ; #ifdef DEBUG printf("run") ; /*DEBUG*/ for (i = 0 ; args[i] != NULL ; i++) /*DEBUG*/ printf(" %s", args[i]) ; /*DEBUG*/ putchar('\n') ; /*DEBUG*/ #endif if ((pid = fork()) == -1) fatal("Cannot fork") ; if (pid == 0) { if (in != 0) { close(0) ; if (dup(in) != 0) { msg("Cannot redirect input") ; exit(127) ; } close(in) ; } if (out != 1) { close(1) ; if (dup(out) != 1) { msg("Cannot redirect output") ; exit(127) ; } close(out) ; close(2) ; if (dup(1) != 2) { msg("Cannot dup 1") ; exit(127) ; } } execv(args[0], args) ; msg("exec failed") ; exit(127) ; } while ((i = wait(&status)) != pid && i != -1) ; return status ; } /* * Fatal error. * Print error message and send mail to administrator. */ fatal(fmt, a1, a2, a3, a4) char *fmt ; { static int reentered = 0 ; if (reentered) { fprintf(stderr, "fatal entered recursively\n") ; fprintf(stderr, fmt, a1, a2, a3, a4) ; exit(3) ; } reentered = 1 ; msg(fmt, a1, a2, a3, a4) ; reentered = 0 ; reset() ; } /* * Send mail to administrator. */ msg(fmt, a1, a2, a3, a4) char *fmt ; { FILE *fp ; int fd ; int e = errno ; errflag = 1 ; fprintf(stderr, fmt, a1, a2, a3, a4) ; putc('\n', stderr) ; /* be sure stdin is open; otherwise popen won't work */ if ((fd = open("/dev/null", 0)) > 0) close(fd) ; if ((fp = popen(MAILCMD, "w")) == NULL) fatal("popen failed") ; fputs("Subject: error in recvnews\n\n", fp) ; fprintf(fp, fmt, a1, a2, a3, a4) ; if (directory != NULL) { fputs("\nprocessing ", fp) ; fputs(directory, fp) ; } putc('\n', fp) ; fprintf(fp, "errno = %d\n", e) ; if (pclose(fp) != 0) fatal("msg failed") ; } setlock(name) char *name ; { FILE *fp ; char buf[10] ; if ((fp = fopen(name, "r")) != NULL) { if (fgets(buf, 10, fp) == NULL) { msg("empty lock file") ; fclose(fp) ; goto lock ; } fclose(fp) ; if (buf[0] < '0' || buf[0] > '9') { msg("no pid in lock file") ; goto lock ; } if (! procexists(atoi(buf))) { msg("previous recvnews didn't remove lock") ; goto lock ; } return 0 ; } lock: if ((fp = fopen(name, "w")) == NULL) fatal("cannot create lock file") ; fprintf(fp, "%d\n", getpid()) ; fclose(fp) ; return 1 ; } ! cat > sendnews.c <<\! #define DEBUG #include "common.h" #define ASKIP 4 struct arglist { int nargs ; char **first ; char *arg[MAXARGS] ; } ; struct arglist uuargs ; char *directory ; int errflag ; char resentflag[] = "resentflag" ; char lastack[] = "lastack" ; char ackfile[] = "ackfile" ; main(argc, argv) char **argv ; { register char **ap ; int rflag ; char *p ; int i ; struct stat statb ; setuid(NETNEWS) ; ap = argv + 1 ; rflag = 0 ; while ((p = *ap++) != NULL && *p == '-') { if (strcmp(p, "-r") == 0) rflag++ ; else usage: fatal("usage: sendnews [ -r ] from to") ; } if (p == NULL || *ap == NULL) goto usage ; directory = p ; if (chdir(p) < 0) fatal("no directory") ; uuargs.first = &uuargs.arg[ASKIP] ; uuargs.nargs = ASKIP ; if (unlink(resentflag) < 0) { sendnews(1) ; } if (uuargs.nargs > ASKIP) { if ((i = creat(resentflag, 0666)) < 0) msg("can't create resent flag") ; close(i) ; msg("resent %d files", uuargs.nargs - ASKIP) ; } sendnews(0) ; if (stat(ackfile, &statb) >= 0 && (uuargs.nargs > ASKIP || statb.st_size >= MINACK)) { long t ; char buf[FNLEN + 5] ; time(&t) ; sprintf(buf, "sent/a%lda", t) ; if (link(ackfile, buf) < 0) { msg("can't link ackfile") ; goto uu ; } if (unlink(ackfile) < 0) { msg("can't unlink ackfile") ; goto uu ; } insarg(buf + 5, &uuargs) ; } uu: uucp(*ap, rflag) ; exit(errflag) ; } sendnews(resend) { char *dir ; char sentname[PATHLEN] ; DIR *dp ; FILE *fp ; struct direct *d ; long last ; int oflow ; if (resend == 0) { dir = "out" ; } else { dir = "sent" ; if ((fp = fopen(lastack, "r")) == NULL) { msg("can't open lastack") ; return ; } if (fgets(sentname, FNLEN, fp) == NULL) { /* Can occur bacause no locking done */ msg("lastack is empty file") ; fclose(fp) ; return ; } fclose(fp) ; last = atol(sentname) - 3600L ; } if (chdir(dir) < 0) fatal("chdir %s failed", dir) ; if ((dp = opendir(".")) == NULL) fatal("no .") ; oflow = 0 ; while ((d = readdir(dp)) != NULL) { if (d->d_name[0] == '.') continue ; else if (badname(d->d_name)) { msg("bad file %s in %s", d->d_name, dir) ; movebad(d->d_name) ; continue ; } if (resend) { if (atol(d->d_name + 1) > last) continue ; printf("resending %s\n", d->d_name) ; } if (uuargs.nargs >= MAXARGS - 3) { oflow++ ; continue ; } addarg(d->d_name, &uuargs) ; if (! resend) { sprintf(sentname, "../sent/%s", d->d_name) ; if (link(d->d_name, sentname) < 0) msg("link %s failed", d->d_name) ; else if (unlink(d->d_name) < 0) msg("unlink %s failed", d->d_name) ; } } closedir(dp) ; if (oflow > 0) msg("too many files: %d not sent", oflow) ; if (chdir("..") < 0) fatal("no ..") ; } uucp(to, rflag) char *to ; { if (uuargs.first == uuargs.arg + uuargs.nargs) return ; if (chdir("sent") < 0) fatal("no sent dir") ; qsort((char *)(uuargs.arg + ASKIP), uuargs.nargs - ASKIP, sizeof(char *), comp) ; addarg(to, &uuargs) ; addarg(NULL, &uuargs) ; if (rflag) insarg("-r", &uuargs) ; insarg("-c", &uuargs) ; /* still not default on some uucps */ insarg(UUCP, &uuargs) ; if (run(uuargs.first, 0, 0) != 0) fatal("uucp failed") ; if (chdir("..") < 0) fatal("no ..") ; } badname(fname) char *fname ; { register c ; if ((c = *fname++) != 'a' && c != 'n' && c != 'm') return -1 ; if ((c = *fname++) < '0' || c > '9') return -1 ; return 0 ; } movebad(fname) char *fname ; { char bad[PATHLEN] ; sprintf(bad, "../bad/%s", fname) ; unlink(bad) ; if (link(fname, bad) < 0) fatal("link to bad failed for %s", fname) ; if (unlink(fname) < 0) fatal("unlink bad file %s failed", fname) ; } comp(a, b) char **a, **b ; { return strcmp(*a, *b) ; } addarg(fname, argl) struct arglist *argl ; char *fname ; { char *p ; if (argl->nargs >= MAXARGS) fatal("too many articles") ; if (fname == NULL) p = NULL ; else { if ((p = malloc(strlen(fname) + 1)) == NULL) fatal("out of space") ; strcpy(p, fname) ; } argl->arg[argl->nargs++] = p ; } insarg(fname, argl) struct arglist *argl ; char *fname ; { char *p ; if (argl->first <= argl->arg) fatal("insarg failed") ; if (fname == NULL) p = NULL ; else { if ((p = malloc(strlen(fname) + 1)) == NULL) fatal("out of space") ; strcpy(p, fname) ; } *--(argl->first) = p ; } run(args, flags, fd) char *args[] ; int flags ; int fd ; { int pid ; int status ; int i ; #ifdef DEBUG printf("run") ; /*DEBUG*/ for (i = 0 ; args[i] != NULL ; i++) /*DEBUG*/ printf(" %s", args[i]) ; /*DEBUG*/ putchar('\n') ; /*DEBUG*/ #endif if ((pid = fork()) == -1) fatal("Cannot fork") ; if (pid == 0) { if (flags & SETIN) { close(0) ; if (dup(fd) != 0) { msg("Cannot redirect input") ; exit(127) ; } close(fd) ; } execv(args[0], args) ; msg("exec failed") ; exit(127) ; } while ((i = wait(&status)) != pid && i != -1) ; return status ; } /* * Fatal error. * Print error message and send mail to administrator. */ fatal(fmt, a1, a2, a3, a4) char *fmt ; { static int reentered = 0 ; if (reentered) { fprintf(stderr, "fatal entered recursively\n") ; fprintf(stderr, fmt, a1, a2, a3, a4) ; exit(3) ; } reentered = 1 ; msg(fmt, a1, a2, a3, a4) ; exit(2) ; } /* * Send mail to administrator. */ msg(fmt, a1, a2, a3, a4) char *fmt ; { FILE *fp ; int fd ; errflag = 1 ; fprintf(stderr, fmt, a1, a2, a3, a4) ; putc('\n', stderr) ; /* be sure stdin is open; otherwise popen won't work */ if ((fd = open("/dev/null", 0)) > 0) close(fd) ; if ((fp = popen(MAILCMD, "w")) == NULL) fatal("popen failed") ; fputs("Subject: error in sendnews\n\n", fp) ; fprintf(fp, fmt, a1, a2, a3, a4) ; if (directory != NULL) { fputs("\nprocessing ", fp) ; fputs(directory, fp) ; } putc('\n', fp) ; if (pclose(fp) != 0) fatal("msg failed") ; } !