| /* |
| * 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, ¬ifybyte, |
| &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, ¬ifybyte, |
| &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; |
| } |