/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "apr_arch_threadproc.h" #include "apr_strings.h" #include "apr_portable.h" #include "apr_signal.h" #include "apr_random.h" /* Heavy on no'ops, here's what we want to pass if there is APR_NO_FILE * requested for a specific child handle; */ static apr_file_t no_file = { NULL, -1, }; APR_DECLARE(apr_status_t) apr_procattr_create(apr_procattr_t **new, apr_pool_t *pool) { (*new) = (apr_procattr_t *)apr_pcalloc(pool, sizeof(apr_procattr_t)); if ((*new) == NULL) { return APR_ENOMEM; } (*new)->pool = pool; (*new)->cmdtype = APR_PROGRAM; (*new)->uid = (*new)->gid = -1; return APR_SUCCESS; } APR_DECLARE(apr_status_t) apr_procattr_io_set(apr_procattr_t *attr, apr_int32_t in, apr_int32_t out, apr_int32_t err) { apr_status_t rv; if ((in != APR_NO_PIPE) && (in != APR_NO_FILE)) { /* APR_CHILD_BLOCK maps to APR_WRITE_BLOCK, while * APR_PARENT_BLOCK maps to APR_READ_BLOCK, so transpose * the CHILD/PARENT blocking flags for the stdin pipe. * stdout/stderr map to the correct mode by default. */ if (in == APR_CHILD_BLOCK) in = APR_READ_BLOCK; else if (in == APR_PARENT_BLOCK) in = APR_WRITE_BLOCK; if ((rv = apr_file_pipe_create_ex(&attr->child_in, &attr->parent_in, in, attr->pool)) == APR_SUCCESS) rv = apr_file_inherit_unset(attr->parent_in); if (rv != APR_SUCCESS) return rv; } else if (in == APR_NO_FILE) attr->child_in = &no_file; if ((out != APR_NO_PIPE) && (out != APR_NO_FILE)) { if ((rv = apr_file_pipe_create_ex(&attr->parent_out, &attr->child_out, out, attr->pool)) == APR_SUCCESS) rv = apr_file_inherit_unset(attr->parent_out); if (rv != APR_SUCCESS) return rv; } else if (out == APR_NO_FILE) attr->child_out = &no_file; if ((err != APR_NO_PIPE) && (err != APR_NO_FILE)) { if ((rv = apr_file_pipe_create_ex(&attr->parent_err, &attr->child_err, err, attr->pool)) == APR_SUCCESS) rv = apr_file_inherit_unset(attr->parent_err); if (rv != APR_SUCCESS) return rv; } else if (err == APR_NO_FILE) attr->child_err = &no_file; return APR_SUCCESS; } APR_DECLARE(apr_status_t) apr_procattr_child_in_set(apr_procattr_t *attr, apr_file_t *child_in, apr_file_t *parent_in) { apr_status_t rv = APR_SUCCESS; if (attr->child_in == NULL && attr->parent_in == NULL && child_in == NULL && parent_in == NULL) if ((rv = apr_file_pipe_create(&attr->child_in, &attr->parent_in, attr->pool)) == APR_SUCCESS) rv = apr_file_inherit_unset(attr->parent_in); if (child_in != NULL && rv == APR_SUCCESS) { if (attr->child_in && (attr->child_in->filedes != -1)) rv = apr_file_dup2(attr->child_in, child_in, attr->pool); else { attr->child_in = NULL; if ((rv = apr_file_dup(&attr->child_in, child_in, attr->pool)) == APR_SUCCESS) rv = apr_file_inherit_set(attr->child_in); } } if (parent_in != NULL && rv == APR_SUCCESS) { if (attr->parent_in) rv = apr_file_dup2(attr->parent_in, parent_in, attr->pool); else rv = apr_file_dup(&attr->parent_in, parent_in, attr->pool); } return rv; } APR_DECLARE(apr_status_t) apr_procattr_child_out_set(apr_procattr_t *attr, apr_file_t *child_out, apr_file_t *parent_out) { apr_status_t rv = APR_SUCCESS; if (attr->child_out == NULL && attr->parent_out == NULL && child_out == NULL && parent_out == NULL) if ((rv = apr_file_pipe_create(&attr->parent_out, &attr->child_out, attr->pool)) == APR_SUCCESS) rv = apr_file_inherit_unset(attr->parent_out); if (child_out != NULL && rv == APR_SUCCESS) { if (attr->child_out && (attr->child_out->filedes != -1)) rv = apr_file_dup2(attr->child_out, child_out, attr->pool); else { attr->child_out = NULL; if ((rv = apr_file_dup(&attr->child_out, child_out, attr->pool)) == APR_SUCCESS) rv = apr_file_inherit_set(attr->child_out); } } if (parent_out != NULL && rv == APR_SUCCESS) { if (attr->parent_out) rv = apr_file_dup2(attr->parent_out, parent_out, attr->pool); else rv = apr_file_dup(&attr->parent_out, parent_out, attr->pool); } return rv; } APR_DECLARE(apr_status_t) apr_procattr_child_err_set(apr_procattr_t *attr, apr_file_t *child_err, apr_file_t *parent_err) { apr_status_t rv = APR_SUCCESS; if (attr->child_err == NULL && attr->parent_err == NULL && child_err == NULL && parent_err == NULL) if ((rv = apr_file_pipe_create(&attr->parent_err, &attr->child_err, attr->pool)) == APR_SUCCESS) rv = apr_file_inherit_unset(attr->parent_err); if (child_err != NULL && rv == APR_SUCCESS) { if (attr->child_err && (attr->child_err->filedes != -1)) rv = apr_file_dup2(attr->child_err, child_err, attr->pool); else { attr->child_err = NULL; if ((rv = apr_file_dup(&attr->child_err, child_err, attr->pool)) == APR_SUCCESS) rv = apr_file_inherit_set(attr->child_err); } } if (parent_err != NULL && rv == APR_SUCCESS) { if (attr->parent_err) rv = apr_file_dup2(attr->parent_err, parent_err, attr->pool); else rv = apr_file_dup(&attr->parent_err, parent_err, attr->pool); } return rv; } APR_DECLARE(apr_status_t) apr_procattr_dir_set(apr_procattr_t *attr, const char *dir) { attr->currdir = apr_pstrdup(attr->pool, dir); if (attr->currdir) { return APR_SUCCESS; } return APR_ENOMEM; } APR_DECLARE(apr_status_t) apr_procattr_cmdtype_set(apr_procattr_t *attr, apr_cmdtype_e cmd) { attr->cmdtype = cmd; return APR_SUCCESS; } APR_DECLARE(apr_status_t) apr_procattr_detach_set(apr_procattr_t *attr, apr_int32_t detach) { attr->detached = detach; return APR_SUCCESS; } APR_DECLARE(apr_status_t) apr_proc_fork(apr_proc_t *proc, apr_pool_t *pool) { int pid; memset(proc, 0, sizeof(apr_proc_t)); if ((pid = fork()) < 0) { return errno; } else if (pid == 0) { #if AP_HAS_THREAD_LOCAL apr_thread_current_after_fork(); #endif proc->pid = getpid(); apr_random_after_fork(proc); return APR_INCHILD; } proc->pid = pid; return APR_INPARENT; } static apr_status_t limit_proc(apr_procattr_t *attr) { #if APR_HAVE_STRUCT_RLIMIT && APR_HAVE_SETRLIMIT #ifdef RLIMIT_CPU if (attr->limit_cpu != NULL) { if ((setrlimit(RLIMIT_CPU, attr->limit_cpu)) != 0) { return errno; } } #endif #ifdef RLIMIT_NPROC if (attr->limit_nproc != NULL) { if ((setrlimit(RLIMIT_NPROC, attr->limit_nproc)) != 0) { return errno; } } #endif #ifdef RLIMIT_NOFILE if (attr->limit_nofile != NULL) { if ((setrlimit(RLIMIT_NOFILE, attr->limit_nofile)) != 0) { return errno; } } #endif #if defined(RLIMIT_AS) if (attr->limit_mem != NULL) { if ((setrlimit(RLIMIT_AS, attr->limit_mem)) != 0) { return errno; } } #elif defined(RLIMIT_DATA) if (attr->limit_mem != NULL) { if ((setrlimit(RLIMIT_DATA, attr->limit_mem)) != 0) { return errno; } } #elif defined(RLIMIT_VMEM) if (attr->limit_mem != NULL) { if ((setrlimit(RLIMIT_VMEM, attr->limit_mem)) != 0) { return errno; } } #endif #else /* * Maybe make a note in error_log that setrlimit isn't supported?? */ #endif return APR_SUCCESS; } APR_DECLARE(apr_status_t) apr_procattr_child_errfn_set(apr_procattr_t *attr, apr_child_errfn_t *errfn) { attr->errfn = errfn; return APR_SUCCESS; } APR_DECLARE(apr_status_t) apr_procattr_error_check_set(apr_procattr_t *attr, apr_int32_t chk) { attr->errchk = chk; return APR_SUCCESS; } APR_DECLARE(apr_status_t) apr_procattr_addrspace_set(apr_procattr_t *attr, apr_int32_t addrspace) { /* won't ever be used on this platform, so don't save the flag */ return APR_SUCCESS; } APR_DECLARE(apr_status_t) apr_procattr_user_set(apr_procattr_t *attr, const char *username, const char *password) { apr_status_t rv; apr_gid_t gid; if ((rv = apr_uid_get(&attr->uid, &gid, username, attr->pool)) != APR_SUCCESS) { attr->uid = -1; return rv; } /* Use default user group if not already set */ if (attr->gid == -1) { attr->gid = gid; } return APR_SUCCESS; } APR_DECLARE(apr_status_t) apr_procattr_group_set(apr_procattr_t *attr, const char *groupname) { apr_status_t rv; if ((rv = apr_gid_get(&attr->gid, groupname, attr->pool)) != APR_SUCCESS) attr->gid = -1; return rv; } APR_DECLARE(apr_status_t) apr_proc_create(apr_proc_t *new, const char *progname, const char * const *args, const char * const *env, apr_procattr_t *attr, apr_pool_t *pool) { int i; const char * const empty_envp[] = {NULL}; if (!env) { /* Specs require an empty array instead of NULL; * Purify will trigger a failure, even if many * implementations don't. */ env = empty_envp; } new->in = attr->parent_in; new->err = attr->parent_err; new->out = attr->parent_out; if (attr->errchk) { if (attr->currdir) { if (access(attr->currdir, X_OK) == -1) { /* chdir() in child wouldn't have worked */ return errno; } } if (attr->cmdtype == APR_PROGRAM || attr->cmdtype == APR_PROGRAM_ENV || *progname == '/') { /* for both of these values of cmdtype, caller must pass * full path, so it is easy to check; * caller can choose to pass full path for other * values of cmdtype */ if (access(progname, X_OK) == -1) { /* exec*() in child wouldn't have worked */ return errno; } } else { /* todo: search PATH for progname then try to access it */ } } if ((new->pid = fork()) < 0) { return errno; } else if (new->pid == 0) { /* child process */ /* * If we do exec cleanup before the dup2() calls to set up pipes * on 0-2, we accidentally close the pipes used by programs like * mod_cgid. * * If we do exec cleanup after the dup2() calls, cleanup can accidentally * close our pipes which replaced any files which previously had * descriptors 0-2. * * The solution is to kill the cleanup for the pipes, then do * exec cleanup, then do the dup2() calls. */ if (attr->child_in) { apr_pool_cleanup_kill(apr_file_pool_get(attr->child_in), attr->child_in, apr_unix_file_cleanup); } if (attr->child_out) { apr_pool_cleanup_kill(apr_file_pool_get(attr->child_out), attr->child_out, apr_unix_file_cleanup); } if (attr->child_err) { apr_pool_cleanup_kill(apr_file_pool_get(attr->child_err), attr->child_err, apr_unix_file_cleanup); } apr_pool_cleanup_for_exec(); if ((attr->child_in) && (attr->child_in->filedes == -1)) { close(STDIN_FILENO); } else if (attr->child_in && attr->child_in->filedes != STDIN_FILENO) { dup2(attr->child_in->filedes, STDIN_FILENO); apr_file_close(attr->child_in); } if ((attr->child_out) && (attr->child_out->filedes == -1)) { close(STDOUT_FILENO); } else if (attr->child_out && attr->child_out->filedes != STDOUT_FILENO) { dup2(attr->child_out->filedes, STDOUT_FILENO); apr_file_close(attr->child_out); } if ((attr->child_err) && (attr->child_err->filedes == -1)) { close(STDERR_FILENO); } else if (attr->child_err && attr->child_err->filedes != STDERR_FILENO) { dup2(attr->child_err->filedes, STDERR_FILENO); apr_file_close(attr->child_err); } apr_signal(SIGCHLD, SIG_DFL); /* not sure if this is needed or not */ if (attr->currdir != NULL) { if (chdir(attr->currdir) == -1) { if (attr->errfn) { attr->errfn(pool, errno, "change of working directory failed"); } _exit(-1); /* We have big problems, the child should exit. */ } } if (!geteuid()) { apr_procattr_pscb_t *c = attr->perms_set_callbacks; while (c) { apr_status_t r; r = (*c->perms_set_fn)((void *)c->data, c->perms, attr->uid, attr->gid); if (r != APR_SUCCESS && r != APR_ENOTIMPL) { _exit(-1); } c = c->next; } } /* Only try to switch if we are running as root */ if (attr->gid != -1 && !geteuid()) { if (setgid(attr->gid)) { if (attr->errfn) { attr->errfn(pool, errno, "setting of group failed"); } _exit(-1); /* We have big problems, the child should exit. */ } } if (attr->uid != -1 && !geteuid()) { if (setuid(attr->uid)) { if (attr->errfn) { attr->errfn(pool, errno, "setting of user failed"); } _exit(-1); /* We have big problems, the child should exit. */ } } if (limit_proc(attr) != APR_SUCCESS) { if (attr->errfn) { attr->errfn(pool, errno, "setting of resource limits failed"); } _exit(-1); /* We have big problems, the child should exit. */ } if (attr->cmdtype == APR_SHELLCMD || attr->cmdtype == APR_SHELLCMD_ENV) { int onearg_len = 0; const char *newargs[4]; newargs[0] = SHELL_PATH; newargs[1] = "-c"; i = 0; while (args[i]) { onearg_len += strlen(args[i]); onearg_len++; /* for space delimiter */ i++; } switch(i) { case 0: /* bad parameters; we're doomed */ break; case 1: /* no args, or caller already built a single string from * progname and args */ newargs[2] = args[0]; break; default: { char *ch, *onearg; ch = onearg = apr_palloc(pool, onearg_len); i = 0; while (args[i]) { size_t len = strlen(args[i]); memcpy(ch, args[i], len); ch += len; *ch = ' '; ++ch; ++i; } --ch; /* back up to trailing blank */ *ch = '\0'; newargs[2] = onearg; } } newargs[3] = NULL; if (attr->detached) { apr_proc_detach(APR_PROC_DETACH_DAEMONIZE); } if (attr->cmdtype == APR_SHELLCMD) { execve(SHELL_PATH, (char * const *) newargs, (char * const *)env); } else { execv(SHELL_PATH, (char * const *)newargs); } } else if (attr->cmdtype == APR_PROGRAM) { if (attr->detached) { apr_proc_detach(APR_PROC_DETACH_DAEMONIZE); } execve(progname, (char * const *)args, (char * const *)env); } else if (attr->cmdtype == APR_PROGRAM_ENV) { if (attr->detached) { apr_proc_detach(APR_PROC_DETACH_DAEMONIZE); } execv(progname, (char * const *)args); } else { /* APR_PROGRAM_PATH */ if (attr->detached) { apr_proc_detach(APR_PROC_DETACH_DAEMONIZE); } execvp(progname, (char * const *)args); } if (attr->errfn) { char *desc; desc = apr_psprintf(pool, "exec of '%s' failed", progname); attr->errfn(pool, errno, desc); } _exit(-1); /* if we get here, there is a problem, so exit with an * error code. */ } /* Parent process */ if (attr->child_in && (attr->child_in->filedes != -1)) { apr_file_close(attr->child_in); } if (attr->child_out && (attr->child_out->filedes != -1)) { apr_file_close(attr->child_out); } if (attr->child_err && (attr->child_err->filedes != -1)) { apr_file_close(attr->child_err); } return APR_SUCCESS; } APR_DECLARE(apr_status_t) apr_proc_wait_all_procs(apr_proc_t *proc, int *exitcode, apr_exit_why_e *exitwhy, apr_wait_how_e waithow, apr_pool_t *p) { proc->pid = -1; return apr_proc_wait(proc, exitcode, exitwhy, waithow); } APR_DECLARE(apr_status_t) apr_proc_wait(apr_proc_t *proc, int *exitcode, apr_exit_why_e *exitwhy, apr_wait_how_e waithow) { pid_t pstatus; int waitpid_options = WUNTRACED; int exit_int; int ignore; apr_exit_why_e ignorewhy; if (exitcode == NULL) { exitcode = &ignore; } if (exitwhy == NULL) { exitwhy = &ignorewhy; } if (waithow != APR_WAIT) { waitpid_options |= WNOHANG; } do { pstatus = waitpid(proc->pid, &exit_int, waitpid_options); } while (pstatus < 0 && errno == EINTR); if (pstatus > 0) { proc->pid = pstatus; if (WIFEXITED(exit_int)) { *exitwhy = APR_PROC_EXIT; *exitcode = WEXITSTATUS(exit_int); } else if (WIFSIGNALED(exit_int)) { *exitwhy = APR_PROC_SIGNAL; #ifdef WCOREDUMP if (WCOREDUMP(exit_int)) { *exitwhy |= APR_PROC_SIGNAL_CORE; } #endif *exitcode = WTERMSIG(exit_int); } else { /* unexpected condition */ return APR_EGENERAL; } return APR_CHILD_DONE; } else if (pstatus == 0) { return APR_CHILD_NOTDONE; } return errno; } #if APR_HAVE_STRUCT_RLIMIT APR_DECLARE(apr_status_t) apr_procattr_limit_set(apr_procattr_t *attr, apr_int32_t what, struct rlimit *limit) { switch(what) { case APR_LIMIT_CPU: #ifdef RLIMIT_CPU attr->limit_cpu = limit; break; #else return APR_ENOTIMPL; #endif case APR_LIMIT_MEM: #if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS) attr->limit_mem = limit; break; #else return APR_ENOTIMPL; #endif case APR_LIMIT_NPROC: #ifdef RLIMIT_NPROC attr->limit_nproc = limit; break; #else return APR_ENOTIMPL; #endif case APR_LIMIT_NOFILE: #ifdef RLIMIT_NOFILE attr->limit_nofile = limit; break; #else return APR_ENOTIMPL; #endif } return APR_SUCCESS; } #endif /* APR_HAVE_STRUCT_RLIMIT */ APR_DECLARE(apr_status_t) apr_procattr_perms_set_register(apr_procattr_t *attr, apr_perms_setfn_t *perms_set_fn, void *data, apr_fileperms_t perms) { apr_procattr_pscb_t *c; c = apr_palloc(attr->pool, sizeof(apr_procattr_pscb_t)); c->data = data; c->perms = perms; c->perms_set_fn = perms_set_fn; c->next = attr->perms_set_callbacks; attr->perms_set_callbacks = c; return APR_SUCCESS; }