| /* |
| * 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 "httpd.h" |
| #include "http_request.h" |
| #include "http_protocol.h" |
| #include "ap_mmn.h" |
| #include "apr_lib.h" |
| #include "apr_buckets.h" |
| #include "apr_strings.h" |
| #include "apr_thread_proc.h" |
| #include "mod_cgi.h" |
| #include "mod_status.h" |
| #include "util_script.h" |
| #include "fcgid_global.h" |
| #include "fcgid_pm.h" |
| #include "fcgid_proctbl.h" |
| #include "fcgid_conf.h" |
| #include "fcgid_spawn_ctl.h" |
| #include "fcgid_bridge.h" |
| #include "fcgid_filter.h" |
| #include "fcgid_protocol.h" |
| #include "fcgid_proc.h" |
| |
| static APR_OPTIONAL_FN_TYPE(ap_cgi_build_command) * cgi_build_command; |
| static ap_filter_rec_t *fcgid_filter_handle; |
| static int g_php_fix_pathinfo_enable = 0; |
| |
| enum fcgid_procnode_type { |
| FCGID_PROCNODE_TYPE_IDLE, |
| FCGID_PROCNODE_TYPE_BUSY, |
| FCGID_PROCNODE_TYPE_ERROR, |
| }; |
| |
| /* Stolen from mod_cgi.c */ |
| /* KLUDGE --- for back-compatibility, we don't have to check ExecCGI |
| * in ScriptAliased directories, which means we need to know if this |
| * request came through ScriptAlias or not... so the Alias module |
| * leaves a note for us. |
| */ |
| |
| static int is_scriptaliased(request_rec * r) |
| { |
| const char *t = apr_table_get(r->notes, "alias-forced-type"); |
| |
| return t && (!strcasecmp(t, "cgi-script")); |
| } |
| |
| static apr_status_t |
| default_build_command(const char **cmd, const char ***argv, |
| request_rec * r, apr_pool_t * p, |
| cgi_exec_info_t * e_info) |
| { |
| int numwords, x, idx; |
| char *w; |
| const char *args = NULL; |
| |
| if (e_info->process_cgi) { |
| *cmd = r->filename; |
| /* Do not process r->args if they contain an '=' assignment |
| */ |
| if (r->args && r->args[0] && !ap_strchr_c(r->args, '=')) { |
| args = r->args; |
| } |
| } |
| |
| if (!args) { |
| numwords = 1; |
| } else { |
| /* count the number of keywords */ |
| for (x = 0, numwords = 2; args[x]; x++) { |
| if (args[x] == '+') { |
| ++numwords; |
| } |
| } |
| } |
| /* Everything is - 1 to account for the first parameter |
| * which is the program name. |
| */ |
| if (numwords > APACHE_ARG_MAX - 1) { |
| numwords = APACHE_ARG_MAX - 1; /* Truncate args to prevent overrun */ |
| } |
| *argv = apr_palloc(p, (numwords + 2) * sizeof(char *)); |
| (*argv)[0] = *cmd; |
| for (x = 1, idx = 1; x < numwords; x++) { |
| w = ap_getword_nulls(p, &args, '+'); |
| ap_unescape_url(w); |
| (*argv)[idx++] = ap_escape_shell_cmd(p, w); |
| } |
| (*argv)[idx] = NULL; |
| |
| return APR_SUCCESS; |
| } |
| |
| /* http2env stolen from util_script.c */ |
| static char *http2env(apr_pool_t *a, const char *w) |
| { |
| char *res = (char *)apr_palloc(a, sizeof("HTTP_") + strlen(w)); |
| char *cp = res; |
| char c; |
| |
| *cp++ = 'H'; |
| *cp++ = 'T'; |
| *cp++ = 'T'; |
| *cp++ = 'P'; |
| *cp++ = '_'; |
| |
| while ((c = *w++) != 0) { |
| if (!apr_isalnum(c)) { |
| *cp++ = '_'; |
| } |
| else { |
| *cp++ = apr_toupper(c); |
| } |
| } |
| *cp = 0; |
| |
| return res; |
| } |
| |
| static void fcgid_add_cgi_vars(request_rec * r) |
| { |
| apr_array_header_t *passheaders = get_pass_headers(r); |
| |
| if (passheaders != NULL) { |
| const char **hdr = (const char **) passheaders->elts; |
| int hdrcnt = passheaders->nelts; |
| int i; |
| |
| for (i = 0; i < hdrcnt; i++, ++hdr) { |
| const char *val = apr_table_get(r->headers_in, *hdr); |
| |
| if (val) { |
| /* no munging of header name to create envvar name; |
| * consistent with legacy mod_fcgid behavior and mod_fastcgi |
| * prior to 2.4.7 |
| */ |
| apr_table_setn(r->subprocess_env, *hdr, val); |
| /* standard munging of header name (upcase, HTTP_, etc.) */ |
| apr_table_setn(r->subprocess_env, http2env(r->pool, *hdr), val); |
| } |
| } |
| } |
| |
| /* Work around cgi.fix_pathinfo = 1 in php.ini */ |
| if (g_php_fix_pathinfo_enable) { |
| char *merge_path; |
| apr_table_t *e = r->subprocess_env; |
| |
| /* "DOCUMENT_ROOT"/"SCRIPT_NAME" -> "SCRIPT_NAME" */ |
| const char *doc_root = apr_table_get(e, "DOCUMENT_ROOT"); |
| const char *script_name = apr_table_get(e, "SCRIPT_NAME"); |
| |
| if (doc_root && script_name |
| && apr_filepath_merge(&merge_path, doc_root, script_name, 0, |
| r->pool) == APR_SUCCESS) { |
| apr_table_setn(e, "SCRIPT_NAME", merge_path); |
| } |
| } |
| } |
| |
| static int fcgid_handler(request_rec * r) |
| { |
| cgi_exec_info_t e_info; |
| const char *command; |
| const char **argv; |
| apr_status_t rv; |
| int http_retcode; |
| fcgid_cmd_conf *wrapper_conf; |
| |
| if (strcmp(r->handler, "fcgid-script")) |
| return DECLINED; |
| |
| if (!(ap_allow_options(r) & OPT_EXECCGI) && !is_scriptaliased(r)) |
| return HTTP_FORBIDDEN; |
| |
| if ((r->used_path_info == AP_REQ_REJECT_PATH_INFO) && |
| r->path_info && *r->path_info) |
| return HTTP_NOT_FOUND; |
| |
| e_info.process_cgi = 1; |
| e_info.cmd_type = APR_PROGRAM; |
| e_info.detached = 0; |
| e_info.in_pipe = APR_CHILD_BLOCK; |
| e_info.out_pipe = APR_CHILD_BLOCK; |
| e_info.err_pipe = APR_CHILD_BLOCK; |
| e_info.prog_type = RUN_AS_CGI; |
| e_info.bb = NULL; |
| e_info.ctx = NULL; |
| e_info.next = NULL; |
| |
| wrapper_conf = get_wrapper_info(r->filename, r); |
| |
| /* Check for existence of requested file, unless we use a virtual wrapper. */ |
| if (wrapper_conf == NULL || !wrapper_conf->virtual) { |
| if (r->finfo.filetype == 0) |
| return HTTP_NOT_FOUND; |
| |
| if (r->finfo.filetype == APR_DIR) |
| return HTTP_FORBIDDEN; |
| } |
| |
| /* Build the command line */ |
| if (wrapper_conf) { |
| if ((rv = |
| default_build_command(&command, &argv, r, r->pool, |
| &e_info)) != APR_SUCCESS) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, |
| "mod_fcgid: don't know how to spawn wrapper child process: %s", |
| r->filename); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| } else { |
| if ((rv = cgi_build_command(&command, &argv, r, r->pool, |
| &e_info)) != APR_SUCCESS) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, |
| "mod_fcgid: don't know how to spawn child process: %s", |
| r->filename); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| |
| /* Check request like "http://localhost/cgi-bin/a.exe/defghi" */ |
| if (r->finfo.inode == 0 && r->finfo.device == 0) { |
| if ((rv = |
| apr_stat(&r->finfo, command, APR_FINFO_IDENT, |
| r->pool)) != APR_SUCCESS) { |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, |
| "mod_fcgid: can't get %s file info", command); |
| return HTTP_NOT_FOUND; |
| } |
| } |
| |
| /* Dummy up a wrapper configuration, using the requested file as |
| * both the executable path and command-line. |
| */ |
| wrapper_conf = apr_pcalloc(r->pool, sizeof(*wrapper_conf)); |
| |
| if (strlen(command) >= fcgid_min(FCGID_PATH_MAX, FCGID_CMDLINE_MAX)) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, |
| "mod_fcgid: Executable path length exceeds compiled-in limit: %s", |
| command); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| |
| wrapper_conf->cgipath = apr_pstrdup(r->pool, command); |
| wrapper_conf->cmdline = wrapper_conf->cgipath; |
| wrapper_conf->inode = r->finfo.inode; |
| wrapper_conf->deviceid = r->finfo.device; |
| } |
| |
| ap_add_common_vars(r); |
| ap_add_cgi_vars(r); |
| fcgid_add_cgi_vars(r); |
| |
| /* Remove hop-by-hop headers handled by http |
| */ |
| apr_table_unset(r->subprocess_env, "HTTP_KEEP_ALIVE"); |
| apr_table_unset(r->subprocess_env, "HTTP_TE"); |
| apr_table_unset(r->subprocess_env, "HTTP_TRAILER"); |
| apr_table_unset(r->subprocess_env, "HTTP_TRANSFER_ENCODING"); |
| apr_table_unset(r->subprocess_env, "HTTP_UPGRADE"); |
| |
| /* Connection hop-by-hop header to prevent the CGI from hanging */ |
| apr_table_set(r->subprocess_env, "HTTP_CONNECTION", "close"); |
| |
| /* Insert output filter */ |
| ap_add_output_filter_handle(fcgid_filter_handle, NULL, r, |
| r->connection); |
| |
| http_retcode = bridge_request(r, FCGI_RESPONDER, wrapper_conf); |
| return (http_retcode == HTTP_OK ? OK : http_retcode); |
| } |
| |
| static int fcgidsort(fcgid_procnode **e1, fcgid_procnode **e2) |
| { |
| int cmp = strcmp((*e1)->executable_path, (*e2)->executable_path); |
| |
| if (cmp != 0) |
| return cmp; |
| if ((*e1)->gid != (*e2)->gid) |
| return (*e1)->gid > (*e2)->gid ? 1 : -1; |
| if ((*e1)->uid != (*e2)->uid) |
| return (*e1)->uid > (*e2)->uid ? 1 : -1; |
| cmp = strcmp((*e1)->cmdline, (*e2)->cmdline); |
| if (cmp != 0) |
| return cmp; |
| if ((*e1)->vhost_id != (*e2)->vhost_id) |
| return (*e1)->vhost_id > (*e2)->vhost_id ? 1 : -1; |
| if ((*e1)->diewhy != (*e2)->diewhy) |
| return (*e1)->diewhy > (*e2)->diewhy ? 1 : -1; |
| if ((*e1)->node_type != (*e2)->node_type) |
| return (*e1)->node_type > (*e2)->node_type ? 1 : -1; |
| return 0; |
| } |
| |
| static char *get_state_desc(fcgid_procnode *node) |
| { |
| if (node->node_type == FCGID_PROCNODE_TYPE_IDLE) |
| return "Ready"; |
| else if (node->node_type == FCGID_PROCNODE_TYPE_BUSY) |
| return "Working"; |
| else { |
| switch (node->diewhy) { |
| case FCGID_DIE_KILLSELF: |
| return "Exiting(normal exit)"; |
| case FCGID_DIE_IDLE_TIMEOUT: |
| return "Exiting(idle timeout)"; |
| case FCGID_DIE_LIFETIME_EXPIRED: |
| return "Exiting(lifetime expired)"; |
| case FCGID_DIE_BUSY_TIMEOUT: |
| return "Exiting(busy timeout)"; |
| case FCGID_DIE_CONNECT_ERROR: |
| return "Exiting(connect error)"; |
| case FCGID_DIE_COMM_ERROR: |
| return "Exiting(communication error)"; |
| case FCGID_DIE_SHUTDOWN: |
| return "Exiting(shutting down)"; |
| default: |
| return "Exiting"; |
| } |
| } |
| } |
| |
| /* fcgid Extension to mod_status */ |
| static int fcgid_status_hook(request_rec *r, int flags) |
| { |
| fcgid_procnode **ar = NULL, *current_node; |
| int num_ent, index; |
| apr_ino_t last_inode = 0; |
| apr_dev_t last_deviceid = 0; |
| gid_t last_gid = 0; |
| uid_t last_uid = 0; |
| const char *last_cmdline = ""; |
| apr_time_t now; |
| int last_vhost_id = -1; |
| const char *basename, *tmpbasename; |
| fcgid_procnode *proc_table = proctable_get_table_array(); |
| fcgid_procnode *error_list_header = proctable_get_error_list(); |
| fcgid_procnode *idle_list_header = proctable_get_idle_list(); |
| fcgid_procnode *busy_list_header = proctable_get_busy_list(); |
| |
| if ((flags & AP_STATUS_SHORT) || (proc_table == NULL)) |
| return OK; |
| |
| proctable_lock(r); |
| |
| /* Get element count */ |
| num_ent = 0; |
| current_node = &proc_table[busy_list_header->next_index]; |
| while (current_node != proc_table) { |
| num_ent++; |
| current_node = &proc_table[current_node->next_index]; |
| } |
| current_node = &proc_table[idle_list_header->next_index]; |
| while (current_node != proc_table) { |
| num_ent++; |
| current_node = &proc_table[current_node->next_index]; |
| } |
| current_node = &proc_table[error_list_header->next_index]; |
| while (current_node != proc_table) { |
| num_ent++; |
| current_node = &proc_table[current_node->next_index]; |
| } |
| |
| /* Create an array for qsort() */ |
| if (num_ent != 0) { |
| ar = (fcgid_procnode **)apr_palloc(r->pool, num_ent * sizeof(fcgid_procnode*)); |
| index = 0; |
| current_node = &proc_table[busy_list_header->next_index]; |
| while (current_node != proc_table) { |
| ar[index] = apr_palloc(r->pool, sizeof(fcgid_procnode)); |
| *ar[index] = *current_node; |
| ar[index++]->node_type = FCGID_PROCNODE_TYPE_BUSY; |
| current_node = &proc_table[current_node->next_index]; |
| } |
| current_node = &proc_table[idle_list_header->next_index]; |
| while (current_node != proc_table) { |
| ar[index] = apr_palloc(r->pool, sizeof(fcgid_procnode)); |
| *ar[index] = *current_node; |
| ar[index++]->node_type = FCGID_PROCNODE_TYPE_IDLE; |
| current_node = &proc_table[current_node->next_index]; |
| } |
| current_node = &proc_table[error_list_header->next_index]; |
| while (current_node != proc_table) { |
| ar[index] = apr_palloc(r->pool, sizeof(fcgid_procnode)); |
| *ar[index] = *current_node; |
| ar[index++]->node_type = FCGID_PROCNODE_TYPE_ERROR; |
| current_node = &proc_table[current_node->next_index]; |
| } |
| } |
| proctable_unlock(r); |
| |
| now = apr_time_now(); |
| |
| /* Sort the array */ |
| if (num_ent != 0) |
| qsort((void *)ar, num_ent, sizeof(fcgid_procnode *), |
| (int (*)(const void *, const void *))fcgidsort); |
| |
| /* Output */ |
| ap_rputs("<hr />\n<h1>mod_fcgid status:</h1>\n", r); |
| ap_rprintf(r, "Total FastCGI processes: %d\n", num_ent); |
| for (index = 0; index < num_ent; index++) { |
| current_node = ar[index]; |
| if (current_node->inode != last_inode || current_node->deviceid != last_deviceid |
| || current_node->gid != last_gid || current_node->uid != last_uid |
| || strcmp(current_node->cmdline, last_cmdline) |
| || current_node->vhost_id != last_vhost_id) { |
| if (index != 0) |
| ap_rputs("</table>\n\n", r); |
| |
| /* Print executable path basename */ |
| tmpbasename = ap_strrchr_c(current_node->executable_path, '/'); |
| if (tmpbasename != NULL) |
| tmpbasename++; |
| basename = ap_strrchr_c(tmpbasename, '\\'); |
| if (basename != NULL) |
| basename++; |
| else |
| basename = tmpbasename; |
| ap_rprintf(r, "<hr />\n<b>Process: %s</b> (%s)<br />\n", |
| basename, current_node->cmdline); |
| |
| /* Create a new table for this process info */ |
| ap_rputs("\n\n<table border=\"0\"><tr>" |
| "<th>Pid</th><th>Active</th><th>Idle</th>" |
| "<th>Accesses</th><th>State</th>" |
| "</tr>\n", r); |
| |
| last_inode = current_node->inode; |
| last_deviceid = current_node->deviceid; |
| last_gid = current_node->gid; |
| last_uid = current_node->uid; |
| last_cmdline = current_node->cmdline; |
| last_vhost_id = current_node->vhost_id; |
| } |
| |
| ap_rprintf(r, "<tr><td>%" APR_PID_T_FMT "</td><td>%" APR_TIME_T_FMT "</td><td>%" APR_TIME_T_FMT "</td><td>%d</td><td>%s</td></tr>", |
| current_node->proc_id.pid, |
| apr_time_sec(now - current_node->start_time), |
| apr_time_sec(now - current_node->last_active_time), |
| current_node->requests_handled, |
| get_state_desc(current_node)); |
| } |
| if (num_ent != 0) { |
| ap_rputs("</table>\n\n", r); |
| ap_rputs("<hr>\n" |
| "<b>Active</b> and <b>Idle</b> are time active and time since\n" |
| "last request, in seconds.\n", r); |
| } |
| |
| return OK; |
| } |
| |
| static int mod_fcgid_modify_auth_header(void *subprocess_env, |
| const char *key, const char *val) |
| { |
| /* When the application gives a 200 response, the server ignores response |
| headers whose names aren't prefixed with Variable- prefix, and ignores |
| any response content */ |
| if (strncasecmp(key, "Variable-", 9) == 0) |
| apr_table_setn(subprocess_env, key + 9, val); |
| return 1; |
| } |
| |
| static int mod_fcgid_authenticator(request_rec * r) |
| { |
| int res = 0; |
| const char *password = NULL; |
| apr_table_t *saved_subprocess_env = NULL; |
| fcgid_cmd_conf *authenticator_info; |
| int authoritative; |
| |
| authenticator_info = get_authenticator_info(r, &authoritative); |
| |
| /* Is authenticator enable? */ |
| if (authenticator_info == NULL) |
| return DECLINED; |
| |
| /* Get the user password */ |
| if ((res = ap_get_basic_auth_pw(r, &password)) != OK) |
| return res; |
| |
| /* Save old process environment */ |
| saved_subprocess_env = apr_table_copy(r->pool, r->subprocess_env); |
| |
| /* Add some environment variables */ |
| ap_add_common_vars(r); |
| ap_add_cgi_vars(r); |
| fcgid_add_cgi_vars(r); |
| apr_table_setn(r->subprocess_env, "REMOTE_PASSWD", password); |
| apr_table_setn(r->subprocess_env, "FCGI_APACHE_ROLE", "AUTHENTICATOR"); |
| |
| /* Drop the variables CONTENT_LENGTH, PATH_INFO, PATH_TRANSLATED, |
| * SCRIPT_NAME and most Hop-By-Hop headers - EXCEPT we will pass |
| * PROXY_AUTH to allow CGI to perform proxy auth for httpd |
| */ |
| apr_table_unset(r->subprocess_env, "CONTENT_LENGTH"); |
| apr_table_unset(r->subprocess_env, "PATH_INFO"); |
| apr_table_unset(r->subprocess_env, "PATH_TRANSLATED"); |
| apr_table_unset(r->subprocess_env, "SCRIPT_NAME"); |
| apr_table_unset(r->subprocess_env, "HTTP_KEEP_ALIVE"); |
| apr_table_unset(r->subprocess_env, "HTTP_TE"); |
| apr_table_unset(r->subprocess_env, "HTTP_TRAILER"); |
| apr_table_unset(r->subprocess_env, "HTTP_TRANSFER_ENCODING"); |
| apr_table_unset(r->subprocess_env, "HTTP_UPGRADE"); |
| |
| /* Connection hop-by-hop header to prevent the CGI from hanging */ |
| apr_table_set(r->subprocess_env, "HTTP_CONNECTION", "close"); |
| |
| /* Handle the request */ |
| res = bridge_request(r, FCGI_AUTHORIZER, authenticator_info); |
| |
| /* Restore r->subprocess_env */ |
| r->subprocess_env = saved_subprocess_env; |
| |
| if (res == OK && r->status == 200 |
| && apr_table_get(r->headers_out, "Location") == NULL) |
| { |
| /* Pass */ |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, |
| "mod_fcgid: user %s authentication pass", r->user); |
| |
| /* Modify headers: An Authorizer application's 200 response may include headers |
| whose names are prefixed with Variable-. */ |
| apr_table_do(mod_fcgid_modify_auth_header, r->subprocess_env, |
| r->err_headers_out, NULL); |
| |
| return OK; |
| } else { |
| /* Print error info first */ |
| if (res != OK) |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, r, |
| "mod_fcgid: user %s authentication failed, respond %d, URI %s", |
| r->user, res, r->uri); |
| else if (r->status != 200) |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, r, |
| "mod_fcgid: user %s authentication failed, status %d, URI %s", |
| r->user, r->status, r->uri); |
| else |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, r, |
| "mod_fcgid: user %s authentication failed, redirected is not allowed", |
| r->user); |
| |
| /* Handle error */ |
| if (!authoritative) |
| return DECLINED; |
| else { |
| ap_note_basic_auth_failure(r); |
| return (res == OK) ? HTTP_UNAUTHORIZED : res; |
| } |
| } |
| } |
| |
| static int mod_fcgid_authorizer(request_rec * r) |
| { |
| int res = 0; |
| apr_table_t *saved_subprocess_env = NULL; |
| fcgid_cmd_conf *authorizer_info; |
| int authoritative; |
| |
| authorizer_info = get_authorizer_info(r, &authoritative); |
| |
| /* Is authenticator enable? */ |
| if (authorizer_info == NULL) |
| return DECLINED; |
| |
| /* Save old process environment */ |
| saved_subprocess_env = apr_table_copy(r->pool, r->subprocess_env); |
| |
| /* Add some environment variables */ |
| ap_add_common_vars(r); |
| ap_add_cgi_vars(r); |
| fcgid_add_cgi_vars(r); |
| apr_table_setn(r->subprocess_env, "FCGI_APACHE_ROLE", "AUTHORIZER"); |
| |
| /* Drop the variables CONTENT_LENGTH, PATH_INFO, PATH_TRANSLATED, |
| * SCRIPT_NAME and most Hop-By-Hop headers - EXCEPT we will pass |
| * PROXY_AUTH to allow CGI to perform proxy auth for httpd |
| */ |
| apr_table_unset(r->subprocess_env, "CONTENT_LENGTH"); |
| apr_table_unset(r->subprocess_env, "PATH_INFO"); |
| apr_table_unset(r->subprocess_env, "PATH_TRANSLATED"); |
| apr_table_unset(r->subprocess_env, "SCRIPT_NAME"); |
| apr_table_unset(r->subprocess_env, "HTTP_KEEP_ALIVE"); |
| apr_table_unset(r->subprocess_env, "HTTP_TE"); |
| apr_table_unset(r->subprocess_env, "HTTP_TRAILER"); |
| apr_table_unset(r->subprocess_env, "HTTP_TRANSFER_ENCODING"); |
| apr_table_unset(r->subprocess_env, "HTTP_UPGRADE"); |
| |
| /* Connection hop-by-hop header to prevent the CGI from hanging */ |
| apr_table_set(r->subprocess_env, "HTTP_CONNECTION", "close"); |
| |
| /* Handle the request */ |
| res = bridge_request(r, FCGI_AUTHORIZER, authorizer_info); |
| |
| /* Restore r->subprocess_env */ |
| r->subprocess_env = saved_subprocess_env; |
| |
| if (res == OK && r->status == 200 |
| && apr_table_get(r->headers_out, "Location") == NULL) |
| { |
| /* Pass */ |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, |
| "mod_fcgid: access granted (authorization)"); |
| |
| /* Modify headers: An Authorizer application's 200 response may include headers |
| whose names are prefixed with Variable-. */ |
| apr_table_do(mod_fcgid_modify_auth_header, r->subprocess_env, |
| r->err_headers_out, NULL); |
| |
| return OK; |
| } else { |
| /* Print error info first */ |
| if (res != OK) |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, r, |
| "mod_fcgid: user %s authorization failed, respond %d, URI %s", |
| r->user, res, r->uri); |
| else if (r->status != 200) |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, r, |
| "mod_fcgid: user %s authorization failed, status %d, URI %s", |
| r->user, r->status, r->uri); |
| else |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, r, |
| "mod_fcgid: user %s authorization failed, redirected is not allowed", |
| r->user); |
| |
| /* Handle error */ |
| if (!authoritative) |
| return DECLINED; |
| else { |
| ap_note_basic_auth_failure(r); |
| return (res == OK) ? HTTP_UNAUTHORIZED : res; |
| } |
| } |
| } |
| |
| static int mod_fcgid_check_access(request_rec * r) |
| { |
| int res = 0; |
| apr_table_t *saved_subprocess_env = NULL; |
| fcgid_cmd_conf *access_info; |
| int authoritative; |
| |
| access_info = get_access_info(r, &authoritative); |
| |
| /* Is access check enable? */ |
| if (access_info == NULL) |
| return DECLINED; |
| |
| /* Save old process environment */ |
| saved_subprocess_env = apr_table_copy(r->pool, r->subprocess_env); |
| |
| /* Add some environment variables */ |
| ap_add_common_vars(r); |
| ap_add_cgi_vars(r); |
| fcgid_add_cgi_vars(r); |
| apr_table_setn(r->subprocess_env, "FCGI_APACHE_ROLE", |
| "ACCESS_CHECKER"); |
| |
| /* Drop the variables CONTENT_LENGTH, PATH_INFO, PATH_TRANSLATED, |
| * SCRIPT_NAME and most Hop-By-Hop headers - EXCEPT we will pass |
| * PROXY_AUTH to allow CGI to perform proxy auth for httpd |
| */ |
| apr_table_unset(r->subprocess_env, "CONTENT_LENGTH"); |
| apr_table_unset(r->subprocess_env, "PATH_INFO"); |
| apr_table_unset(r->subprocess_env, "PATH_TRANSLATED"); |
| apr_table_unset(r->subprocess_env, "SCRIPT_NAME"); |
| apr_table_unset(r->subprocess_env, "HTTP_KEEP_ALIVE"); |
| apr_table_unset(r->subprocess_env, "HTTP_TE"); |
| apr_table_unset(r->subprocess_env, "HTTP_TRAILER"); |
| apr_table_unset(r->subprocess_env, "HTTP_TRANSFER_ENCODING"); |
| apr_table_unset(r->subprocess_env, "HTTP_UPGRADE"); |
| |
| /* Connection hop-by-hop header to prevent the CGI from hanging */ |
| apr_table_set(r->subprocess_env, "HTTP_CONNECTION", "close"); |
| |
| /* Handle the request */ |
| res = bridge_request(r, FCGI_AUTHORIZER, access_info); |
| |
| /* Restore r->subprocess_env */ |
| r->subprocess_env = saved_subprocess_env; |
| |
| if (res == OK && r->status == 200 |
| && apr_table_get(r->headers_out, "Location") == NULL) |
| { |
| /* Pass */ |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, r, |
| "mod_fcgid: access check pass"); |
| |
| /* Modify headers: An Authorizer application's 200 response may include headers |
| whose names are prefixed with Variable-. */ |
| apr_table_do(mod_fcgid_modify_auth_header, r->subprocess_env, |
| r->err_headers_out, NULL); |
| |
| return OK; |
| } else { |
| /* Print error info first */ |
| if (res != OK) |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, r, |
| "mod_fcgid: user %s access check failed, respond %d, URI %s", |
| r->user, res, r->uri); |
| else if (r->status != 200) |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, r, |
| "mod_fcgid: user %s access check failed, status %d, URI %s", |
| r->user, r->status, r->uri); |
| else |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, r, |
| "mod_fcgid: user %s access check failed, redirected is not allowed", |
| r->user); |
| |
| /* Handle error */ |
| if (!authoritative) |
| return DECLINED; |
| else { |
| return (res == OK) ? HTTP_UNAUTHORIZED : res; |
| } |
| } |
| } |
| |
| static void initialize_child(apr_pool_t * pchild, server_rec * main_server) |
| { |
| apr_status_t rv; |
| |
| if ((rv = proctable_child_init(main_server, pchild)) != APR_SUCCESS) { |
| ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, |
| "mod_fcgid: Can't initialize shared memory or mutex in child"); |
| return; |
| } |
| |
| if ((rv = procmgr_child_init(main_server, pchild)) != APR_SUCCESS) { |
| ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, |
| "mod_fcgid: Can't initialize process manager"); |
| return; |
| } |
| |
| return; |
| } |
| |
| static int |
| fcgid_init(apr_pool_t * config_pool, apr_pool_t * plog, apr_pool_t * ptemp, |
| server_rec * main_server) |
| { |
| const char *userdata_key = "fcgid_init"; |
| apr_status_t rv; |
| void *dummy = NULL; |
| fcgid_server_conf *sconf = ap_get_module_config(main_server->module_config, |
| &fcgid_module); |
| |
| ap_add_version_component(config_pool, MODFCGID_PRODUCT); |
| |
| g_php_fix_pathinfo_enable = sconf->php_fix_pathinfo_enable; |
| |
| /* Initialize process manager only once */ |
| apr_pool_userdata_get(&dummy, userdata_key, |
| main_server->process->pool); |
| if (!dummy) { |
| apr_pool_userdata_set((const void *)1, userdata_key, |
| apr_pool_cleanup_null, |
| main_server->process->pool); |
| return OK; |
| } |
| |
| /* Initialize share memory and share lock */ |
| if ((rv = |
| proctable_post_config(main_server, config_pool)) != APR_SUCCESS) { |
| ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, |
| "mod_fcgid: Can't initialize shared memory or mutex"); |
| return rv; |
| } |
| |
| /* Initialize process manager */ |
| if ((rv = |
| procmgr_post_config(main_server, config_pool)) != APR_SUCCESS) { |
| ap_log_error(APLOG_MARK, APLOG_EMERG, rv, main_server, |
| "mod_fcgid: Can't initialize process manager"); |
| return rv; |
| } |
| |
| /* This is the means by which unusual (non-unix) os's may find alternate |
| * means to run a given command (e.g. shebang/registry parsing on Win32) |
| */ |
| cgi_build_command = APR_RETRIEVE_OPTIONAL_FN(ap_cgi_build_command); |
| if (!cgi_build_command) { |
| cgi_build_command = default_build_command; |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| static const command_rec fcgid_cmds[] = { |
| AP_INIT_TAKE1("FcgidAccessChecker", set_access_info, NULL, |
| ACCESS_CONF | OR_FILEINFO, |
| "a absolute access checker file path"), |
| AP_INIT_FLAG("FcgidAccessCheckerAuthoritative", |
| set_access_authoritative, NULL, ACCESS_CONF | OR_FILEINFO, |
| "Set to 'off' to allow access control to be passed along to lower modules upon failure"), |
| AP_INIT_TAKE1("FcgidAuthenticator", set_authenticator_info, NULL, |
| ACCESS_CONF | OR_FILEINFO, |
| "a absolute authenticator file path"), |
| AP_INIT_FLAG("FcgidAuthenticatorAuthoritative", |
| set_authenticator_authoritative, NULL, |
| ACCESS_CONF | OR_FILEINFO, |
| "Set to 'off' to allow authentication to be passed along to lower modules upon failure"), |
| AP_INIT_TAKE1("FcgidAuthorizer", set_authorizer_info, NULL, |
| ACCESS_CONF | OR_FILEINFO, |
| "a absolute authorizer file path"), |
| AP_INIT_FLAG("FcgidAuthorizerAuthoritative", |
| set_authorizer_authoritative, NULL, |
| ACCESS_CONF | OR_FILEINFO, |
| "Set to 'off' to allow authorization to be passed along to lower modules upon failure"), |
| AP_INIT_TAKE1("FcgidBusyScanInterval", set_busy_scan_interval, NULL, |
| RSRC_CONF, |
| "scan interval for busy timeout process"), |
| AP_INIT_TAKE1("FcgidBusyTimeout", set_busy_timeout, NULL, RSRC_CONF, |
| "a fastcgi application will be killed after handling a request for BusyTimeout"), |
| AP_INIT_RAW_ARGS("FcgidCmdOptions", set_cmd_options, NULL, RSRC_CONF, |
| "set processing options for a FastCGI command"), |
| AP_INIT_TAKE12("FcgidInitialEnv", add_default_env_vars, NULL, RSRC_CONF, |
| "an environment variable name and optional value to pass to FastCGI."), |
| AP_INIT_TAKE1("FcgidMaxProcessesPerClass", |
| set_max_class_process, |
| NULL, RSRC_CONF, |
| "Max process count of one class of fastcgi application"), |
| AP_INIT_TAKE1("FcgidMinProcessesPerClass", |
| set_min_class_process, |
| NULL, RSRC_CONF, |
| "Min process count of one class of fastcgi application"), |
| AP_INIT_TAKE1("FcgidErrorScanInterval", set_error_scan_interval, NULL, |
| RSRC_CONF, |
| "scan interval for exited process"), |
| AP_INIT_TAKE1("FcgidIdleScanInterval", set_idle_scan_interval, NULL, |
| RSRC_CONF, |
| "scan interval for idle timeout process"), |
| AP_INIT_TAKE1("FcgidIdleTimeout", set_idle_timeout, NULL, RSRC_CONF, |
| "an idle fastcgi application will be killed after IdleTimeout"), |
| AP_INIT_TAKE1("FcgidIOTimeout", set_ipc_comm_timeout, NULL, RSRC_CONF, |
| "Communication timeout to fastcgi server"), |
| AP_INIT_TAKE1("FcgidConnectTimeout", set_ipc_connect_timeout, NULL, |
| RSRC_CONF, |
| "Connect timeout to fastcgi server"), |
| AP_INIT_TAKE1("FcgidMaxProcesses", set_max_process, NULL, RSRC_CONF, |
| "Max total process count"), |
| AP_INIT_TAKE1("FcgidMaxRequestInMem", set_max_mem_request_len, NULL, |
| RSRC_CONF, |
| "The part of HTTP request which greater than this limit will swap to disk"), |
| AP_INIT_TAKE1("FcgidMaxRequestLen", set_max_request_len, NULL, RSRC_CONF, |
| "Max HTTP request length in byte"), |
| AP_INIT_TAKE1("FcgidMaxRequestsPerProcess", set_max_requests_per_process, |
| NULL, RSRC_CONF, |
| "Max requests handled by each fastcgi application"), |
| AP_INIT_TAKE1("FcgidOutputBufferSize", set_output_buffersize, NULL, |
| RSRC_CONF, |
| "CGI output buffer size"), |
| AP_INIT_TAKE1("FcgidPassHeader", add_pass_headers, NULL, RSRC_CONF, |
| "Header name which will be passed to FastCGI as environment variable."), |
| AP_INIT_TAKE1("FcgidFixPathinfo", |
| set_php_fix_pathinfo_enable, |
| NULL, RSRC_CONF, |
| "Set 1, if cgi.fix_pathinfo=1 in php.ini"), |
| AP_INIT_TAKE1("FcgidProcessLifeTime", set_proc_lifetime, NULL, RSRC_CONF, |
| "fastcgi application lifetime"), |
| AP_INIT_TAKE1("FcgidProcessTableFile", set_shmpath, NULL, RSRC_CONF, |
| "fastcgi shared memory file path"), |
| AP_INIT_TAKE1("FcgidIPCDir", set_socketpath, NULL, RSRC_CONF, |
| "fastcgi socket file path"), |
| AP_INIT_TAKE1("FcgidSpawnScore", set_spawn_score, NULL, RSRC_CONF, |
| "Score of spawn"), |
| AP_INIT_TAKE1("FcgidSpawnScoreUpLimit", set_spawnscore_uplimit, NULL, |
| RSRC_CONF, |
| "Spawn score up limit"), |
| AP_INIT_TAKE1("FcgidTerminationScore", set_termination_score, NULL, |
| RSRC_CONF, |
| "Score of termination"), |
| AP_INIT_TAKE1("FcgidTimeScore", set_time_score, NULL, |
| RSRC_CONF, |
| "Score of passage of time (in seconds)"), |
| AP_INIT_TAKE123("FcgidWrapper", set_wrapper_config, NULL, |
| RSRC_CONF | ACCESS_CONF | OR_FILEINFO, |
| "The CGI wrapper file an optional URL suffix and an optional flag"), |
| AP_INIT_TAKE1("FcgidZombieScanInterval", set_zombie_scan_interval, NULL, |
| RSRC_CONF, |
| "scan interval for zombie process"), |
| |
| /* The following directives are all deprecated in favor |
| * of a consistent use of the Fcgid prefix. |
| * Add all new command above this line. |
| */ |
| AP_INIT_TAKE1("BusyScanInterval", set_busy_scan_interval, NULL, |
| RSRC_CONF, |
| "Deprecated - Use 'FcgidBusyScanInterval' instead"), |
| AP_INIT_TAKE1("BusyTimeout", set_busy_timeout, NULL, RSRC_CONF, |
| "Deprecated - Use 'FcgidBusyTimeout' instead"), |
| AP_INIT_TAKE12("DefaultInitEnv", add_default_env_vars, NULL, RSRC_CONF, |
| "Deprecated - Use 'FcgidInitialEnv' instead"), |
| AP_INIT_TAKE1("DefaultMaxClassProcessCount", |
| set_max_class_process, |
| NULL, RSRC_CONF, |
| "Deprecated - Use 'FcgidMaxProcessesPerClass' instead"), |
| AP_INIT_TAKE1("DefaultMinClassProcessCount", |
| set_min_class_process, |
| NULL, RSRC_CONF, |
| "Deprecated - Use 'FcgidMinProcessesPerClass' instead"), |
| AP_INIT_TAKE1("ErrorScanInterval", set_error_scan_interval, NULL, |
| RSRC_CONF, |
| "Deprecated - Use 'FcgidErrorScanInterval' instead"), |
| AP_INIT_TAKE1("FastCgiAccessChecker", set_access_info, NULL, |
| ACCESS_CONF | OR_FILEINFO, |
| "Deprecated - Use 'FcgidAccessChecker' instead"), |
| AP_INIT_FLAG("FastCgiAccessCheckerAuthoritative", |
| set_access_authoritative, NULL, ACCESS_CONF | OR_FILEINFO, |
| "Deprecated - Use 'FcgidAccessCheckerAuthoritative' instead"), |
| AP_INIT_TAKE1("FastCgiAuthenticator", set_authenticator_info, NULL, |
| ACCESS_CONF | OR_FILEINFO, |
| "Deprecated - Use 'FcgidAuthenticator' instead"), |
| AP_INIT_FLAG("FastCgiAuthenticatorAuthoritative", |
| set_authenticator_authoritative, NULL, |
| ACCESS_CONF | OR_FILEINFO, |
| "Deprecated - Use 'FcgidAuthenticatorAuthoritative' instead"), |
| AP_INIT_TAKE1("FastCgiAuthorizer", set_authorizer_info, NULL, |
| ACCESS_CONF | OR_FILEINFO, |
| "Deprecated - Use 'FcgidAuthorizer' instead"), |
| AP_INIT_FLAG("FastCgiAuthorizerAuthoritative", |
| set_authorizer_authoritative, NULL, |
| ACCESS_CONF | OR_FILEINFO, |
| "Deprecated - Use 'FcgidAuthorizerAuthoritative' instead"), |
| AP_INIT_TAKE123("FCGIWrapper", set_wrapper_config, NULL, |
| RSRC_CONF | ACCESS_CONF | OR_FILEINFO, |
| "Deprecated - Use 'FcgidWrapper' instead"), |
| AP_INIT_TAKE1("IdleScanInterval", set_idle_scan_interval, NULL, |
| RSRC_CONF, |
| "Deprecated - Use 'FcgidIdleScanInterval' instead"), |
| AP_INIT_TAKE1("IdleTimeout", set_idle_timeout, NULL, RSRC_CONF, |
| "Deprecated - Use 'FcgidIdleTimeout' instead"), |
| AP_INIT_TAKE1("IPCCommTimeout", set_ipc_comm_timeout, NULL, RSRC_CONF, |
| "Deprecated - Use 'FcgidIOTimeout' instead"), |
| AP_INIT_TAKE1("IPCConnectTimeout", set_ipc_connect_timeout, NULL, |
| RSRC_CONF, |
| "Deprecated - Use 'FcgidConnectTimeout' instead"), |
| AP_INIT_TAKE1("MaxProcessCount", set_max_process, NULL, RSRC_CONF, |
| "Deprecated - Use 'FcgidMaxProcesses' instead"), |
| AP_INIT_TAKE1("MaxRequestInMem", set_max_mem_request_len, NULL, |
| RSRC_CONF, |
| "Deprecated - Use 'FcgidMaxRequestInMem' instead"), |
| AP_INIT_TAKE1("MaxRequestLen", set_max_request_len, NULL, RSRC_CONF, |
| "Deprecated - Use 'FcgidMaxRequestLen' instead"), |
| AP_INIT_TAKE1("MaxRequestsPerProcess", set_max_requests_per_process, |
| NULL, RSRC_CONF, |
| "Deprecated - Use 'FcgidMaxRequestsPerProcess' instead"), |
| AP_INIT_TAKE1("OutputBufferSize", set_output_buffersize, NULL, |
| RSRC_CONF, |
| "Deprecated - Use 'FcgidOutputBufferSize' instead"), |
| AP_INIT_TAKE1("PassHeader", add_pass_headers, NULL, RSRC_CONF, |
| "Deprecated - Use 'FcgidPassHeader' instead"), |
| AP_INIT_TAKE1("PHP_Fix_Pathinfo_Enable", |
| set_php_fix_pathinfo_enable, |
| NULL, RSRC_CONF, |
| "Deprecated - Use 'FcgidFixPathinfo' instead"), |
| AP_INIT_TAKE1("ProcessLifeTime", set_proc_lifetime, NULL, RSRC_CONF, |
| "Deprecated - Use 'FcgidProcessLifeTime' instead"), |
| AP_INIT_TAKE1("SharememPath", set_shmpath, NULL, RSRC_CONF, |
| "Deprecated - Use 'FcgidProcessTableFile' instead"), |
| AP_INIT_TAKE1("SocketPath", set_socketpath, NULL, RSRC_CONF, |
| "Deprecated - Use 'FcgidIPCDir' instead"), |
| AP_INIT_TAKE1("SpawnScore", set_spawn_score, NULL, RSRC_CONF, |
| "Deprecated - Use 'FcgidSpawnScore' instead"), |
| AP_INIT_TAKE1("SpawnScoreUpLimit", set_spawnscore_uplimit, NULL, |
| RSRC_CONF, |
| "Deprecated - Use 'FcgidSpawnScoreUpLimit' instead"), |
| AP_INIT_TAKE1("TerminationScore", set_termination_score, NULL, |
| RSRC_CONF, |
| "Deprecated - Use 'FcgidTerminationScore' instead"), |
| AP_INIT_TAKE1("TimeScore", set_time_score, NULL, |
| RSRC_CONF, |
| "Deprecated - Use 'FcgidTimeScore' instead"), |
| AP_INIT_TAKE1("ZombieScanInterval", set_zombie_scan_interval, NULL, |
| RSRC_CONF, |
| "Deprecated - Use 'FcgidZombieScanInterval' instead"), |
| {NULL} |
| }; |
| |
| static int fcgid_pre_config(apr_pool_t *pconf, apr_pool_t *plog, |
| apr_pool_t *ptemp) |
| { |
| apr_status_t rv; |
| |
| APR_OPTIONAL_HOOK(ap, status_hook, fcgid_status_hook, NULL, NULL, |
| APR_HOOK_MIDDLE); |
| |
| rv = procmgr_pre_config(pconf, plog, ptemp); |
| if (rv != APR_SUCCESS) { |
| return rv; |
| } |
| |
| rv = proctable_pre_config(pconf, plog, ptemp); |
| if (rv != APR_SUCCESS) { |
| return rv; |
| } |
| |
| return OK; |
| } |
| |
| static void register_hooks(apr_pool_t * p) |
| { |
| ap_hook_pre_config(fcgid_pre_config, NULL, NULL, APR_HOOK_MIDDLE); |
| ap_hook_post_config(fcgid_init, NULL, NULL, APR_HOOK_MIDDLE); |
| ap_hook_child_init(initialize_child, NULL, NULL, APR_HOOK_MIDDLE); |
| ap_hook_handler(fcgid_handler, NULL, NULL, APR_HOOK_MIDDLE); |
| ap_hook_check_user_id(mod_fcgid_authenticator, NULL, NULL, |
| APR_HOOK_MIDDLE); |
| ap_hook_auth_checker(mod_fcgid_authorizer, NULL, NULL, |
| APR_HOOK_MIDDLE); |
| ap_hook_access_checker(mod_fcgid_check_access, NULL, NULL, |
| APR_HOOK_MIDDLE); |
| |
| /* Insert fcgid output filter */ |
| fcgid_filter_handle = |
| ap_register_output_filter("FCGID_OUT", |
| fcgid_filter, |
| NULL, AP_FTYPE_RESOURCE - 10); |
| } |
| |
| module AP_MODULE_DECLARE_DATA fcgid_module = { |
| STANDARD20_MODULE_STUFF, |
| create_fcgid_dir_config, /* create per-directory config structure */ |
| merge_fcgid_dir_config, /* merge per-directory config structures */ |
| create_fcgid_server_config, /* create per-server config structure */ |
| merge_fcgid_server_config, /* merge per-server config structures */ |
| fcgid_cmds, /* command apr_table_t */ |
| register_hooks /* register hooks */ |
| }; |