#ifndef lint
static	char	*sccsid = "@(#)$Header: spawn.c,v 1.21 91/01/12 11:25:33 sob Exp $";
#endif

#include "common.h"

#include <signal.h>
#ifdef sparc
#include <vfork.h>
#endif
#ifdef XFER_TIMEOUT
static int	xfer_lines;
static int	old_xfer_lines;
#endif

static char	tempfile[256];
#ifndef CNEWS
static char	badfile[256];
#endif

/*
 * spawn -- create a child process with the input from the client
 * as stdin.
 *
 *	Parameters:	"path" is the path of the program to invoke.
 *			"name" is the name to call the program.
 *			"flag" is a single flag to be passed to the program.
 *			"cont_code" is the response code to transmit
 *			on successful startup.
 *			"err_code" is the response code to transmit when
 *			something goes wrong.
 *
 *	Returns:	-1 on non-zero return from child,
 *			0 on error before fork/exec,
 *			1 otherwise.
 *
 *	Side effects:	Creates and removes temporary file;
 *			accepts input from client; forks and execs.
 *			Can time out if XFER_TIMEOUT is defined.
 */

spawn(path, name, flag, cont_code, err_code, errbuf, msg_id)
	char		*path;
	char		*name;
	char		*flag;
	int		cont_code;
	int		err_code;
	char		*errbuf;
	char		*msg_id;
{
	char		line[NNTP_STRLEN];
	register char	*cp;
	int		i, fd;
	int		fds[2];
	int		pid, npid;
	int		exit_status;
#ifdef POSTER
	char *envp[4], user[sizeof(POSTER) + 5], logname[sizeof(POSTER) + 8];
	char *home;
#else
	char *envp[1];
#endif
#ifdef XFER_TIMEOUT
	int		xfer_timeout();
#endif
#ifdef USG
	int		status;
#else not USG
#ifdef LINUX
	int		status;
#else
	union wait	status;
#endif
#endif not USG
	register FILE	*fp;

#ifdef CNEWS
	(void) strcpy(tempfile, "/usr/tmp/rpostXXXXXX");
#else
	sprintf(tempfile, "%s/.tmp/rpostXXXXXX",SPOOLDIR);
#endif
	(void) mktemp(tempfile);

	fp = fopen(tempfile, "w");
	if (fp == NULL) {
		printf("%d Cannot create temporary file.\r\n", err_code);
		(void) fflush(stdout);
		return (0);
	}

#ifdef AUTH
	/*
	* If this is a posting, rather than an XFER, leave a trail of
	* breadcrumbs by stuffing where it came from into the header
	*/
	if (cont_code == CONT_POST)
		fprintf(fp, "Nntp-Posting-Host: %s\n", hostname);
#endif AUTH

	printf("%d Ok\r\n", cont_code);
	(void) fflush(stdout);

#ifdef XFER_TIMEOUT
	xfer_lines = old_xfer_lines = 0;
	signal(SIGALRM, xfer_timeout);
	(void) alarm(XFER_TIMEOUT);
#endif

	while (fgets(line, sizeof(line), stdin) != NULL) {
#ifdef XFER_TIMEOUT
		xfer_lines++;
#endif
		if ((cp = index(line, '\r')) != NULL)
			*cp = '\0';
		else if ((cp = index(line, '\n')) != NULL)
			*cp = '\0';

		if (line[0] == '.' && line[1] == '\0')
			break;

		if (line[0] == '.')
			fputs(line+1, fp);
		else
			fputs(line, fp);
		putc('\n', fp);
	}
	(void) fclose(fp);

#ifdef XFER_TIMEOUT
	(void) alarm(0);
	(void) signal(SIGALRM, SIG_DFL);
#endif

	/* See if the connection got closed somehow... */

	if (line[0] != '.' && line[1] != '\0') {
		(void) unlink(tempfile);
#ifdef SYSLOG
# ifdef LOG
		syslog(LOG_ERR,
		    "%s spawn: EOF before period on line by itself %s",
		    hostname, msg_id);
# else
		syslog(LOG_ERR,
		    "spawn: EOF before period on line by itself %s", msg_id);
# endif
#endif
		return (0);
	}
		
#ifdef POSTER
	if (tempfile[0])
		(void) chown(tempfile, uid_poster, gid_poster);
#endif

	/* Set up a pipe so we can see errors from rnews */

	if (pipe(fds) < 0) {
#ifdef SYSLOG
		syslog(LOG_ERR, "spawn: pipe: %m");
#endif
		(void) unlink(tempfile);
		return (-1);
	}

	/*
	 * Ok, now we have the article in "tempfile".  We
	 * should be able to fork off, close fd's 0 to 31 (or
	 * whatever), open "tempfile" for input, thus making
	 * it stdin, and then execle the inews.  We think.
	 */
#ifdef SYSLOG
	/*
	 * Close in such a way that syslog() will know to reopen.
	 * We must do this before the vfork() otherwise the parent
	 * will think the syslog fd is closed and open a new one,
	 * eventually using up all the available file descriptors.
	 */
	closelog();
#endif

	pid = vfork();
	if (pid == 0) {		/* We're in child */
#ifdef POSTER
#ifndef USG
		if (getuid() == 0) initgroups(POSTER,gid_poster);
#endif
		(void) setgid(gid_poster);
		(void) setuid(uid_poster);
#endif

		/* Set up stdout and stderr for child */

		if (fds[1] != 1) {
			(void) dup2(fds[1], 1);
			(void) close(fds[1]);
		}
		(void) dup2(1, 2);

		for (i = 3; i < 10; ++i) /* XXX but getdtablesize is too big */
			(void) close(i);

		fd = open(tempfile, O_RDONLY);
		if (fd != 0) {
			(void) dup2(fd, 0);
			(void) close(fd);
		}

		/* Empty environment keeps cnews inews from telling lies */
#ifdef POSTER
		sprintf(user, "USER=%s", POSTER);
		sprintf(logname, "LOGNAME=%s", POSTER);
		if ((home = (char *)malloc(strlen(home_poster)+5)) != NULL)
			sprintf(home, "HOME=%s", home_poster);
		envp[0] = user;
		envp[1] = logname;
		envp[2] = home;
		envp[3] = 0;
#else
		envp[0] = 0;
#endif

#ifdef USG
 		/* execle() fails if name is a shell procedure */
 		execle("/bin/sh", "sh", path, flag, (char *)NULL, envp);
#else
		execle(path, name, flag, (char *) NULL, envp);
#endif
		fprintf(stderr, "spawn: execle(%s)", path);
		perror(path);
#ifdef SYSLOG
		syslog(LOG_ERR, "spawn: execle(%s): %m", path);
#endif
		_exit(-1);	/* Error */
		/*NOTREACHED*/
	} else {	/* We're in parent. */
		if (pid == -1) {
			/* fork failed! */
			printf("%d Cannot fork %s\r\n", err_code, path);
			(void) fflush(stdout);
#ifdef SYSLOG
			syslog(LOG_ERR, "spawn: fork: %m");
#endif
			(void) unlink(tempfile);
			return (0);
		}
		(void) close(fds[1]);
		fp = fdopen(fds[0], "r");
		if (fp == NULL) {
			printf("%d Cannot fdopen %s pipe\r\n", err_code, path);
			(void) fflush(stdout);
#ifdef SYSLOG
			syslog(LOG_ERR, "spawn: pipe: %m");
#endif
			(void) unlink(tempfile);
			return (0);
		}

		if (errbuf)
			*errbuf = '\0';

		while (fgets(line, sizeof (line), fp) != NULL) {
			if (line[0] != '\n') {
				if (errbuf) {
					if (cp = index(line, '\n'))
						*cp = '\0';
					(void) strcat(errbuf, line);
				}
#ifdef SYSLOG
				syslog(LOG_INFO, "%s: %s", path, line);
#endif
			}
		}

		while ((npid = wait(&status)) > 0)
			if (npid == pid) {
#ifdef USG
				exit_status = (status >> 8) & 0xff;
#else /* not USG */
#ifdef LINUX
				exit_status = (status >> 8) & 0xff;
#else
				exit_status = status.w_T.w_Retcode;
#endif
#endif /*not USG */
				break;
			}

		(void) fclose(fp);
		(void) fflush(stdout);
		if (npid < 0) {
#ifdef SYSLOG
			syslog(LOG_ERR, "spawn: wait pid %d: %m", pid);
#endif
			return (-1);
		}

#ifdef notdef
		/* Make ctags happy */
		{
#endif
		if (exit_status != 0) {
#ifdef SYSLOG
			syslog(LOG_ERR, "spawn: %s exit status %d",
				path, exit_status);
#endif
			/*
			 * Save the tempfile away in .bad.
			 */
#ifdef CNEWS
			}
		(void) unlink(tempfile);
#else
			sprintf(badfile, "%s/.bad/nntpXXXXXX",SPOOLDIR);
			(void) mktemp(badfile);
			(void) rename(tempfile, badfile);
		} else {
			(void) unlink(tempfile);
		}
#endif			
		return (exit_status ? -1 : 1);
	}
}

#ifdef XFER_TIMEOUT

xfer_timeout()
{
	if (old_xfer_lines < xfer_lines) {
		old_xfer_lines = xfer_lines;
		(void) alarm(XFER_TIMEOUT);
		return;
	}

	/* Timed out. */

	printf("%d timeout after %d seconds, closing connection.\r\n",
		ERR_FAULT, XFER_TIMEOUT);
	fflush(stdout);

#ifdef LOG
	syslog(LOG_ERR, "%s transfer_timeout", hostname);
#endif LOG

	(void) unlink(tempfile);

	exit(1);
}

#endif XFER_TIMEOUT

