/* $Header: procs.c,v 2.6 90/02/23 16:35:59 chip Exp $
 *
 * Process management and misc support.
 *
 * $Log:	procs.c,v $
 * Revision 2.6  90/02/23  16:35:59  chip
 * \Fix problems determining legality of user references.
 * 
 * Revision 2.5  90/02/23  14:16:51  chip
 * Support "#!" in delivery files.
 * Support "user|program" and "user?error" from delivery files.
 * Improve debugging and error message formatting.
 * Rearrange code for clarity.
 * 
 * Revision 2.4  89/11/01  12:19:05  network
 * Delintify.
 * 
 * Revision 2.3  89/11/01  11:51:50  network
 * Add logging.
 * 
 * Revision 2.2  89/09/29  18:18:03  network
 * Save message when delivery file produces no output,
 * unless delivery file output the "DROP" string.
 * Don't recopy temp files for sys and post-user delfiles.
 * 
 * Revision 2.1  89/06/09  12:25:37  network
 * Update RCS revisions.
 * 
 * Revision 1.5  89/06/09  12:23:57  network
 * Baseline for 2.0 release.
 * 
 */

#include "deliver.h"
#include <errno.h>
#include <signal.h>

/*
 * External data.
 */

extern  int     errno;

/*
 * Local data.
 */

static  int     child_pid = -1;
static  SIGTYPE (*saved_sigpipe)() = SIG_DFL;

/*----------------------------------------------------------------------
 * Like popen(), but execute the child in a specific context.
 * Also, the argument list is already a vector.
 */

FILE *
ct_fopenv(ct, prog, av, mode)
CONTEXT *ct;
char    *prog;
char    **av;
char    *mode;
{
	FILE	*fp;
	int	fd, m;

	if (mode && mode[0] == 'r' && mode[1] == 0)
		m = O_RDONLY;
	else if (mode && mode[0] == 'w' && mode[1] == 0)
		m = O_WRONLY;
	else
		return NULL;

	if ((fd = ct_openv(ct, prog, av, m)) == -1)
		return NULL;

	if ((fp = fdopen(fd, mode)) == NULL)
		(void) ct_close(fd);

	return fp;
}

/*----------------------------------------------------------------------
 * Close the stream opened by ct_fopen().
 */

ct_fclose(fp)
FILE    *fp;
{
	int	fd;

	if (fp)
	{
		fd = dup(fileno(fp));
		(void) fclose(fp);
	}
	else
		fd = -1;

	return ct_close(fd);
}

/*----------------------------------------------------------------------
 * Like popen(), but execute the child in a specific context.
 * Also, the argument list is already a vector.
 * And return a file descriptor instead of a FILE *.
 */

int
ct_openv(ct, prog, av, mode)
CONTEXT *ct;
char    *prog;
char    **av;
int	mode;
{
	char    ch;
	int     child, parent;
	int     pfd[2];

	if (!ct || !prog || !av)
		return NULL;

	if (mode == O_RDONLY)
		child = 1, parent = 0;
	else if (mode == O_WRONLY)
		child = 0, parent = 1;
	else
	{
		error("in ct_open: invalid mode");
		return -1;
	}

	/* We can't have more than one child at a time. */

	if (child_pid >= 0)
	{
		error("in ct_open: a process is already open");
		return -1;
	}

	/* Make a stab at predicting uid-related failure. */

	if (! ok_context(eff_uid, real_uid, real_gid, ct))
	{
		error("in ct_open: no permissions to become %s", ct->ct_name);
		return -1;
	}

	/* Pipes?  Like, tubular, fer shur! */

	if (pipe(pfd) == -1)
	{
		syserr("can't create a pipe");
		return -1;
	}

	/* Generate a debugging message. */

	if (verbose)
	{
		int a;

		message("%s: spawning", progname);
		for (a = 0; av[a]; ++a)
			message(" %s", av[a]);
		message("\n");
	}

	/* Handle the child case */

	if (sfork() == 0)
	{
		if (child == 0)
		{
			(void) close(0);
			(void) dup(pfd[0]);     /* ass_u_me 0 */
		}
		else
		{
			(void) close(0);
			if (open("/dev/null", O_RDONLY) != 0)
			{
				/* This should _never_ happen, but... */
				syserr("can't open /dev/null");
				(void) dup(1);  /* ass_u_me 0 */
			}

			(void) close(1);
			(void) dup(pfd[1]);     /* ass_u_me 1 */
		}

		if (become(ct, TRUE) < 0)
			(void) write(pfd[1], "n", 1);
		else
		{
			int     t;

			(void) write(pfd[1], "y", 1);

			(void) close(pfd[child]);
			(void) close(pfd[parent]);
			for (t = 0; t < T_MAX; ++t)
				(void) close(tfd[t]);
			if (log)
				(void) fclose(log);
			if (errlog)
				(void) fclose(errlog);

			(void) execv(prog, av);
			syserr("can't execute %s", prog);
		}

		exit(127);
	}

	/* Make sure that a broken pipe won't kill us */

	saved_sigpipe = signal(SIGPIPE, SIG_IGN);

	/* The child must report "OK" before we continue. */

	if ((read(pfd[0], &ch, 1) < 1) || (ch != 'y'))
	{
		(void) close(pfd[0]);
		(void) close(pfd[1]);
		(void) await_child();
		return -1;
	}

	(void) close(pfd[child]);
	return pfd[parent];
}

