diff options
Diffstat (limited to '')
-rw-r--r-- | threadproc/unix/proc.c | 739 |
1 files changed, 739 insertions, 0 deletions
diff --git a/threadproc/unix/proc.c b/threadproc/unix/proc.c new file mode 100644 index 0000000..004772f --- /dev/null +++ b/threadproc/unix/proc.c @@ -0,0 +1,739 @@ +/* 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) { + 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; +} |