apr_status_t proc_spawn_process()

in modules/fcgid/fcgid_proc_unix.c [194:400]


apr_status_t proc_spawn_process(const char *cmdline, fcgid_proc_info *procinfo,
                                fcgid_procnode *procnode)
{
    server_rec *main_server = procinfo->main_server;
    fcgid_server_conf *sconf = ap_get_module_config(main_server->module_config,
                                                    &fcgid_module);
    apr_status_t rv = APR_SUCCESS;
    apr_file_t *file;
    apr_proc_t tmpproc;
    int omask, retcode, unix_socket;
    char **proc_environ;
    struct sockaddr_un unix_addr;
    apr_procattr_t *procattr = NULL;
    int len;
    const char **wargv;

    /* Build wrapper args */
    apr_tokenize_to_argv(cmdline, (char ***)&wargv, procnode->proc_pool);

    /*
       Create UNIX domain socket before spawn
     */

    /* Generate a UNIX domain socket file path */
    memset(&unix_addr, 0, sizeof(unix_addr));
    unix_addr.sun_family = AF_UNIX;
    len = apr_snprintf(unix_addr.sun_path, sizeof(unix_addr.sun_path),
                       "%s/%" APR_PID_T_FMT ".%d", sconf->sockname_prefix,
                       getpid(), g_process_counter++);

    /* check for truncation of the socket path
     *
     * cheap but overly zealous check for sun_path overflow: if length of
     * prepared string is at the limit, assume truncation
     */
    if (len + 1 == sizeof(unix_addr.sun_path)
        || len >= sizeof procnode->socket_path) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
                     "mod_fcgid: socket path length exceeds compiled-in limits");
        return APR_EGENERAL;
    }

    apr_cpystrn(procnode->socket_path, unix_addr.sun_path,
                sizeof(procnode->socket_path));

    /* truncation already checked for in handler or FcgidWrapper parser */
    AP_DEBUG_ASSERT(wargv[0] != NULL);
    AP_DEBUG_ASSERT(strlen(wargv[0]) < sizeof(procnode->executable_path));
    apr_cpystrn(procnode->executable_path, wargv[0],
                sizeof(procnode->executable_path));

    /* Unlink the file just in case */
    unlink(unix_addr.sun_path);

    /* Create the socket */
    if ((unix_socket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server,
                     "mod_fcgid: couldn't create unix domain socket");
        return errno;
    }

    /* Register cleanups to
     * 1. Unlink the socket when the process exits
     * 2. (suexec mode only, in the child cleanup) Switch to the configured uid
     */
    if (ap_unixd_config.suexec_enabled) {
        apr_pool_cleanup_register(procnode->proc_pool,
                                  procnode, socket_file_cleanup,
                                  exec_setuid_cleanup);
    }
    else {
        apr_pool_cleanup_register(procnode->proc_pool,
                                  procnode, socket_file_cleanup,
                                  apr_pool_cleanup_null);
    }

    /* Bind the socket */
    omask = umask(0077);
    retcode = bind(unix_socket, (struct sockaddr *) &unix_addr,
                   sizeof(unix_addr));
    umask(omask);
    if (retcode < 0) {
        ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server,
                     "mod_fcgid: couldn't bind unix domain socket %s",
                     unix_addr.sun_path);
        close(unix_socket);
        return errno;
    }

    /* IPC directory permissions are safe, but avoid confusion */
    /* Not all flavors of unix use the current umask for AF_UNIX perms */

    rv = apr_file_perms_set(unix_addr.sun_path,
                            APR_FPROT_UREAD|APR_FPROT_UWRITE|APR_FPROT_UEXECUTE);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, main_server,
                     "mod_fcgid: Couldn't set permissions on unix domain socket %s",
                     unix_addr.sun_path);
        return rv;
    }

    /* Listen the socket */
    if (listen(unix_socket, DEFAULT_FCGID_LISTENBACKLOG) < 0) {
        ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server,
                     "mod_fcgid: couldn't listen on unix domain socket");
        close(unix_socket);
        return errno;
    }

    /* Correct the file owner */
    if (!geteuid()) {
        if (chown(unix_addr.sun_path, ap_unixd_config.user_id, -1) < 0) {
            ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server,
                         "mod_fcgid: couldn't change owner of unix domain socket %s",
                         unix_addr.sun_path);
            close(unix_socket);
            return errno;
        }
    }

    {
        int oldflags = fcntl(unix_socket, F_GETFD, 0);

        if (oldflags < 0) {
            ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(),
                         procinfo->main_server,
                         "mod_fcgid: fcntl F_GETFD failed");
            close(unix_socket);
            return errno;
        }

        oldflags |= FD_CLOEXEC;
        if (fcntl(unix_socket, F_SETFD, oldflags) < 0) {
            ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(),
                         procinfo->main_server,
                         "mod_fcgid: fcntl F_SETFD failed");
            close(unix_socket);
            return errno;
        }
    }

    /* Build environment variables */
    proc_environ = ap_create_environment(procnode->proc_pool,
                                         procinfo->proc_environ);
    if (!proc_environ) {
        ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(),
                     procinfo->main_server,
                     "mod_fcgid: can't build environment variables");
        close(unix_socket);
        return APR_ENOMEM;
    }

    /* Prepare the fork */
    if ((rv = apr_procattr_create(&procattr, procnode->proc_pool)) != APR_SUCCESS
        || (rv = apr_procattr_child_err_set(procattr,
                                            procinfo->main_server->error_log,
                                            NULL)) != APR_SUCCESS
        || (rv = apr_procattr_child_out_set(procattr,
                                            procinfo->main_server->error_log,
                                            NULL)) != APR_SUCCESS
        || (rv = apr_procattr_dir_set(procattr,
                                      ap_make_dirstr_parent(procnode->proc_pool,
                                                            wargv[0]))) != APR_SUCCESS
        || (rv = apr_procattr_cmdtype_set(procattr, APR_PROGRAM)) != APR_SUCCESS
        || (rv = apr_os_file_put(&file, &unix_socket, 0,
                                 procnode->proc_pool)) != APR_SUCCESS
        || (rv = apr_procattr_child_in_set(procattr, file, NULL)) != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, procinfo->main_server,
                     "mod_fcgid: couldn't set child process attributes: %s",
                     unix_addr.sun_path);
        close(unix_socket);
        return rv;
    }

    /* fork and exec now */
    /* Note, don't pass &(procnode->proc_id) to fcgid_create_privileged_process(),
     * for it's a share memory address, both parent and child process may modify
     * procnode->proc_id->pid, so sometimes it's 0 and sometimes it's >0
     */
    rv = fcgid_create_privileged_process(&tmpproc, wargv[0], wargv,
                                         (const char *const *)proc_environ,
                                         procattr, procinfo,
                                         procnode->proc_pool);

    if (ap_unixd_config.suexec_enabled) {
        /* Prior to creating the child process, a child cleanup was registered
         * to switch the uid in the child.  No-op the child cleanup for this
         * pool so that it won't run again as other child processes are created.
         * (The cleanup will be registered for the pool associated with those
         * processes too.)
         */
        apr_pool_child_cleanup_set(procnode->proc_pool, procnode,
                                   socket_file_cleanup, apr_pool_cleanup_null);
    }

    /* Close socket before try to connect to it */
    close(unix_socket);
    procnode->proc_id = tmpproc;

    if (rv != APR_SUCCESS) {
        memset(&procnode->proc_id, 0, sizeof(procnode->proc_id));
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, procinfo->main_server,
                     "mod_fcgid: can't run %s", wargv[0]);
    }

    return rv;
}