diff options
Diffstat (limited to 'gnu/libexec/uucp/libunix/spawn.c')
-rw-r--r-- | gnu/libexec/uucp/libunix/spawn.c | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/gnu/libexec/uucp/libunix/spawn.c b/gnu/libexec/uucp/libunix/spawn.c new file mode 100644 index 000000000000..7ab080d1a9ca --- /dev/null +++ b/gnu/libexec/uucp/libunix/spawn.c @@ -0,0 +1,398 @@ +/* spawn.c + Spawn a program securely. + + Copyright (C) 1992 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + The author of the program may be contacted at ian@airs.com or + c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254. + */ + +#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; + 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; + + 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 (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 (aidescs[i], F_SETFD, + fcntl (aidescs[i], 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) + { + (void) setuid (getuid ()); + (void) setgid (getgid ()); + } + + 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 + } + + (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; +} |