/*----------------------------------------------------------------------
 * Close the file descriptor opened by ct_open().
 */

ct_close(fd)
int	fd;
{
	if (fd != -1)
		(void) close(fd);
	return await_child();
}

/*----------------------------------------------------------------------
 * Assume the identity of the given user.
 */

int
become(ct, chd)
CONTEXT *ct;
int     chd;
{
	char    env_path[sizeof(SAFEPATH) + 8];

	/*
	 * Assume a new identity.
	 * Note the importance of doing the setgid() before the setuid().
	 */

	if (setgid(ct->ct_gid) == -1)
	{
		syserr("can't setgid to %d", ct->ct_gid);
		return -1;
	}
	if (setuid(ct->ct_uid) == -1)
	{
		syserr("can't setgid to %u", ct->ct_uid);
		return -1;
	}
	if (chd && chdir(ct->ct_home) == -1)
	{
		syserr("can't chdir to %s", ct->ct_home);
		return -1;
	}

	/* Set up the environment */

	env_path[0] = '\0';
	if (ct->ct_uid == 0)
	    (void) strcat(env_path, "/etc:");
	(void) strcat(env_path, SAFEPATH);

	alloc_env("HOME", ct->ct_home);
	alloc_env("PATH", env_path);

	/* I guess it worked. */

	return 0;
}

/*----------------------------------------------------------------------
 * Safe fork.  If it doesn't work, it exits.
 */

int
sfork()
{
	int     tries;

	/*
	 * A few safety measures.
	 */

	(void) await_child();
	(void) fflush(stdout);
	(void) fflush(stderr);
	if (log)
		(void) fflush(log);
	if (errlog)
		(void) fflush(errlog);

	/*
	 * Be patient in waiting for a fork().
	 */

	for (tries = 0; tries < 10; ++tries)
	{
		if (tries)
			snooze(3);
		if ((child_pid = fork()) >= 0)
			return child_pid;
		if (errno != EAGAIN)
			break;
	}

	syserr("can't fork");
	leave(1);
	/* NOTREACHED */
}

/*----------------------------------------------------------------------
 * Wait for our child (if any) to exit.
 * Returns child's exit status or -1 if there is a problem.
 */

int
await_child()
{
	int     wpid, st;

	if (child_pid < 0)
		return -1;

	while ((wpid = wait(&st)) >= 0)
	{
		if (wpid == child_pid)
			break;
	}

	child_pid = -1;
	if (wpid == -1)
		syserr("waiting for child");

	(void) signal(SIGPIPE, saved_sigpipe);
	saved_sigpipe = SIG_DFL;

	if (wpid == -1)
		return -1;

	if (st & 0xFF)
	{
		error("child process died%s due to signal %d.",
			((st & 0x80) ? " and dumped core" : ""),
			(st & 0x7F));
		return -1;
	}

	if (verbose)
	{
		message("%s: child process exited with status %d.\n",
			progname, (st >> 8) & 0xFF);
	}

	return ((st >> 8) & 0xFF);
}
