/*===========================================================================*
 *                                                                           *
 *  sflprocu.imp - Process control functions for Unix                        *
 *                                                                           *
 *  Copyright (c) 1991-2010 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 3 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, see <http://www.gnu.org/licenses/>.                                 *
 *                                                                           *
 *  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 = NULL,          /*  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);                 /*  Statement never reached          */
      }
    /*  --- 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;                                      \
      ASSERT (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 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 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);


