[net.sources] xfernews

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") ;
}
!