NetBSD/gnu/libexec/uucp/libunix/spawn.c

454 lines
12 KiB
C

/* spawn.c
Spawn a program securely.
Copyright (C) 1992, 1993, 1994, 1995 Ian Lance Taylor
This file is part of the Taylor UUCP package.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
The author of the program may be contacted at ian@airs.com or
c/o Cygnus Support, 48 Grove Street, Somerville, MA 02144.
*/
#include "uucp.h"
#include "uudefs.h"
#include "sysdep.h"
#include <errno.h>
#if HAVE_FCNTL_H
#include <fcntl.h>
#else
#if HAVE_SYS_FILE_H
#include <sys/file.h>
#endif
#endif
#ifndef O_RDONLY
#define O_RDONLY 0
#define O_WRONLY 1
#define O_RDWR 2
#endif
#ifndef FD_CLOEXEC
#define FD_CLOEXEC 1
#endif
#ifndef environ
extern char **environ;
#endif
/* Spawn a child in a fairly secure fashion. This returns the process
ID of the child or -1 on error. It takes far too many arguments:
pazargs -- arguments (element 0 is command)
aidescs -- file descriptors for stdin, stdout and stderr
fkeepuid -- TRUE if euid should be left unchanged
fkeepenv -- TRUE if environment should be left unmodified
zchdir -- directory to chdir to
fnosigs -- TRUE if child should ignore SIGHUP, SIGINT and SIGQUIT
fshell -- TRUE if should try /bin/sh if execve gets ENOEXEC
zpath -- value for environment variable PATH
zuu_machine -- value for environment variable UU_MACHINE
zuu_user -- value for environment variable UU_USER
The aidescs array is three elements long. 0 is stdin, 1 is stdout
and 2 is stderr. The array may contain either file descriptor
numbers to dup appropriately, or one of the following:
SPAWN_NULL -- set descriptor to /dev/null
SPAWN_READ_PIPE -- set aidescs element to pipe for parent to read
SPAWN_WRITE_PIPE -- set aidescs element to pipe for parent to write
If fkeepenv is FALSE, a standard environment is created. The
environment arguments (zpath, zuu_machine and zuu_user) are only
used if fkeepenv is FALSE; any of them may be NULL.
This routine expects that all file descriptors have been set to
close-on-exec, so it doesn't have to worry about closing them
explicitly. It sets the close-on-exec flag for the new pipe
descriptors it returns. */
pid_t
ixsspawn (pazargs, aidescs, fkeepuid, fkeepenv, zchdir, fnosigs, fshell,
zpath, zuu_machine, zuu_user)
const char **pazargs;
int aidescs[3];
boolean fkeepuid;
boolean fkeepenv;
const char *zchdir;
boolean fnosigs;
boolean fshell;
const char *zpath;
const char *zuu_machine;
const char *zuu_user;
{
char *zshcmd;
int i;
char *azenv[9];
char **pazenv;
boolean ferr;
#if HAVE_FULLDUPLEX_PIPES
boolean ffullduplex;
#endif
int ierr = 0;
int onull;
int aichild_descs[3];
int cpar_close;
int aipar_close[4];
int cchild_close;
int aichild_close[3];
pid_t iret = 0;
const char *zcmd;
/* If we might have to use the shell, allocate enough space for the
quoted command before forking. Otherwise the allocation would
modify the data segment and we could not safely use vfork. */
zshcmd = NULL;
if (fshell)
{
size_t clen;
clen = 0;
for (i = 0; pazargs[i] != NULL; i++)
clen += strlen (pazargs[i]);
zshcmd = zbufalc (2 * clen + i);
}
/* Set up a standard environment. This is again done before forking
because it will modify the data segment. */
if (fkeepenv)
pazenv = environ;
else
{
const char *zterm, *ztz;
char *zspace;
int ienv;
if (zpath == NULL)
zpath = CMDPATH;
azenv[0] = zbufalc (sizeof "PATH=" + strlen (zpath));
sprintf (azenv[0], "PATH=%s", zpath);
zspace = azenv[0] + sizeof "PATH=" - 1;
while ((zspace = strchr (zspace, ' ')) != NULL)
*zspace = ':';
azenv[1] = zbufalc (sizeof "HOME=" + strlen (zSspooldir));
sprintf (azenv[1], "HOME=%s", zSspooldir);
zterm = getenv ("TERM");
if (zterm == NULL)
zterm = "unknown";
azenv[2] = zbufalc (sizeof "TERM=" + strlen (zterm));
sprintf (azenv[2], "TERM=%s", zterm);
azenv[3] = zbufcpy ("SHELL=/bin/sh");
azenv[4] = zbufalc (sizeof "USER=" + strlen (OWNER));
sprintf (azenv[4], "USER=%s", OWNER);
ienv = 5;
ztz = getenv ("TZ");
if (ztz != NULL)
{
azenv[ienv] = zbufalc (sizeof "TZ=" + strlen (ztz));
sprintf (azenv[ienv], "TZ=%s", ztz);
++ienv;
}
if (zuu_machine != NULL)
{
azenv[ienv] = zbufalc (sizeof "UU_MACHINE="
+ strlen (zuu_machine));
sprintf (azenv[ienv], "UU_MACHINE=%s", zuu_machine);
++ienv;
}
if (zuu_user != NULL)
{
azenv[ienv] = zbufalc (sizeof "UU_USER="
+ strlen (zuu_user));
sprintf (azenv[ienv], "UU_USER=%s", zuu_user);
++ienv;
}
azenv[ienv] = NULL;
pazenv = azenv;
}
/* Set up any needed pipes. */
ferr = FALSE;
onull = -1;
cpar_close = 0;
cchild_close = 0;
#if HAVE_FULLDUPLEX_PIPES
ffullduplex = (aidescs[0] == SPAWN_WRITE_PIPE
&& aidescs[1] == SPAWN_READ_PIPE);
#endif
for (i = 0; i < 3; i++)
{
if (aidescs[i] == SPAWN_NULL)
{
if (onull < 0)
{
onull = open ((char *) "/dev/null", O_RDWR);
if (onull < 0
|| fcntl (onull, F_SETFD,
fcntl (onull, F_GETFD, 0) | FD_CLOEXEC) < 0)
{
ierr = errno;
(void) close (onull);
ferr = TRUE;
break;
}
aipar_close[cpar_close] = onull;
++cpar_close;
}
aichild_descs[i] = onull;
}
else if (aidescs[i] != SPAWN_READ_PIPE
&& aidescs[i] != SPAWN_WRITE_PIPE)
aichild_descs[i] = aidescs[i];
else
{
int aipipe[2];
#if HAVE_FULLDUPLEX_PIPES
if (ffullduplex && i == 1)
{
/* Just use the fullduplex pipe. */
aidescs[i] = aidescs[0];
aichild_descs[i] = aichild_descs[0];
continue;
}
#endif
if (pipe (aipipe) < 0)
{
ierr = errno;
ferr = TRUE;
break;
}
if (aidescs[i] == SPAWN_READ_PIPE)
{
aidescs[i] = aipipe[0];
aichild_close[cchild_close] = aipipe[0];
aichild_descs[i] = aipipe[1];
aipar_close[cpar_close] = aipipe[1];
}
else
{
aidescs[i] = aipipe[1];
aichild_close[cchild_close] = aipipe[1];
aichild_descs[i] = aipipe[0];
aipar_close[cpar_close] = aipipe[0];
}
++cpar_close;
++cchild_close;
if (fcntl (aipipe[0], F_SETFD,
fcntl (aipipe[0], F_GETFD, 0) | FD_CLOEXEC) < 0
|| fcntl (aipipe[1], F_SETFD,
fcntl (aipipe[1], F_GETFD, 0) | FD_CLOEXEC) < 0)
{
ierr = errno;
ferr = TRUE;
break;
}
}
}
#if DEBUG > 1
if (! ferr && FDEBUGGING (DEBUG_EXECUTE))
{
ulog (LOG_DEBUG_START, "Forking %s", pazargs[0]);
for (i = 1; pazargs[i] != NULL; i++)
ulog (LOG_DEBUG_CONTINUE, " %s", pazargs[i]);
ulog (LOG_DEBUG_END, "%s", "");
}
#endif
if (! ferr)
{
/* This should really be vfork if available. */
iret = ixsfork ();
if (iret < 0)
{
ferr = TRUE;
ierr = errno;
}
}
if (ferr)
{
for (i = 0; i < cchild_close; i++)
(void) close (aichild_close[i]);
iret = -1;
}
if (iret != 0)
{
/* The parent. Close the child's ends of the pipes and return
the process ID, or an error. */
for (i = 0; i < cpar_close; i++)
(void) close (aipar_close[i]);
ubuffree (zshcmd);
if (! fkeepenv)
{
char **pz;
for (pz = azenv; *pz != NULL; pz++)
ubuffree (*pz);
}
errno = ierr;
return iret;
}
/* The child. */
#ifdef STDIN_FILENO
#if STDIN_FILENO != 0 || STDOUT_FILENO != 1 || STDERR_FILENO != 2
#error The following code makes invalid assumptions
#endif
#endif
for (i = 0; i < 3; i++)
{
if (aichild_descs[i] != i)
(void) dup2 (aichild_descs[i], i);
/* This should only be necessary if aichild_descs[i] == i, but
some systems copy the close-on-exec flag for a dupped
descriptor, which is wrong according to POSIX. */
(void) fcntl (i, F_SETFD, fcntl (i, F_GETFD, 0) &~ FD_CLOEXEC);
}
zcmd = pazargs[0];
pazargs[0] = strrchr (zcmd, '/');
if (pazargs[0] == NULL)
pazargs[0] = zcmd;
else
++pazargs[0];
if (! fkeepuid)
{
/* Return to the uid of the invoking user. */
(void) setuid (getuid ());
(void) setgid (getgid ());
}
else
{
/* Try to force the UUCP uid to be both real and effective user
ID, in order to present a consistent environment regardless
of the invoking user. This won't work on older System V
based systems, where it can cause trouble if ordinary users
wind up executing uuxqt, perhaps via uucico; any program
which uuxqt executes will have an arbitrary real user ID, so
if the program is itself a setuid program, any security
checks it does based on the real user ID will be incorrect.
Fixing this problem would seem to require a special setuid
root program; I have not used this approach because
modern systems should not suffer from it. */
#if HAVE_SETREUID
(void) setreuid (geteuid (), -1);
(void) setregid (getegid (), -1);
#else
(void) setuid (geteuid ());
(void) setgid (getegid ());
#endif
}
if (zchdir != NULL)
(void) chdir (zchdir);
if (fnosigs)
{
#ifdef SIGHUP
(void) signal (SIGHUP, SIG_IGN);
#endif
#ifdef SIGINT
(void) signal (SIGINT, SIG_IGN);
#endif
#ifdef SIGQUIT
(void) signal (SIGQUIT, SIG_IGN);
#endif
}
#ifdef isc386
#ifdef _POSIX_SOURCE
/* ISC has a remarkably stupid notion of environments. If a program
is compiled in the POSIX environment, it sets a process state.
If you then exec a program which expects the USG environment, the
process state is not reset, so the execed program fails. The
__setostype call is required to change back to the USG
environment. This ought to be a switch in policy.h, but it seems
too trivial, so I will leave this code here and wait for it to
break in some fashion in the next version of ISC. */
__setostype (0);
#endif
#endif
(void) execve ((char *) zcmd, (char **) pazargs, pazenv);
/* The exec failed. If permitted, try using /bin/sh to execute a
shell script. */
if (errno == ENOEXEC && fshell)
{
char *zto;
const char *azshargs[4];
pazargs[0] = zcmd;
zto = zshcmd;
for (i = 0; pazargs[i] != NULL; i++)
{
const char *zfrom;
for (zfrom = pazargs[i]; *zfrom != '\0'; zfrom++)
{
/* Some versions of /bin/sh appear to have a bug such
that quoting a '/' sometimes causes an error. I
don't know exactly when this happens (I can recreate
it on Ultrix 4.0), but in any case it is harmless to
not quote a '/'. */
if (*zfrom != '/')
*zto++ = '\\';
*zto++ = *zfrom;
}
*zto++ = ' ';
}
*(zto - 1) = '\0';
azshargs[0] = "sh";
azshargs[1] = "-c";
azshargs[2] = zshcmd;
azshargs[3] = NULL;
(void) execve ((char *) "/bin/sh", (char **) azshargs, pazenv);
}
_exit (EXIT_FAILURE);
/* Avoid compiler warning. */
return -1;
}