blob: bfd9f17d00c19fcf27518691ada74d17b0b0adf9 [file] [log] [blame]
/*
* 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 "unixd.h"
#include "ap_mpm.h"
#include "apr_thread_proc.h"
#include "apr_strings.h"
#include "apr_queue.h"
#include "apr_global_mutex.h"
#include "apr_support.h"
#include "http_config.h"
#include "fcgid_pm.h"
#include "fcgid_pm_main.h"
#include "fcgid_conf.h"
#include "fcgid_proctbl.h"
#include "fcgid_spawn_ctl.h"
#include "fcgid_mutex.h"
#include <unistd.h>
#if MODULE_MAGIC_NUMBER_MAJOR >= 20090209
#include "mod_unixd.h"
#endif
#if MODULE_MAGIC_NUMBER_MAJOR < 20081201
#define ap_unixd_config unixd_config
#define ap_unixd_setup_child unixd_setup_child
#endif
/* The APR other-child API doesn't tell us how the daemon exited
* (SIGSEGV vs. exit(1)). The other-child maintenance function
* needs to decide whether to restart the daemon after a failure
* based on whether or not it exited due to a fatal startup error
* or something that happened at steady-state. This exit status
* is unlikely to collide with exit signals.
*/
#define DAEMON_STARTUP_ERROR 254
static apr_status_t create_process_manager(server_rec * main_server,
apr_pool_t * configpool);
static int g_wakeup_timeout = 0;
static apr_proc_t *g_process_manager = NULL;
static apr_file_t *g_pm_read_pipe = NULL;
static apr_file_t *g_pm_write_pipe = NULL;
static apr_file_t *g_ap_write_pipe = NULL;
static apr_file_t *g_ap_read_pipe = NULL;
static apr_global_mutex_t *g_pipelock = NULL;
static const char *g_pipelock_name;
static const char *g_pipelock_mutex_type = "fcgid-pipe";
static int volatile g_caughtSigTerm = 0;
static pid_t g_pm_pid;
static void signal_handler(int signo)
{
/* Sanity check, Make sure I am not the subprocess. A subprocess may
get signale after fork() and before execve() */
if (getpid() != g_pm_pid) {
exit(0);
return;
}
if ((signo == SIGTERM) || (signo == SIGUSR1) || (signo == SIGHUP)) {
g_caughtSigTerm = 1;
/* Tell the world it's time to die */
proctable_get_globalshare()->must_exit = 1;
}
}
static apr_status_t init_signal(server_rec * main_server)
{
struct sigaction sa;
/* Setup handlers */
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGTERM, &sa, NULL) < 0) {
ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server,
"mod_fcgid: Can't install SIGTERM handler");
return APR_EGENERAL;
}
/* Httpd restart */
if (sigaction(SIGHUP, &sa, NULL) < 0) {
ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server,
"mod_fcgid: Can't install SIGHUP handler");
return APR_EGENERAL;
}
/* Httpd graceful restart */
if (sigaction(SIGUSR1, &sa, NULL) < 0) {
ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server,
"mod_fcgid: Can't install SIGUSR1 handler");
return APR_EGENERAL;
}
/* Ignore SIGPIPE */
sa.sa_handler = SIG_IGN;
if (sigaction(SIGPIPE, &sa, NULL) < 0) {
ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server,
"mod_fcgid: Can't install SIGPIPE handler");
return APR_EGENERAL;
}
return APR_SUCCESS;
}
static void fcgid_maint(int reason, void *data, apr_wait_t status)
{
apr_proc_t *proc = data;
int mpm_state;
switch (reason) {
case APR_OC_REASON_DEATH:
apr_proc_other_child_unregister(data);
if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state) == APR_SUCCESS
&& mpm_state != AP_MPMQ_STOPPING) {
if (status == DAEMON_STARTUP_ERROR) {
ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL,
"mod_fcgid: fcgid process manager failed to initialize; "
"stopping httpd");
/* mod_fcgid requests will hang due to lack of a process manager;
* try to terminate httpd
*/
kill(getpid(), SIGTERM);
}
else {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
"mod_fcgid: fcgid process manager died, restarting the server");
/* HACK: I can't just call create_process_manager() to
restart a process manager, because it will use the dirty
share memory, I have to kill myself a SIGHUP, to make
a clean restart */
/* FIXME: This is the httpd parent; mod_fcgid is doing a hard
* restart of the server!
*/
if (kill(getpid(), SIGHUP) < 0) {
ap_log_error(APLOG_MARK, APLOG_EMERG,
apr_get_os_error(), NULL,
"mod_fcgid: can't send SIGHUP to self");
exit(0);
}
}
}
break;
case APR_OC_REASON_RESTART:
apr_proc_other_child_unregister(data);
break;
case APR_OC_REASON_LOST:
apr_proc_other_child_unregister(data);
/* It hack here too, a note above */
/* FIXME: This is the httpd parent; mod_fcgid is doing a hard
* restart of the server!
*/
if (kill(getpid(), SIGHUP) < 0) {
ap_log_error(APLOG_MARK, APLOG_EMERG,
apr_get_os_error(), NULL,
"mod_fcgid: can't send SIGHUP to self");
exit(0);
}
break;
case APR_OC_REASON_UNREGISTER:
/* I don't think it's going to happen */
kill(proc->pid, SIGHUP);
break;
}
}
static int set_group_privs(void)
{
if (!geteuid()) {
const char *name;
/* Get username if passed as a uid */
if (ap_unixd_config.user_name[0] == '#') {
struct passwd *ent;
uid_t uid = atoi(&ap_unixd_config.user_name[1]);
if ((ent = getpwuid(uid)) == NULL) {
ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL,
"getpwuid: couldn't determine user name from uid %u, "
"you probably need to modify the User directive",
(unsigned) uid);
return -1;
}
name = ent->pw_name;
}
else
name = ap_unixd_config.user_name;
#if !defined(OS2) && !defined(TPF)
/* OS/2 and TPF don't support groups. */
/*
* Set the GID before initgroups(), since on some platforms
* setgid() is known to zap the group list.
*/
if (setgid(ap_unixd_config.group_id) == -1) {
ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL,
"setgid: unable to set group id to Group %u",
(unsigned) ap_unixd_config.group_id);
return -1;
}
/* Reset `groups' attributes. */
if (initgroups(name, ap_unixd_config.group_id) == -1) {
ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL,
"initgroups: unable to set groups for User %s "
"and Group %u", name,
(unsigned) ap_unixd_config.group_id);
return -1;
}
#endif /* !defined(OS2) && !defined(TPF) */
}
return 0;
}
/* Base on ap_unixd_setup_child() */
static int suexec_setup_child(void)
{
if (set_group_privs()) {
exit(-1);
}
/* Only try to switch if we're running as root */
if (!geteuid() && (seteuid(ap_unixd_config.user_id) == -1)) {
ap_log_error(APLOG_MARK, APLOG_ALERT, errno, NULL,
"seteuid: unable to change to uid %ld",
(long) ap_unixd_config.user_id);
exit(-1);
}
return 0;
}
static apr_status_t
create_process_manager(server_rec * main_server, apr_pool_t * configpool)
{
apr_status_t rv;
g_process_manager =
(apr_proc_t *) apr_pcalloc(configpool, sizeof(*g_process_manager));
rv = apr_proc_fork(g_process_manager, configpool);
if (rv == APR_INCHILD) {
/* I am the child */
g_pm_pid = getpid();
ap_log_error(APLOG_MARK, APLOG_INFO, 0, main_server,
"mod_fcgid: Process manager %" APR_PID_T_FMT " started", getpid());
if ((rv = init_signal(main_server)) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server,
"mod_fcgid: can't install signal handler, exiting now");
exit(DAEMON_STARTUP_ERROR);
}
/* If running as root, switch to configured user.
*
* When running children via suexec, only the effective uid is
* switched, so that the PM can return to euid 0 to kill child
* processes.
*
* When running children as the configured user, the real uid
* is switched.
*/
if (ap_unixd_config.suexec_enabled) {
if (getuid() != 0) {
ap_log_error(APLOG_MARK, APLOG_EMERG, 0, main_server,
"mod_fcgid: current user is not root while suexec is enabled, exiting now");
exit(DAEMON_STARTUP_ERROR);
}
suexec_setup_child();
} else
ap_unixd_setup_child();
apr_file_pipe_timeout_set(g_pm_read_pipe,
apr_time_from_sec(g_wakeup_timeout));
apr_file_close(g_ap_write_pipe);
apr_file_close(g_ap_read_pipe);
/* Initialize spawn controler */
spawn_control_init(main_server, configpool);
pm_main(main_server, configpool);
ap_log_error(APLOG_MARK, APLOG_INFO, 0, main_server,
"mod_fcgid: Process manager %" APR_PID_T_FMT " stopped", getpid());
exit(0);
} else if (rv != APR_INPARENT) {
ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server,
"mod_fcgid: Create process manager error");
exit(1);
}
/* I am the parent
I will send the stop signal in procmgr_stop_procmgr() */
apr_pool_note_subprocess(configpool, g_process_manager,
APR_KILL_ONLY_ONCE);
apr_proc_other_child_register(g_process_manager, fcgid_maint,
g_process_manager, NULL, configpool);
return APR_SUCCESS;
}
apr_status_t
procmgr_child_init(server_rec * main_server, apr_pool_t * configpool)
{
apr_status_t rv;
if ((rv = apr_global_mutex_child_init(&g_pipelock,
g_pipelock_name,
main_server->process->pconf)) !=
APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server,
"mod_fcgid: apr_global_mutex_child_init error for pipe mutex");
exit(1);
}
return APR_SUCCESS;
}
apr_status_t procmgr_pre_config(apr_pool_t *p, apr_pool_t *plog,
apr_pool_t *ptemp)
{
return fcgid_mutex_register(g_pipelock_mutex_type, p);
}
apr_status_t
procmgr_post_config(server_rec * main_server, apr_pool_t * configpool)
{
apr_status_t rv;
apr_finfo_t finfo;
fcgid_server_conf *sconf = ap_get_module_config(main_server->module_config,
&fcgid_module);
/* Calculate procmgr_peek_cmd wake up interval */
g_wakeup_timeout = fcgid_min(sconf->error_scan_interval,
sconf->busy_scan_interval);
g_wakeup_timeout = fcgid_min(sconf->idle_scan_interval,
g_wakeup_timeout);
if (g_wakeup_timeout == 0)
g_wakeup_timeout = 1; /* Make it reasonable */
rv = apr_stat(&finfo, sconf->sockname_prefix, APR_FINFO_USER,
configpool);
if (rv != APR_SUCCESS) {
/* Make dir for unix domain socket */
if ((rv = apr_dir_make_recursive(sconf->sockname_prefix,
APR_UREAD | APR_UWRITE |
APR_UEXECUTE,
configpool)) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, main_server,
"mod_fcgid: Can't create unix socket dir %s",
sconf->sockname_prefix);
exit(1);
}
/* Child processes need to be able to create sockets in the unix
* socket dir. Change the ownership to the child user only if
* running as root and we just successfully created the directory
* (avoiding any concerns about changing the target of a link
* created by another user).
*
* If the directory already existed and was owned by a different user,
* FastCGI requests will fail at steady state, and manual intervention
* will be required.
*/
if (!geteuid()) {
if (chown(sconf->sockname_prefix,
ap_unixd_config.user_id, -1) < 0) {
ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server,
"mod_fcgid: Can't set ownership of unix socket dir %s",
sconf->sockname_prefix);
exit(1);
}
}
}
/* Create pipes to communicate between process manager and apache */
if ((rv = apr_file_pipe_create(&g_pm_read_pipe, &g_ap_write_pipe,
configpool)) != APR_SUCCESS
|| (rv = apr_file_pipe_create(&g_ap_read_pipe, &g_pm_write_pipe,
configpool))) {
ap_log_error(APLOG_MARK, APLOG_ERR, rv, main_server,
"mod_fcgid: Can't create pipe between PM and stub");
return rv;
}
/* Create mutex for pipe reading and writing */
rv = fcgid_mutex_create(&g_pipelock, &g_pipelock_name,
g_pipelock_mutex_type,
main_server->process->pconf, main_server);
if (rv != APR_SUCCESS) {
exit(1);
}
/* Create process manager process */
return create_process_manager(main_server, configpool);
}
void procmgr_init_spawn_cmd(fcgid_command * command, request_rec * r,
fcgid_cmd_conf *cmd_conf)
{
ap_unix_identity_t *ugid;
fcgid_server_conf *sconf =
ap_get_module_config(r->server->module_config, &fcgid_module);
memset(command, 0, sizeof(*command));
/* suEXEC check */
if ((ugid = ap_run_get_suexec_identity(r))) {
command->uid = ugid->uid;
command->gid = ugid->gid;
command->userdir = ugid->userdir;
} else {
command->uid = (uid_t) - 1;
command->gid = (gid_t) - 1;
command->userdir = 0;
}
/* no truncation should ever occur */
AP_DEBUG_ASSERT(sizeof command->cgipath > strlen(cmd_conf->cgipath));
apr_cpystrn(command->cgipath, cmd_conf->cgipath, sizeof command->cgipath);
AP_DEBUG_ASSERT(sizeof command->cmdline > strlen(cmd_conf->cmdline));
apr_cpystrn(command->cmdline, cmd_conf->cmdline, sizeof command->cmdline);
command->deviceid = cmd_conf->deviceid;
command->inode = cmd_conf->inode;
command->vhost_id = sconf->vhost_id;
if (r->server->server_hostname) {
apr_cpystrn(command->server_hostname, r->server->server_hostname,
sizeof command->server_hostname);
}
get_cmd_options(r, command->cgipath, &command->cmdopts, &command->cmdenv);
}
apr_status_t procmgr_post_spawn_cmd(fcgid_command * command,
request_rec * r)
{
apr_status_t rv;
char notifybyte;
apr_size_t nbytes = sizeof(*command);
/* Get the global mutex before posting the request */
if ((rv = apr_global_mutex_lock(g_pipelock)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r,
"mod_fcgid: can't get pipe mutex");
exit(0);
}
if ((rv =
apr_file_write_full(g_ap_write_pipe, command, nbytes,
NULL)) != APR_SUCCESS) {
/* Just print some error log and fall through */
ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r,
"mod_fcgid: can't write spawn command");
} else {
/* Wait the finish notify while send the request successfully */
nbytes = sizeof(notifybyte);
if ((rv =
apr_file_read(g_ap_read_pipe, &notifybyte,
&nbytes)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r,
"mod_fcgid: can't get notify from process manager");
}
}
/* Release the lock */
if ((rv = apr_global_mutex_unlock(g_pipelock)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r,
"mod_fcgid: can't release pipe mutex");
exit(0);
}
return APR_SUCCESS;
}
apr_status_t procmgr_finish_notify(server_rec * main_server)
{
apr_status_t rv;
char notifybyte = 'p';
apr_size_t nbytes = sizeof(notifybyte);
if ((rv =
apr_file_write(g_pm_write_pipe, &notifybyte,
&nbytes)) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_WARNING, rv, main_server,
"mod_fcgid: can't send notify from process manager");
}
return rv;
}
#define FOR_READ 1
apr_status_t procmgr_peek_cmd(fcgid_command * command,
server_rec * main_server)
{
apr_status_t rv;
/* Sanity check */
if (!g_pm_read_pipe)
return APR_EPIPE;
/* Wait for next command */
rv = apr_wait_for_io_or_timeout(g_pm_read_pipe, NULL, FOR_READ);
/* Log any unexpect result */
if (rv != APR_SUCCESS && !APR_STATUS_IS_TIMEUP(rv)) {
ap_log_error(APLOG_MARK, APLOG_WARNING, rv, main_server,
"mod_fcgid: error while waiting for message from pipe");
return rv;
}
/* Timeout */
if (rv != APR_SUCCESS)
return rv;
return apr_file_read_full(g_pm_read_pipe, command, sizeof(*command),
NULL);
}
int procmgr_must_exit()
{
return g_caughtSigTerm;
}
apr_status_t procmgr_stop_procmgr(void *server)
{
return APR_SUCCESS;
}