/*===========================================================================* * * * sflprocu.imp - * * * * Copyright (c) 1991-2003 iMatix Corporation * * * * ------------------ GPL Licensed Source Code ------------------ * * iMatix makes this software available under the GNU General * * Public License (GPL) license for open source projects. For * * details of the GPL license please see www.gnu.org or read the * * file license.gpl provided in this 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 in the file 'license.gpl'; if * * not, write to the Free Software Foundation, Inc., 59 Temple * * Place - Suite 330, Boston, MA 02111-1307, USA. * * * * You can also license this software under iMatix's General Terms * * of Business (GTB) for commercial projects. If you have not * * explicitly licensed this software under the iMatix GTB you may * * only use it under the terms of the GNU General Public License. * * * * For more information, send an email to info@imatix.com. * * -------------------------------------------------------------- * *===========================================================================*/ /* UNIX implementation of the SFL process_create_full() function * This function receives input in the form of a pointer to a PROCESS_DATA * structure called procinfo. See sflproc.c for details. * * The general strategy here is to perform some very basic checks, and then * create a pipe (for child -> parent communication), and fork off a new * process. The new (child) process then does all the rest of the checking * and setup. This strategy is used for two reasons: firstly if we aren't * waiting to find out if there are any errors it is faster, and secondly * it means memory allocation, etc is less critical as it is done in a * process that will soon be overwritten (by exec*()), or abandoned. * * Processing is done in the order required to ensure that the process has * the rights required to do the next step; in particular the chroot() is * done prior to the setgid() and setuid() (if any), all of which are done * prior to the chdir(), and exec*(). */ ARGLIST *arglist; /* Argument list */ int pipe_handle [2], /* Parent-to-child pipe */ pipe_readsize, /* Amount of data read from pipe */ pipe_data, /* Data read from pipe */ old_stdin = NULL_HANDLE, /* Dup'd handle for old stdin */ old_stdout = NULL_HANDLE, /* Dup'd handle for old stdout */ old_stderr = NULL_HANDLE; /* Dup'd handle for old stderr */ Bool dosetuid = FALSE, /* True if we need to set user */ dosetgid = FALSE, /* True if we need to set group */ free_envv = FALSE; /* True if we should free envv */ const char *path, /* Path to search */ *shell, /* Shell to use */ **searchext, /* Extensions to search */ *interpreter; /* Name of script interpreter */ char *full_filename, /* Actual filename to run */ *new_username, /* New username to use, or NULL */ *new_groupname, /* New group name to use, or NULL */ **argv, /* Arguments for program */ **envv; /* Environment for program */ pid_t fork_result; /* Result from fork() */ uid_t new_uid = 99; /* UID to change to if dosetuid */ /* Do NOT default to zero! */ gid_t new_gid = 99; /* GID to change to if dosetgid */ /* First, check that minimum arguments needed to do something are set */ ASSERT (procinfo); if (!procinfo) return (NULL_PROCESS); ASSERT (procinfo-> filename); if (!procinfo-> filename) { procinfo-> error = EINVAL; return (NULL_PROCESS); } /* Initialise return information */ procinfo-> pid = NULL_PROCESS; procinfo-> error = 0; procinfo-> returncode = -1; /* Create pipe for feedback from child to parent; quit if this fails */ if (pipe (pipe_handle) != 0) { procinfo-> error = errno; return NULL_PROCESS; } /* Create subprocess - this returns 0 if we are the child, the pid if */ /* we are the parent, or -1 if there was an error (not enough memory). */ fork_result = fork (); if (fork_result < 0) /* < 0 is an error */ { procinfo-> error = errno; close (pipe_handle [0]); /* Close the pipe */ close (pipe_handle [1]); return NULL_PROCESS; /* Could not fork */ } else if (fork_result > 0) /* > 0 is the parent process */ { /* --- PARENT PROCESS HANDLING ------------------------------------ */ /* If the child process has a problem with the exec() call, it */ /* sends us an errno value across the pipe. If the exec() call */ /* works okay, we get no feedback across the pipe. We wait for a */ /* small time (number of msecs specified by "delay"). If nothing */ /* comes across the pipe, we assume everything went okay. */ /* We also close the write end of the pipe here, and set the pipe */ /* to close-on-exec in the child process, so the pipe closing lets */ /* us know that the exec*() is taking place. */ close (pipe_handle [1]); /* Close the write handle */ if (procinfo-> delay > 0) { fd_set readset; /* select() on input end of pipe */ struct timeval timeout; /* Wait for response from child */ FD_ZERO (&readset); FD_SET (pipe_handle [0], &readset); timeout.tv_sec = procinfo-> delay / 1000; timeout.tv_usec = (procinfo-> delay % 1000) * 1000; /* Now wait for data on the pipe until it arrives or time out */ if (select ( (pipe_handle [0] + 1), &readset, NULL, NULL, &timeout) > 0) { /* Something has happened on the pipe; either it closed or */ /* there is some data to read. Assume we get all the data */ pipe_readsize = read (pipe_handle [0], &pipe_data, sizeof (pipe_data)); } else { /* Nothing turned up to read, nor did it close; pretend */ /* the read was interrupted. */ pipe_readsize = -1; errno = EINTR; } } else pipe_readsize = 0; close (pipe_handle [0]); /* Close the pipe */ close (pipe_handle [1]); if (pipe_readsize == -1) { if (errno == EBADF || errno == EINTR) { /* Normal - SIGALRM arrived or FD_CLOEXEC worked :) */ if (procinfo-> wait) procinfo-> returncode = waitpid (fork_result, 0, 0); procinfo-> pid = ((PROCESS) fork_result); return ((PROCESS) fork_result); } else { waitpid (fork_result, 0, 0); /* Collect zombie */ return (NULL_PROCESS); /* Error on read() */ } } else /* We come here if FD_CLOEXEC did its job and the pipe was closed by the child process. */ if (pipe_readsize == 0) { if (procinfo-> wait) procinfo-> returncode = waitpid (fork_result, 0, 0); procinfo-> pid = ((PROCESS) fork_result); return ((PROCESS) fork_result); } else { /* We read data from the pipe - this is an error feedback from */ /* the child - i.e. file not found, or a permission problem. */ procinfo-> error = pipe_data; /* Save it for the caller */ waitpid (fork_result, 0, 0); /* Collect zombie */ return (NULL_PROCESS); } ASSERT (FALSE); /* Unreachable */ return (NULL_PROCESS); } /* --- CHILD PROCESS HANDLING ----------------------------------------- * Prepare the process environment and execute the file * If anything goes wrong we write the error number back across the * pipe to our parent, and exit. */ /* This macro is used to "give up" -- sending the error to the parent, * and then exiting. The scope is used to allow us to define a variable * to hold the value to be sent, so we can be sure that we can take its * address */ # define SEND_ERROR_AND_EXIT(errcode) \ { \ int SEAE_error = errcode; \ write (pipe_handle [1], &(SEAE_error), sizeof (SEAE_error)); \ exit (EXIT_FAILURE); \ } /* Sort out privileged issues immediately, to minimise the amount of * time we have more privilege than the program eventually run will * have. */ /* Force empty strings to be NULL strings, to simplify logic */ if (! procinfo-> username || ! *(procinfo-> username)) new_username = NULL; else new_username = procinfo-> username; if (! procinfo-> groupname || ! *(procinfo-> groupname)) new_groupname = NULL; else new_groupname = procinfo-> groupname; ASSERT (new_username == procinfo-> username || ! *(procinfo-> username)); ASSERT (new_groupname == procinfo-> groupname || ! *(procinfo-> groupname)); if (new_username) { struct passwd *pwdbuf; /* User information from passwd */ pwdbuf = getpwnam (new_username); if (pwdbuf) { new_uid = pwdbuf-> pw_uid; if (new_uid != getuid ()) dosetuid = TRUE; /* Only set uid if different */ } else SEND_ERROR_AND_EXIT (errno) } if (new_groupname) { struct group *grpbuf; /* Group information */ grpbuf = getgrnam (new_groupname); if (grpbuf) { new_gid = grpbuf-> gr_gid; if (new_gid != getgid ()) dosetgid = TRUE; /* Only set gid if different */ } else SEND_ERROR_AND_EXIT (errno) } /* HP/UX and BeOS don't provide seteuid() functions */ # if (!defined (__UTYPE_HPUX) && !defined (__UTYPE_BEOS)) /* If we are not to preserve root privileges, and won't otherwise be * setting the uid, then check to see if we can get root privileges now * (eg, we have them "saved"). If we do, force a setuid/setgid to * happen by setting the appropriate flags. */ if (!(procinfo-> preserveroot) && !(dosetuid && dosetgid)) { if (seteuid (0) == 0) { /* CAREFUL: We're root now; the seteuid worked. */ /* Arrange for setuid/setgid to happen to current real UID/GID. */ dosetuid = TRUE; new_uid = getuid (); dosetgid = TRUE; new_gid = getgid (); } } /* We first set the effective user to be root, to ensure that we can * carry out the requests. If that fails, we give up immediately. * This means that the setuid() and setgid() calls will give up all * other privilege, which is deisred at this point. To make use of * the saved uid/saved gid ability of some setuid()/setgid() calls, * use them prior to call create_process(). This is not recommended. * And chroot() will work only if the euid can be made root (0). */ if (procinfo-> rootdir != NULL || dosetgid || dosetuid) { if (seteuid (0) != 0) SEND_ERROR_AND_EXIT (errno); /* CAUTION: We are now root. Careful. */ /* Change root directory, if required */ if (procinfo-> rootdir) if (chroot (procinfo-> rootdir) != 0) SEND_ERROR_AND_EXIT (errno); /* Change uid and gid if required. Since we are root at this point * these functions should give away all our other gids and our * original uid. */ if (dosetgid) { if (setgid (new_gid) != 0) SEND_ERROR_AND_EXIT (errno); ASSERT (getgid () == new_gid); } if (dosetuid) { if (setuid (new_uid) != 0) SEND_ERROR_AND_EXIT (errno); ASSERT (getuid () == new_uid); } } /* SANITY CHECK * ----------------------------------------------------------------- * Unless we are preserving root, or explicitly set our uid to root, * we should no longer be root, and an attempt to set our euid to root * should fail. If this turns out not to be the case, we give up * immediately. * NOTE: Returning EPERM at this point is probably undesireable, but * it is not clear what would be better to return. */ if (!(procinfo-> preserveroot) && !(dosetuid && new_uid == 0)) { if (getuid () == 0 || geteuid () == 0) SEND_ERROR_AND_EXIT(EPERM); if (seteuid (0) == 0) /* Should fail */ SEND_ERROR_AND_EXIT(EPERM); ASSERT (geteuid () != 0 && getuid () != 0); } # endif /* Now we are not root, unless the user really wanted us to be root. */ # include "sflprocx.imp" /* Get implementation core */ if (procinfo-> error) SEND_ERROR_AND_EXIT (procinfo-> error); /* If requested, make this into a daemon process */ if (procinfo-> createdaemon) { /* XXX: Maybe we should have a lockfilename as well? */ if (process_server (procinfo-> workdir, NULL, 0, NULL, NULL) == -1) SEND_ERROR_AND_EXIT (errno); } else /* If requested, change to working directory */ if (procinfo-> workdir) if (chdir (procinfo-> workdir) == -1) SEND_ERROR_AND_EXIT (errno); /* Mark extra file handles to close when we exec() */ if (procinfo-> no_handles < FILEHANDLE_MAX) { int fh; for (fh = procinfo-> no_handles; fh < FILEHANDLE_MAX; fh++) fcntl (fh, F_SETFD, FD_CLOEXEC); /* Ignore errors */ } /* Tell the system to close the pipe when we've done the exec() */ fcntl (pipe_handle [0], F_SETFD, FD_CLOEXEC); fcntl (pipe_handle [1], F_SETFD, FD_CLOEXEC); /* Execute the program - normally this call does not return, as it * replaces the current process image by the new one. If we ever do * return, it is because there was an error. */ argv = arglist_to_table (arglist); if (! argv) SEND_ERROR_AND_EXIT (ENOMEM); #if defined (DEBUG) #if DEBUG > 1 { char **ptr = argv; fprintf(stderr, "About to run: (%s) ", full_filename); while (*ptr != NULL) fprintf(stderr, "[%s] ", *ptr++); fprintf(stderr, "\n"); errno = 0; } #endif #endif execve (full_filename, argv, envv); /* If we're still here, then exec*() failed for some reason. Tell our * parent about this. */ SEND_ERROR_AND_EXIT (errno);