Security
Headlines
HeadlinesLatestCVEs

Headline

CVE-2022-2255: mod_wsgi/mod_wsgi.c at 4.9.2 · GrahamDumpleton/mod_wsgi

A vulnerability was found in mod_wsgi. The X-Client-IP header is not removed from a request from an untrusted proxy, allowing an attacker to pass the X-Client-IP header to the target WSGI application because the condition to remove it is missing.

CVE
#vulnerability#web#mac#apple#linux#apache#git#c++#perl#auth#sap#ssl

/* ------------------------------------------------------------------------- */ /* * Copyright 2007-2022 GRAHAM DUMPLETON * * Licensed 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 “wsgi_apache.h” #include “wsgi_python.h” #ifdef HAVE_SYS_PRCTL_H #include <sys/prctl.h> #endif #ifndef WIN32 #include <pwd.h> #endif static PyTypeObject Auth_Type; #if AP_SERVER_MINORVERSION_NUMBER >= 2 #define MOD_WSGI_WITH_AUTHN_PROVIDER 1 #endif #if AP_MODULE_MAGIC_AT_LEAST(20060110,0) #define MOD_WSGI_WITH_AUTHZ_PROVIDER 1 #if AP_MODULE_MAGIC_AT_LEAST(20100919,0) #define MOD_WSGI_WITH_AUTHZ_PROVIDER_PARSED 1 #endif #endif #if defined(MOD_WSGI_WITH_AUTHN_PROVIDER) #include “mod_auth.h” #include “ap_provider.h” #ifndef AUTHN_PROVIDER_VERSION #define AUTHN_PROVIDER_VERSION “0” #endif #endif /* Local project header files. */ #include “wsgi_version.h” #include “wsgi_convert.h” #include “wsgi_validate.h” #include “wsgi_interp.h” #include “wsgi_server.h” #include “wsgi_logger.h” #include “wsgi_restrict.h” #include “wsgi_stream.h” #include “wsgi_metrics.h” #include “wsgi_daemon.h” #include “wsgi_buckets.h” #include “wsgi_thread.h” /* Module information. */ module AP_MODULE_DECLARE_DATA wsgi_module; /* Process information. */ static int wsgi_multiprocess = 1; static int wsgi_multithread = 1; /* Daemon information. */ static apr_array_header_t *wsgi_daemon_list = NULL; static apr_pool_t *wsgi_parent_pool = NULL; int volatile wsgi_daemon_shutdown = 0; static int volatile wsgi_daemon_graceful = 0; static int wsgi_dump_stack_traces = 0; static char *wsgi_shutdown_reason = ""; #if defined(MOD_WSGI_WITH_DAEMONS) static apr_interval_time_t wsgi_startup_timeout = 0; static apr_interval_time_t wsgi_deadlock_timeout = 0; static apr_interval_time_t wsgi_idle_timeout = 0; static apr_interval_time_t wsgi_request_timeout = 0; static apr_interval_time_t wsgi_graceful_timeout = 0; static apr_interval_time_t wsgi_eviction_timeout = 0; static apr_interval_time_t wsgi_restart_interval = 0; static apr_time_t volatile wsgi_startup_shutdown_time = 0; static apr_time_t volatile wsgi_deadlock_shutdown_time = 0; static apr_time_t volatile wsgi_idle_shutdown_time = 0; static apr_time_t volatile wsgi_graceful_shutdown_time = 0; static apr_time_t volatile wsgi_restart_shutdown_time = 0; #endif /* Script information. */ static apr_array_header_t *wsgi_import_list = NULL; static void *wsgi_create_server_config(apr_pool_t *p, server_rec *s) { WSGIServerConfig *config = NULL; config = newWSGIServerConfig§; return config; } static void *wsgi_merge_server_config(apr_pool_t *p, void *base_conf, void *new_conf) { WSGIServerConfig *config = NULL; WSGIServerConfig *parent = NULL; WSGIServerConfig *child = NULL; config = newWSGIServerConfig§; parent = (WSGIServerConfig *)base_conf; child = (WSGIServerConfig *)new_conf; if (child->alias_list && parent->alias_list) { config->alias_list = apr_array_append(p, child->alias_list, parent->alias_list); } else if (child->alias_list) { config->alias_list = apr_array_make(p, 20, sizeof(WSGIAliasEntry)); apr_array_cat(config->alias_list, child->alias_list); } else if (parent->alias_list) { config->alias_list = apr_array_make(p, 20, sizeof(WSGIAliasEntry)); apr_array_cat(config->alias_list, parent->alias_list); } if (child->restrict_process) config->restrict_process = child->restrict_process; else config->restrict_process = parent->restrict_process; if (child->process_group) config->process_group = child->process_group; else config->process_group = parent->process_group; if (child->application_group) config->application_group = child->application_group; else config->application_group = parent->application_group; if (child->callable_object) config->callable_object = child->callable_object; else config->callable_object = parent->callable_object; if (child->dispatch_script) config->dispatch_script = child->dispatch_script; else config->dispatch_script = parent->dispatch_script; if (child->pass_apache_request != -1) config->pass_apache_request = child->pass_apache_request; else config->pass_apache_request = parent->pass_apache_request; if (child->pass_authorization != -1) config->pass_authorization = child->pass_authorization; else config->pass_authorization = parent->pass_authorization; if (child->script_reloading != -1) config->script_reloading = child->script_reloading; else config->script_reloading = parent->script_reloading; if (child->error_override != -1) config->error_override = child->error_override; else config->error_override = parent->error_override; if (child->chunked_request != -1) config->chunked_request = child->chunked_request; else config->chunked_request = parent->chunked_request; if (child->map_head_to_get != -1) config->map_head_to_get = child->map_head_to_get; else config->map_head_to_get = parent->map_head_to_get; if (child->ignore_activity != -1) config->ignore_activity = child->ignore_activity; else config->ignore_activity = parent->ignore_activity; if (child->trusted_proxy_headers) config->trusted_proxy_headers = child->trusted_proxy_headers; else config->trusted_proxy_headers = parent->trusted_proxy_headers; if (child->trusted_proxies) config->trusted_proxies = child->trusted_proxies; else config->trusted_proxies = parent->trusted_proxies; if (child->enable_sendfile != -1) config->enable_sendfile = child->enable_sendfile; else config->enable_sendfile = parent->enable_sendfile; if (!child->handler_scripts) config->handler_scripts = parent->handler_scripts; else if (!parent->handler_scripts) config->handler_scripts = child->handler_scripts; else { config->handler_scripts = apr_hash_overlay(p, child->handler_scripts, parent->handler_scripts); } return config; } typedef struct { apr_pool_t *pool; apr_table_t *restrict_process; const char *process_group; const char *application_group; const char *callable_object; WSGIScriptFile *dispatch_script; int pass_apache_request; int pass_authorization; int script_reloading; int error_override; int chunked_request; int map_head_to_get; int ignore_activity; apr_array_header_t *trusted_proxy_headers; apr_array_header_t *trusted_proxies; int enable_sendfile; WSGIScriptFile *access_script; WSGIScriptFile *auth_user_script; WSGIScriptFile *auth_group_script; int user_authoritative; int group_authoritative; apr_hash_t *handler_scripts; } WSGIDirectoryConfig; static WSGIDirectoryConfig *newWSGIDirectoryConfig(apr_pool_t *p) { WSGIDirectoryConfig *object = NULL; object = (WSGIDirectoryConfig *)apr_pcalloc(p, sizeof(WSGIDirectoryConfig)); object->pool = p; object->process_group = NULL; object->application_group = NULL; object->callable_object = NULL; object->dispatch_script = NULL; object->pass_apache_request = -1; object->pass_authorization = -1; object->script_reloading = -1; object->error_override = -1; object->chunked_request = -1; object->map_head_to_get = -1; object->ignore_activity = -1; object->trusted_proxy_headers = NULL; object->trusted_proxies = NULL; object->enable_sendfile = -1; object->access_script = NULL; object->auth_user_script = NULL; object->auth_group_script = NULL; object->user_authoritative = -1; object->group_authoritative = -1; return object; } static void *wsgi_create_dir_config(apr_pool_t *p, char *dir) { WSGIDirectoryConfig *config = NULL; config = newWSGIDirectoryConfig§; return config; } static void *wsgi_merge_dir_config(apr_pool_t *p, void *base_conf, void *new_conf) { WSGIDirectoryConfig *config = NULL; WSGIDirectoryConfig *parent = NULL; WSGIDirectoryConfig *child = NULL; config = newWSGIDirectoryConfig§; parent = (WSGIDirectoryConfig *)base_conf; child = (WSGIDirectoryConfig *)new_conf; if (child->restrict_process) config->restrict_process = child->restrict_process; else config->restrict_process = parent->restrict_process; if (child->process_group) config->process_group = child->process_group; else config->process_group = parent->process_group; if (child->application_group) config->application_group = child->application_group; else config->application_group = parent->application_group; if (child->callable_object) config->callable_object = child->callable_object; else config->callable_object = parent->callable_object; if (child->dispatch_script) config->dispatch_script = child->dispatch_script; else config->dispatch_script = parent->dispatch_script; if (child->pass_apache_request != -1) config->pass_apache_request = child->pass_apache_request; else config->pass_apache_request = parent->pass_apache_request; if (child->pass_authorization != -1) config->pass_authorization = child->pass_authorization; else config->pass_authorization = parent->pass_authorization; if (child->script_reloading != -1) config->script_reloading = child->script_reloading; else config->script_reloading = parent->script_reloading; if (child->error_override != -1) config->error_override = child->error_override; else config->error_override = parent->error_override; if (child->chunked_request != -1) config->chunked_request = child->chunked_request; else config->chunked_request = parent->chunked_request; if (child->map_head_to_get != -1) config->map_head_to_get = child->map_head_to_get; else config->map_head_to_get = parent->map_head_to_get; if (child->ignore_activity != -1) config->ignore_activity = child->ignore_activity; else config->ignore_activity = parent->ignore_activity; if (child->trusted_proxy_headers) config->trusted_proxy_headers = child->trusted_proxy_headers; else config->trusted_proxy_headers = parent->trusted_proxy_headers; if (child->trusted_proxies) config->trusted_proxies = child->trusted_proxies; else config->trusted_proxies = parent->trusted_proxies; if (child->enable_sendfile != -1) config->enable_sendfile = child->enable_sendfile; else config->enable_sendfile = parent->enable_sendfile; if (child->access_script) config->access_script = child->access_script; else config->access_script = parent->access_script; if (child->auth_user_script) config->auth_user_script = child->auth_user_script; else config->auth_user_script = parent->auth_user_script; if (child->auth_group_script) config->auth_group_script = child->auth_group_script; else config->auth_group_script = parent->auth_group_script; if (child->user_authoritative != -1) config->user_authoritative = child->user_authoritative; else config->user_authoritative = parent->user_authoritative; if (child->group_authoritative != -1) config->group_authoritative = child->group_authoritative; else config->group_authoritative = parent->group_authoritative; if (!child->handler_scripts) config->handler_scripts = parent->handler_scripts; else if (!parent->handler_scripts) config->handler_scripts = child->handler_scripts; else { config->handler_scripts = apr_hash_overlay(p, child->handler_scripts, parent->handler_scripts); } return config; } typedef struct { apr_pool_t *pool; apr_table_t *restrict_process; const char *process_group; const char *application_group; const char *callable_object; WSGIScriptFile *dispatch_script; int pass_apache_request; int pass_authorization; int script_reloading; int error_override; int chunked_request; int map_head_to_get; int ignore_activity; apr_array_header_t *trusted_proxy_headers; apr_array_header_t *trusted_proxies; int enable_sendfile; WSGIScriptFile *access_script; WSGIScriptFile *auth_user_script; WSGIScriptFile *auth_group_script; int user_authoritative; int group_authoritative; apr_hash_t *handler_scripts; const char *handler_script; int daemon_connects; int daemon_restarts; apr_time_t request_start; apr_time_t queue_start; apr_time_t daemon_start; } WSGIRequestConfig; static long wsgi_find_path_info(const char *uri, const char *path_info) { long lu = strlen(uri); long lp = strlen(path_info); while (lu-- && lp-- && uri[lu] == path_info[lp]) { if (path_info[lp] == ‘/’) { while (lu && uri[lu-1] == ‘/’) lu–; } } if (lu == -1) { lu = 0; } while (uri[lu] != ‘\0’ && uri[lu] != ‘/’) { lu++; } return lu; } static const char *wsgi_script_name(request_rec *r) { char *script_name = NULL; long path_info_start = 0; if (!r->path_info || !*r->path_info) { script_name = apr_pstrdup(r->pool, r->uri); } else { path_info_start = wsgi_find_path_info(r->uri, r->path_info); script_name = apr_pstrndup(r->pool, r->uri, path_info_start); } if (*script_name) { while (*script_name && (*(script_name+1) == ‘/’)) script_name++; script_name = apr_pstrdup(r->pool, script_name); ap_no2slash((char*)script_name); } ap_str_tolower(script_name); return script_name; } static const char *wsgi_process_group(request_rec *r, const char *s) { const char *name = NULL; const char *value = NULL; const char *h = NULL; apr_port_t p = 0; const char *n = NULL; if (!s) return ""; if (*s != ‘%’) return s; name = s + 1; if (*name) { if (!strcmp(name, “{GLOBAL}”)) return ""; if (!strcmp(name, “{RESOURCE}”)) { h = r->server->server_hostname; p = ap_get_server_port®; n = wsgi_script_name®; if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT) return apr_psprintf(r->pool, "%s:%u|%s", h, p, n); else return apr_psprintf(r->pool, "%s|%s", h, n); } if (!strcmp(name, “{SERVER}”)) { h = r->server->server_hostname; p = ap_get_server_port®; if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT) return apr_psprintf(r->pool, "%s:%u", h, p); else return h; } if (!strcmp(name, “{HOST}”)) { h = r->hostname; p = ap_get_server_port®; /* * The Host header could be empty or absent for HTTP/1.0 * or older. In that case fallback to ServerName. */ if (h == NULL || *h == 0) h = r->server->server_hostname; if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT) return apr_psprintf(r->pool, "%s:%u", h, p); else return h; } if (strstr(name, “{ENV:”) == name) { long len = 0; name = name + 5; len = strlen(name); if (len && name[len-1] == ‘}’) { name = apr_pstrndup(r->pool, name, len-1); value = apr_table_get(r->notes, name); if (!value) value = apr_table_get(r->subprocess_env, name); if (!value) value = getenv(name); if (value) { if (*value == ‘%’ && strstr(value, “%{ENV:”) != value) return wsgi_process_group(r, value); return value; } } } } return s; } static const char *wsgi_server_group(request_rec *r, const char *s) { const char *name = NULL; const char *h = NULL; apr_port_t p = 0; if (!s) return ""; if (*s != ‘%’) return s; name = s + 1; if (*name) { if (!strcmp(name, “{GLOBAL}”)) return ""; if (!strcmp(name, “{SERVER}”)) { h = r->server->server_hostname; p = ap_get_server_port®; if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT) return apr_psprintf(r->pool, "%s:%u", h, p); else return h; } if (!strcmp(name, “{HOST}”)) { h = r->hostname; p = ap_get_server_port®; /* * The Host header could be empty or absent for HTTP/1.0 * or older. In that case fallback to ServerName. */ if (h == NULL || *h == 0) h = r->server->server_hostname; if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT) return apr_psprintf(r->pool, "%s:%u", h, p); else return h; } } return s; } static const char *wsgi_application_group(request_rec *r, const char *s) { const char *name = NULL; const char *value = NULL; const char *h = NULL; apr_port_t p = 0; const char *n = NULL; if (!s) { h = r->server->server_hostname; p = ap_get_server_port®; n = wsgi_script_name®; if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT) return apr_psprintf(r->pool, "%s:%u|%s", h, p, n); else return apr_psprintf(r->pool, "%s|%s", h, n); } if (*s != ‘%’) return s; name = s + 1; if (*name) { if (!strcmp(name, “{GLOBAL}”)) return ""; if (!strcmp(name, “{RESOURCE}”)) { h = r->server->server_hostname; p = ap_get_server_port®; n = wsgi_script_name®; if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT) return apr_psprintf(r->pool, "%s:%u|%s", h, p, n); else return apr_psprintf(r->pool, "%s|%s", h, n); } if (!strcmp(name, “{SERVER}”)) { h = r->server->server_hostname; p = ap_get_server_port®; if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT) return apr_psprintf(r->pool, "%s:%u", h, p); else return h; } if (!strcmp(name, “{HOST}”)) { h = r->hostname; p = ap_get_server_port®; /* * The Host header could be empty or absent for HTTP/1.0 * or older. In that case fallback to ServerName. */ if (h == NULL || *h == 0) h = r->server->server_hostname; if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT) return apr_psprintf(r->pool, "%s:%u", h, p); else return h; } if (strstr(name, “{ENV:”) == name) { long len = 0; name = name + 5; len = strlen(name); if (len && name[len-1] == ‘}’) { name = apr_pstrndup(r->pool, name, len-1); value = apr_table_get(r->notes, name); if (!value) value = apr_table_get(r->subprocess_env, name); if (!value) value = getenv(name); if (value) { if (*value == ‘%’ && strstr(value, “%{ENV:”) != value) return wsgi_application_group(r, value); return value; } } } } return s; } static const char *wsgi_callable_object(request_rec *r, const char *s) { const char *name = NULL; const char *value = NULL; if (!s) return "application"; if (*s != ‘%’) return s; name = s + 1; if (!*name) return "application"; if (strstr(name, “{ENV:”) == name) { long len = 0; name = name + 5; len = strlen(name); if (len && name[len-1] == ‘}’) { name = apr_pstrndup(r->pool, name, len-1); value = apr_table_get(r->notes, name); if (!value) value = apr_table_get(r->subprocess_env, name); if (!value) value = getenv(name); if (value) return value; } } return "application"; } static WSGIRequestConfig *wsgi_create_req_config(apr_pool_t *p, request_rec *r) { WSGIRequestConfig *config = NULL; WSGIServerConfig *sconfig = NULL; WSGIDirectoryConfig *dconfig = NULL; config = (WSGIRequestConfig *)apr_pcalloc(p, sizeof(WSGIRequestConfig)); dconfig = ap_get_module_config(r->per_dir_config, &wsgi_module); sconfig = ap_get_module_config(r->server->module_config, &wsgi_module); config->pool = p; config->restrict_process = dconfig->restrict_process; if (!config->restrict_process) config->restrict_process = sconfig->restrict_process; config->process_group = dconfig->process_group; if (!config->process_group) config->process_group = sconfig->process_group; config->process_group = wsgi_process_group(r, config->process_group); config->application_group = dconfig->application_group; if (!config->application_group) config->application_group = sconfig->application_group; config->application_group = wsgi_application_group(r, config->application_group); config->callable_object = dconfig->callable_object; if (!config->callable_object) config->callable_object = sconfig->callable_object; config->callable_object = wsgi_callable_object(r, config->callable_object); config->dispatch_script = dconfig->dispatch_script; if (!config->dispatch_script) config->dispatch_script = sconfig->dispatch_script; config->pass_apache_request = dconfig->pass_apache_request; if (config->pass_apache_request < 0) { config->pass_apache_request = sconfig->pass_apache_request; if (config->pass_apache_request < 0) config->pass_apache_request = 0; } config->pass_authorization = dconfig->pass_authorization; if (config->pass_authorization < 0) { config->pass_authorization = sconfig->pass_authorization; if (config->pass_authorization < 0) config->pass_authorization = 0; } config->script_reloading = dconfig->script_reloading; if (config->script_reloading < 0) { config->script_reloading = sconfig->script_reloading; if (config->script_reloading < 0) config->script_reloading = 1; } config->error_override = dconfig->error_override; if (config->error_override < 0) { config->error_override = sconfig->error_override; if (config->error_override < 0) config->error_override = 0; } config->chunked_request = dconfig->chunked_request; if (config->chunked_request < 0) { config->chunked_request = sconfig->chunked_request; if (config->chunked_request < 0) config->chunked_request = 0; } config->map_head_to_get = dconfig->map_head_to_get; if (config->map_head_to_get < 0) { config->map_head_to_get = sconfig->map_head_to_get; if (config->map_head_to_get < 0) config->map_head_to_get = 2; } config->ignore_activity = dconfig->ignore_activity; if (config->ignore_activity < 0) { config->ignore_activity = sconfig->ignore_activity; if (config->ignore_activity < 0) config->ignore_activity = 0; } config->trusted_proxy_headers = dconfig->trusted_proxy_headers; if (!config->trusted_proxy_headers) config->trusted_proxy_headers = sconfig->trusted_proxy_headers; config->trusted_proxies = dconfig->trusted_proxies; if (!config->trusted_proxies) config->trusted_proxies = sconfig->trusted_proxies; config->enable_sendfile = dconfig->enable_sendfile; if (config->enable_sendfile < 0) { config->enable_sendfile = sconfig->enable_sendfile; if (config->enable_sendfile < 0) config->enable_sendfile = 0; } config->access_script = dconfig->access_script; config->auth_user_script = dconfig->auth_user_script; config->auth_group_script = dconfig->auth_group_script; config->user_authoritative = dconfig->user_authoritative; if (config->user_authoritative == -1) config->user_authoritative = 1; config->group_authoritative = dconfig->group_authoritative; if (config->group_authoritative == -1) config->group_authoritative = 1; if (!dconfig->handler_scripts) config->handler_scripts = sconfig->handler_scripts; else if (!sconfig->handler_scripts) config->handler_scripts = dconfig->handler_scripts; else { config->handler_scripts = apr_hash_overlay(p, dconfig->handler_scripts, sconfig->handler_scripts); } config->handler_script = ""; config->daemon_connects = 0; config->daemon_restarts = 0; config->request_start = 0; config->queue_start = 0; config->daemon_start = 0; return config; } /* Error reporting. */ static void wsgi_log_script_error(request_rec *r, const char *e, const char *n) { char *message = NULL; if (!n) n = r->filename; message = apr_psprintf(r->pool, "%s: %s", e, n); ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", message); } /* Class objects used by response handler. */ static PyTypeObject Dispatch_Type; typedef struct { PyObject_HEAD request_rec *r; int init; int done; char *buffer; apr_off_t size; apr_off_t offset; apr_off_t length; apr_bucket_brigade *bb; int seen_eos; int seen_error; apr_off_t bytes; apr_off_t reads; apr_time_t time; int ignore_activity; } InputObject; static PyTypeObject Input_Type; static InputObject *newInputObject(request_rec *r, int ignore_activity) { InputObject *self; self = PyObject_New(InputObject, &Input_Type); if (self == NULL) return NULL; self->r = r; self->init = 0; self->done = 0; self->buffer = NULL; self->size = 0; self->offset = 0; self->length = 0; self->bb = NULL; self->seen_eos = 0; self->seen_error = 0; self->bytes = 0; self->reads = 0; self->time = 0; self->ignore_activity = ignore_activity; return self; } static void Input_dealloc(InputObject *self) { if (self->buffer) free(self->buffer); PyObject_Del(self); } static void Input_finish(InputObject *self) { if (self->bb) { Py_BEGIN_ALLOW_THREADS apr_brigade_destroy(self->bb); Py_END_ALLOW_THREADS self->bb = NULL; } self->r = NULL; } static PyObject *Input_close(InputObject *self, PyObject *args) { if (!self->r) { PyErr_SetString(PyExc_RuntimeError, “request object has expired”); return NULL; } Py_INCREF(Py_None); return Py_None; } static apr_status_t wsgi_strtoff(apr_off_t *offset, const char *nptr, char **endptr, int base) { errno = 0; if (sizeof(apr_off_t) == 4) { *offset = strtol(nptr, endptr, base); } else { *offset = apr_strtoi64(nptr, endptr, base); } return APR_FROM_OS_ERROR(errno); } static apr_int64_t Input_read_from_input(InputObject *self, char *buffer, apr_size_t bufsiz) { request_rec *r = self->r; apr_bucket_brigade *bb = self->bb; apr_status_t rv; apr_status_t error_status = 0; const char *error_message = NULL; apr_time_t start = 0; apr_time_t finish = 0; /* If have already seen end of input, return an empty string. */ if (self->seen_eos) return 0; /* If have already encountered an error, then raise a new error. */ if (self->seen_error) { PyErr_SetString(PyExc_IOError, "Apache/mod_wsgi request data read " “error: Input is already in error state.”); return -1; } /* * When reaading the request content we will be saying that we * should block if there is no input data available at that * point but not all data has been exhausted. We therefore need * to ensure that we do not cause Python as a whole to block by * releasing the GIL, but also must remember to reacquire the GIL * when we exit. */ Py_BEGIN_ALLOW_THREADS start = apr_time_now(); self->reads += 1; /* * Create the bucket brigade the first time it is required and * save it against the input object. We need to make sure we * perform a cleanup, but not destroy, the bucket brigade each * time we exit this function. */ if (!bb) { bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); if (bb == NULL) { r->connection->keepalive = AP_CONN_CLOSE; error_message = "Unable to create bucket brigade"; goto finally; } self->bb = bb; } /* Force the required amount of input to be read. */ rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES, APR_BLOCK_READ, bufsiz); if (rv != APR_SUCCESS) { /* * If we actually fail here, we want to just return and * stop trying to read data from the client. The HTTP_IN * input filter is a bit of a pain here as it can return * EAGAIN in various strange situations where it isn’t * believed that it means to retry, but that it is still * a permanent failure. This can include timeouts and * errors in chunked encoding format. To avoid a message * of ‘Resource temporarily unavailable’ which could be * confusing, replace it with a generic message that the * connection was terminated. */ r->connection->keepalive = AP_CONN_CLOSE; if (APR_STATUS_IS_EAGAIN(rv)) error_message = "Connection was terminated"; else error_status = rv; goto finally; } /* * If this fails, it means that a filter is written incorrectly and * that it needs to learn how to properly handle APR_BLOCK_READ * requests by returning data when requested. */ AP_DEBUG_ASSERT(!APR_BRIGADE_EMPTY(bb)); /* * Check to see if EOS terminates the brigade. If so, we remember * this to avoid any attempts to read more data in future calls. */ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) self->seen_eos = 1; /* Now extract the actual data from the bucket brigade. */ rv = apr_brigade_flatten(bb, buffer, &bufsiz); if (rv != APR_SUCCESS) { error_status = rv; goto finally; } finally: /* * We must always cleanup up, not destroy, the brigade after * each call. */ if (bb) apr_brigade_cleanup(bb); finish = apr_time_now(); if (finish > start) self->time += (finish - start); /* Make sure we reacquire the GIL when all done. */ Py_END_ALLOW_THREADS /* * Set any Python exception when an error has occurred and * remember there was an error so can flag on subsequent * reads that already in an error state. */ if (error_status) { char status_buffer[512]; error_message = apr_psprintf(r->pool, "Apache/mod_wsgi request " "data read error: %s.", apr_strerror(error_status, status_buffer, sizeof(status_buffer)-1)); PyErr_SetString(PyExc_IOError, error_message); self->seen_error = 1; return -1; } else if (error_message) { error_message = apr_psprintf(r->pool, "Apache/mod_wsgi request " "data read error: %s.", error_message); PyErr_SetString(PyExc_IOError, error_message); self->seen_error = 1; return -1; } /* * Finally return the amount of data that was read. This will be * zero if all data has been consumed. */ return bufsiz; } static PyObject *Input_read(InputObject *self, PyObject *args) { #if defined(HAVE_LONG_LONG) PY_LONG_LONG size = -1; #else long size = -1; #endif PyObject *result = NULL; char *buffer = NULL; apr_off_t length = 0; int init = 0; apr_int64_t n; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, “request object has expired”); return NULL; } #if defined(HAVE_LONG_LONG) if (!PyArg_ParseTuple(args, "|L:read", &size)) return NULL; #else if (!PyArg_ParseTuple(args, "|l:read", &size)) return NULL; #endif #if defined(MOD_WSGI_WITH_DAEMONS) if (wsgi_idle_timeout && !self->ignore_activity) { apr_thread_mutex_lock(wsgi_monitor_lock); if (wsgi_idle_timeout) { wsgi_idle_shutdown_time = apr_time_now(); wsgi_idle_shutdown_time += wsgi_idle_timeout; } apr_thread_mutex_unlock(wsgi_monitor_lock); } #endif if (self->seen_error) { PyErr_SetString(PyExc_IOError, "Apache/mod_wsgi request data read " “error: Input is already in error state.”); return NULL; } init = self->init; if (!self->init) self->init = 1; /* No point continuing if no more data to be consumed. */ if (self->done && self->length == 0) return PyString_FromString(“”); /* * If requested size is zero bytes, then still need to pass * this through to Apache input filters so that any * 100-continue response is triggered. Only do this if very * first attempt to read data. Note that this will cause an * assertion failure in HTTP_IN input filter when Apache * maintainer mode is enabled. It is arguable that the * assertion check, which prohibits a zero length read, * shouldn’t exist, as why should a zero length read be not * allowed if input filter processing still works when it * does occur. */ if (size == 0) { if (!init) { char dummy[1]; n = Input_read_from_input(self, dummy, 0); if (n == -1) return NULL; } return PyString_FromString(“”); } /* * First deal with case where size has been specified. After * that deal with case where expected that all remaining * data is to be read in and returned as one string. */ if (size > 0) { /* Allocate string of the exact size required. */ result = PyString_FromStringAndSize(NULL, size); if (!result) return NULL; buffer = PyString_AS_STRING((PyStringObject *)result); /* Copy any residual data from use of readline(). */ if (self->buffer && self->length) { if (size >= self->length) { length = self->length; memcpy(buffer, self->buffer + self->offset, length); self->offset = 0; self->length = 0; } else { length = size; memcpy(buffer, self->buffer + self->offset, length); self->offset += length; self->length -= length; } } /* If all data residual buffer consumed then free it. */ if (!self->length) { free(self->buffer); self->buffer = NULL; } /* Read in remaining data required to achieve size. */ if (length < size) { while (length != size) { n = Input_read_from_input(self, buffer+length, size-length); if (n == -1) { Py_DECREF(result); return NULL; } else if (n == 0) { /* Have exhausted all the available input data. */ self->done = 1; break; } length += n; } /* * Resize the final string. If the size reduction is * by more than 25% of the string size, then Python * will allocate a new block of memory and copy the * data into it. */ if (length != size) { if (_PyString_Resize(&result, length)) return NULL; } } } else { /* * Here we are going to try and read in all the * remaining data. First we have to allocate a suitably * large string, but we can’t fully trust the amount * that the request structure says is remaining based on * the original content length though, as an input * filter can insert/remove data from the input stream * thereby invalidating the original content length. * What we do is allow for an extra 25% above what we * have already buffered and what the request structure * says is remaining. A value of 25% has been chosen so * as to match best how Python handles resizing of * strings. Note that even though we do this and allow * all available content, strictly speaking the WSGI * specification says we should only read up until content * length. This though is because the WSGI specification * is deficient in dealing with the concept of mutating * input filters. Since read() with no argument is also * not allowed by WSGI specification implement it in the * way which is most logical and ensure that input data * is not truncated. */ if (self->buffer) { size = self->length; size = size + (size >> 2); if (size < HUGE_STRING_LEN) size = HUGE_STRING_LEN; } else size = HUGE_STRING_LEN; /* Allocate string of the estimated size. */ result = PyString_FromStringAndSize(NULL, size); if (!result) return NULL; buffer = PyString_AS_STRING((PyStringObject *)result); /* * Copy any residual data from use of readline(). The * residual should always be less in size than the * string we have allocated to hold it, so can consume * all of it. */ if (self->buffer && self->length) { length = self->length; memcpy(buffer, self->buffer + self->offset, length); self->offset = 0; self->length = 0; free(self->buffer); self->buffer = NULL; } /* Now make first attempt at reading remaining data. */ n = Input_read_from_input(self, buffer+length, size-length); if (n == -1) { Py_DECREF(result); return NULL; } else if (n == 0) { /* Have exhausted all the available input data. */ self->done = 1; } length += n; /* * Don’t just assume that all data has been read if * amount read was less than that requested. Still must * perform a read which returns that no more data found. */ while (!self->done) { if (length == size) { /* Increase the size of the string by 25%. */ size = size + (size >> 2); if (_PyString_Resize(&result, size)) return NULL; buffer = PyString_AS_STRING((PyStringObject *)result); } /* Now make succesive attempt at reading data. */ n = Input_read_from_input(self, buffer+length, size-length); if (n == -1) { Py_DECREF(result); return NULL; } else if (n == 0) { /* Have exhausted all the available input data. */ self->done = 1; } length += n; } /* * Resize the final string. If the size reduction is by * more than 25% of the string size, then Python will * allocate a new block of memory and copy the data into * it. */ if (length != size) { if (_PyString_Resize(&result, length)) return NULL; } } self->bytes += length; return result; } static PyObject *Input_readline(InputObject *self, PyObject *args) { #if defined(HAVE_LONG_LONG) PY_LONG_LONG size = -1; #else long size = -1; #endif PyObject *result = NULL; char *buffer = NULL; apr_off_t length = 0; apr_int64_t n; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, “request object has expired”); return NULL; } #if defined(HAVE_LONG_LONG) if (!PyArg_ParseTuple(args, "|L:readline", &size)) return NULL; #else if (!PyArg_ParseTuple(args, "|l:readline", &size)) return NULL; #endif if (self->seen_error) { PyErr_SetString(PyExc_IOError, "Apache/mod_wsgi request data read " “error: Input is already in error state.”); return NULL; } if (!self->init) self->init = 1; /* * No point continuing if requested size is zero or if no * more data to read and no buffered data. */ if ((self->done && self->length == 0) || size == 0) return PyString_FromString(“”); /* * First deal with case where size has been specified. After * that deal with case where expected that a complete line * is returned regardless of the size. */ if (size > 0) { /* Allocate string of the exact size required. */ result = PyString_FromStringAndSize(NULL, size); if (!result) return NULL; buffer = PyString_AS_STRING((PyStringObject *)result); /* Copy any residual data from use of readline(). */ if (self->buffer && self->length) { char *p = NULL; const char *q = NULL; p = buffer; q = self->buffer + self->offset; while (self->length && length < size) { self->offset++; self->length–; length++; if ((*p++ = *q++) == ‘\n’) break; } /* If all data in residual buffer consumed then free it. */ if (!self->length) { free(self->buffer); self->buffer = NULL; } } /* * Read in remaining data required to achieve size. Note * that can’t just return whatever the first read might * have returned if no EOL encountered as must return * exactly the required size if no EOL unless that would * have exhausted all input. */ while ((!length || buffer[length-1] != ‘\n’) && !self->done && length < size) { char *p = NULL; char *q = NULL; n = Input_read_from_input(self, buffer+length, size-length); if (n == -1) { Py_DECREF(result); return NULL; } else if (n == 0) { /* Have exhausted all the available input data. */ self->done = 1; } else { /* * Search for embedded EOL in what was read and if * found copy any residual into a buffer for use * next time the read functions are called. */ p = buffer + length; q = p + n; while (p != q) { length++; if (*p++ == ‘\n’) break; } if (p != q) { self->size = q - p; self->buffer = (char *)malloc(self->size); self->offset = 0; self->length = self->size; memcpy(self->buffer, p, self->size); } } } /* * Resize the final string. If the size reduction is * by more than 25% of the string size, then Python * will allocate a new block of memory and copy the * data into it. */ if (length != size) { if (_PyString_Resize(&result, length)) return NULL; } } else { /* * Here we have to read in a line but where we have no * idea how long it may be. What we can do first is if * we have any residual data from a previous read * operation, see if it contains an EOL. This means we * have to do a search, but this is likely going to be * better than having to resize and copy memory later on. */ if (self->buffer && self->length) { const char *p = NULL; const char *q = NULL; p = self->buffer + self->offset; q = memchr(p, ‘\n’, self->length); if (q) size = q - p; } /* * If residual data buffer didn’t contain an EOL, all we * can do is allocate a reasonably sized string and if * that isn’t big enough keep increasing it in size. For * this we will start out with a buffer 25% greater in * size than what is stored in the residual data buffer * or one the same size as Apache string size, whichever * is greater. */ if (self->buffer && size < 0) { size = self->length; size = size + (size >> 2); } if (size < HUGE_STRING_LEN) size = HUGE_STRING_LEN; /* Allocate string of the initial size. */ result = PyString_FromStringAndSize(NULL, size); if (!result) return NULL; buffer = PyString_AS_STRING((PyStringObject *)result); /* Copy any residual data from use of readline(). */ if (self->buffer && self->length) { char *p = NULL; const char *q = NULL; p = buffer; q = self->buffer + self->offset; while (self->length && length < size) { self->offset++; self->length–; length++; if ((*p++ = *q++) == ‘\n’) break; } /* If all data in residual buffer consumed then free it. */ if (!self->length) { free(self->buffer); self->buffer = NULL; } } /* * Read in remaining data until find an EOL, or until all * data has been consumed. */ while ((!length || buffer[length-1] != ‘\n’) && !self->done) { char *p = NULL; char *q = NULL; n = Input_read_from_input(self, buffer+length, size-length); if (n == -1) { Py_DECREF(result); return NULL; } else if (n == 0) { /* Have exhausted all the available input data. */ self->done = 1; } else { /* * Search for embedded EOL in what was read and if * found copy any residual into a buffer for use * next time the read functions are called. */ p = buffer + length; q = p + n; while (p != q) { length++; if (*p++ == ‘\n’) break; } if (p != q) { self->size = q - p; self->buffer = (char *)malloc(self->size); self->offset = 0; self->length = self->size; memcpy(self->buffer, p, self->size); } if (buffer[length-1] != ‘\n’ && length == size) { /* Increase size of string and keep going. */ size = size + (size >> 2); if (_PyString_Resize(&result, size)) return NULL; buffer = PyString_AS_STRING((PyStringObject *)result); } } } /* * Resize the final string. If the size reduction is by * more than 25% of the string size, then Python will * allocate a new block of memory and copy the data into * it. */ if (length != size) { if (_PyString_Resize(&result, length)) return NULL; } } self->bytes += length; return result; } static PyObject *Input_readlines(InputObject *self, PyObject *args) { long hint = 0; long length = 0; PyObject *result = NULL; PyObject *line = NULL; PyObject *rlargs = NULL; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, “request object has expired”); return NULL; } if (!PyArg_ParseTuple(args, "|l:readlines", &hint)) return NULL; result = PyList_New(0); if (!result) return NULL; rlargs = PyTuple_New(0); if (!rlargs) { Py_DECREF(result); return NULL; } while (1) { long n; if (!(line = Input_readline(self, rlargs))) { Py_DECREF(result); result = NULL; break; } if ((n = PyString_Size(line)) == 0) { Py_DECREF(line); break; } if (PyList_Append(result, line) == -1) { Py_DECREF(line); Py_DECREF(result); result = NULL; break; } Py_DECREF(line); length += n; if (hint > 0 && length >= hint) break; } Py_DECREF(rlargs); return result; } static PyMethodDef Input_methods[] = { { "close", (PyCFunction)Input_close, METH_NOARGS, 0 }, { "read", (PyCFunction)Input_read, METH_VARARGS, 0 }, { "readline", (PyCFunction)Input_readline, METH_VARARGS, 0 }, { "readlines", (PyCFunction)Input_readlines, METH_VARARGS, 0 }, { NULL, NULL} }; static PyObject *Input_iter(InputObject *self) { if (!self->r) { PyErr_SetString(PyExc_RuntimeError, “request object has expired”); return NULL; } Py_INCREF(self); return (PyObject *)self; } static PyObject *Input_iternext(InputObject *self) { PyObject *line = NULL; PyObject *rlargs = NULL; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, “request object has expired”); return NULL; } rlargs = PyTuple_New(0); if (!rlargs) return NULL; line = Input_readline(self, rlargs); Py_DECREF(rlargs); if (!line) return NULL; if (PyString_GET_SIZE(line) == 0) { PyErr_SetObject(PyExc_StopIteration, Py_None); Py_DECREF(line); return NULL; } return line; } static PyTypeObject Input_Type = { PyVarObject_HEAD_INIT(NULL, 0) "mod_wsgi.Input", /*tp_name*/ sizeof(InputObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)Input_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ #if defined(Py_TPFLAGS_HAVE_ITER) Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER, /*tp_flags*/ #else Py_TPFLAGS_DEFAULT, /*tp_flags*/ #endif 0, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ (getiterfunc)Input_iter, /*tp_iter*/ (iternextfunc)Input_iternext, /*tp_iternext*/ Input_methods, /*tp_methods*/ 0, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ 0, /*tp_init*/ 0, /*tp_alloc*/ 0, /*tp_new*/ 0, /*tp_free*/ 0, /*tp_is_gc*/ }; typedef struct { PyObject_HEAD int result; request_rec *r; apr_bucket_brigade *bb; WSGIRequestConfig *config; InputObject *input; PyObject *log_buffer; PyObject *log; int status; const char *status_line; PyObject *headers; PyObject *sequence; int content_length_set; apr_off_t content_length; apr_off_t output_length; apr_off_t output_writes; apr_time_t output_time; apr_time_t start_time; } AdapterObject; static PyTypeObject Adapter_Type; static AdapterObject *newAdapterObject(request_rec *r) { AdapterObject *self; self = PyObject_New(AdapterObject, &Adapter_Type); if (self == NULL) return NULL; self->result = HTTP_INTERNAL_SERVER_ERROR; self->r = r; self->bb = NULL; self->config = (WSGIRequestConfig *)ap_get_module_config(r->request_config, &wsgi_module); self->status = HTTP_INTERNAL_SERVER_ERROR; self->status_line = NULL; self->headers = NULL; self->sequence = NULL; self->content_length_set = 0; self->content_length = 0; self->output_length = 0; self->output_writes = 0; self->output_time = 0; self->input = newInputObject(r, self->config->ignore_activity); self->log_buffer = newLogBufferObject(r, APLOG_ERR, "<wsgi.errors>", 0); self->log = newLogWrapperObject(self->log_buffer); return self; } static void Adapter_dealloc(AdapterObject *self) { Py_XDECREF(self->headers); Py_XDECREF(self->sequence); Py_DECREF(self->input); Py_DECREF(self->log_buffer); Py_DECREF(self->log); PyObject_Del(self); } static PyObject *Adapter_start_response(AdapterObject *self, PyObject *args) { PyObject *result = NULL; PyObject *status_line = NULL; PyObject *headers = NULL; PyObject *exc_info = Py_None; PyObject *status_line_as_bytes = NULL; PyObject *headers_as_bytes = NULL; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, “request object has expired”); return NULL; } if (!PyArg_ParseTuple(args, "OO!|O:start_response", &status_line, &PyList_Type, &headers, &exc_info)) { return NULL; } if (exc_info != Py_None && !PyTuple_Check(exc_info)) { PyErr_SetString(PyExc_RuntimeError, “exception info must be a tuple”); return NULL; } if (exc_info != Py_None) { if (self->status_line && !self->headers) { PyObject *type = NULL; PyObject *value = NULL; PyObject *traceback = NULL; if (!PyArg_ParseTuple(exc_info, "OOO", &type, &value, &traceback)) { return NULL; } Py_INCREF(type); Py_INCREF(value); Py_INCREF(traceback); PyErr_Restore(type, value, traceback); return NULL; } } else if (self->status_line && !self->headers) { PyErr_SetString(PyExc_RuntimeError, “headers have already been sent”); return NULL; } /* Publish event for the start of the response. */ if (wsgi_event_subscribers()) { WSGIThreadInfo *thread_info; PyObject *event = NULL; PyObject *value = NULL; thread_info = wsgi_thread_info(0, 0); event = PyDict_New(); #if AP_MODULE_MAGIC_AT_LEAST(20100923,2) if (self->r->log_id) { #if PY_MAJOR_VERSION >= 3 value = PyUnicode_DecodeLatin1(self->r->log_id, strlen(self->r->log_id), NULL); #else value = PyString_FromString(self->r->log_id); #endif PyDict_SetItemString(event, "request_id", value); Py_DECREF(value); } #endif PyDict_SetItemString(event, "response_status", status_line); PyDict_SetItemString(event, "response_headers", headers); PyDict_SetItemString(event, "exception_info", exc_info); PyDict_SetItemString(event, "request_data", thread_info->request_data); wsgi_publish_event("response_started", event); Py_DECREF(event); } status_line_as_bytes = wsgi_convert_status_line_to_bytes(status_line); if (!status_line_as_bytes) goto finally; headers_as_bytes = wsgi_convert_headers_to_bytes(headers); if (!headers_as_bytes) goto finally; self->status_line = apr_pstrdup(self->r->pool, PyString_AsString( status_line_as_bytes)); self->status = (int)strtol(self->status_line, NULL, 10); Py_XDECREF(self->headers); self->headers = headers_as_bytes; Py_INCREF(headers_as_bytes); result = PyObject_GetAttrString((PyObject *)self, “write”); finally: Py_XDECREF(status_line_as_bytes); Py_XDECREF(headers_as_bytes); return result; } static int Adapter_output(AdapterObject *self, const char *data, apr_off_t length, PyObject *string_object, int exception_when_aborted) { int i = 0; apr_status_t rv; request_rec *r; apr_time_t output_start = 0; apr_time_t output_finish = 0; #if defined(MOD_WSGI_WITH_DAEMONS) if (wsgi_idle_timeout && !self->config->ignore_activity) { apr_thread_mutex_lock(wsgi_monitor_lock); if (wsgi_idle_timeout) { wsgi_idle_shutdown_time = apr_time_now(); wsgi_idle_shutdown_time += wsgi_idle_timeout; } apr_thread_mutex_unlock(wsgi_monitor_lock); } #endif if (!self->status_line) { PyErr_SetString(PyExc_RuntimeError, “response has not been started”); return 0; } r = self->r; /* Remember we started sending this block of output. */ output_start = apr_time_now(); /* Count how many separate blocks have been output. */ if (string_object) self->output_writes++; /* Have response headers yet been sent. */ if (self->headers) { /* * Apache prior to Apache 2.2.8 has a bug in it * whereby it doesn’t force ‘100 Continue’ * response before responding with headers if no * read. So, force a zero length read before * sending the headers if haven’t yet attempted * to read anything. This will ensure that if no * request content has been read that any ‘100 * Continue’ response will be flushed and sent * back to the client if client was expecting * one. Only want to do this for 2xx and 3xx * status values. Note that even though Apple * supplied version of Apache on MacOS X Leopard * is newer than version 2.2.8, the header file * has never been patched when they make updates * and so anything compiled against it thinks it * is older. */ #if (AP_SERVER_MAJORVERSION_NUMBER == 2 && \ AP_SERVER_MINORVERSION_NUMBER < 2) || \ (AP_SERVER_MAJORVERSION_NUMBER == 2 && \ AP_SERVER_MINORVERSION_NUMBER == 2 && \ AP_SERVER_PATCHLEVEL_NUMBER < 8) if (!self->input->init) { if (self->status >= 200 && self->status < 400) { PyObject *args = NULL; PyObject *result = NULL; args = Py_BuildValue("(i)", 0); result = Input_read(self->input, args); if (PyErr_Occurred()) PyErr_Clear(); Py_DECREF(args); Py_XDECREF(result); } } #endif /* * Now setup the response headers in request object. We * have already converted any native strings in the * headers to byte strings and validated the format of * the header names and values so can skip all the error * checking. */ r->status = self->status; r->status_line = self->status_line; for (i = 0; i < PyList_Size(self->headers); i++) { PyObject *tuple = NULL; PyObject *object1 = NULL; PyObject *object2 = NULL; char *name = NULL; char *value = NULL; tuple = PyList_GetItem(self->headers, i); object1 = PyTuple_GetItem(tuple, 0); object2 = PyTuple_GetItem(tuple, 1); name = PyBytes_AsString(object1); value = PyBytes_AsString(object2); if (!strcasecmp(name, “Content-Type”)) { /* * In a daemon child process we cannot call the * function ap_set_content_type() as want to * avoid adding any output filters based on the * type of file being served as this will be * done in the main Apache child process which * proxied the request to the daemon process. */ if (*self->config->process_group) r->content_type = apr_pstrdup(r->pool, value); else ap_set_content_type(r, apr_pstrdup(r->pool, value)); } else if (!strcasecmp(name, “Content-Length”)) { char *endstr; apr_off_t length; if (wsgi_strtoff(&length, value, &endstr, 10) || *endstr || length < 0) { PyErr_SetString(PyExc_ValueError, “invalid content length”); output_finish = apr_time_now(); if (output_finish > output_start) self->output_time += (output_finish - output_start); return 0; } ap_set_content_length(r, length); self->content_length_set = 1; self->content_length = length; } else if (!strcasecmp(name, “WWW-Authenticate”)) { apr_table_add(r->err_headers_out, name, value); } else { apr_table_add(r->headers_out, name, value); } } /* * Reset flag indicating whether ‘100 Continue’ response * expected. If we don’t do this then if an attempt to read * input for the first time is after headers have been * sent, then Apache is wrongly generate the ‘100 Continue’ * response into the response content. Not sure if this is * a bug in Apache, or that it truly believes that input * will never be read after the response headers have been * sent. */ r->expecting_100 = 0; /* No longer need headers now that they have been sent. */ Py_DECREF(self->headers); self->headers = NULL; } /* * If content length was specified, ensure that we don’t * actually output more data than was specified as being * sent as otherwise technically in violation of HTTP RFC. */ if (length) { apr_off_t output_length = length; if (self->content_length_set) { if (self->output_length < self->content_length) { if (self->output_length + length > self->content_length) { length = self->content_length - self->output_length; } } else length = 0; } self->output_length += output_length; } /* Now output any data. */ if (length) { apr_bucket *b; /* * When using Apache 2.X can use lower level * bucket brigade APIs. This is preferred as * ap_rwrite()/ap_rflush() will grow memory in * the request pool on each call, which will * result in an increase in memory use over time * when streaming of data is being performed. * The memory is still reclaimed, but only at * the end of the request. Using bucket brigade * API avoids this, and also avoids any copying * of response data due to buffering performed * by ap_rwrite(). */ if (r->connection->aborted) { if (!exception_when_aborted) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, self->r, "mod_wsgi (pid=%d): Client closed connection.", getpid()); } else PyErr_SetString(PyExc_IOError, "Apache/mod_wsgi client " “connection closed.”); output_finish = apr_time_now(); if (output_finish > output_start) self->output_time += (output_finish - output_start); return 0; } if (!self->bb) { self->bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); } #if 0 if (string_object) { b = wsgi_apr_bucket_python_create(data, length, self->config->application_group, string_object, r->connection->bucket_alloc); } else { #endif b = apr_bucket_transient_create(data, (apr_size_t)length, r->connection->bucket_alloc); #if 0 } #endif APR_BRIGADE_INSERT_TAIL(self->bb, b); b = apr_bucket_flush_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(self->bb, b); Py_BEGIN_ALLOW_THREADS rv = ap_pass_brigade(r->output_filters, self->bb); Py_END_ALLOW_THREADS if (rv != APR_SUCCESS) { char status_buffer[512]; const char *error_message; if (!exception_when_aborted) { error_message = apr_psprintf(r->pool, "Failed to write " "response data: %s", apr_strerror(rv, status_buffer, sizeof(status_buffer)-1)); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, self->r, "mod_wsgi (pid=%d): %s.", getpid(), error_message); } else { error_message = apr_psprintf(r->pool, "Apache/mod_wsgi " "failed to write response data: %s", apr_strerror(rv, status_buffer, sizeof(status_buffer)-1)); PyErr_SetString(PyExc_IOError, error_message); } output_finish = apr_time_now(); if (output_finish > output_start) self->output_time += (output_finish - output_start); return 0; } Py_BEGIN_ALLOW_THREADS apr_brigade_cleanup(self->bb); Py_END_ALLOW_THREADS } /* Add how much time we spent send this block of output. */ output_finish = apr_time_now(); if (output_finish > output_start) self->output_time += (output_finish - output_start); /* * Check whether aborted connection was found when data * being written, otherwise will not be flagged until next * time that data is being written. Early detection is * better as it may have been the last data block being * written and application may think that data has all * been written. In a streaming application, we also want * to avoid any additional data processing to generate any * successive data. */ if (r->connection->aborted) { if (!exception_when_aborted) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, self->r, "mod_wsgi (pid=%d): Client closed connection.", getpid()); } else PyErr_SetString(PyExc_IOError, "Apache/mod_wsgi client " “connection closed.”); return 0; } return 1; } /* Split buckets at 1GB when sending large files. */ #define MAX_BUCKET_SIZE (0x40000000) static int Adapter_output_file(AdapterObject *self, apr_file_t* tmpfile, apr_off_t offset, apr_off_t len) { request_rec *r; apr_bucket *b; apr_status_t rv; apr_bucket_brigade *bb; apr_file_t* dupfile = NULL; r = self->r; if (r->connection->aborted) { PyErr_SetString(PyExc_IOError, "Apache/mod_wsgi client " “connection closed.”); return 0; } if (len == 0) return 1; bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); apr_file_dup(&dupfile, tmpfile, r->pool); if (sizeof(apr_off_t) == sizeof(apr_size_t) || len < MAX_BUCKET_SIZE) { /* Can use a single bucket to send file. */ #if 0 b = apr_bucket_file_create(tmpfile, offset, (apr_size_t)len, r->pool, r->connection->bucket_alloc); #endif b = apr_bucket_file_create(dupfile, offset, (apr_size_t)len, r->pool, r->connection->bucket_alloc); } else { /* Need to create multiple buckets to send file. */ #if 0 b = apr_bucket_file_create(tmpfile, offset, MAX_BUCKET_SIZE, r->pool, r->connection->bucket_alloc); #endif b = apr_bucket_file_create(dupfile, offset, MAX_BUCKET_SIZE, r->pool, r->connection->bucket_alloc); while (len > MAX_BUCKET_SIZE) { apr_bucket *cb; apr_bucket_copy(b, &cb); APR_BRIGADE_INSERT_TAIL(bb, cb); b->start += MAX_BUCKET_SIZE; len -= MAX_BUCKET_SIZE; } /* Resize just the last bucket */ b->length = (apr_size_t)len; } APR_BRIGADE_INSERT_TAIL(bb, b); b = apr_bucket_flush_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); b = apr_bucket_eos_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); Py_BEGIN_ALLOW_THREADS rv = ap_pass_brigade(r->output_filters, bb); Py_END_ALLOW_THREADS if (rv != APR_SUCCESS) { char status_buffer[512]; const char *error_message; error_message = apr_psprintf(r->pool, "Apache/mod_wsgi failed " "to write response data: %s.", apr_strerror(rv, status_buffer, sizeof(status_buffer)-1)); PyErr_SetString(PyExc_IOError, error_message); return 0; } Py_BEGIN_ALLOW_THREADS apr_brigade_destroy(bb); Py_END_ALLOW_THREADS if (r->connection->aborted) { PyErr_SetString(PyExc_IOError, "Apache/mod_wsgi client connection " “closed.”); return 0; } return 1; } static APR_OPTIONAL_FN_TYPE(ssl_is_https) *wsgi_is_https = NULL; static PyObject *Adapter_environ(AdapterObject *self) { request_rec *r = NULL; PyObject *vars = NULL; PyObject *object = NULL; const apr_array_header_t *head = NULL; const apr_table_entry_t *elts = NULL; int i = 0; const char *scheme = NULL; /* Create the WSGI environment dictionary. */ vars = PyDict_New(); /* Merge the CGI environment into the WSGI environment. */ r = self->r; head = apr_table_elts(r->subprocess_env); elts = (apr_table_entry_t *)head->elts; for (i = 0; i < head->nelts; ++i) { if (elts[i].key) { if (elts[i].val) { #if PY_MAJOR_VERSION >= 3 if (!strcmp(elts[i].val, “DOCUMENT_ROOT”)) { object = PyUnicode_Decode(elts[i].val, strlen(elts[i].val), Py_FileSystemDefaultEncoding, “surrogateescape”); } else if (!strcmp(elts[i].val, “SCRIPT_FILENAME”)) { object = PyUnicode_Decode(elts[i].val, strlen(elts[i].val), Py_FileSystemDefaultEncoding, “surrogateescape”); } else { object = PyUnicode_DecodeLatin1(elts[i].val, strlen(elts[i].val), NULL); } #else object = PyString_FromString(elts[i].val); #endif PyDict_SetItemString(vars, elts[i].key, object); Py_DECREF(object); } else PyDict_SetItemString(vars, elts[i].key, Py_None); } } PyDict_DelItemString(vars, “PATH”); /* Now setup all the WSGI specific environment values. */ object = Py_BuildValue("(ii)", 1, 0); PyDict_SetItemString(vars, "wsgi.version", object); Py_DECREF(object); object = PyBool_FromLong(wsgi_multithread); PyDict_SetItemString(vars, "wsgi.multithread", object); Py_DECREF(object); object = PyBool_FromLong(wsgi_multiprocess); PyDict_SetItemString(vars, "wsgi.multiprocess", object); Py_DECREF(object); #if defined(MOD_WSGI_WITH_DAEMONS) if (wsgi_daemon_process) { if (wsgi_daemon_process->group->threads == 1 && wsgi_daemon_process->group->maximum_requests == 1) { PyDict_SetItemString(vars, "wsgi.run_once", Py_True); } else PyDict_SetItemString(vars, "wsgi.run_once", Py_False); } else PyDict_SetItemString(vars, "wsgi.run_once", Py_False); #else PyDict_SetItemString(vars, "wsgi.run_once", Py_False); #endif scheme = apr_table_get(r->subprocess_env, “HTTPS”); if (scheme && (!strcasecmp(scheme, “On”) || !strcmp(scheme, “1”))) { #if PY_MAJOR_VERSION >= 3 object = PyUnicode_FromString(“https”); #else object = PyString_FromString(“https”); #endif PyDict_SetItemString(vars, "wsgi.url_scheme", object); Py_DECREF(object); } else { #if PY_MAJOR_VERSION >= 3 object = PyUnicode_FromString(“http”); #else object = PyString_FromString(“http”); #endif PyDict_SetItemString(vars, "wsgi.url_scheme", object); Py_DECREF(object); } /* * We remove the HTTPS variable because WSGI compliant * applications shouldn’t rely on it. Instead they should * use wsgi.url_scheme. We do this even if SetEnv was * used to set HTTPS from Apache configuration. That is * we convert it into the correct variable and remove the * original. */ if (scheme) PyDict_DelItemString(vars, “HTTPS”); /* * Setup log object for WSGI errors. Don’t decrement * reference to log object as keep reference to it. */ object = (PyObject *)self->log; PyDict_SetItemString(vars, "wsgi.errors", object); /* Setup input object for request content. */ object = (PyObject *)self->input; PyDict_SetItemString(vars, "wsgi.input", object); PyDict_SetItemString(vars, "wsgi.input_terminated", Py_True); /* Setup file wrapper object for efficient file responses. */ PyDict_SetItemString(vars, "wsgi.file_wrapper", (PyObject *)&Stream_Type); /* Add Apache and mod_wsgi version information. */ object = Py_BuildValue("(iii)", AP_SERVER_MAJORVERSION_NUMBER, AP_SERVER_MINORVERSION_NUMBER, AP_SERVER_PATCHLEVEL_NUMBER); PyDict_SetItemString(vars, "apache.version", object); Py_DECREF(object); object = Py_BuildValue("(iii)", MOD_WSGI_MAJORVERSION_NUMBER, MOD_WSGI_MINORVERSION_NUMBER, MOD_WSGI_MICROVERSION_NUMBER); PyDict_SetItemString(vars, "mod_wsgi.version", object); Py_DECREF(object); /* * If Apache extensions are enabled and running in embedded * mode add a CObject reference to the Apache request_rec * structure instance. */ if (!wsgi_daemon_pool && self->config->pass_apache_request) { #if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 2) || \ (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 7) object = PyCapsule_New(self->r, 0, 0); #else object = PyCObject_FromVoidPtr(self->r, 0); #endif PyDict_SetItemString(vars, "apache.request_rec", object); Py_DECREF(object); } /* * Extensions for accessing SSL certificate information from * mod_ssl when in use. */ #if 0 if (!wsgi_daemon_pool) { object = PyObject_GetAttrString((PyObject *)self, “ssl_is_https”); PyDict_SetItemString(vars, "mod_ssl.is_https", object); Py_DECREF(object); object = PyObject_GetAttrString((PyObject *)self, “ssl_var_lookup”); PyDict_SetItemString(vars, "mod_ssl.var_lookup", object); Py_DECREF(object); } #endif return vars; } static int Adapter_process_file_wrapper(AdapterObject *self) { int done = 0; #ifndef WIN32 PyObject *filelike = NULL; PyObject *method = NULL; PyObject *object = NULL; apr_status_t rv = 0; apr_os_file_t fd = -1; apr_file_t *tmpfile = NULL; apr_finfo_t finfo; apr_off_t fd_offset = 0; apr_off_t fo_offset = 0; apr_off_t length = 0; /* Perform file wrapper optimisations where possible. */ if (!PyObject_IsInstance(self->sequence, (PyObject *)&Stream_Type)) return 0; /* * Only attempt to perform optimisations if the * write() function returned by start_response() * function has not been called with non zero length * data. In other words if no prior response content * generated. Technically it could be done, but want * to have a consistent rule about how specifying a * content length affects how much of a file is * sent. Don’t want to have to take into * consideration whether write() function has been * called or not as just complicates things. */ if (self->output_length != 0) return 0; /* * Work out if file wrapper is associated with a * file like object, where that file object is * associated with a regular file. If it does then * we can optimise how the contents of the file are * sent out. If no such associated file descriptor * then it needs to be processed like any other * iterable value. */ filelike = PyObject_GetAttrString((PyObject *)self->sequence, “filelike”); if (!filelike) { PyErr_SetString(PyExc_KeyError, “file wrapper no filelike attribute”); return 0; } fd = PyObject_AsFileDescriptor(filelike); if (fd == -1) { PyErr_Clear(); Py_DECREF(filelike); return 0; } Py_DECREF(filelike); /* * On some platforms, such as Linux, sendfile() system call * will not work on UNIX sockets. Thus when using daemon mode * cannot enable that feature. */ if (self->config->enable_sendfile) apr_os_file_put(&tmpfile, &fd, APR_SENDFILE_ENABLED, self->r->pool); else apr_os_file_put(&tmpfile, &fd, 0, self->r->pool); rv = apr_file_info_get(&finfo, APR_FINFO_SIZE|APR_FINFO_TYPE, tmpfile); if (rv != APR_SUCCESS || finfo.filetype != APR_REG) return 0; /* * Because Python file like objects potentially have * their own buffering layering, or use an operating * system FILE object which also has a buffering * layer on top of a normal file descriptor, need to * determine from the file like object its position * within the file and use that as starting position. * Note that it is assumed that user had flushed any * modifications to the file as necessary. Also, we * need to make sure we remember the original file * descriptor position as will need to restore that * position so it matches the upper buffering layers * when done. This is done to avoid any potential * problems if file like object does anything strange * in its close() method which relies on file position * being what it thought it should be. */ rv = apr_file_seek(tmpfile, APR_CUR, &fd_offset); if (rv != APR_SUCCESS) return 0; method = PyObject_GetAttrString(filelike, “tell”); if (!method) return 0; object = PyObject_CallObject(method, NULL); Py_DECREF(method); if (!object) { PyErr_Clear(); return 0; } if (PyLong_Check(object)) { #if defined(HAVE_LONG_LONG) fo_offset = PyLong_AsLongLong(object); #else fo_offset = PyLong_AsLong(object); #endif } #if PY_MAJOR_VERSION < 3 else if (PyInt_Check(object)) { fo_offset = PyInt_AsLong(object); } #endif else { Py_DECREF(object); return 0; } if (PyErr_Occurred()){ Py_DECREF(object); PyErr_Clear(); return 0; } Py_DECREF(object); /* * For a file wrapper object need to always ensure * that response headers are parsed. This is done so * that if the content length header has been * defined we can get its value and use it to limit * how much of a file is being sent. The WSGI 1.0 * specification says that we are meant to send all * available bytes from the file, however this is * questionable as sending more than content length * would violate HTTP RFC. Note that this doesn’t * actually flush the headers out when using Apache * 2.X. This is good, as we want to still be able to * set the content length header if none set and file * is seekable. If processing response headers fails, * then need to return as if done, with error being * logged later. */ if (!Adapter_output(self, "", 0, NULL, 0)) return 1; /* * If content length wasn’t defined then determine * the amount of data which is available to send and * set the content length response header. Either * way, if can work out length then send data * otherwise fall through and treat it as normal * iterable. */ if (!self->content_length_set) { length = finfo.size - fo_offset; self->output_length += length; ap_set_content_length(self->r, length); self->content_length_set = 1; self->content_length = length; if (Adapter_output_file(self, tmpfile, fo_offset, length)) self->result = OK; done = 1; } else { length = finfo.size - fo_offset; self->output_length += length; /* Use user specified content length instead. */ length = self->content_length; if (Adapter_output_file(self, tmpfile, fo_offset, length)) self->result = OK; done = 1; } /* * Restore position of underlying file descriptor. * If this fails, then not much we can do about it. */ apr_file_seek(tmpfile, APR_SET, &fd_offset); #endif return done; } static int Adapter_run(AdapterObject *self, PyObject *object) { PyObject *vars = NULL; PyObject *start = NULL; PyObject *args = NULL; PyObject *iterator = NULL; PyObject *close = NULL; PyObject *nrwrapper = NULL; PyObject *evwrapper = NULL; PyObject *value = NULL; PyObject *event = NULL; const char *msg = NULL; apr_off_t length = 0; WSGIThreadInfo *thread_handle = NULL; apr_time_t finish_time; WSGIThreadCPUUsage start_usage; WSGIThreadCPUUsage end_usage; int aborted = 0; #if defined(MOD_WSGI_WITH_DAEMONS) if (wsgi_idle_timeout && !self->config->ignore_activity) { apr_thread_mutex_lock(wsgi_monitor_lock); if (wsgi_idle_timeout) { wsgi_idle_shutdown_time = apr_time_now(); wsgi_idle_shutdown_time += wsgi_idle_timeout; } apr_thread_mutex_unlock(wsgi_monitor_lock); } #endif if (wsgi_newrelic_config_file) { PyObject *module = NULL; module = PyImport_ImportModule(“newrelic.agent”); if (module) { PyObject *dict; PyObject *factory; dict = PyModule_GetDict(module); factory = PyDict_GetItemString(dict, “WSGIApplicationWrapper”); if (factory) { Py_INCREF(factory); nrwrapper = PyObject_CallFunctionObjArgs( factory, object, Py_None, NULL); if (!nrwrapper) { wsgi_log_python_error(self->r, self->log, self->r->filename, 0); PyErr_Clear(); } Py_DECREF(factory); } Py_DECREF(module); } } if (nrwrapper) object = nrwrapper; self->start_time = apr_time_now(); apr_table_setn(self->r->subprocess_env, "mod_wsgi.script_start", apr_psprintf(self->r->pool, “%” APR_TIME_T_FMT, self->start_time)); vars = Adapter_environ(self); value = wsgi_PyInt_FromLongLong(wsgi_total_requests); PyDict_SetItemString(vars, "mod_wsgi.total_requests", value); Py_DECREF(value); thread_handle = wsgi_thread_info(1, 1); value = wsgi_PyInt_FromLong(thread_handle->thread_id); PyDict_SetItemString(vars, "mod_wsgi.thread_id", value); Py_DECREF(value); value = wsgi_PyInt_FromLongLong(thread_handle->request_count); PyDict_SetItemString(vars, "mod_wsgi.thread_requests", value); Py_DECREF(value); /* Publish event for the start of the request. */ start_usage.user_time = 0.0; start_usage.system_time = 0.0; if (wsgi_event_subscribers()) { wsgi_thread_cpu_usage(&start_usage); event = PyDict_New(); #if AP_MODULE_MAGIC_AT_LEAST(20100923,2) if (self->r->log_id) { #if PY_MAJOR_VERSION >= 3 value = PyUnicode_DecodeLatin1(self->r->log_id, strlen(self->r->log_id), NULL); #else value = PyString_FromString(self->r->log_id); #endif PyDict_SetItemString(event, "request_id", value); Py_DECREF(value); } #endif value = wsgi_PyInt_FromLong(thread_handle->thread_id); PyDict_SetItemString(event, "thread_id", value); Py_DECREF(value); value = wsgi_PyInt_FromLong(self->config->daemon_connects); PyDict_SetItemString(event, "daemon_connects", value); Py_DECREF(value); value = wsgi_PyInt_FromLong(self->config->daemon_restarts); PyDict_SetItemString(event, "daemon_restarts", value); Py_DECREF(value); value = PyFloat_FromDouble(apr_time_sec( (double)self->config->request_start)); PyDict_SetItemString(event, "request_start", value); Py_DECREF(value); value = PyFloat_FromDouble(apr_time_sec( (double)self->config->queue_start)); PyDict_SetItemString(event, "queue_start", value); Py_DECREF(value); value = PyFloat_FromDouble(apr_time_sec( (double)self->config->daemon_start)); PyDict_SetItemString(event, "daemon_start", value); Py_DECREF(value); PyDict_SetItemString(event, "application_object", object); PyDict_SetItemString(event, "request_environ", vars); value = PyFloat_FromDouble(apr_time_sec((double)self->start_time)); PyDict_SetItemString(event, "application_start", value); Py_DECREF(value); PyDict_SetItemString(event, "request_data", thread_handle->request_data); wsgi_publish_event("request_started", event); evwrapper = PyDict_GetItemString(event, “application_object”); if (evwrapper) { if (evwrapper != object) { Py_INCREF(evwrapper); object = evwrapper; } else evwrapper = NULL; } Py_DECREF(event); } /* Pass the request through to the WSGI application. */ thread_handle->request_count++; start = PyObject_GetAttrString((PyObject *)self, “start_response”); args = Py_BuildValue("(OO)", vars, start); self->sequence = PyObject_CallObject(object, args); if (self->sequence != NULL) { if (!Adapter_process_file_wrapper(self)) { iterator = PyObject_GetIter(self->sequence); if (iterator != NULL) { PyObject *item = NULL; while ((item = PyIter_Next(iterator))) { if (!PyString_Check(item)) { PyErr_Format(PyExc_TypeError, "sequence of byte " "string values expected, value of " "type %.200s found", item->ob_type->tp_name); Py_DECREF(item); break; } msg = PyString_AsString(item); length = PyString_Size(item); if (!msg) { Py_DECREF(item); break; } if (length && !Adapter_output(self, msg, length, item, 0)) { if (!PyErr_Occurred()) aborted = 1; Py_DECREF(item); break; } Py_DECREF(item); } } if (!PyErr_Occurred()) { if (!aborted) { /* * In the case where the response was empty we * need to ensure we explicitly flush out the * headers. This is done by calling the output * routine but with an empty string as content. * This could be gated on whether any content * had already been sent, but easier to just call * it all the time. */ if (Adapter_output(self, "", 0, NULL, 0)) self->result = OK; } else { /* * If the client connection was already marked * as aborted, then it indicates the client has * closed the connection. In this case mark the * final result as okay rather than an error so * that the access log still records the original * HTTP response code for the request rather than * overriding it. If don’t do this then access * log will show 500 when the WSGI application * itself had run fine. */ self->result = OK; } } Py_XDECREF(iterator); } /* * Log warning if more response content generated than was * indicated, or less, if there was no errors generated by * the application and connection wasn’t aborted. */ if (self->content_length_set && ((!PyErr_Occurred() && !aborted && self->output_length != self->content_length) || (self->output_length > self->content_length))) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, self->r, "mod_wsgi (pid=%d): Content length mismatch, " "expected %s, response generated %s: %s", getpid(), apr_off_t_toa(self->r->pool, self->content_length), apr_off_t_toa(self->r->pool, self->output_length), self->r->filename); } if (PyErr_Occurred()) { /* * Response content has already been sent, so cannot * return an internal server error as Apache will * append its own error page. Thus need to return OK * and just truncate the response. */ if (self->status_line && !self->headers) self->result = OK; wsgi_log_python_error(self->r, self->log, self->r->filename, 1); /* * If response content is being chunked and an error * occurred, we need to prevent the sending of the EOS * bucket so a client is able to detect that the the * response was incomplete. */ if (self->r->chunked) self->r->eos_sent = 1; } if (PyObject_HasAttrString(self->sequence, “close”)) { PyObject *args = NULL; PyObject *data = NULL; close = PyObject_GetAttrString(self->sequence, “close”); args = Py_BuildValue("()"); data = PyObject_CallObject(close, args); Py_DECREF(args); Py_XDECREF(data); Py_DECREF(close); } if (PyErr_Occurred()) wsgi_log_python_error(self->r, self->log, self->r->filename, 1); } else wsgi_log_python_error(self->r, self->log, self->r->filename, 1); /* Publish event for the end of the request. */ finish_time = apr_time_now(); if (wsgi_event_subscribers()) { double application_time = 0.0; double output_time = 0.0; event = PyDict_New(); #if AP_MODULE_MAGIC_AT_LEAST(20100923,2) if (self->r->log_id) { #if PY_MAJOR_VERSION >= 3 value = PyUnicode_DecodeLatin1(self->r->log_id, strlen(self->r->log_id), NULL); #else value = PyString_FromString(self->r->log_id); #endif PyDict_SetItemString(event, "request_id", value); Py_DECREF(value); } #endif value = wsgi_PyInt_FromLongLong(self->input->reads); PyDict_SetItemString(event, "input_reads", value); Py_DECREF(value); value = wsgi_PyInt_FromLongLong(self->input->bytes); PyDict_SetItemString(event, "input_length", value); Py_DECREF(value); value = PyFloat_FromDouble(apr_time_sec((double)self->input->time)); PyDict_SetItemString(event, "input_time", value); Py_DECREF(value); value = wsgi_PyInt_FromLongLong(self->output_length); PyDict_SetItemString(event, "output_length", value); Py_DECREF(value); value = wsgi_PyInt_FromLongLong(self->output_writes); PyDict_SetItemString(event, "output_writes", value); Py_DECREF(value); output_time = apr_time_sec((double)self->output_time); if (output_time < 0.0) output_time = 0.0; application_time = apr_time_sec((double)finish_time-self->start_time); if (application_time < 0.0) application_time = 0.0; if (start_usage.user_time != 0.0) { if (wsgi_thread_cpu_usage(&end_usage)) { double user_seconds; double system_seconds; double total_seconds; user_seconds = end_usage.user_time; user_seconds -= start_usage.user_time; if (user_seconds < 0.0) user_seconds = 0.0; system_seconds = end_usage.system_time; system_seconds -= start_usage.system_time; if (system_seconds < 0.0) system_seconds = 0.0; total_seconds = user_seconds + system_seconds; if (total_seconds && total_seconds > application_time) { user_seconds = (user_seconds/total_seconds)*application_time; system_seconds = application_time - user_seconds; } value = PyFloat_FromDouble(user_seconds); PyDict_SetItemString(event, "cpu_user_time", value); Py_DECREF(value); value = PyFloat_FromDouble(system_seconds); PyDict_SetItemString(event, "cpu_system_time", value); Py_DECREF(value); } } value = PyFloat_FromDouble(output_time); PyDict_SetItemString(event, "output_time", value); Py_DECREF(value); value = PyFloat_FromDouble(apr_time_sec((double)finish_time)); PyDict_SetItemString(event, "application_finish", value); Py_DECREF(value); value = PyFloat_FromDouble(application_time); PyDict_SetItemString(event, "application_time", value); Py_DECREF(value); PyDict_SetItemString(event, "request_data", thread_handle->request_data); wsgi_publish_event("request_finished", event); Py_DECREF(event); } /* * Record server and application time for metrics. Values * are the time request first accepted by child workers, * the time that the WSGI application started processing * the request, and when the WSGI application finished the * request. */ wsgi_record_request_times(self->config->request_start, self->config->queue_start, self->config->daemon_start, self->start_time, finish_time); /* * If result indicates an internal server error, then * replace the status line in the request object else * that provided by the application will be what is used * in any error page automatically generated by Apache. */ if (self->result == HTTP_INTERNAL_SERVER_ERROR) self->r->status_line = "500 Internal Server Error"; Py_DECREF(args); Py_DECREF(start); Py_DECREF(vars); Py_XDECREF(nrwrapper); Py_XDECREF(evwrapper); Py_XDECREF(self->sequence); self->sequence = NULL; return self->result; } static PyObject *Adapter_write(AdapterObject *self, PyObject *args) { PyObject *item = NULL; const char *data = NULL; long length = 0; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, “request object has expired”); return NULL; } if (!PyArg_ParseTuple(args, "O:write", &item)) return NULL; if (!PyString_Check(item)) { PyErr_Format(PyExc_TypeError, "byte string value expected, value " "of type %.200s found", item->ob_type->tp_name); return NULL; } data = PyString_AsString(item); length = PyString_Size(item); if (!Adapter_output(self, data, length, item, 1)) { return NULL; } Py_INCREF(Py_None); return Py_None; } static PyObject *Adapter_ssl_is_https(AdapterObject *self, PyObject *args) { APR_OPTIONAL_FN_TYPE(ssl_is_https) *ssl_is_https = 0; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, “request object has expired”); return NULL; } if (!PyArg_ParseTuple(args, “:ssl_is_https”)) return NULL; ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); if (ssl_is_https == 0) return Py_BuildValue("i", 0); return Py_BuildValue("i", ssl_is_https(self->r->connection)); } static PyObject *Adapter_ssl_var_lookup(AdapterObject *self, PyObject *args) { APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *ssl_var_lookup = 0; PyObject *item = NULL; PyObject *latin_item = NULL; char *name = 0; char *value = 0; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, “request object has expired”); return NULL; } if (!PyArg_ParseTuple(args, "O:ssl_var_lookup", &item)) return NULL; #if PY_MAJOR_VERSION >= 3 if (PyUnicode_Check(item)) { latin_item = PyUnicode_AsLatin1String(item); if (!latin_item) { PyErr_Format(PyExc_TypeError, "byte string value expected, " “value containing non ‘latin-1’ characters found”); return NULL; } item = latin_item; } #endif if (!PyString_Check(item)) { PyErr_Format(PyExc_TypeError, "byte string value expected, value " "of type %.200s found", item->ob_type->tp_name); Py_XDECREF(latin_item); return NULL; } name = PyString_AsString(item); ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); if (ssl_var_lookup == 0) { Py_XDECREF(latin_item); Py_INCREF(Py_None); return Py_None; } value = ssl_var_lookup(self->r->pool, self->r->server, self->r->connection, self->r, name); Py_XDECREF(latin_item); if (!value) { Py_INCREF(Py_None); return Py_None; } #if PY_MAJOR_VERSION >= 3 return PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else return PyString_FromString(value); #endif } static PyMethodDef Adapter_methods[] = { { "start_response", (PyCFunction)Adapter_start_response, METH_VARARGS, 0 }, { "write", (PyCFunction)Adapter_write, METH_VARARGS, 0 }, { "ssl_is_https", (PyCFunction)Adapter_ssl_is_https, METH_VARARGS, 0 }, { "ssl_var_lookup", (PyCFunction)Adapter_ssl_var_lookup, METH_VARARGS, 0 }, { NULL, NULL} }; static PyTypeObject Adapter_Type = { PyVarObject_HEAD_INIT(NULL, 0) "mod_wsgi.Adapter", /*tp_name*/ sizeof(AdapterObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)Adapter_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ 0, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ 0, /*tp_iter*/ 0, /*tp_iternext*/ Adapter_methods, /*tp_methods*/ 0, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ 0, /*tp_init*/ 0, /*tp_alloc*/ 0, /*tp_new*/ 0, /*tp_free*/ 0, /*tp_is_gc*/ }; /* * Code for importing a module from source by absolute path. */ static PyObject *wsgi_load_source(apr_pool_t *pool, request_rec *r, const char *name, int exists, const char* filename, const char *process_group, const char *application_group, int ignore_system_exit) { PyObject *m = NULL; PyObject *co = NULL; PyObject *io_module = NULL; PyObject *fileobject = NULL; PyObject *source_bytes_object = NULL; PyObject *result = NULL; char *source_buf = NULL; if (exists) { Py_BEGIN_ALLOW_THREADS if ® { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "mod_wsgi (pid=%d, process=’%s’, application=’%s’): " "Reloading WSGI script ‘%s’.", getpid(), process_group, application_group, filename); } else { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d, process=’%s’, application=’%s’): " "Reloading WSGI script ‘%s’.", getpid(), process_group, application_group, filename); } Py_END_ALLOW_THREADS } else { Py_BEGIN_ALLOW_THREADS if ® { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "mod_wsgi (pid=%d, process=’%s’, application=’%s’): " "Loading Python script file ‘%s’.", getpid(), process_group, application_group, filename); } else { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d, process=’%s’, application=’%s’): " "Loading Python script file ‘%s’.", getpid(), process_group, application_group, filename); } Py_END_ALLOW_THREADS } io_module = PyImport_ImportModule(“io”); if (!io_module) goto load_source_finally; fileobject = PyObject_CallMethod(io_module, "open", "ss", filename, “rb”); if (!fileobject) goto load_source_finally; source_bytes_object = PyObject_CallMethod(fileobject, "read", “”); if (!source_bytes_object) goto load_source_finally; result = PyObject_CallMethod(fileobject, "close", “”); if (!result) goto load_source_finally; source_buf = PyBytes_AsString(source_bytes_object); if (!source_buf) goto load_source_finally; co = Py_CompileString(source_buf, filename, Py_file_input); load_source_finally: if (!co) { Py_BEGIN_ALLOW_THREADS if ® { ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, "mod_wsgi (pid=%d, process=’%s’, application=’%s’): " "Could not read/compile source file ‘%s’.", getpid(), process_group, application_group, filename); } else { ap_log_error(APLOG_MARK, APLOG_ERR, errno, wsgi_server, "mod_wsgi (pid=%d, process=’%s’, application=’%s’): " "Could not read/compile source file '%s’.", getpid(), process_group, application_group, filename); } Py_END_ALLOW_THREADS wsgi_log_python_error(r, NULL, filename, 0); Py_XDECREF(io_module); Py_XDECREF(fileobject); Py_XDECREF(source_bytes_object); Py_XDECREF(result); return NULL; } Py_XDECREF(io_module); Py_XDECREF(fileobject); Py_XDECREF(source_bytes_object); Py_XDECREF(result); m = PyImport_ExecCodeModuleEx((char *)name, co, (char *)filename); if (m) { PyObject *object = NULL; if (!r || strcmp(r->filename, filename)) { apr_finfo_t finfo; apr_status_t status; Py_BEGIN_ALLOW_THREADS status = apr_stat(&finfo, filename, APR_FINFO_NORM, pool); Py_END_ALLOW_THREADS if (status != APR_SUCCESS) object = PyLong_FromLongLong(0); else object = PyLong_FromLongLong(finfo.mtime); } else { object = PyLong_FromLongLong(r->finfo.mtime); } PyModule_AddObject(m, "__mtime__", object); } else { if (PyErr_ExceptionMatches(PyExc_SystemExit)) { if (!ignore_system_exit) { Py_BEGIN_ALLOW_THREADS if ® { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): SystemExit exception " "raised when doing exec of Python script " "file '%s’.", getpid(), filename); } else { ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server, "mod_wsgi (pid=%d): SystemExit exception " "raised when doing exec of Python script " "file '%s’.", getpid(), filename); } Py_END_ALLOW_THREADS } } else { Py_BEGIN_ALLOW_THREADS if ® { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): Failed to exec Python script " "file '%s’.", getpid(), filename); } else { ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server, "mod_wsgi (pid=%d): Failed to exec Python script " "file '%s’.", getpid(), filename); } Py_END_ALLOW_THREADS wsgi_log_python_error(r, NULL, filename, 0); } } Py_XDECREF(co); return m; } static int wsgi_reload_required(apr_pool_t *pool, request_rec *r, const char *filename, PyObject *module, const char *resource) { PyObject *dict = NULL; PyObject *object = NULL; apr_time_t mtime = 0; dict = PyModule_GetDict(module); object = PyDict_GetItemString(dict, “__mtime__”); if (object) { mtime = PyLong_AsLongLong(object); if (!r || strcmp(r->filename, filename)) { apr_finfo_t finfo; apr_status_t status; Py_BEGIN_ALLOW_THREADS status = apr_stat(&finfo, filename, APR_FINFO_NORM, pool); Py_END_ALLOW_THREADS if (status != APR_SUCCESS) return 1; else if (mtime != finfo.mtime) return 1; } else { if (mtime != r->finfo.mtime) return 1; } } else return 1; if (resource) { PyObject *dict = NULL; PyObject *object = NULL; dict = PyModule_GetDict(module); object = PyDict_GetItemString(dict, “reload_required”); if (object) { PyObject *args = NULL; PyObject *result = NULL; #if PY_MAJOR_VERSION >= 3 PyObject *path = NULL; #endif Py_INCREF(object); #if PY_MAJOR_VERSION >= 3 path = PyUnicode_Decode(resource, strlen(resource), Py_FileSystemDefaultEncoding, “surrogateescape”); args = Py_BuildValue("(O)", path); Py_DECREF(path); #else args = Py_BuildValue("(s)", resource); #endif result = PyObject_CallObject(object, args); Py_DECREF(args); Py_DECREF(object); if (result && PyObject_IsTrue(result)) { Py_DECREF(result); return 1; } if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, filename, 0); Py_XDECREF(result); } } return 0; } static char *wsgi_module_name(apr_pool_t *pool, const char *filename) { char *hash = NULL; char *file = NULL; /* * Calculate a name for the module using the MD5 of its full * pathname. This is so that different code files with the * same basename are still considered unique. Note that where * we believe a case insensitive file system is being used, * we always change the file name to lower case so that use * of different case in name doesn’t result in duplicate * modules being loaded for the same file. */ file = (char *)filename; if (wsgi_server_config->case_sensitivity) { file = apr_pstrdup(pool, file); ap_str_tolower(file); } hash = ap_md5(pool, (const unsigned char *)file); return apr_pstrcat(pool, "_mod_wsgi_", hash, NULL); } #if APR_HAS_THREADS static apr_thread_mutex_t* wsgi_module_lock = NULL; #endif static int wsgi_execute_script(request_rec *r) { WSGIRequestConfig *config = NULL; InterpreterObject *interp = NULL; PyObject *modules = NULL; PyObject *module = NULL; const char *script = NULL; const char *name = NULL; int exists = 0; int status; WSGIThreadInfo *thread_info = NULL; /* Grab request configuration. */ config = (WSGIRequestConfig *)ap_get_module_config(r->request_config, &wsgi_module); /* * Acquire the desired python interpreter. Once this is done * it is safe to start manipulating python objects. */ interp = wsgi_acquire_interpreter(config->application_group); if (!interp) { ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, "mod_wsgi (pid=%d): Cannot acquire interpreter '%s’.", getpid(), config->application_group); return HTTP_INTERNAL_SERVER_ERROR; } /* Setup startup timeout if first request and specified. */ #if defined(MOD_WSGI_WITH_DAEMONS) if (wsgi_daemon_process) { if (wsgi_startup_shutdown_time == 0) { if (wsgi_startup_timeout > 0) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Application startup " "timer triggered '%s’.", getpid(), config->process_group); apr_thread_mutex_lock(wsgi_monitor_lock); wsgi_startup_shutdown_time = apr_time_now(); wsgi_startup_shutdown_time += wsgi_startup_timeout; apr_thread_mutex_unlock(wsgi_monitor_lock); } } } #endif /* * Use a lock around the check to see if the module is * already loaded and the import of the module to prevent * two request handlers trying to import the module at the * same time. */ #if APR_HAS_THREADS Py_BEGIN_ALLOW_THREADS apr_thread_mutex_lock(wsgi_module_lock); Py_END_ALLOW_THREADS #endif /* Calculate the Python module name to be used for script. */ if (config->handler_script && *config->handler_script) { script = config->handler_script; #if 0 /* * Check for whether a module reference is provided * as opposed to a filesystem path. */ if (strlen(script) > 2 && script[0] == '(' && script[strlen(script)-1] == ')') { name = apr_pstrndup(r->pool, script+1, strlen(script)-2); module = PyImport_ImportModule(name); if (!module) { Py_BEGIN_ALLOW_THREADS ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): Failed to import handler " "via Python module reference %s.", getpid(), script); Py_END_ALLOW_THREADS wsgi_log_python_error(r, NULL, r->filename, 0); } } #endif } else script = r->filename; if (!module) { name = wsgi_module_name(r->pool, script); modules = PyImport_GetModuleDict(); module = PyDict_GetItemString(modules, name); Py_XINCREF(module); if (module) exists = 1; /* * If script reloading is enabled and the module for it has * previously been loaded, see if it has been modified since * the last time it was accessed. For a handler script will * also see if it contains a custom function for determining * if a reload should be performed. */ if (module && config->script_reloading) { if (wsgi_reload_required(r->pool, r, script, module, r->filename)) { /* * Script file has changed. Discard reference to * loaded module and work out what action we are * supposed to take. Choices are process reloading * and module reloading. Process reloading cannot be * performed unless a daemon process is being used. */ Py_DECREF(module); module = NULL; #if defined(MOD_WSGI_WITH_DAEMONS) if (*config->process_group) { /* * Need to restart the daemon process. We bail * out on the request process here, sending back * a special response header indicating that * process is being restarted and that remote * end should abandon connection and attempt to * reconnect again. We also need to signal this * process so it will actually shutdown. The * process supervisor code will ensure that it * is restarted. */ Py_BEGIN_ALLOW_THREADS ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "mod_wsgi (pid=%d): Force restart of " "process '%s’.", getpid(), config->process_group); Py_END_ALLOW_THREADS #if APR_HAS_THREADS apr_thread_mutex_unlock(wsgi_module_lock); #endif wsgi_release_interpreter(interp); r->status = HTTP_INTERNAL_SERVER_ERROR; r->status_line = "200 Rejected"; wsgi_daemon_shutdown++; kill(getpid(), SIGINT); return OK; } else { /* * Need to reload just the script module. Remove * the module from the modules dictionary before * reloading it again. If code is executing * within the module at the time, the callers * reference count on the module should ensure * it isn’t actually destroyed until it is * finished. */ PyDict_DelItemString(modules, name); } #else /* * Need to reload just the script module. Remove * the module from the modules dictionary before * reloading it again. If code is executing * within the module at the time, the callers * reference count on the module should ensure * it isn’t actually destroyed until it is * finished. */ PyDict_DelItemString(modules, name); #endif } } } /* * When process reloading is in use, or a queue timeout is * set, need to indicate that request content should now be * sent through. This is done by writing a special response * header directly out onto the appropriate network output * filter. The special response is picked up by remote end * and data will then be sent. */ #if defined(MOD_WSGI_WITH_DAEMONS) if (*config->process_group && (config->script_reloading || wsgi_daemon_process->group->queue_timeout != 0)) { ap_filter_t *filters; apr_bucket_brigade *bb; apr_bucket *b; const char *data = "Status: 200 Continue\r\n\r\n"; long length = strlen(data); Py_BEGIN_ALLOW_THREADS filters = r->output_filters; while (filters && filters->frec->ftype != AP_FTYPE_NETWORK) { filters = filters->next; } bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); b = apr_bucket_transient_create(data, length, r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); b = apr_bucket_flush_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); /* * This should always work, so ignore any errors * from passing the brigade to the network * output filter. If there are are problems they * will be picked up further down in processing * anyway. */ ap_pass_brigade(filters, bb); Py_END_ALLOW_THREADS } #endif /* Setup metrics for start of request. */ thread_info = wsgi_start_request®; /* Load module if not already loaded. */ if (!module) { module = wsgi_load_source(r->pool, r, name, exists, script, config->process_group, config->application_group, 0); } /* Safe now to release the module lock. */ #if APR_HAS_THREADS apr_thread_mutex_unlock(wsgi_module_lock); #endif /* * Clear startup timeout and prevent from running again if the * module was successfully loaded. */ #if defined(MOD_WSGI_WITH_DAEMONS) if (module && wsgi_startup_shutdown_time > 0) { wsgi_startup_shutdown_time = -1; ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Application startup " "timer cancelled ‘%s’.", getpid(), config->process_group); } #endif /* Assume an internal server error unless everything okay. */ status = HTTP_INTERNAL_SERVER_ERROR; /* Determine if script exists and execute it. */ if (module) { PyObject *module_dict = NULL; PyObject *object = NULL; module_dict = PyModule_GetDict(module); object = PyDict_GetItemString(module_dict, config->callable_object); if (object) { AdapterObject *adapter = NULL; adapter = newAdapterObject®; if (adapter) { PyObject *method = NULL; PyObject *args = NULL; Py_INCREF(adapter->log_buffer); thread_info->log_buffer = adapter->log_buffer; Py_INCREF(object); status = Adapter_run(adapter, object); Py_DECREF(object); /* * Wipe out references to Apache request object * held by Python objects, so can detect when an * application holds on to the transient Python * objects beyond the life of the request and * thus raise an exception if they are used. */ adapter->r = NULL; Input_finish(adapter->input); /* Close the log object so data is flushed. */ method = PyObject_GetAttrString(adapter->log, “close”); if (!method) { PyErr_Format(PyExc_AttributeError, "’%s’ object has no attribute 'close’", adapter->log->ob_type->tp_name); } else { args = PyTuple_New(0); object = PyObject_CallObject(method, args); Py_DECREF(args); } Py_XDECREF(object); Py_XDECREF(method); Py_CLEAR(thread_info->log_buffer); adapter->bb = NULL; } Py_XDECREF((PyObject *)adapter); } else { Py_BEGIN_ALLOW_THREADS ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): Target WSGI script ‘%s’ does " "not contain WSGI application '%s’.", getpid(), script, config->callable_object); Py_END_ALLOW_THREADS status = HTTP_NOT_FOUND; } } /* Log any details of exceptions if execution failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, r->filename, 0); Py_XDECREF(module); /* Finalise any metrics at end of the request. */ wsgi_end_request(); /* Cleanup and release interpreter, */ wsgi_release_interpreter(interp); return status; } /* * Apache child process initialisation and cleanup. Initialise * global table containing Python interpreter instances and * cache reference to main interpreter. Also register cleanup * function to delete interpreter on process shutdown. */ static apr_status_t wsgi_python_child_cleanup(void *data) { PyObject *interp = NULL; /* * If not a daemon process need to publish that process * is shutting down here. For daemon we did it earlier * before trying to wait on request threads. */ #if defined(MOD_WSGI_WITH_DAEMONS) if (!wsgi_daemon_process) wsgi_publish_process_stopping(wsgi_shutdown_reason); #else wsgi_publish_process_stopping(wsgi_shutdown_reason); #endif /* Skip destruction of Python interpreter. */ if (wsgi_server_config->destroy_interpreter == 0) return APR_SUCCESS; /* In a multithreaded MPM must protect table. */ #if APR_HAS_THREADS apr_thread_mutex_lock(wsgi_interp_lock); #endif /* * We should be executing in the main thread again at this * point but without the GIL, so simply restore the original * thread state for that thread that we remembered when we * initialised the interpreter. */ PyEval_AcquireThread(wsgi_main_tstate); /* * Extract a handle to the main Python interpreter from * interpreters dictionary as want to process that one last. */ interp = PyDict_GetItemString(wsgi_interpreters, “”); Py_INCREF(interp); /* * Remove all items from interpreters dictionary. This will * have side affect of calling any exit functions and * destroying interpreters we own. */ ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Destroying interpreters.", getpid()); PyDict_Clear(wsgi_interpreters); #if APR_HAS_THREADS apr_thread_mutex_unlock(wsgi_interp_lock); #endif /* * Now we decrement reference on handle for main Python * interpreter. This only causes exit functions to be called * and doesn’t result in interpreter being destroyed as we * we didn’t previously mark ourselves as the owner of the * interpreter. Note that when Python as a whole is later * being destroyed it will also call exit functions, but by * then the exit function registrations have been removed * and so they will not actually be run a second time. */ Py_DECREF(interp); /* * The code which performs actual shutdown of the main * interpreter expects to be called without the GIL, so * we release it here again. */ PyEval_ReleaseThread(wsgi_main_tstate); /* * Destroy Python itself including the main interpreter. * If mod_python is being loaded it is left to mod_python to * destroy Python, although it currently doesn’t do so. */ if (wsgi_python_initialized) wsgi_python_term(); return APR_SUCCESS; } static void wsgi_python_child_init(apr_pool_t *p) { PyGILState_STATE state; PyObject *object = NULL; int ignore_system_exit = 0; /* Working with Python, so must acquire GIL. */ state = PyGILState_Ensure(); /* * Trigger any special Python stuff required after a fork. * Only do this though if we were responsible for the * initialisation of the Python interpreter in the first * place to avoid it being done multiple times. Also only * do it if Python was initialised in parent process. */ #ifdef HAVE_FORK if (wsgi_python_initialized && !wsgi_python_after_fork) { #if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 7) PyOS_AfterFork_Child(); #else PyOS_AfterFork(); #endif } #endif /* Finalise any Python objects required by child process. */ PyType_Ready(&Log_Type); PyType_Ready(&Stream_Type); PyType_Ready(&Input_Type); PyType_Ready(&Adapter_Type); PyType_Ready(&Restricted_Type); PyType_Ready(&Interpreter_Type); PyType_Ready(&Dispatch_Type); PyType_Ready(&Auth_Type); PyType_Ready(&SignalIntercept_Type); #if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 4) PyType_Ready(&ShutdownInterpreter_Type); #endif /* Initialise Python interpreter instance table and lock. */ wsgi_interpreters = PyDict_New(); #if APR_HAS_THREADS apr_thread_mutex_create(&wsgi_interp_lock, APR_THREAD_MUTEX_UNNESTED, p); apr_thread_mutex_create(&wsgi_module_lock, APR_THREAD_MUTEX_UNNESTED, p); apr_thread_mutex_create(&wsgi_shutdown_lock, APR_THREAD_MUTEX_UNNESTED, p); #endif /* * Create an interpreters index using Apache data structure so * can iterate over interpreter names without needing Python GIL. */ wsgi_interpreters_index = apr_hash_make§; /* * Initialise the key for data related to a thread and force * creation of thread info. */ apr_threadkey_private_create(&wsgi_thread_key, NULL, p); wsgi_thread_info(1, 0); /* * Cache a reference to the first Python interpreter * instance. This interpreter is special as some third party * Python modules will only work when used from within this * interpreter. This is generally when they use the Python * simplified GIL API or otherwise don’t use threading API * properly. An empty string for name is used to identify * the first Python interpreter instance. */ object = (PyObject *)newInterpreterObject(NULL); PyDict_SetItemString(wsgi_interpreters, "", object); Py_DECREF(object); apr_hash_set(wsgi_interpreters_index, "", APR_HASH_KEY_STRING, “”); /* Restore the prior thread state and release the GIL. */ PyGILState_Release(state); /* Register cleanups to performed on process shutdown. */ apr_pool_cleanup_register(p, NULL, wsgi_python_child_cleanup, apr_pool_cleanup_null); /* Loop through import scripts for this process and load them. */ #if defined(MOD_WSGI_WITH_DAEMONS) if (wsgi_daemon_process && wsgi_daemon_process->group->threads == 0) ignore_system_exit = 1; #endif if (wsgi_import_list) { apr_array_header_t *scripts = NULL; WSGIScriptFile *entries; WSGIScriptFile *entry; int i; scripts = wsgi_import_list; entries = (WSGIScriptFile *)scripts->elts; for (i = 0; i < scripts->nelts; ++i) { entry = &entries[i]; /* * Stop loading scripts if this is a daemon process and * we have already been flagged to be shutdown. */ if (wsgi_daemon_shutdown) break; if (!strcmp(wsgi_daemon_group, entry->process_group)) { InterpreterObject *interp = NULL; PyObject *modules = NULL; PyObject *module = NULL; char *name = NULL; int exists = 0; interp = wsgi_acquire_interpreter(entry->application_group); if (!interp) { ap_log_error(APLOG_MARK, APLOG_CRIT, 0, wsgi_server, "mod_wsgi (pid=%d): Cannot acquire " "interpreter '%s’.", getpid(), entry->application_group); } /* Calculate the Python module name to be used for script. */ name = wsgi_module_name(p, entry->handler_script); /* * Use a lock around the check to see if the module is * already loaded and the import of the module. Strictly * speaking shouldn’t be required at this point. */ #if APR_HAS_THREADS Py_BEGIN_ALLOW_THREADS apr_thread_mutex_lock(wsgi_module_lock); Py_END_ALLOW_THREADS #endif modules = PyImport_GetModuleDict(); module = PyDict_GetItemString(modules, name); Py_XINCREF(module); if (module) exists = 1; /* * If script reloading is enabled and the module for it has * previously been loaded, see if it has been modified since * the last time it was accessed. */ if (module && wsgi_server_config->script_reloading) { if (wsgi_reload_required(p, NULL, entry->handler_script, module, NULL)) { /* * Script file has changed. Only support module * reloading for dispatch scripts. Remove the * module from the modules dictionary before * reloading it again. If code is executing within * the module at the time, the callers reference * count on the module should ensure it isn’t * actually destroyed until it is finished. */ Py_DECREF(module); module = NULL; PyDict_DelItemString(modules, name); } } if (!module) { module = wsgi_load_source(p, NULL, name, exists, entry->handler_script, entry->process_group, entry->application_group, ignore_system_exit); if (PyErr_Occurred()) PyErr_Clear(); } /* Safe now to release the module lock. */ #if APR_HAS_THREADS apr_thread_mutex_unlock(wsgi_module_lock); #endif /* Cleanup and release interpreter, */ Py_XDECREF(module); wsgi_release_interpreter(interp); } } } } /* The processors for directives. */ static int wsgi_parse_option(apr_pool_t *p, const char **line, const char **name, const char **value) { const char *str = *line, *strend; while (*str && apr_isspace(*str)) ++str; if (!*str || *str == ‘=’) { *line = str; return !APR_SUCCESS; } /* Option must be of form name=value. Extract the name. */ strend = str; while (*strend && *strend != ‘=’ && !apr_isspace(*strend)) ++strend; if (*strend != ‘=’) { *line = str; return !APR_SUCCESS; } *name = apr_pstrndup(p, str, strend-str); *line = strend+1; /* Now extract the value. Note that value can be quoted. */ *value = ap_getword_conf(p, line); return APR_SUCCESS; } static const char *wsgi_add_script_alias(cmd_parms *cmd, void *mconfig, const char *args) { const char *l = NULL; const char *a = NULL; WSGIServerConfig *sconfig = NULL; WSGIAliasEntry *entry = NULL; const char *option = NULL; const char *value = NULL; #if defined(MOD_WSGI_WITH_DAEMONS) const char *process_group = NULL; #else const char *process_group = ""; #endif const char *application_group = NULL; const char *callable_object = NULL; int pass_authorization = -1; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (!sconfig->alias_list) { sconfig->alias_list = apr_array_make(sconfig->pool, 20, sizeof(WSGIAliasEntry)); } l = ap_getword_conf(cmd->pool, &args); if (*l == ‘\0’ || *args == 0) { return apr_pstrcat(cmd->pool, cmd->cmd->name, " requires at least two arguments", cmd->cmd->errmsg ? ", " : NULL, cmd->cmd->errmsg, NULL); } a = ap_getword_conf(cmd->pool, &args); if (*a == ‘\0’) { return apr_pstrcat(cmd->pool, cmd->cmd->name, " requires at least two arguments", cmd->cmd->errmsg ? ", " : NULL, cmd->cmd->errmsg, NULL); } while (*args) { if (wsgi_parse_option(cmd->pool, &args, &option, &value) != APR_SUCCESS) { return "Invalid option to WSGI script alias definition."; } if (!strcmp(option, “application-group”)) { if (!*value) return "Invalid name for WSGI application group."; if (!strcmp(value, “%{GLOBAL}”)) value = ""; application_group = value; } #if defined(MOD_WSGI_WITH_DAEMONS) else if (!strcmp(option, “process-group”)) { if (!*value) return "Invalid name for WSGI process group."; if (!strcmp(value, “%{GLOBAL}”)) value = ""; process_group = value; } #endif else if (!strcmp(option, “callable-object”)) { if (!*value) return "Invalid name for WSGI callable object."; callable_object = value; } else if (!strcmp(option, “pass-authorization”)) { if (!*value) return "Invalid value for authorization flag."; if (strcasecmp(value, “Off”) == 0) pass_authorization = 0; else if (strcasecmp(value, “On”) == 0) pass_authorization = 1; else return "Invalid value for authorization flag."; } else return "Invalid option to WSGI script alias definition."; } entry = (WSGIAliasEntry *)apr_array_push(sconfig->alias_list); if (cmd->info) { entry->regexp = ap_pregcomp(cmd->pool, l, AP_REG_EXTENDED); if (!entry->regexp) return "Regular expression could not be compiled."; } entry->location = l; entry->application = a; entry->process_group = process_group; entry->application_group = application_group; entry->callable_object = callable_object; entry->pass_authorization = pass_authorization; /* * Only add to import list if both process group and application * group are specified, that they don’t include substitution values, * and in the case of WSGIScriptAliasMatch, that the WSGI script * target path doesn’t include substitutions from URL pattern. */ if (process_group && application_group && !strstr(process_group, “%{”) && !strstr(application_group, “%{”) && (!cmd->info || !strstr(a, “$”))) { WSGIScriptFile *object = NULL; if (!wsgi_import_list) { wsgi_import_list = apr_array_make(cmd->pool, 20, sizeof(WSGIScriptFile)); apr_pool_cleanup_register(cmd->pool, &wsgi_import_list, ap_pool_cleanup_set_null, apr_pool_cleanup_null); } object = (WSGIScriptFile *)apr_array_push(wsgi_import_list); object->handler_script = a; object->process_group = process_group; object->application_group = application_group; #if defined(MOD_WSGI_WITH_DAEMONS) if (*object->process_group && strcmp(object->process_group, “%{RESOURCE}”) != 0 && strcmp(object->process_group, “%{SERVER}”) != 0 && strcmp(object->process_group, “%{HOST}”) != 0) { WSGIProcessGroup *group = NULL; WSGIProcessGroup *entries = NULL; WSGIProcessGroup *entry = NULL; int i; if (!wsgi_daemon_list) return "WSGI process group not yet configured."; entries = (WSGIProcessGroup *)wsgi_daemon_list->elts; for (i = 0; i < wsgi_daemon_list->nelts; ++i) { entry = &entries[i]; if (!strcmp(entry->name, object->process_group)) { group = entry; break; } } if (!group) return "WSGI process group not yet configured."; if (cmd->server->server_hostname && group->server->server_hostname && strcmp(cmd->server->server_hostname, group->server->server_hostname) && group->server->is_virtual) { return "WSGI process group not accessible."; } if (!cmd->server->server_hostname && group->server->server_hostname && group->server->is_virtual) { return "WSGI process group not matchable."; } if (cmd->server->server_hostname && !group->server->server_hostname && group->server->is_virtual) { return "WSGI process group not matchable."; } } #endif } return NULL; } static const char *wsgi_set_verbose_debugging(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->verbose_debugging = 0; else if (strcasecmp(f, “On”) == 0) sconfig->verbose_debugging = 1; else return "WSGIVerboseDebugging must be one of: Off | On"; return NULL; } static const char *wsgi_set_lazy_initialization(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; if (strcasecmp(f, “Off”) == 0) wsgi_python_after_fork = 0; else if (strcasecmp(f, “On”) == 0) wsgi_python_after_fork = 1; else return "WSGILazyInitialization must be one of: Off | On"; return NULL; } static const char *wsgi_add_python_warnings(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; char **entry = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (!sconfig->python_warnings) { sconfig->python_warnings = apr_array_make(sconfig->pool, 5, sizeof(char*)); } entry = (char **)apr_array_push(sconfig->python_warnings); *entry = apr_pstrdup(sconfig->pool, f); return NULL; } #if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6 static const char *wsgi_set_py3k_warning_flag(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->py3k_warning_flag = 0; else if (strcasecmp(f, “On”) == 0) sconfig->py3k_warning_flag = 1; else return "WSGIPy3kWarningFlag must be one of: Off | On"; return NULL; } #endif #if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 3) || \ (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6) static const char *wsgi_set_dont_write_bytecode(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->dont_write_bytecode = 0; else if (strcasecmp(f, “On”) == 0) sconfig->dont_write_bytecode = 1; else return "WSGIDontWriteBytecode must be one of: Off | On"; return NULL; } #endif static const char *wsgi_set_python_optimize(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); sconfig->python_optimize = atoi(f); return NULL; } static const char *wsgi_set_python_home(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); sconfig->python_home = f; return NULL; } static const char *wsgi_set_python_path(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); sconfig->python_path = f; return NULL; } static const char *wsgi_set_python_eggs(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); sconfig->python_eggs = f; return NULL; } static const char *wsgi_set_python_hash_seed(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; /* * Must check this here because if we don’t and is wrong, then * Python interpreter will check later and may kill the process. */ if (f && *f != ‘\0’ && strcmp(f, “random”) != 0) { const char *endptr = f; unsigned long seed; seed = PyOS_strtoul((char *)f, (char **)&endptr, 10); if (*endptr != ‘\0’ || seed > 4294967295UL || (errno == ERANGE && seed == ULONG_MAX)) { return “WSGIPythonHashSeed must be \"random\” or an integer " "in range [0; 4294967295]"; } } sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); sconfig->python_hash_seed = f; return NULL; } static const char *wsgi_set_destroy_interpreter(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->destroy_interpreter = 0; else if (strcasecmp(f, “On”) == 0) sconfig->destroy_interpreter = 1; else return "WSGIDestroyInterpreter must be one of: Off | On"; return NULL; } static const char *wsgi_set_restrict_embedded(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->restrict_embedded = 0; else if (strcasecmp(f, “On”) == 0) sconfig->restrict_embedded = 1; else return "WSGIRestrictEmbedded must be one of: Off | On"; if (sconfig->restrict_embedded) { if (wsgi_python_required == -1) wsgi_python_required = 0; } return NULL; } static const char *wsgi_set_restrict_stdin(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->restrict_stdin = 0; else if (strcasecmp(f, “On”) == 0) sconfig->restrict_stdin = 1; else return "WSGIRestrictStdin must be one of: Off | On"; return NULL; } static const char *wsgi_set_restrict_stdout(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->restrict_stdout = 0; else if (strcasecmp(f, “On”) == 0) sconfig->restrict_stdout = 1; else return "WSGIRestrictStdout must be one of: Off | On"; return NULL; } static const char *wsgi_set_restrict_signal(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->restrict_signal = 0; else if (strcasecmp(f, “On”) == 0) sconfig->restrict_signal = 1; else return "WSGIRestrictSignal must be one of: Off | On"; return NULL; } static const char *wsgi_set_case_sensitivity(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->case_sensitivity = 0; else if (strcasecmp(f, “On”) == 0) sconfig->case_sensitivity = 1; else return "WSGICaseSensitivity must be one of: Off | On"; return NULL; } static const char *wsgi_set_restrict_process(cmd_parms *cmd, void *mconfig, const char *args) { apr_table_t *index = apr_table_make(cmd->pool, 5); if (cmd->path) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; dconfig->restrict_process = index; } else { WSGIServerConfig *sconfig = NULL; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); sconfig->restrict_process = index; } while (*args) { const char *option; option = ap_getword_conf(cmd->pool, &args); if (!strcmp(option, “%{GLOBAL}”)) option = ""; apr_table_setn(index, option, option); } return NULL; } static const char *wsgi_set_process_group(cmd_parms *cmd, void *mconfig, const char *n) { if (cmd->path) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; dconfig->process_group = n; } else { WSGIServerConfig *sconfig = NULL; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); sconfig->process_group = n; } return NULL; } static const char *wsgi_set_application_group(cmd_parms *cmd, void *mconfig, const char *n) { if (cmd->path) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; dconfig->application_group = n; } else { WSGIServerConfig *sconfig = NULL; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); sconfig->application_group = n; } return NULL; } static const char *wsgi_set_callable_object(cmd_parms *cmd, void *mconfig, const char *n) { if (cmd->path) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; dconfig->callable_object = n; } else { WSGIServerConfig *sconfig = NULL; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); sconfig->callable_object = n; } return NULL; } static const char *wsgi_add_import_script(cmd_parms *cmd, void *mconfig, const char *args) { WSGIScriptFile *object = NULL; const char *option = NULL; const char *value = NULL; if (!wsgi_import_list) { wsgi_import_list = apr_array_make(cmd->pool, 20, sizeof(WSGIScriptFile)); apr_pool_cleanup_register(cmd->pool, &wsgi_import_list, ap_pool_cleanup_set_null, apr_pool_cleanup_null); } object = (WSGIScriptFile *)apr_array_push(wsgi_import_list); object->handler_script = ap_getword_conf(cmd->pool, &args); object->process_group = NULL; object->application_group = NULL; if (!object->handler_script || !*object->handler_script) return "Location of import script not supplied."; while (*args) { if (wsgi_parse_option(cmd->pool, &args, &option, &value) != APR_SUCCESS) { return "Invalid option to WSGI import script definition."; } if (!strcmp(option, “application-group”)) { if (!*value) return "Invalid name for WSGI application group."; object->application_group = value; } #if defined(MOD_WSGI_WITH_DAEMONS) else if (!strcmp(option, “process-group”)) { if (!*value) return "Invalid name for WSGI process group."; object->process_group = value; } #endif else return "Invalid option to WSGI import script definition."; } if (!object->application_group) return "Name of WSGI application group required."; if (!strcmp(object->application_group, “%{GLOBAL}”)) object->application_group = ""; #if defined(MOD_WSGI_WITH_DAEMONS) if (!object->process_group) return "Name of WSGI process group required."; if (!strcmp(object->process_group, “%{GLOBAL}”)) object->process_group = ""; if (*object->process_group) { WSGIProcessGroup *group = NULL; WSGIProcessGroup *entries = NULL; WSGIProcessGroup *entry = NULL; int i; if (!wsgi_daemon_list) return "WSGI process group not yet configured."; entries = (WSGIProcessGroup *)wsgi_daemon_list->elts; for (i = 0; i < wsgi_daemon_list->nelts; ++i) { entry = &entries[i]; if (!strcmp(entry->name, object->process_group)) { group = entry; break; } } if (!group) return "WSGI process group not yet configured."; if (cmd->server->server_hostname && group->server->server_hostname && strcmp(cmd->server->server_hostname, group->server->server_hostname) && group->server->is_virtual) { return "WSGI process group not accessible."; } if (!cmd->server->server_hostname && group->server->server_hostname && group->server->is_virtual) { return "WSGI process group not matchable."; } if (cmd->server->server_hostname && !group->server->server_hostname && group->server->is_virtual) { return "WSGI process group not matchable."; } } #else object->process_group = ""; #endif if (!*object->process_group) wsgi_python_required = 1; return NULL; } static const char *wsgi_set_dispatch_script(cmd_parms *cmd, void *mconfig, const char *args) { WSGIScriptFile *object = NULL; const char *option = NULL; const char *value = NULL; object = newWSGIScriptFile(cmd->pool); object->handler_script = ap_getword_conf(cmd->pool, &args); if (!object->handler_script || !*object->handler_script) return "Location of dispatch script not supplied."; while (*args) { if (wsgi_parse_option(cmd->pool, &args, &option, &value) != APR_SUCCESS) { return "Invalid option to WSGI dispatch script definition."; } if (!strcmp(option, “application-group”)) { if (!*value) return "Invalid name for WSGI application group."; object->application_group = value; } else return "Invalid option to WSGI dispatch script definition."; } if (cmd->path) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; dconfig->dispatch_script = object; } else { WSGIServerConfig *sconfig = NULL; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); sconfig->dispatch_script = object; } wsgi_python_required = 1; return NULL; } static const char *wsgi_set_pass_apache_request(cmd_parms *cmd, void *mconfig, const char *f) { if (cmd->path) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; if (strcasecmp(f, “Off”) == 0) dconfig->pass_apache_request = 0; else if (strcasecmp(f, “On”) == 0) dconfig->pass_apache_request = 1; else return "WSGIPassApacheRequest must be one of: Off | On"; } else { WSGIServerConfig *sconfig = NULL; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->pass_apache_request = 0; else if (strcasecmp(f, “On”) == 0) sconfig->pass_apache_request = 1; else return "WSGIPassApacheRequest must be one of: Off | On"; } return NULL; } static const char *wsgi_set_pass_authorization(cmd_parms *cmd, void *mconfig, const char *f) { if (cmd->path) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; if (strcasecmp(f, “Off”) == 0) dconfig->pass_authorization = 0; else if (strcasecmp(f, “On”) == 0) dconfig->pass_authorization = 1; else return "WSGIPassAuthorization must be one of: Off | On"; } else { WSGIServerConfig *sconfig = NULL; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->pass_authorization = 0; else if (strcasecmp(f, “On”) == 0) sconfig->pass_authorization = 1; else return "WSGIPassAuthorization must be one of: Off | On"; } return NULL; } static const char *wsgi_set_script_reloading(cmd_parms *cmd, void *mconfig, const char *f) { if (cmd->path) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; if (strcasecmp(f, “Off”) == 0) dconfig->script_reloading = 0; else if (strcasecmp(f, “On”) == 0) dconfig->script_reloading = 1; else return "WSGIScriptReloading must be one of: Off | On"; } else { WSGIServerConfig *sconfig = NULL; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->script_reloading = 0; else if (strcasecmp(f, “On”) == 0) sconfig->script_reloading = 1; else return "WSGIScriptReloading must be one of: Off | On"; } return NULL; } static const char *wsgi_set_error_override(cmd_parms *cmd, void *mconfig, const char *f) { if (cmd->path) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; if (strcasecmp(f, “Off”) == 0) dconfig->error_override = 0; else if (strcasecmp(f, “On”) == 0) dconfig->error_override = 1; else return "WSGIErrorOverride must be one of: Off | On"; } else { WSGIServerConfig *sconfig = NULL; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->error_override = 0; else if (strcasecmp(f, “On”) == 0) sconfig->error_override = 1; else return "WSGIErrorOverride must be one of: Off | On"; } return NULL; } static const char *wsgi_set_chunked_request(cmd_parms *cmd, void *mconfig, const char *f) { if (cmd->path) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; if (strcasecmp(f, “Off”) == 0) dconfig->chunked_request = 0; else if (strcasecmp(f, “On”) == 0) dconfig->chunked_request = 1; else return "WSGIChunkedRequest must be one of: Off | On"; } else { WSGIServerConfig *sconfig = NULL; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->chunked_request = 0; else if (strcasecmp(f, “On”) == 0) sconfig->chunked_request = 1; else return "WSGIChunkedRequest must be one of: Off | On"; } return NULL; } static const char *wsgi_set_map_head_to_get(cmd_parms *cmd, void *mconfig, const char *f) { if (cmd->path) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; if (strcasecmp(f, “Off”) == 0) dconfig->map_head_to_get = 0; else if (strcasecmp(f, “On”) == 0) dconfig->map_head_to_get = 1; else if (strcasecmp(f, “Auto”) == 0) dconfig->map_head_to_get = 2; else return "WSGIMapHEADToGET must be one of: Off | On | Auto"; } else { WSGIServerConfig *sconfig = NULL; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->map_head_to_get = 0; else if (strcasecmp(f, “On”) == 0) sconfig->map_head_to_get = 1; else if (strcasecmp(f, “Auto”) == 0) sconfig->map_head_to_get = 2; else return "WSGIMapHEADToGET must be one of: Off | On | Auto"; } return NULL; } static const char *wsgi_set_ignore_activity(cmd_parms *cmd, void *mconfig, const char *f) { if (cmd->path) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; if (strcasecmp(f, “Off”) == 0) dconfig->ignore_activity = 0; else if (strcasecmp(f, “On”) == 0) dconfig->ignore_activity = 1; else return "WSGIIgnoreActivity must be one of: Off | On"; } else { WSGIServerConfig *sconfig = NULL; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->ignore_activity = 0; else if (strcasecmp(f, “On”) == 0) sconfig->ignore_activity = 1; else return "WSGIIgnoreActivity must be one of: Off | On"; } return NULL; } static char *wsgi_http2env(apr_pool_t *a, const char *w); static const char *wsgi_set_trusted_proxy_headers(cmd_parms *cmd, void *mconfig, const char *args) { apr_array_header_t *headers = NULL; if (cmd->path) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; if (!dconfig->trusted_proxy_headers) { headers = apr_array_make(cmd->pool, 3, sizeof(char*)); dconfig->trusted_proxy_headers = headers; } else headers = dconfig->trusted_proxy_headers; } else { WSGIServerConfig *sconfig = NULL; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (!sconfig->trusted_proxy_headers) { headers = apr_array_make(cmd->pool, 3, sizeof(char*)); sconfig->trusted_proxy_headers = headers; } else headers = sconfig->trusted_proxy_headers; } while (*args) { const char **entry = NULL; entry = (const char **)apr_array_push(headers); *entry = wsgi_http2env(cmd->pool, ap_getword_conf(cmd->pool, &args)); } return NULL; } static int wsgi_looks_like_ip(const char *ip) { static const char ipv4_set[] = "0123456789./"; static const char ipv6_set[] = "0123456789abcdef:/"; const char *ptr; /* Zero length value is not valid. */ if (!*ip) return 0; /* Determine if this could be a IPv6 or IPv4 address. */ ptr = ip; if (strchr(ip, ‘:’)) { while(*ptr && strchr(ipv6_set, *ptr) != NULL) ++ptr; } else { while(*ptr && strchr(ipv4_set, *ptr) != NULL) ++ptr; } return (*ptr == ‘\0’); } static const char *wsgi_set_trusted_proxies(cmd_parms *cmd, void *mconfig, const char *args) { apr_array_header_t *proxy_ips = NULL; if (cmd->path) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; if (!dconfig->trusted_proxies) { proxy_ips = apr_array_make(cmd->pool, 3, sizeof(char*)); dconfig->trusted_proxies = proxy_ips; } else proxy_ips = dconfig->trusted_proxies; } else { WSGIServerConfig *sconfig = NULL; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (!sconfig->trusted_proxies) { proxy_ips = apr_array_make(cmd->pool, 3, sizeof(char*)); sconfig->trusted_proxies = proxy_ips; } else proxy_ips = sconfig->trusted_proxies; } while (*args) { const char *proxy_ip; proxy_ip = ap_getword_conf(cmd->pool, &args); if (wsgi_looks_like_ip(proxy_ip)) { char *ip; char *mask; apr_ipsubnet_t **sub; apr_status_t rv; ip = apr_pstrdup(cmd->temp_pool, proxy_ip); if ((mask = ap_strchr(ip, ‘/’))) *mask++ = '\0’; sub = (apr_ipsubnet_t **)apr_array_push(proxy_ips); rv = apr_ipsubnet_create(sub, ip, mask, cmd->pool); if (rv != APR_SUCCESS) { char msgbuf[128]; apr_strerror(rv, msgbuf, sizeof(msgbuf)); return apr_pstrcat(cmd->pool, "Unable to parse trusted " "proxy IP address/subnet of \"", proxy_ip, "\". ", msgbuf, NULL); } } else { return apr_pstrcat(cmd->pool, "Unable to parse trusted proxy " "IP address/subnet of \"", proxy_ip, "\".", NULL); } } return NULL; } static const char *wsgi_set_enable_sendfile(cmd_parms *cmd, void *mconfig, const char *f) { if (cmd->path) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; if (strcasecmp(f, “Off”) == 0) dconfig->enable_sendfile = 0; else if (strcasecmp(f, “On”) == 0) dconfig->enable_sendfile = 1; else return "WSGIEnableSendfile must be one of: Off | On"; } else { WSGIServerConfig *sconfig = NULL; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->enable_sendfile = 0; else if (strcasecmp(f, “On”) == 0) sconfig->enable_sendfile = 1; else return "WSGIEnableSendfile must be one of: Off | On"; } return NULL; } static const char *wsgi_set_access_script(cmd_parms *cmd, void *mconfig, const char *args) { WSGIDirectoryConfig *dconfig = NULL; WSGIScriptFile *object = NULL; const char *option = NULL; const char *value = NULL; object = newWSGIScriptFile(cmd->pool); object->handler_script = ap_getword_conf(cmd->pool, &args); if (!object->handler_script || !*object->handler_script) return "Location of access script not supplied."; while (*args) { if (wsgi_parse_option(cmd->pool, &args, &option, &value) != APR_SUCCESS) { return "Invalid option to WSGI access script definition."; } if (!strcmp(option, “application-group”)) { if (!*value) return "Invalid name for WSGI application group."; object->application_group = value; } else return "Invalid option to WSGI access script definition."; } dconfig = (WSGIDirectoryConfig *)mconfig; dconfig->access_script = object; wsgi_python_required = 1; return NULL; } static const char *wsgi_set_auth_user_script(cmd_parms *cmd, void *mconfig, const char *args) { WSGIDirectoryConfig *dconfig = NULL; WSGIScriptFile *object = NULL; const char *option = NULL; const char *value = NULL; object = newWSGIScriptFile(cmd->pool); object->handler_script = ap_getword_conf(cmd->pool, &args); if (!object->handler_script || !*object->handler_script) return "Location of auth user script not supplied."; while (*args) { if (wsgi_parse_option(cmd->pool, &args, &option, &value) != APR_SUCCESS) { return "Invalid option to WSGI auth user script definition."; } if (!strcmp(option, “application-group”)) { if (!*value) return "Invalid name for WSGI application group."; object->application_group = value; } else return "Invalid option to WSGI auth user script definition."; } dconfig = (WSGIDirectoryConfig *)mconfig; dconfig->auth_user_script = object; wsgi_python_required = 1; return NULL; } static const char *wsgi_set_auth_group_script(cmd_parms *cmd, void *mconfig, const char *args) { WSGIDirectoryConfig *dconfig = NULL; WSGIScriptFile *object = NULL; const char *option = NULL; const char *value = NULL; object = newWSGIScriptFile(cmd->pool); object->handler_script = ap_getword_conf(cmd->pool, &args); if (!object->handler_script || !*object->handler_script) return "Location of auth group script not supplied."; while (*args) { if (wsgi_parse_option(cmd->pool, &args, &option, &value) != APR_SUCCESS) { return "Invalid option to WSGI auth group script definition."; } if (!strcmp(option, “application-group”)) { if (!*value) return "Invalid name for WSGI application group."; object->application_group = value; } else return "Invalid option to WSGI auth group script definition."; } dconfig = (WSGIDirectoryConfig *)mconfig; dconfig->auth_group_script = object; wsgi_python_required = 1; return NULL; } #if !defined(MOD_WSGI_WITH_AUTHN_PROVIDER) static const char *wsgi_set_user_authoritative(cmd_parms *cmd, void *mconfig, const char *f) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; if (strcasecmp(f, “Off”) == 0) dconfig->user_authoritative = 0; else if (strcasecmp(f, “On”) == 0) dconfig->user_authoritative = 1; else return "WSGIUserAuthoritative must be one of: Off | On"; return NULL; } #endif static const char *wsgi_set_group_authoritative(cmd_parms *cmd, void *mconfig, const char *f) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; if (strcasecmp(f, “Off”) == 0) dconfig->group_authoritative = 0; else if (strcasecmp(f, “On”) == 0) dconfig->group_authoritative = 1; else return "WSGIGroupAuthoritative must be one of: Off | On"; return NULL; } static const char *wsgi_add_handler_script(cmd_parms *cmd, void *mconfig, const char *args) { WSGIScriptFile *object = NULL; const char *name = NULL; const char *option = NULL; const char *value = NULL; name = ap_getword_conf(cmd->pool, &args); if (!name || !*name) return "Name for handler script not supplied."; object = newWSGIScriptFile(cmd->pool); object->handler_script = ap_getword_conf(cmd->pool, &args); if (!object->handler_script || !*object->handler_script) return "Location of handler script not supplied."; while (*args) { if (wsgi_parse_option(cmd->pool, &args, &option, &value) != APR_SUCCESS) { return "Invalid option to WSGI handler script definition."; } if (!strcmp(option, “process-group”)) { if (!*value) return "Invalid name for WSGI process group."; object->process_group = value; } else if (!strcmp(option, “application-group”)) { if (!*value) return "Invalid name for WSGI application group."; object->application_group = value; } else if (!strcmp(option, “pass-authorization”)) { if (!*value) return "Invalid value for authorization flag."; if (strcasecmp(value, “Off”) == 0) object->pass_authorization = "0"; else if (strcasecmp(value, “On”) == 0) object->pass_authorization = "1"; else return "Invalid value for authorization flag."; } else return "Invalid option to WSGI handler script definition."; } if (cmd->path) { WSGIDirectoryConfig *dconfig = NULL; dconfig = (WSGIDirectoryConfig *)mconfig; if (!dconfig->handler_scripts) dconfig->handler_scripts = apr_hash_make(cmd->pool); apr_hash_set(dconfig->handler_scripts, name, APR_HASH_KEY_STRING, object); } else { WSGIServerConfig *sconfig = NULL; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (!sconfig->handler_scripts) sconfig->handler_scripts = apr_hash_make(cmd->pool); apr_hash_set(sconfig->handler_scripts, name, APR_HASH_KEY_STRING, object); } return NULL; } static const char *wsgi_set_server_metrics(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->server_metrics = 0; else if (strcasecmp(f, “On”) == 0) sconfig->server_metrics = 1; else return "WSGIServerMetrics must be one of: Off | On"; return NULL; } static const char *wsgi_set_newrelic_config_file( cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); sconfig->newrelic_config_file = f; return NULL; } static const char *wsgi_set_newrelic_environment( cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); sconfig->newrelic_environment = f; return NULL; } /* Handler for the translate name phase. */ static long wsgi_alias_matches(const char *uri, const char *alias_fakename) { /* Code for this function from Apache mod_alias module. */ const char *aliasp = alias_fakename, *urip = uri; while (*aliasp) { if (*aliasp == ‘/’) { /* any number of ‘/’ in the alias matches any number in * the supplied URI, but there must be at least one… */ if (*urip != ‘/’) return 0; do { ++aliasp; } while (*aliasp == ‘/’); do { ++urip; } while (*urip == ‘/’); } else { /* Other characters are compared literally */ if (*urip++ != *aliasp++) return 0; } } /* Check last alias path component matched all the way */ if (aliasp[-1] != ‘/’ && *urip != ‘\0’ && *urip != ‘/’) return 0; /* Return number of characters from URI which matched (may be * greater than length of alias, since we may have matched * doubled slashes) */ return urip - uri; } static int wsgi_hook_intercept(request_rec *r) { WSGIServerConfig *config = NULL; apr_array_header_t *aliases = NULL; WSGIAliasEntry *entries = NULL; WSGIAliasEntry *entry = NULL; ap_regmatch_t matches[AP_MAX_REG_MATCH]; const char *location = NULL; const char *application = NULL; int i = 0; config = ap_get_module_config(r->server->module_config, &wsgi_module); if (!config->alias_list) return DECLINED; if (r->uri[0] != ‘/’ && r->uri[0]) return DECLINED; aliases = config->alias_list; entries = (WSGIAliasEntry *)aliases->elts; for (i = 0; i < aliases->nelts; ++i) { long l = 0; entry = &entries[i]; if (entry->regexp) { if (!ap_regexec(entry->regexp, r->uri, AP_MAX_REG_MATCH, matches, 0)) { if (entry->application) { l = matches[0].rm_eo; location = apr_pstrndup(r->pool, r->uri, l); application = ap_pregsub(r->pool, entry->application, r->uri, AP_MAX_REG_MATCH, matches); } } } else if (entry->location) { l = wsgi_alias_matches(r->uri, entry->location); location = entry->location; application = entry->application; } if (l > 0) { if (!strcmp(location, “/”)) { r->filename = apr_pstrcat(r->pool, application, r->uri, NULL); } else { r->filename = apr_pstrcat(r->pool, application, r->uri + l, NULL); } r->handler = "wsgi-script"; apr_table_setn(r->notes, "alias-forced-type", r->handler); if (entry->process_group) { apr_table_setn(r->notes, "mod_wsgi.process_group", entry->process_group); } if (entry->application_group) { apr_table_setn(r->notes, "mod_wsgi.application_group", entry->application_group); } if (entry->callable_object) { apr_table_setn(r->notes, "mod_wsgi.callable_object", entry->callable_object); } if (entry->pass_authorization == 0) apr_table_setn(r->notes, "mod_wsgi.pass_authorization", “0”); else if (entry->pass_authorization == 1) apr_table_setn(r->notes, "mod_wsgi.pass_authorization", “1”); return OK; } } return DECLINED; } /* Handler for the response handler phase. */ static void wsgi_drop_invalid_headers(request_rec *r); static void wsgi_process_proxy_headers(request_rec *r); static void wsgi_build_environment(request_rec *r) { WSGIRequestConfig *config = NULL; const char *value = NULL; const char *script_name = NULL; const char *path_info = NULL; conn_rec *c = r->connection; /* Grab request configuration. */ config = (WSGIRequestConfig *)ap_get_module_config(r->request_config, &wsgi_module); /* * Remove any invalid headers which use invalid characters. * This is necessary to ensure that someone doesn’t try and * take advantage of header spoofing. This can come about * where characters other than alphanumerics or '-' are used * as the conversion of non alphanumerics to ‘_’ means one * can get collisions. This is technically only an issue * with Apache 2.2 as Apache 2.4 addresses the problem and * drops them anyway. Still go through and drop them even * for Apache 2.4 as not sure which version of Apache 2.4 * introduces the change. */ wsgi_drop_invalid_headers®; /* Populate environment with standard CGI variables. */ ap_add_cgi_vars®; ap_add_common_vars®; /* * Mutate a HEAD request into a GET request. This is * required because WSGI specification doesn’t lay out * clearly how WSGI applications should treat a HEAD * request. Generally authors of WSGI applications or * frameworks take it that they do not need to return any * content, but this screws up any Apache output filters * which need to see all the response content in order to * correctly set up response headers for a HEAD request such * that they are the same as a GET request. Thus change a * HEAD request into a GET request to ensure that request * content is generated. If using Apache 2.X we can skip * doing this if we know there is no output filter that * might change the content and/or headers. * * The default behaviour here of changing it if an output * filter is detected can be overridden using the directive * WSGIMapHEADToGet. The default value is 'Auto’. If set to * ‘On’ then it remapped regardless of whether an output * filter is present. If ‘Off’ then it will be left alone * and the original value used. */ if (config->map_head_to_get == 2) { if (r->method_number == M_GET && r->header_only && r->output_filters->frec->ftype < AP_FTYPE_PROTOCOL) apr_table_setn(r->subprocess_env, "REQUEST_METHOD", “GET”); } else if (config->map_head_to_get == 1) { if (r->method_number == M_GET) apr_table_setn(r->subprocess_env, "REQUEST_METHOD", “GET”); } /* * If enabled, pass along authorisation headers which Apache * leaves out of CGI environment. WSGI still needs to see * these if it needs to implement any of the standard * authentication schemes such as Basic and Digest. We do * not pass these through by default though as it can result * in passwords being leaked though to a WSGI application * when it shouldn’t. This would be a problem where there is * some sort of site wide authorisation scheme in place * which has got nothing to do with specific applications. */ if (config->pass_authorization) { value = apr_table_get(r->headers_in, “Authorization”); if (value) apr_table_setn(r->subprocess_env, "HTTP_AUTHORIZATION", value); } /* If PATH_INFO not set, set it to an empty string. */ value = apr_table_get(r->subprocess_env, “PATH_INFO”); if (!value) apr_table_setn(r->subprocess_env, "PATH_INFO", “”); /* * Multiple slashes are not always collapsed into a single * slash in SCRIPT_NAME and PATH_INFO with Apache 1.3 and * Apache 2.X behaving a bit differently. Because some WSGI * applications don’t deal with multiple slashes properly we * collapse any duplicate slashes to a single slash so * Apache behaviour is consistent across all versions. We * don’t care that PATH_TRANSLATED can on Apache 1.3 still * contain multiple slashes as that should not be getting * used from a WSGI application anyway. */ script_name = apr_table_get(r->subprocess_env, “SCRIPT_NAME”); if (*script_name == ‘/’) { while (*script_name && (*(script_name+1) == ‘/’)) script_name++; script_name = apr_pstrdup(r->pool, script_name); ap_no2slash((char*)script_name); apr_table_setn(r->subprocess_env, "SCRIPT_NAME", script_name); } path_info = apr_table_get(r->subprocess_env, “PATH_INFO”); if (*path_info == ‘/’) { while (*path_info && (*(path_info+1) == ‘/’)) path_info++; path_info = apr_pstrdup(r->pool, path_info); ap_no2slash((char*)path_info); apr_table_setn(r->subprocess_env, "PATH_INFO", path_info); } /* * Save away the SCRIPT_NAME and PATH_INFO values at this point * so we have a way of determining if they are rewritten somehow. * This can be important when dealing with rewrite rules and * a trusted header was being handled for SCRIPT_NAME. */ apr_table_setn(r->subprocess_env, "mod_wsgi.script_name", script_name); apr_table_setn(r->subprocess_env, "mod_wsgi.path_info", path_info); /* * Perform fixups on environment based on trusted proxy headers * sent through from a front end proxy. */ wsgi_process_proxy_headers®; /* * Determine whether connection uses HTTPS protocol. This has * to be done after and fixups due to trusted proxy headers. */ if (!wsgi_is_https) wsgi_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); if (wsgi_is_https && wsgi_is_https(r->connection)) apr_table_set(r->subprocess_env, "HTTPS", “1”); /* * Set values specific to mod_wsgi configuration. These control * aspects of how a request is managed but don’t strictly need * to be passed through to the application itself. It is though * easier to set them here as then they are carried across to * the daemon process as part of the environment where they can * be extracted and used. */ apr_table_setn(r->subprocess_env, "mod_wsgi.process_group", config->process_group); apr_table_setn(r->subprocess_env, "mod_wsgi.application_group", config->application_group); apr_table_setn(r->subprocess_env, "mod_wsgi.callable_object", config->callable_object); apr_table_setn(r->subprocess_env, "mod_wsgi.request_handler", r->handler); apr_table_setn(r->subprocess_env, "mod_wsgi.handler_script", config->handler_script); apr_table_setn(r->subprocess_env, "mod_wsgi.script_reloading", apr_psprintf(r->pool, "%d", config->script_reloading)); #if defined(MOD_WSGI_WITH_DAEMONS) apr_table_setn(r->subprocess_env, "mod_wsgi.listener_host", c->local_addr->hostname ? c->local_addr->hostname : “”); apr_table_setn(r->subprocess_env, "mod_wsgi.listener_port", apr_psprintf(r->pool, "%d", c->local_addr->port)); #endif apr_table_setn(r->subprocess_env, "mod_wsgi.enable_sendfile", apr_psprintf(r->pool, "%d", config->enable_sendfile)); apr_table_setn(r->subprocess_env, "mod_wsgi.ignore_activity", apr_psprintf(r->pool, "%d", config->ignore_activity)); apr_table_setn(r->subprocess_env, "mod_wsgi.request_start", apr_psprintf(r->pool, “%” APR_TIME_T_FMT, r->request_time)); #if AP_MODULE_MAGIC_AT_LEAST(20100923,2) if (!r->log_id) { const char **id; /* Need to cast const away. */ id = &((request_rec *)r)->log_id; ap_run_generate_log_id(c, r, id); } if (r->log_id) apr_table_setn(r->subprocess_env, "mod_wsgi.request_id", r->log_id); if (r->connection->log_id) apr_table_setn(r->subprocess_env, "mod_wsgi.connection_id", r->connection->log_id); #endif } typedef struct { PyObject_HEAD request_rec *r; WSGIRequestConfig *config; PyObject *log; } DispatchObject; static DispatchObject *newDispatchObject(request_rec *r, WSGIRequestConfig *config) { DispatchObject *self; self = PyObject_New(DispatchObject, &Dispatch_Type); if (self == NULL) return NULL; self->config = config; self->r = r; self->log = newLogObject(r, APLOG_ERR, NULL, 0); return self; } static void Dispatch_dealloc(DispatchObject *self) { Py_DECREF(self->log); PyObject_Del(self); } static PyObject *Dispatch_environ(DispatchObject *self, const char *group) { request_rec *r = NULL; PyObject *vars = NULL; PyObject *object = NULL; const apr_array_header_t *head = NULL; const apr_table_entry_t *elts = NULL; int i = 0; /* Create the WSGI environment dictionary. */ vars = PyDict_New(); /* Merge the CGI environment into the WSGI environment. */ r = self->r; head = apr_table_elts(r->subprocess_env); elts = (apr_table_entry_t *)head->elts; for (i = 0; i < head->nelts; ++i) { if (elts[i].key) { if (elts[i].val) { #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(elts[i].val, strlen(elts[i].val), NULL); #else object = PyString_FromString(elts[i].val); #endif PyDict_SetItemString(vars, elts[i].key, object); Py_DECREF(object); } else PyDict_SetItemString(vars, elts[i].key, Py_None); } } /* * Need to override process and application group as they * are set to the default target, where as we need to set * them to context dispatch script is run in. Also need * to remove callable object reference. */ #if PY_MAJOR_VERSION >= 3 object = PyUnicode_FromString(“”); #else object = PyString_FromString(“”); #endif PyDict_SetItemString(vars, "mod_wsgi.process_group", object); Py_DECREF(object); #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(group, strlen(group), NULL); #else object = PyString_FromString(group); #endif PyDict_SetItemString(vars, "mod_wsgi.application_group", object); Py_DECREF(object); PyDict_DelItemString(vars, “mod_wsgi.callable_object”); /* * Setup log object for WSGI errors. Don’t decrement * reference to log object as keep reference to it. */ object = (PyObject *)self->log; PyDict_SetItemString(vars, "wsgi.errors", object); /* * If Apache extensions are enabled add a CObject reference * to the Apache request_rec structure instance. */ if (!wsgi_daemon_pool && self->config->pass_apache_request) { #if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 2) || \ (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 7) object = PyCapsule_New(self->r, 0, 0); #else object = PyCObject_FromVoidPtr(self->r, 0); #endif PyDict_SetItemString(vars, "apache.request_rec", object); Py_DECREF(object); } /* * Extensions for accessing SSL certificate information from * mod_ssl when in use. */ #if 0 object = PyObject_GetAttrString((PyObject *)self, “ssl_is_https”); PyDict_SetItemString(vars, "mod_ssl.is_https", object); Py_DECREF(object); object = PyObject_GetAttrString((PyObject *)self, “ssl_var_lookup”); PyDict_SetItemString(vars, "mod_ssl.var_lookup", object); Py_DECREF(object); #endif return vars; } static PyObject *Dispatch_ssl_is_https(DispatchObject *self, PyObject *args) { APR_OPTIONAL_FN_TYPE(ssl_is_https) *ssl_is_https = 0; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, “request object has expired”); return NULL; } if (!PyArg_ParseTuple(args, “:ssl_is_https”)) return NULL; ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); if (ssl_is_https == 0) return Py_BuildValue("i", 0); return Py_BuildValue("i", ssl_is_https(self->r->connection)); } static PyObject *Dispatch_ssl_var_lookup(DispatchObject *self, PyObject *args) { APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *ssl_var_lookup = 0; PyObject *item = NULL; char *name = 0; char *value = 0; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, “request object has expired”); return NULL; } if (!PyArg_ParseTuple(args, "O:ssl_var_lookup", &item)) return NULL; #if PY_MAJOR_VERSION >= 3 if (PyUnicode_Check(item)) { PyObject *latin_item; latin_item = PyUnicode_AsLatin1String(item); if (!latin_item) { PyErr_Format(PyExc_TypeError, "byte string value expected, " “value containing non ‘latin-1’ characters found”); Py_DECREF(item); return NULL; } Py_DECREF(item); item = latin_item; } #endif if (!PyString_Check(item)) { PyErr_Format(PyExc_TypeError, "byte string value expected, value " "of type %.200s found", item->ob_type->tp_name); Py_DECREF(item); return NULL; } name = PyString_AsString(item); ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); if (ssl_var_lookup == 0) { Py_INCREF(Py_None); return Py_None; } value = ssl_var_lookup(self->r->pool, self->r->server, self->r->connection, self->r, name); if (!value) { Py_INCREF(Py_None); return Py_None; } #if PY_MAJOR_VERSION >= 3 return PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else return PyString_FromString(value); #endif } static PyMethodDef Dispatch_methods[] = { { "ssl_is_https", (PyCFunction)Dispatch_ssl_is_https, METH_VARARGS, 0 }, { "ssl_var_lookup", (PyCFunction)Dispatch_ssl_var_lookup, METH_VARARGS, 0 }, { NULL, NULL} }; static PyTypeObject Dispatch_Type = { PyVarObject_HEAD_INIT(NULL, 0) "mod_wsgi.Dispatch", /*tp_name*/ sizeof(DispatchObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)Dispatch_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ 0, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ 0, /*tp_iter*/ 0, /*tp_iternext*/ Dispatch_methods, /*tp_methods*/ 0, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ 0, /*tp_init*/ 0, /*tp_alloc*/ 0, /*tp_new*/ 0, /*tp_free*/ 0, /*tp_is_gc*/ }; static int wsgi_execute_dispatch(request_rec *r) { WSGIRequestConfig *config; InterpreterObject *interp = NULL; PyObject *modules = NULL; PyObject *module = NULL; char *name = NULL; int exists = 0; const char *script = NULL; const char *group = NULL; int status; /* Grab request configuration. */ config = (WSGIRequestConfig *)ap_get_module_config(r->request_config, &wsgi_module); if (!config->dispatch_script) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server, "mod_wsgi (pid=%d): Location of WSGI dispatch " "script not provided.", getpid()); return HTTP_INTERNAL_SERVER_ERROR; } /* * Acquire the desired python interpreter. Once this is done * it is safe to start manipulating python objects. */ script = config->dispatch_script->handler_script; group = wsgi_server_group(r, config->dispatch_script->application_group); interp = wsgi_acquire_interpreter(group); if (!interp) { ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, "mod_wsgi (pid=%d): Cannot acquire interpreter ‘%s’.", getpid(), group); return HTTP_INTERNAL_SERVER_ERROR; } /* Calculate the Python module name to be used for script. */ name = wsgi_module_name(r->pool, script); /* * Use a lock around the check to see if the module is * already loaded and the import of the module to prevent * two request handlers trying to import the module at the * same time. */ #if APR_HAS_THREADS Py_BEGIN_ALLOW_THREADS apr_thread_mutex_lock(wsgi_module_lock); Py_END_ALLOW_THREADS #endif modules = PyImport_GetModuleDict(); module = PyDict_GetItemString(modules, name); Py_XINCREF(module); if (module) exists = 1; /* * If script reloading is enabled and the module for it has * previously been loaded, see if it has been modified since * the last time it was accessed. */ if (module && config->script_reloading) { if (wsgi_reload_required(r->pool, r, script, module, NULL)) { /* * Script file has changed. Only support module * reloading for dispatch scripts. Remove the * module from the modules dictionary before * reloading it again. If code is executing within * the module at the time, the callers reference * count on the module should ensure it isn’t * actually destroyed until it is finished. */ Py_DECREF(module); module = NULL; PyDict_DelItemString(modules, name); } } if (!module) { module = wsgi_load_source(r->pool, r, name, exists, script, "", group, 0); } /* Safe now to release the module lock. */ #if APR_HAS_THREADS apr_thread_mutex_unlock(wsgi_module_lock); #endif /* Log any details of exceptions if import failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); /* Assume everything will be okay for now. */ status = OK; /* Determine if script exists and execute it. */ if (module) { PyObject *module_dict = NULL; PyObject *object = NULL; DispatchObject *adapter = NULL; module_dict = PyModule_GetDict(module); adapter = newDispatchObject(r, config); if (adapter) { PyObject *vars = NULL; PyObject *args = NULL; PyObject *method = NULL; vars = Dispatch_environ(adapter, group); /* First check process_group(). */ #if defined(MOD_WSGI_WITH_DAEMONS) object = PyDict_GetItemString(module_dict, “process_group”); if (object) { PyObject *result = NULL; if (adapter) { Py_INCREF(object); args = Py_BuildValue("(O)", vars); result = PyObject_CallObject(object, args); Py_DECREF(args); Py_DECREF(object); if (result) { if (result != Py_None) { if (PyString_Check(result)) { const char *s; s = PyString_AsString(result); s = apr_pstrdup(r->pool, s); s = wsgi_process_group(r, s); config->process_group = s; apr_table_setn(r->subprocess_env, "mod_wsgi.process_group", config->process_group); } #if PY_MAJOR_VERSION >= 3 else if (PyUnicode_Check(result)) { PyObject *latin_item; latin_item = PyUnicode_AsLatin1String(result); if (!latin_item) { PyErr_SetString(PyExc_TypeError, "Process group must be " "a byte string, value " "containing non ‘latin-1’ " “characters found”); status = HTTP_INTERNAL_SERVER_ERROR; } else { const char *s; Py_DECREF(result); result = latin_item; s = PyString_AsString(result); s = apr_pstrdup(r->pool, s); s = wsgi_process_group(r, s); config->process_group = s; apr_table_setn(r->subprocess_env, "mod_wsgi.process_group", config->process_group); } } #endif else { PyErr_SetString(PyExc_TypeError, "Process " “group must be a byte string”); status = HTTP_INTERNAL_SERVER_ERROR; } } Py_DECREF(result); } else status = HTTP_INTERNAL_SERVER_ERROR; /* Log any details of exceptions if execution failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); } object = NULL; } #endif /* Now check application_group(). */ if (status == OK) object = PyDict_GetItemString(module_dict, “application_group”); if (object) { PyObject *result = NULL; if (adapter) { Py_INCREF(object); args = Py_BuildValue("(O)", vars); result = PyObject_CallObject(object, args); Py_DECREF(args); Py_DECREF(object); if (result) { if (result != Py_None) { if (PyString_Check(result)) { const char *s; s = PyString_AsString(result); s = apr_pstrdup(r->pool, s); s = wsgi_application_group(r, s); config->application_group = s; apr_table_setn(r->subprocess_env, "mod_wsgi.application_group", config->application_group); } #if PY_MAJOR_VERSION >= 3 else if (PyUnicode_Check(result)) { PyObject *latin_item; latin_item = PyUnicode_AsLatin1String(result); if (!latin_item) { PyErr_SetString(PyExc_TypeError, "Application group must " "be a byte string, value " "containing non ‘latin-1’ " “characters found”); status = HTTP_INTERNAL_SERVER_ERROR; } else { const char *s; Py_DECREF(result); result = latin_item; s = PyString_AsString(result); s = apr_pstrdup(r->pool, s); s = wsgi_application_group(r, s); config->application_group = s; apr_table_setn(r->subprocess_env, "mod_wsgi.application_group", config->application_group); } } #endif else { PyErr_SetString(PyExc_TypeError, "Application " "group must be a string " “object”); status = HTTP_INTERNAL_SERVER_ERROR; } } Py_DECREF(result); } else status = HTTP_INTERNAL_SERVER_ERROR; /* Log any details of exceptions if execution failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); } object = NULL; } /* Now check callable_object(). */ if (status == OK) object = PyDict_GetItemString(module_dict, “callable_object”); if (object) { PyObject *result = NULL; if (adapter) { Py_INCREF(object); args = Py_BuildValue("(O)", vars); result = PyObject_CallObject(object, args); Py_DECREF(args); Py_DECREF(object); if (result) { if (result != Py_None) { if (PyString_Check(result)) { const char *s; s = PyString_AsString(result); s = apr_pstrdup(r->pool, s); s = wsgi_callable_object(r, s); config->callable_object = s; apr_table_setn(r->subprocess_env, "mod_wsgi.callable_object", config->callable_object); } #if PY_MAJOR_VERSION >= 3 else if (PyUnicode_Check(result)) { PyObject *latin_item; latin_item = PyUnicode_AsLatin1String(result); if (!latin_item) { PyErr_SetString(PyExc_TypeError, "Callable object must " "be a byte string, value " "containing non ‘latin-1’ " “characters found”); status = HTTP_INTERNAL_SERVER_ERROR; } else { const char *s; Py_DECREF(result); result = latin_item; s = PyString_AsString(result); s = apr_pstrdup(r->pool, s); s = wsgi_callable_object(r, s); config->callable_object = s; apr_table_setn(r->subprocess_env, "mod_wsgi.callable_object", config->callable_object); } } #endif else { PyErr_SetString(PyExc_TypeError, "Callable " "object must be a string " “object”); status = HTTP_INTERNAL_SERVER_ERROR; } } Py_DECREF(result); } else status = HTTP_INTERNAL_SERVER_ERROR; /* Log any details of exceptions if execution failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); } object = NULL; } /* * Wipe out references to Apache request object * held by Python objects, so can detect when an * application holds on to the transient Python * objects beyond the life of the request and * thus raise an exception if they are used. */ adapter->r = NULL; /* Close the log object so data is flushed. */ method = PyObject_GetAttrString(adapter->log, “close”); if (!method) { PyErr_Format(PyExc_AttributeError, "’%s’ object has no attribute 'close’", adapter->log->ob_type->tp_name); } else { object = PyObject_CallObject(method, NULL); } Py_XDECREF(object); Py_XDECREF(method); /* No longer need adapter object. */ Py_DECREF((PyObject *)adapter); /* Log any details of exceptions if execution failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); Py_DECREF(vars); } } /* Cleanup and release interpreter, */ Py_XDECREF(module); wsgi_release_interpreter(interp); return status; } static int wsgi_is_script_aliased(request_rec *r) { const char *t = NULL; t = apr_table_get(r->notes, “alias-forced-type”); return t && (!strcasecmp(t, “wsgi-script”)); } #if defined(MOD_WSGI_WITH_DAEMONS) static int wsgi_execute_remote(request_rec *r); #endif static int wsgi_hook_handler(request_rec *r) { int status; apr_off_t limit = 0; WSGIRequestConfig *config = NULL; const char *value = NULL; const char *tenc = NULL; const char *lenp = NULL; /* Filter out the obvious case of no handler defined. */ if (!r->handler) return DECLINED; /* * Construct request configuration and cache it in the * request object against this module so can access it later * from handler code. */ config = wsgi_create_req_config(r->pool, r); ap_set_module_config(r->request_config, &wsgi_module, config); /* * Only process requests for this module. First check for * where target is the actual WSGI script. Then need to * check for the case where handler name mapped to a handler * script definition. */ if (!strcmp(r->handler, “wsgi-script”) || !strcmp(r->handler, “application/x-httpd-wsgi”)) { /* * Ensure that have adequate privileges to run the WSGI * script. Require ExecCGI to be specified in Options for * this. In doing this, using the wider interpretation that * ExecCGI refers to any executable like script even though * not a separate process execution. */ if (!(ap_allow_options® & OPT_EXECCGI) && !wsgi_is_script_aliased®) { wsgi_log_script_error(r, "Options ExecCGI is off in this " "directory", r->filename); return HTTP_FORBIDDEN; } /* Ensure target script exists and is a file. */ if (r->finfo.filetype == 0) { wsgi_log_script_error(r, "Target WSGI script not found or unable " "to stat", r->filename); return HTTP_NOT_FOUND; } if (r->finfo.filetype == APR_DIR) { wsgi_log_script_error(r, "Attempt to invoke directory as WSGI " "application", r->filename); return HTTP_FORBIDDEN; } if (wsgi_is_script_aliased®) { /* * Allow any configuration supplied through request notes to * override respective values. Request notes are used when * configuration supplied with WSGIScriptAlias directives. */ if ((value = apr_table_get(r->notes, “mod_wsgi.process_group”))) config->process_group = wsgi_process_group(r, value); if ((value = apr_table_get(r->notes, “mod_wsgi.application_group”))) config->application_group = wsgi_application_group(r, value); if ((value = apr_table_get(r->notes, “mod_wsgi.callable_object”))) config->callable_object = value; if ((value = apr_table_get(r->notes, “mod_wsgi.pass_authorization”))) { if (!strcmp(value, “1”)) config->pass_authorization = 1; else config->pass_authorization = 0; } } } #if 0 else if (strstr(r->handler, “wsgi-handler=”) == r->handler) { config->handler_script = apr_pstrcat(r->pool, r->handler+13, NULL); config->callable_object = "handle_request"; } #endif else if (config->handler_scripts) { WSGIScriptFile *entry; entry = (WSGIScriptFile *)apr_hash_get(config->handler_scripts, r->handler, APR_HASH_KEY_STRING); if (entry) { config->handler_script = entry->handler_script; config->callable_object = "handle_request"; if ((value = entry->process_group)) config->process_group = wsgi_process_group(r, value); if ((value = entry->application_group)) config->application_group = wsgi_application_group(r, value); if ((value = entry->pass_authorization)) { if (!strcmp(value, “1”)) config->pass_authorization = 1; else config->pass_authorization = 0; } } else return DECLINED; } else return DECLINED; /* * Honour AcceptPathInfo directive. Default behaviour is * accept additional path information. */ #if AP_MODULE_MAGIC_AT_LEAST(20011212,0) if ((r->used_path_info == AP_REQ_REJECT_PATH_INFO) && r->path_info && *r->path_info) { wsgi_log_script_error(r, "AcceptPathInfo off disallows user’s path", r->filename); return HTTP_NOT_FOUND; } #endif /* * Setup policy to apply if request contains a body. Note that the * WSGI specification doesn’t strictly allow for chunked request * content as CONTENT_LENGTH is required when reading input and * an application isn’t meant to read more than what is defined by * CONTENT_LENGTH. We still optionally allow chunked request content. * For an application to use the content, it has to ignore the WSGI * specification and use read() with no arguments to read all * available input, or call read() with specific block size until * read() returns an empty string. */ tenc = apr_table_get(r->headers_in, “Transfer-Encoding”); if (tenc) { /* Only chunked transfer encoding is supported. */ if (strcasecmp(tenc, “chunked”)) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Unexpected value for Transfer-Encoding of ‘%s’ " "supplied. Only ‘chunked’ supported.", tenc), r->filename); return HTTP_NOT_IMPLEMENTED; } /* Only allow chunked requests when explicitly enabled. */ if (!config->chunked_request) { wsgi_log_script_error(r, "Received request requiring chunked " "transfer encoding, but optional support for chunked " "transfer encoding has not been enabled.", r->filename); return HTTP_LENGTH_REQUIRED; } /* * When chunked transfer encoding is specified, there should * not be any content length specified. */ if (lenp) { wsgi_log_script_error(r, "Unexpected Content-Length header " "supplied where Transfer-Encoding was specified " "as 'chunked’.", r->filename); return HTTP_BAD_REQUEST; } } /* * Check to see if the request content is too large if the * Content-Length header is defined then end the request here. We do * this as otherwise it will not be done until first time input data * is read in by the application. Problem is that underlying HTTP * output filter will also generate a 413 response and the error * raised from the application will be appended to that. The call to * ap_discard_request_body() is hopefully enough to trigger sending * of the 413 response by the HTTP filter. */ lenp = apr_table_get(r->headers_in, “Content-Length”); if (lenp) { char *endstr; apr_off_t length; if (wsgi_strtoff(&length, lenp, &endstr, 10) || *endstr || length < 0) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Invalid Content-Length header value of ‘%s’ was " "supplied.", lenp), r->filename); return HTTP_BAD_REQUEST; } limit = ap_get_limit_req_body®; if (limit && limit < length) { ap_discard_request_body®; return OK; } } /* Build the sub process environment. */ config->request_start = r->request_time; wsgi_build_environment®; /* * If a dispatch script has been provided, as appropriate * allow it to override any of the configuration related * to what context the script will be executed in and what * the target callable object for the application is. */ if (config->dispatch_script) { status = wsgi_execute_dispatch®; if (status != OK) return status; } /* * Execute the target WSGI application script or proxy * request to one of the daemon processes as appropriate. */ #if defined(MOD_WSGI_WITH_DAEMONS) status = wsgi_execute_remote®; if (status != DECLINED) return status; #endif #if defined(MOD_WSGI_DISABLE_EMBEDDED) wsgi_log_script_error(r, "Embedded mode of mod_wsgi disabled at compile " "time", r->filename); return HTTP_INTERNAL_SERVER_ERROR; #endif if (wsgi_server_config->restrict_embedded == 1) { wsgi_log_script_error(r, "Embedded mode of mod_wsgi disabled by " "runtime configuration", r->filename); return HTTP_INTERNAL_SERVER_ERROR; } return wsgi_execute_script®; } /* * Apache 2.X and UNIX specific code for creation and management * of distinct daemon processes. */ #if defined(MOD_WSGI_WITH_DAEMONS) static const char *wsgi_add_daemon_process(cmd_parms *cmd, void *mconfig, const char *args) { const char *name = NULL; const char *user = NULL; const char *group = NULL; int processes = 1; int multiprocess = 0; int threads = 15; long umask = -1; const char *root = NULL; const char *home = NULL; const char *lang = NULL; const char *locale = NULL; const char *python_home = NULL; const char *python_path = NULL; const char *python_eggs = NULL; int stack_size = 0; int maximum_requests = 0; int startup_timeout = 0; int shutdown_timeout = 5; int deadlock_timeout = 300; int inactivity_timeout = 0; int request_timeout = 0; int graceful_timeout = 15; int eviction_timeout = 0; int restart_interval = 0; int connect_timeout = 15; int socket_timeout = 0; int queue_timeout = 0; const char *socket_user = NULL; int listen_backlog = WSGI_LISTEN_BACKLOG; const char *display_name = NULL; int send_buffer_size = 0; int recv_buffer_size = 0; int header_buffer_size = 0; int response_buffer_size = 0; int response_socket_timeout = 0; const char *script_user = NULL; const char *script_group = NULL; int cpu_time_limit = 0; int cpu_priority = 0; apr_int64_t memory_limit = 0; apr_int64_t virtual_memory_limit = 0; uid_t uid; uid_t gid; const char *groups_list = NULL; int groups_count = 0; gid_t *groups = NULL; int server_metrics = 0; const char *newrelic_config_file = NULL; const char *newrelic_environment = NULL; const char *option = NULL; const char *value = NULL; WSGIProcessGroup *entries = NULL; WSGIProcessGroup *entry = NULL; int i; /* * Set the defaults for user/group from values * defined for the User/Group directives in main * Apache configuration. */ uid = ap_unixd_config.user_id; user = ap_unixd_config.user_name; gid = ap_unixd_config.group_id; /* Now parse options for directive. */ name = ap_getword_conf(cmd->pool, &args); if (!name || !*name) return "Name of WSGI daemon process not supplied."; while (*args) { if (wsgi_parse_option(cmd->pool, &args, &option, &value) != APR_SUCCESS) { return "Invalid option to WSGI daemon process definition."; } if (!strcmp(option, “user”)) { if (!*value) return "Invalid user for WSGI daemon process."; user = value; uid = ap_uname2id(user); if (uid == 0) return "WSGI process blocked from running as root."; if (*user == ‘#’) { struct passwd *entry = NULL; if ((entry = getpwuid(uid)) == NULL) return "Couldn’t determine user name from uid."; user = entry->pw_name; } } else if (!strcmp(option, “group”)) { if (!*value) return "Invalid group for WSGI daemon process."; group = value; gid = ap_gname2id(group); } else if (!strcmp(option, “supplementary-groups”)) { groups_list = value; } else if (!strcmp(option, “processes”)) { if (!*value) return "Invalid process count for WSGI daemon process."; processes = atoi(value); if (processes < 1) return "Invalid process count for WSGI daemon process."; multiprocess = 1; } else if (!strcmp(option, “threads”)) { if (!*value) return "Invalid thread count for WSGI daemon process."; threads = atoi(value); if (threads < 0 || threads >= WSGI_STACK_LAST-1) return "Invalid thread count for WSGI daemon process."; } else if (!strcmp(option, “umask”)) { if (!*value) return "Invalid umask for WSGI daemon process."; errno = 0; umask = strtol(value, (char **)&value, 8); if (*value || errno == ERANGE || umask < 0) return "Invalid umask for WSGI daemon process."; } else if (!strcmp(option, “chroot”)) { if (geteuid()) return "Cannot chroot WSGI daemon process when not root."; if (*value != ‘/’) return "Invalid chroot directory for WSGI daemon process."; root = value; } else if (!strcmp(option, “home”)) { if (*value != ‘/’) return "Invalid home directory for WSGI daemon process."; home = value; } else if (!strcmp(option, “lang”)) { lang = value; } else if (!strcmp(option, “locale”)) { locale = value; } else if (!strcmp(option, “python-home”)) { python_home = value; } else if (!strcmp(option, “python-path”)) { python_path = value; } else if (!strcmp(option, “python-eggs”)) { python_eggs = value; } #if (APR_MAJOR_VERSION >= 1) else if (!strcmp(option, “stack-size”)) { if (!*value) return "Invalid stack size for WSGI daemon process."; stack_size = atoi(value); if (stack_size <= 0) return "Invalid stack size for WSGI daemon process."; } #endif else if (!strcmp(option, “maximum-requests”)) { if (!*value) return "Invalid request count for WSGI daemon process."; maximum_requests = atoi(value); if (maximum_requests < 0) return "Invalid request count for WSGI daemon process."; } else if (!strcmp(option, “startup-timeout”)) { if (!*value) return "Invalid startup timeout for WSGI daemon process."; startup_timeout = atoi(value); if (startup_timeout < 0) return "Invalid startup timeout for WSGI daemon process."; } else if (!strcmp(option, “shutdown-timeout”)) { if (!*value) return "Invalid shutdown timeout for WSGI daemon process."; shutdown_timeout = atoi(value); if (shutdown_timeout < 0) return "Invalid shutdown timeout for WSGI daemon process."; } else if (!strcmp(option, “deadlock-timeout”)) { if (!*value) return "Invalid deadlock timeout for WSGI daemon process."; deadlock_timeout = atoi(value); if (deadlock_timeout < 0) return "Invalid deadlock timeout for WSGI daemon process."; } else if (!strcmp(option, “inactivity-timeout”)) { if (!*value) return "Invalid inactivity timeout for WSGI daemon process."; inactivity_timeout = atoi(value); if (inactivity_timeout < 0) return "Invalid inactivity timeout for WSGI daemon process."; } else if (!strcmp(option, “request-timeout”)) { if (!*value) return "Invalid request timeout for WSGI daemon process."; request_timeout = atoi(value); if (request_timeout < 0) return "Invalid request timeout for WSGI daemon process."; } else if (!strcmp(option, “graceful-timeout”)) { if (!*value) return "Invalid graceful timeout for WSGI daemon process."; graceful_timeout = atoi(value); if (graceful_timeout < 0) return "Invalid graceful timeout for WSGI daemon process."; } else if (!strcmp(option, “eviction-timeout”)) { if (!*value) return "Invalid eviction timeout for WSGI daemon process."; eviction_timeout = atoi(value); if (eviction_timeout < 0) return "Invalid eviction timeout for WSGI daemon process."; } else if (!strcmp(option, “restart-interval”)) { if (!*value) return "Invalid restart interval for WSGI daemon process."; restart_interval = atoi(value); if (restart_interval < 0) return "Invalid restart interval for WSGI daemon process."; } else if (!strcmp(option, “connect-timeout”)) { if (!*value) return "Invalid connect timeout for WSGI daemon process."; connect_timeout = atoi(value); if (connect_timeout < 0) return "Invalid connect timeout for WSGI daemon process."; } else if (!strcmp(option, “socket-timeout”)) { if (!*value) return "Invalid socket timeout for WSGI daemon process."; socket_timeout = atoi(value); if (socket_timeout < 0) return "Invalid socket timeout for WSGI daemon process."; } else if (!strcmp(option, “queue-timeout”)) { if (!*value) return "Invalid queue timeout for WSGI daemon process."; queue_timeout = atoi(value); if (queue_timeout < 0) return "Invalid queue timeout for WSGI daemon process."; } else if (!strcmp(option, “listen-backlog”)) { if (!*value) return "Invalid listen backlog for WSGI daemon process."; listen_backlog = atoi(value); if (listen_backlog < 0) return "Invalid listen backlog for WSGI daemon process."; } else if (!strcmp(option, “display-name”)) { display_name = value; } else if (!strcmp(option, “send-buffer-size”)) { if (!*value) return "Invalid send buffer size for WSGI daemon process."; send_buffer_size = atoi(value); if (send_buffer_size < 512 && send_buffer_size != 0) { return "Send buffer size must be >= 512 bytes, " "or 0 for system default."; } } else if (!strcmp(option, “receive-buffer-size”)) { if (!*value) return "Invalid receive buffer size for WSGI daemon process."; recv_buffer_size = atoi(value); if (recv_buffer_size < 512 && recv_buffer_size != 0) { return "Receive buffer size must be >= 512 bytes, " "or 0 for system default."; } } else if (!strcmp(option, “header-buffer-size”)) { if (!*value) return "Invalid header buffer size for WSGI daemon process."; header_buffer_size = atoi(value); if (header_buffer_size < 8192 && header_buffer_size != 0) { return "Header buffer size must be >= 8192 bytes, " "or 0 for default."; } } else if (!strcmp(option, “response-buffer-size”)) { if (!*value) return "Invalid response buffer size for WSGI daemon process."; response_buffer_size = atoi(value); if (response_buffer_size < 65536 && response_buffer_size != 0) { return "Response buffer size must be >= 65536 bytes, " "or 0 for default."; } } else if (!strcmp(option, “response-socket-timeout”)) { if (!*value) return "Invalid response socket timeout for WSGI daemon process."; response_socket_timeout = atoi(value); if (response_socket_timeout < 0) return "Invalid response socket timeout for WSGI daemon process."; } else if (!strcmp(option, “socket-user”)) { uid_t socket_uid; if (!*value) return "Invalid socket user for WSGI daemon process."; socket_uid = ap_uname2id(value); if (*value == ‘#’) { struct passwd *entry = NULL; if ((entry = getpwuid(socket_uid)) == NULL) return "Couldn’t determine user name from socket user."; value = entry->pw_name; } socket_user = value; } else if (!strcmp(option, “script-user”)) { uid_t script_uid; if (!*value) return "Invalid script user for WSGI daemon process."; script_uid = ap_uname2id(value); if (*value == ‘#’) { struct passwd *entry = NULL; if ((entry = getpwuid(script_uid)) == NULL) return "Couldn’t determine uid from script user."; value = entry->pw_name; } script_user = value; } else if (!strcmp(option, “script-group”)) { gid_t script_gid; if (!*value) return "Invalid script group for WSGI daemon process."; script_gid = ap_gname2id(value); if (*value == ‘#’) { struct group *entry = NULL; if ((entry = getgrgid(script_gid)) == NULL) return "Couldn’t determine gid from script group."; value = entry->gr_name; } script_group = value; } else if (!strcmp(option, “cpu-time-limit”)) { if (!*value) return "Invalid CPU time limit for WSGI daemon process."; cpu_time_limit = atoi(value); if (cpu_time_limit < 0) return "Invalid CPU time limit for WSGI daemon process."; } else if (!strcmp(option, “cpu-priority”)) { if (!*value) return "Invalid CPU priority for WSGI daemon process."; cpu_priority = atoi(value); } else if (!strcmp(option, “memory-limit”)) { if (!*value) return "Invalid memory limit for WSGI daemon process."; memory_limit = apr_atoi64(value); if (memory_limit < 0) return "Invalid memory limit for WSGI daemon process."; } else if (!strcmp(option, “virtual-memory-limit”)) { if (!*value) return "Invalid virtual memory limit for WSGI daemon process."; virtual_memory_limit = apr_atoi64(value); if (virtual_memory_limit < 0) return "Invalid virtual memory limit for WSGI daemon process."; } else if (!strcmp(option, “server-metrics”)) { if (!*value) return "Invalid server metrics flag for WSGI daemon process."; if (strcasecmp(value, “Off”) == 0) server_metrics = 0; else if (strcasecmp(value, “On”) == 0) server_metrics = 1; else return "Invalid server metrics flag for WSGI daemon process."; } else if (!strcmp(option, “newrelic-config-file”)) { newrelic_config_file = value; } else if (!strcmp(option, “newrelic-environment”)) { newrelic_environment = value; } else return "Invalid option to WSGI daemon process definition."; } if (script_user && script_group) return "Only one of script-user and script-group allowed."; if (groups_list) { const char *group_name = NULL; long groups_maximum = NGROUPS_MAX; const char *items = NULL; #ifdef _SC_NGROUPS_MAX groups_maximum = sysconf(_SC_NGROUPS_MAX); if (groups_maximum < 0) groups_maximum = NGROUPS_MAX; #endif groups = (gid_t *)apr_pcalloc(cmd->pool, groups_maximum*sizeof(groups[0])); groups[groups_count++] = gid; items = groups_list; group_name = ap_getword(cmd->pool, &items, ‘,’); while (group_name && *group_name) { if (groups_count >= groups_maximum) return "Too many supplementary groups WSGI daemon process"; groups[groups_count++] = ap_gname2id(group_name); group_name = ap_getword(cmd->pool, &items, ‘,’); } } if (!wsgi_daemon_list) { wsgi_daemon_list = apr_array_make(cmd->pool, 20, sizeof(WSGIProcessGroup)); apr_pool_cleanup_register(cmd->pool, &wsgi_daemon_list, ap_pool_cleanup_set_null, apr_pool_cleanup_null); } entries = (WSGIProcessGroup *)wsgi_daemon_list->elts; for (i = 0; i < wsgi_daemon_list->nelts; ++i) { entry = &entries[i]; if (!strcmp(entry->name, name)) return "Name duplicates previous WSGI daemon definition."; } wsgi_daemon_count++; entry = (WSGIProcessGroup *)apr_array_push(wsgi_daemon_list); entry->server = cmd->server; entry->random = random(); entry->id = wsgi_daemon_count; entry->name = apr_pstrdup(cmd->pool, name); entry->user = apr_pstrdup(cmd->pool, user); entry->group = apr_pstrdup(cmd->pool, group); entry->uid = uid; entry->gid = gid; entry->groups_list = groups_list; entry->groups_count = groups_count; entry->groups = groups; entry->processes = processes; entry->multiprocess = multiprocess; entry->threads = threads; entry->umask = umask; entry->root = root; entry->home = home; entry->lang = lang; entry->locale = locale; entry->python_home = python_home; entry->python_path = python_path; entry->python_eggs = python_eggs; entry->stack_size = stack_size; entry->maximum_requests = maximum_requests; entry->shutdown_timeout = shutdown_timeout; entry->startup_timeout = apr_time_from_sec(startup_timeout); entry->deadlock_timeout = apr_time_from_sec(deadlock_timeout); entry->inactivity_timeout = apr_time_from_sec(inactivity_timeout); entry->request_timeout = apr_time_from_sec(request_timeout); entry->graceful_timeout = apr_time_from_sec(graceful_timeout); entry->eviction_timeout = apr_time_from_sec(eviction_timeout); entry->restart_interval = apr_time_from_sec(restart_interval); entry->connect_timeout = apr_time_from_sec(connect_timeout); entry->socket_timeout = apr_time_from_sec(socket_timeout); entry->queue_timeout = apr_time_from_sec(queue_timeout); entry->socket_user = apr_pstrdup(cmd->pool, socket_user); entry->listen_backlog = listen_backlog; entry->display_name = display_name; entry->send_buffer_size = send_buffer_size; entry->recv_buffer_size = recv_buffer_size; entry->header_buffer_size = header_buffer_size; entry->response_buffer_size = response_buffer_size; if (response_socket_timeout == 0) response_socket_timeout = socket_timeout; entry->response_socket_timeout = apr_time_from_sec(response_socket_timeout); entry->script_user = script_user; entry->script_group = script_group; entry->cpu_time_limit = cpu_time_limit; entry->cpu_priority = cpu_priority; entry->memory_limit = memory_limit; entry->virtual_memory_limit = virtual_memory_limit; entry->server_metrics = server_metrics; entry->newrelic_config_file = newrelic_config_file; entry->newrelic_environment = newrelic_environment; entry->listener_fd = -1; return NULL; } static const char *wsgi_set_socket_prefix(cmd_parms *cmd, void *mconfig, const char *arg) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); sconfig->socket_prefix = ap_server_root_relative(cmd->pool, arg); if (!sconfig->socket_prefix) { return apr_pstrcat(cmd->pool, "Invalid WSGISocketPrefix '", arg, "’.", NULL); } return NULL; } static const char *wsgi_set_socket_rotation(cmd_parms *cmd, void *mconfig, const char *f) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); if (strcasecmp(f, “Off”) == 0) sconfig->socket_rotation = 0; else if (strcasecmp(f, “On”) == 0) sconfig->socket_rotation = 1; else return "WSGISocketRotation must be one of: Off | On"; return NULL; } static const char wsgi_valid_accept_mutex_string[] = “Valid accept mutex mechanisms for this platform are: default” #if APR_HAS_FLOCK_SERIALIZE “, flock” #endif #if APR_HAS_FCNTL_SERIALIZE “, fcntl” #endif #if APR_HAS_SYSVSEM_SERIALIZE “, sysvsem” #endif #if APR_HAS_POSIXSEM_SERIALIZE “, posixsem” #endif #if APR_HAS_PROC_PTHREAD_SERIALIZE “, pthread” #endif "."; static const char *wsgi_set_accept_mutex(cmd_parms *cmd, void *mconfig, const char *arg) { const char *error = NULL; WSGIServerConfig *sconfig = NULL; error = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (error != NULL) return error; sconfig = ap_get_module_config(cmd->server->module_config, &wsgi_module); #if !defined(AP_ACCEPT_MUTEX_TYPE) sconfig->lock_mechanism = ap_accept_lock_mech; #else sconfig->lock_mechanism = APR_LOCK_DEFAULT; #endif if (!strcasecmp(arg, “default”)) { sconfig->lock_mechanism = APR_LOCK_DEFAULT; } #if APR_HAS_FLOCK_SERIALIZE else if (!strcasecmp(arg, “flock”)) { sconfig->lock_mechanism = APR_LOCK_FLOCK; } #endif #if APR_HAS_FCNTL_SERIALIZE else if (!strcasecmp(arg, “fcntl”)) { sconfig->lock_mechanism = APR_LOCK_FCNTL; } #endif #if APR_HAS_SYSVSEM_SERIALIZE else if (!strcasecmp(arg, “sysvsem”)) { sconfig->lock_mechanism = APR_LOCK_SYSVSEM; } #endif #if APR_HAS_POSIXSEM_SERIALIZE else if (!strcasecmp(arg, “posixsem”)) { sconfig->lock_mechanism = APR_LOCK_POSIXSEM; } #endif #if APR_HAS_PROC_PTHREAD_SERIALIZE else if (!strcasecmp(arg, “pthread”)) { sconfig->lock_mechanism = APR_LOCK_PROC_PTHREAD; } #endif else { return apr_pstrcat(cmd->pool, "Accept mutex lock mechanism ‘", arg, "’ is invalid. ", wsgi_valid_accept_mutex_string, NULL); } return NULL; } static apr_file_t *wsgi_signal_pipe_in = NULL; static apr_file_t *wsgi_signal_pipe_out = NULL; static void wsgi_signal_handler(int signum) { apr_size_t nbytes = 1; if (wsgi_daemon_pid != 0 && wsgi_daemon_pid != getpid()) exit(-1); if (signum == AP_SIG_GRACEFUL) { apr_file_write(wsgi_signal_pipe_out, "G", &nbytes); apr_file_flush(wsgi_signal_pipe_out); } else if (signum == SIGXCPU) { if (!wsgi_graceful_timeout) wsgi_daemon_shutdown++; apr_file_write(wsgi_signal_pipe_out, "C", &nbytes); apr_file_flush(wsgi_signal_pipe_out); } else { wsgi_daemon_shutdown++; apr_file_write(wsgi_signal_pipe_out, "S", &nbytes); apr_file_flush(wsgi_signal_pipe_out); } } static void wsgi_exit_daemon_process(int status) { if (wsgi_server && wsgi_daemon_group) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Exiting process '%s’.", getpid(), wsgi_daemon_group); } exit(status); } static int wsgi_start_process(apr_pool_t *p, WSGIDaemonProcess *daemon); static void wsgi_manage_process(int reason, void *data, apr_wait_t status) { WSGIDaemonProcess *daemon = data; switch (reason) { /* Child daemon process has died. */ case APR_OC_REASON_DEATH: { int mpm_state; int stopping; /* * Determine if Apache is being shutdown or not and * if it is not being shutdown, we will need to * restart the child daemon process that has died. * If MPM doesn’t support query assume that child * daemon process shouldn’t be restarted. Both * prefork and worker MPMs support this query so * should always be okay. */ stopping = 1; if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state) == APR_SUCCESS && mpm_state != AP_MPMQ_STOPPING) { stopping = 0; } if (!stopping) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): " "Process ‘%s’ has died, deregister and " "restart it.", daemon->process.pid, daemon->group->name); if (WIFEXITED(status)) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): " "Process ‘%s’ terminated normally, exit code %d", daemon->process.pid, daemon->group->name, WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): " "Process ‘%s’ terminated by signal %d", daemon->process.pid, daemon->group->name, WTERMSIG(status)); } } else { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): " "Process ‘%s’ has died but server is " "being stopped, deregister it.", daemon->process.pid, daemon->group->name); } /* Deregister existing process so we stop watching it. */ apr_proc_other_child_unregister(daemon); /* Now restart process if not shutting down. */ if (!stopping) wsgi_start_process(wsgi_parent_pool, daemon); break; } /* Apache is being restarted or shutdown. */ case APR_OC_REASON_RESTART: { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): " "Process ‘%s’ to be deregistered, as server is " "restarting or being shutdown.", daemon->process.pid, daemon->group->name); /* Deregister existing process so we stop watching it. */ apr_proc_other_child_unregister(daemon); break; } /* Child daemon process vanished. */ case APR_OC_REASON_LOST: { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): " "Process ‘%s’ appears to have been lost, " "deregister and restart it.", daemon->process.pid, daemon->group->name); /* Deregister existing process so we stop watching it. */ apr_proc_other_child_unregister(daemon); /* Restart the child daemon process that has died. */ wsgi_start_process(wsgi_parent_pool, daemon); break; } /* Call to unregister the process. */ case APR_OC_REASON_UNREGISTER: { /* Nothing to do at present. */ ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): " "Process ‘%s’ has been deregistered and will " "no longer be monitored.", daemon->process.pid, daemon->group->name); break; } default: { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): " "Process ‘%s’ targeted by unexpected event %d.", daemon->process.pid, daemon->group->name, reason); } } } static void wsgi_setup_daemon_name(WSGIDaemonProcess *daemon, apr_pool_t *p) { const char *display_name = NULL; #if !(defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)) long slen = 0; long dlen = 0; char *argv0 = NULL; #endif display_name = daemon->group->display_name; if (!display_name) return; if (!strcmp(display_name, “%{GROUP}”)) { display_name = apr_pstrcat(p, "(wsgi:", daemon->group->name, ")", NULL); } /* * Only argv[0] is guaranteed to be the real things as MPM * modules may make modifications to subsequent arguments. * Thus can only replace the argv[0] value. Because length * is restricted, need to truncate display name if too long. */ #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) setproctitle("%s", display_name); #else argv0 = (char*)wsgi_server->process->argv[0]; dlen = strlen(argv0); slen = strlen(display_name); memset(argv0, ' ', dlen); if (slen < dlen) memcpy(argv0, display_name, slen); else memcpy(argv0, display_name, dlen); #endif } static int wsgi_setup_access(WSGIDaemonProcess *daemon) { /* Change to chroot environment. */ if (daemon->group->root) { if (chroot(daemon->group->root) == -1) { ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server, "mod_wsgi (pid=%d): Unable to change root " "directory to '%s’.", getpid(), daemon->group->root); return -1; } } /* We don’t need to switch user/group if not root. */ if (geteuid() == 0) { /* Setup the daemon process real and effective group. */ if (setgid(daemon->group->gid) == -1) { ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server, "mod_wsgi (pid=%d): Unable to set group id " "to gid=%u.", getpid(), (unsigned)daemon->group->gid); return -1; } else { if (daemon->group->groups) { if (setgroups(daemon->group->groups_count, daemon->group->groups) == -1) { ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server, "mod_wsgi (pid=%d): Unable " "to set supplementary groups for uname=%s " "of '%s’.", getpid(), daemon->group->user, daemon->group->groups_list); return -1; } } else if (initgroups(daemon->group->user, daemon->group->gid) == -1) { ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server, "mod_wsgi (pid=%d): Unable " "to set groups for uname=%s and gid=%u.", getpid(), daemon->group->user, (unsigned)daemon->group->gid); return -1; } } /* Setup the daemon process real and effective user. */ if (setuid(daemon->group->uid) == -1) { ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server, "mod_wsgi (pid=%d): Unable to change to uid=%ld.", getpid(), (long)daemon->group->uid); /* * On true UNIX systems this should always succeed at * this point. With certain Linux kernel versions though * we can get back EAGAIN where the target user had * reached their process limit. In that case will be left * running as wrong user. Just exit on all failures to be * safe. Don’t die immediately to avoid a fork bomb. * * We could just return -1 here and let the caller do the * sleep() and exit() but this failure is critical enough * that we still do it here so it is obvious that the issue * is being addressed. */ ap_log_error(APLOG_MARK, APLOG_ALERT, 0, wsgi_server, "mod_wsgi (pid=%d): Failure to configure the " "daemon process correctly and process left in " "unspecified state. Restarting daemon process " "after delay.", getpid()); sleep(20); wsgi_exit_daemon_process(-1); return -1; } } /* * Setup the working directory for the process. It is either set to * what the ‘home’ option explicitly provides, or the home home * directory of the user, where it has been set to be different to * the user that Apache’s own processes run as. */ if (daemon->group->home) { if (chdir(daemon->group->home) == -1) { ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server, "mod_wsgi (pid=%d): Unable to change working " "directory to '%s’.", getpid(), daemon->group->home); return -1; } } else if (geteuid() != ap_unixd_config.user_id) { struct passwd *pwent; pwent = getpwuid(geteuid()); if (pwent) { if (chdir(pwent->pw_dir) == -1) { ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server, "mod_wsgi (pid=%d): Unable to change working " "directory to home directory ‘%s’ for uid=%ld.", getpid(), pwent->pw_dir, (long)geteuid()); return -1; } } else { ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server, "mod_wsgi (pid=%d): Unable to determine home " "directory for uid=%ld.", getpid(), (long)geteuid()); return -1; } } /* Setup the umask for the effective user. */ if (daemon->group->umask != -1) umask(daemon->group->umask); /* * Linux prevents generation of core dumps after setuid() * has been used. Attempt to reenable ability to dump core * so that the CoreDumpDirectory directive still works. */ #if defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE) /* This applies to Linux 2.4 and later. */ if (ap_coredumpdir_configured) { if (prctl(PR_SET_DUMPABLE, 1)) { ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server, "mod_wsgi (pid=%d): Set dumpable failed. This child " "will not coredump after software errors.", getpid()); } } #endif return 0; } static int wsgi_setup_socket(WSGIProcessGroup *process) { int sockfd = -1; struct sockaddr_un addr; mode_t omask; int rc; int sendsz = process->send_buffer_size; int recvsz = process->recv_buffer_size; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Socket for ‘%s’ is '%s’.", getpid(), process->name, process->socket_path); if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server, "mod_wsgi (pid=%d): Couldn’t create unix domain " "socket.", getpid()); return -1; } #ifdef SO_SNDBUF if (sendsz) { if (setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (void *)&sendsz, sizeof(sendsz)) == -1) { ap_log_error(APLOG_MARK, APLOG_WARNING, errno, wsgi_server, "mod_wsgi (pid=%d): Failed to set send buffer " "size on daemon process socket.", getpid()); } } #endif #ifdef SO_RCVBUF if (recvsz) { if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (void *)&recvsz, sizeof(recvsz)) == -1) { ap_log_error(APLOG_MARK, APLOG_WARNING, errno, wsgi_server, "mod_wsgi (pid=%d): Failed to set receive buffer " "size on daemon process socket.", getpid()); } } #endif if (strlen(process->socket_path) > sizeof(addr.sun_path)) { ap_log_error(APLOG_MARK, APLOG_ALERT, 0, wsgi_server, "mod_wsgi (pid=%d): Length of path for daemon process " "socket exceeds maxmimum allowed value and will be " "truncated, resulting in likely failure to bind the " "socket, or other later related failure.", getpid()); } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; apr_cpystrn(addr.sun_path, process->socket_path, sizeof(addr.sun_path)); omask = umask(0077); rc = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)); if (rc < 0 && errno == EADDRINUSE) { ap_log_error(APLOG_MARK, APLOG_WARNING, errno, wsgi_server, "mod_wsgi (pid=%d): Removing stale unix domain " "socket '%s’.", getpid(), process->socket_path); unlink(process->socket_path); rc = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)); } umask(omask); if (rc < 0) { ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server, "mod_wsgi (pid=%d): Couldn’t bind unix domain " "socket '%s’.", getpid(), process->socket_path); close(sockfd); return -1; } ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Listen backlog for socket ‘%s’ is '%d’.", getpid(), process->socket_path, process->listen_backlog); if (listen(sockfd, process->listen_backlog) < 0) { ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server, "mod_wsgi (pid=%d): Couldn’t listen on unix domain " "socket.", getpid()); close(sockfd); return -1; } /* * Set the ownership of the UNIX listener socket. This would * normally be the Apache user that the Apache server child * processes run as, as they are the only processes that * would connect to the sockets. In the case of ITK MPM, * having them owned by Apache user is useless as at the * time the request is to be proxied, the Apache server * child process will have uid corresponding to the user * whose request they are handling. For ITK, thus set the * ownership to be the same as the daemon processes. This is * still restrictive, in that can only connect to daemon * process group running under same user, but most of the * time that is what you would want anyway when using ITK * MPM. */ if (!geteuid()) { #if defined(MPM_ITK) || defined(ITK_MPM) uid_t socket_uid = process->uid; #else uid_t socket_uid = ap_unixd_config.user_id; #endif if (process->socket_user) socket_uid = ap_uname2id(process->socket_user); if (chown(process->socket_path, socket_uid, -1) < 0) { ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server, "mod_wsgi (pid=%d): Couldn’t change owner of unix " "domain socket ‘%s’ to uid=%ld.", getpid(), process->socket_path, (long)socket_uid); close(sockfd); return -1; } } return sockfd; } static int wsgi_hook_daemon_handler(conn_rec *c); static void wsgi_process_socket(apr_pool_t *p, apr_socket_t *sock, apr_bucket_alloc_t *bucket_alloc, WSGIDaemonProcess *daemon) { apr_status_t rv; conn_rec *c; ap_sb_handle_t *sbh; core_net_rec *net; /* * This duplicates Apache connection setup. This is done * here rather than letting Apache do it so that avoid the * possibility that any Apache modules, such as mod_ssl * will add their own input/output filters to the chain. */ #if AP_MODULE_MAGIC_AT_LEAST(20110619,0) /* For 2.4 a NULL sbh pointer should work. */ sbh = NULL; #else /* For 2.2 a dummy sbh pointer is needed. */ ap_create_sb_handle(&sbh, p, -1, 0); #endif c = (conn_rec *)apr_pcalloc(p, sizeof(conn_rec)); c->sbh = sbh; c->conn_config = ap_create_conn_config§; c->notes = apr_table_make(p, 5); c->pool = p; if ((rv = apr_socket_addr_get(&c->local_addr, APR_LOCAL, sock)) != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_INFO, rv, wsgi_server, "mod_wsgi (pid=%d): Failed call " "apr_socket_addr_get(APR_LOCAL).", getpid()); apr_socket_close(sock); return; } apr_sockaddr_ip_get(&c->local_ip, c->local_addr); #if AP_MODULE_MAGIC_AT_LEAST(20111130,0) if ((rv = apr_socket_addr_get(&c->client_addr, APR_REMOTE, sock)) != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_INFO, rv, wsgi_server, "mod_wsgi (pid=%d): Failed call " "apr_socket_addr_get(APR_REMOTE).", getpid()); apr_socket_close(sock); return; } c->client_ip = "unknown"; #else if ((rv = apr_socket_addr_get(&c->remote_addr, APR_REMOTE, sock)) != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_INFO, rv, wsgi_server, "mod_wsgi (pid=%d): Failed call " "apr_socket_addr_get(APR_REMOTE).", getpid()); apr_socket_close(sock); return; } c->remote_ip = "unknown"; #endif c->base_server = daemon->group->server; c->bucket_alloc = bucket_alloc; c->id = 1; net = apr_palloc(c->pool, sizeof(core_net_rec)); if (daemon->group->socket_timeout) rv = apr_socket_timeout_set(sock, daemon->group->socket_timeout); else rv = apr_socket_timeout_set(sock, c->base_server->timeout); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, wsgi_server, "mod_wsgi (pid=%d): Failed call " "apr_socket_timeout_set().", getpid()); } net->c = c; net->in_ctx = NULL; net->out_ctx = NULL; net->client_socket = sock; ap_set_module_config(net->c->conn_config, &core_module, sock); ap_add_input_filter_handle(ap_core_input_filter_handle, net, NULL, net->c); ap_add_output_filter_handle(ap_core_output_filter_handle, net, NULL, net->c); wsgi_hook_daemon_handler©; ap_lingering_close©; } static apr_status_t wsgi_worker_acquire(int id) { WSGIThreadStack *stack = wsgi_worker_stack; WSGIDaemonThread *thread = &wsgi_worker_threads[id]; while (1) { apr_uint32_t state = stack->state; if (state & (WSGI_STACK_TERMINATED | WSGI_STACK_NO_LISTENER)) { if (state & WSGI_STACK_TERMINATED) { return APR_EINVAL; } if (apr_atomic_cas32(&(stack->state), WSGI_STACK_LAST, state) != state) { continue; } else { return APR_SUCCESS; } } thread->next = state; if (apr_atomic_cas32(&(stack->state), (unsigned)id, state) != state) { continue; } else { apr_status_t rv; if (thread->wakeup) { thread->wakeup = 0; return APR_SUCCESS; } rv = apr_thread_cond_wait(thread->condition, thread->mutex); while (rv == APR_SUCCESS && !thread->wakeup) rv = apr_thread_cond_wait(thread->condition, thread->mutex); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, wsgi_server, "mod_wsgi (pid=%d): " "Wait on thread %d wakeup condition variable " "failed.", getpid(), id); } thread->wakeup = 0; return rv; } } } static apr_status_t wsgi_worker_release(void) { WSGIThreadStack *stack = wsgi_worker_stack; while (1) { apr_uint32_t state = stack->state; unsigned int first = state & WSGI_STACK_HEAD; if (first == WSGI_STACK_LAST) { if (apr_atomic_cas32(&(stack->state), state | WSGI_STACK_NO_LISTENER, state) != state) { continue; } else { return APR_SUCCESS; } } else { WSGIDaemonThread *thread = &wsgi_worker_threads[first]; if (apr_atomic_cas32(&(stack->state), (state ^ first) | thread->next, state) != state) { continue; } else { /* * Flag that thread should be woken up and then * signal it via the condition variable. */ apr_status_t rv; if ((rv = apr_thread_mutex_lock(thread->mutex)) != APR_SUCCESS) { return rv; } thread->wakeup = 1; if ((rv = apr_thread_mutex_unlock(thread->mutex)) != APR_SUCCESS) { return rv; } return apr_thread_cond_signal(thread->condition); } } } } static apr_status_t wsgi_worker_shutdown(void) { int i; apr_status_t rv; WSGIThreadStack *stack = wsgi_worker_stack; while (1) { apr_uint32_t state = stack->state; if (apr_atomic_cas32(&(stack->state), state | WSGI_STACK_TERMINATED, state) == state) { break; } } for (i = 0; i < wsgi_daemon_process->group->threads; i++) { if ((rv = wsgi_worker_release()) != APR_SUCCESS) { return rv; } } return APR_SUCCESS; } static void wsgi_daemon_worker(apr_pool_t *p, WSGIDaemonThread *thread) { apr_status_t status; apr_socket_t *socket; apr_pool_t *ptrans; apr_pollset_t *pollset; apr_pollfd_t pfd = { 0 }; apr_int32_t numdesc; const apr_pollfd_t *pdesc; apr_bucket_alloc_t *bucket_alloc; WSGIDaemonProcess *daemon = thread->process; WSGIProcessGroup *group = daemon->group; /* Loop until signal received to shutdown daemon process. */ while (!wsgi_daemon_shutdown) { apr_status_t rv; /* * Only allow one thread in this process to attempt to * acquire the global process lock as the global process * lock will actually allow all threads in this process * through once one in this process acquires lock. Only * allowing one means better chance of another process * subsequently getting it thereby distributing requests * across processes better and reducing chance of Python * GIL contention. */ wsgi_worker_acquire(thread->id); if (wsgi_daemon_shutdown) break; if (group->mutex) { /* * Grab the accept mutex across all daemon processes * in this process group. */ rv = apr_proc_mutex_lock(group->mutex); if (rv != APR_SUCCESS) { #if 0 #if defined(EIDRM) /* * When using multiple threads locking the * process accept mutex fails with an EIDRM when * process being shutdown but signal check * hasn’t triggered quick enough to set shutdown * flag. This causes lots of error messages to * be logged which make it look like something * nasty has happened even when it hasn’t. For * now assume that if multiple threads and EIDRM * occurs that it is okay and the process is * being shutdown. The condition should by * rights only occur when the Apache parent * process is being shutdown or has died for * some reason so daemon process would logically * therefore also be in process of being * shutdown or killed. */ if (!strcmp(apr_proc_mutex_name(group->mutex), “sysvsem”)) { if (errno == EIDRM && group->threads > 1) wsgi_daemon_shutdown = 1; } #endif #endif if (!wsgi_daemon_shutdown) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, wsgi_server, "mod_wsgi (pid=%d): " "Couldn’t acquire accept mutex '%s’. " "Shutting down daemon process.", getpid(), group->socket_path); wsgi_daemon_shutdown++; kill(getpid(), SIGTERM); sleep(5); } break; } /* * Daemon process being shutdown so don’t accept the * connection after all. */ if (wsgi_daemon_shutdown) { apr_proc_mutex_unlock(group->mutex); wsgi_worker_release(); break; } } apr_pool_create(&ptrans, p); /* * Accept socket connection from the child process. We * test the socket for whether it is ready before actually * performing the accept() so that can know for sure that * we will be processing a request and flag thread as * running. Only bother to do join with thread which is * actually running when process is being shutdown. */ apr_pollset_create(&pollset, 1, ptrans, 0); memset(&pfd, '\0’, sizeof(pfd)); pfd.desc_type = APR_POLL_SOCKET; pfd.desc.s = daemon->listener; pfd.reqevents = APR_POLLIN; pfd.client_data = daemon; apr_pollset_add(pollset, &pfd); rv = apr_pollset_poll(pollset, -1, &numdesc, &pdesc); if (rv != APR_SUCCESS && !APR_STATUS_IS_EINTR(rv)) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, wsgi_server, "mod_wsgi (pid=%d): " "Unable to poll daemon socket for '%s’. " "Shutting down daemon process.", getpid(), group->socket_path); wsgi_daemon_shutdown++; kill(getpid(), SIGTERM); sleep(5); break; } if (wsgi_daemon_shutdown) { if (group->mutex) apr_proc_mutex_unlock(group->mutex); wsgi_worker_release(); apr_pool_destroy(ptrans); break; } if (rv != APR_SUCCESS && APR_STATUS_IS_EINTR(rv)) { if (group->mutex) apr_proc_mutex_unlock(group->mutex); wsgi_worker_release(); apr_pool_destroy(ptrans); continue; } thread->running = 1; status = apr_socket_accept(&socket, daemon->listener, ptrans); if (group->mutex) { apr_status_t rv; rv = apr_proc_mutex_unlock(group->mutex); if (rv != APR_SUCCESS) { if (!wsgi_daemon_shutdown) { wsgi_worker_release(); ap_log_error(APLOG_MARK, APLOG_CRIT, rv, wsgi_server, "mod_wsgi (pid=%d): " "Couldn’t release accept mutex '%s’.", getpid(), group->socket_path); apr_pool_destroy(ptrans); thread->running = 0; break; } } } wsgi_worker_release(); if (status != APR_SUCCESS && APR_STATUS_IS_EINTR(status)) { apr_pool_destroy(ptrans); thread->running = 0; continue; } /* Process the request proxied from the child process. */ apr_thread_mutex_lock(wsgi_monitor_lock); thread->request = apr_time_now(); apr_thread_mutex_unlock(wsgi_monitor_lock); bucket_alloc = apr_bucket_alloc_create(ptrans); wsgi_process_socket(ptrans, socket, bucket_alloc, daemon); apr_thread_mutex_lock(wsgi_monitor_lock); thread->request = 0; apr_thread_mutex_unlock(wsgi_monitor_lock); /* Cleanup ready for next request. */ apr_pool_destroy(ptrans); thread->running = 0; /* Check to see if maximum number of requests reached. */ if (daemon->group->maximum_requests) { if (–wsgi_request_count <= 0) { if (wsgi_graceful_timeout && wsgi_active_requests) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Maximum requests " "reached, attempt a graceful shutdown " "’%s’.", getpid(), daemon->group->name); apr_thread_mutex_lock(wsgi_monitor_lock); wsgi_graceful_shutdown_time = apr_time_now(); wsgi_graceful_shutdown_time += wsgi_graceful_timeout; apr_thread_mutex_unlock(wsgi_monitor_lock); } else { if (!wsgi_daemon_shutdown) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Maximum requests " "reached, triggering immediate shutdown " "’%s’.", getpid(), daemon->group->name); } wsgi_daemon_shutdown++; kill(getpid(), SIGINT); } } } /* Check if graceful shutdown and no active requests. */ if (wsgi_daemon_graceful && !wsgi_daemon_shutdown) { if (wsgi_active_requests == 0) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Requests have completed, " "triggering immediate shutdown '%s’.", getpid(), daemon->group->name); wsgi_daemon_shutdown++; kill(getpid(), SIGINT); } } } if (wsgi_server_config->verbose_debugging) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Exiting thread %d in daemon " "process '%s’.", getpid(), thread->id, thread->process->group->name); } else { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Exiting thread %d in daemon " "process '%s’.", getpid(), thread->id, thread->process->group->name); } } static void *wsgi_daemon_thread(apr_thread_t *thd, void *data) { WSGIDaemonThread *thread = data; apr_pool_t *p = apr_thread_pool_get(thd); if (wsgi_server_config->verbose_debugging) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Started thread %d in daemon " "process '%s’.", getpid(), thread->id, thread->process->group->name); } else { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Started thread %d in daemon " "process '%s’.", getpid(), thread->id, thread->process->group->name); } apr_thread_mutex_lock(thread->mutex); wsgi_daemon_worker(p, thread); apr_thread_exit(thd, APR_SUCCESS); return NULL; } static void *wsgi_reaper_thread(apr_thread_t *thd, void *data) { WSGIDaemonProcess *daemon = data; sleep(daemon->group->shutdown_timeout); ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Aborting process '%s’.", getpid(), daemon->group->name); wsgi_exit_daemon_process(-1); return NULL; } static void *wsgi_deadlock_thread(apr_thread_t *thd, void *data) { WSGIDaemonProcess *daemon = data; PyGILState_STATE gilstate; if (wsgi_server_config->verbose_debugging) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Enable deadlock thread in " "process '%s’.", getpid(), daemon->group->name); } apr_thread_mutex_lock(wsgi_monitor_lock); wsgi_deadlock_shutdown_time = apr_time_now(); wsgi_deadlock_shutdown_time += wsgi_deadlock_timeout; apr_thread_mutex_unlock(wsgi_monitor_lock); while (1) { apr_sleep(apr_time_from_sec(1)); apr_thread_mutex_lock(wsgi_shutdown_lock); if (!wsgi_daemon_shutdown) { gilstate = PyGILState_Ensure(); PyGILState_Release(gilstate); } apr_thread_mutex_unlock(wsgi_shutdown_lock); apr_thread_mutex_lock(wsgi_monitor_lock); wsgi_deadlock_shutdown_time = apr_time_now(); wsgi_deadlock_shutdown_time += wsgi_deadlock_timeout; apr_thread_mutex_unlock(wsgi_monitor_lock); } return NULL; } static void *wsgi_monitor_thread(apr_thread_t *thd, void *data) { WSGIDaemonProcess *daemon = data; WSGIProcessGroup *group = daemon->group; int restart = 0; if (wsgi_server_config->verbose_debugging) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Enable monitor thread in " "process '%s’.", getpid(), group->name); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Startup timeout is %d.", getpid(), (int)(apr_time_sec(wsgi_startup_timeout))); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Deadlock timeout is %d.", getpid(), (int)(apr_time_sec(wsgi_deadlock_timeout))); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Idle inactivity timeout is %d.", getpid(), (int)(apr_time_sec(wsgi_idle_timeout))); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Request time limit is %d.", getpid(), (int)(apr_time_sec(wsgi_request_timeout))); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Graceful timeout is %d.", getpid(), (int)(apr_time_sec(wsgi_graceful_timeout))); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Eviction timeout is %d.", getpid(), (int)(apr_time_sec(wsgi_eviction_timeout))); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Restart interval is %d.", getpid(), (int)(apr_time_sec(wsgi_restart_interval))); } /* * If a restart interval was specified then set up the time for * when the restart should occur. */ if (wsgi_restart_interval) { wsgi_restart_shutdown_time = apr_time_now(); wsgi_restart_shutdown_time += wsgi_restart_interval; } while (1) { apr_time_t now; apr_time_t startup_time; apr_time_t deadlock_time; apr_time_t idle_time; apr_time_t graceful_time; apr_time_t restart_time; apr_time_t request_time = 0; apr_interval_time_t period = 0; int i = 0; now = apr_time_now(); apr_thread_mutex_lock(wsgi_monitor_lock); startup_time = wsgi_startup_shutdown_time; deadlock_time = wsgi_deadlock_shutdown_time; idle_time = wsgi_idle_shutdown_time; graceful_time = wsgi_graceful_shutdown_time; restart_time = wsgi_restart_shutdown_time; if (wsgi_request_timeout && wsgi_worker_threads) { for (i = 0; i<wsgi_daemon_process->group->threads; i++) { if (wsgi_worker_threads[i].request) request_time += (now - wsgi_worker_threads[i].request); } } request_time /= wsgi_daemon_process->group->threads; apr_thread_mutex_unlock(wsgi_monitor_lock); if (!restart && wsgi_request_timeout) { if (request_time > wsgi_request_timeout) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Daemon process request " "time limit exceeded, stopping process " "’%s’.", getpid(), group->name); wsgi_shutdown_reason = "request_timeout"; wsgi_dump_stack_traces = 1; restart = 1; } } if (!restart && wsgi_startup_timeout) { if (startup_time > 0) { if (startup_time <= now) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Application startup " "timer expired, stopping process '%s’.", getpid(), group->name); wsgi_shutdown_reason = "startup_timeout"; restart = 1; } else { period = startup_time - now; } } } if (!restart && wsgi_restart_interval) { if (restart_time > 0) { if (restart_time <= now) { if (!wsgi_daemon_graceful) { if (wsgi_active_requests) { wsgi_daemon_graceful++; apr_thread_mutex_lock(wsgi_monitor_lock); wsgi_graceful_shutdown_time = apr_time_now(); wsgi_graceful_shutdown_time += wsgi_graceful_timeout; apr_thread_mutex_unlock(wsgi_monitor_lock); ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): " "Application restart timer expired, " "waiting for requests to complete " "’%s’.", getpid(), daemon->group->name); } else { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): " "Application restart timer expired, " "stopping process '%s’.", getpid(), daemon->group->name); wsgi_shutdown_reason = "restart_interval"; restart = 1; } } } else { if (!period || ((restart_time - now) < period)) period = restart_time - now; } } } if (!restart && wsgi_deadlock_timeout) { if (deadlock_time) { if (deadlock_time <= now) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Daemon process deadlock " "timer expired, stopping process '%s’.", getpid(), group->name); restart = 1; } else { if (!period || ((deadlock_time - now) < period)) period = deadlock_time - now; } } else { if (!period || (wsgi_deadlock_timeout < period)) period = wsgi_deadlock_timeout; } } if (!restart && wsgi_idle_timeout) { if (idle_time) { if (idle_time <= now) { if (wsgi_active_requests == 0) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Daemon process " "idle inactivity timer expired, " "stopping process '%s’.", getpid(), group->name); wsgi_shutdown_reason = "inactivity_timeout"; restart = 1; } else { /* Ignore for now as still have requests. */ if (!period || (wsgi_idle_timeout < period)) period = wsgi_idle_timeout; } } else { if (!period || ((idle_time - now) < period)) period = idle_time - now; } } else { if (!period || (wsgi_idle_timeout < period)) period = wsgi_idle_timeout; } } if (!restart && wsgi_graceful_timeout) { if (graceful_time) { if (graceful_time <= now) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Daemon process " "graceful timer expired '%s’.", getpid(), group->name); restart = 1; } else { if (!period || ((graceful_time - now) < period)) period = graceful_time - now; else if (wsgi_graceful_timeout < period) period = wsgi_graceful_timeout; } } else { if (!period || (wsgi_graceful_timeout < period)) period = wsgi_graceful_timeout; } } if (!restart && wsgi_eviction_timeout) { if (graceful_time) { if (graceful_time <= now) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Daemon process " "graceful timer expired '%s’.", getpid(), group->name); restart = 1; } else { if (!period || ((graceful_time - now) < period)) period = graceful_time - now; else if (wsgi_eviction_timeout < period) period = wsgi_eviction_timeout; } } else { if (!period || (wsgi_eviction_timeout < period)) period = wsgi_eviction_timeout; } } if (restart) { wsgi_daemon_shutdown++; kill(getpid(), SIGINT); } if (restart || wsgi_request_timeout || period <= 0 || (wsgi_startup_timeout && !wsgi_startup_shutdown_time)) { period = apr_time_from_sec(1); } apr_sleep(period); } return NULL; } #if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 5) static void wsgi_log_stack_traces(void) { PyGILState_STATE state; PyObject *threads = NULL; /* * This should only be called on shutdown so don’t try and log * any errors, just dump them straight out. */ state = PyGILState_Ensure(); threads = _PyThread_CurrentFrames(); if (threads && PyDict_Size(threads) != 0) { PyObject *seq = NULL; seq = PyObject_GetIter(threads); if (seq) { PyObject *id = NULL; PyObject *frame = NULL; Py_ssize_t i = 0; ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Dumping stack trace for " "active Python threads.", getpid()); while (PyDict_Next(threads, &i, &id, &frame)) { apr_int64_t thread_id = 0; PyFrameObject *current = NULL; thread_id = PyLong_AsLong(id); current = (PyFrameObject *)frame; while (current) { int lineno; const char *filename = NULL; const char *name = NULL; #if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 9) lineno = PyFrame_GetLineNumber(current); #else if (current->f_trace) { lineno = current->f_lineno; } else { lineno = PyCode_Addr2Line(current->f_code, current->f_lasti); } #endif #if PY_MAJOR_VERSION >= 3 #if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 9) filename = PyUnicode_AsUTF8(PyFrame_GetCode(current)->co_filename); name = PyUnicode_AsUTF8(PyFrame_GetCode(current)->co_name); #else filename = PyUnicode_AsUTF8(current->f_code->co_filename); name = PyUnicode_AsUTF8(current->f_code->co_name); #endif #else filename = PyString_AsString(current->f_code->co_filename); name = PyString_AsString(current->f_code->co_name); #endif if (current == (PyFrameObject *)frame) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, “mod_wsgi (pid=%d): Thread %” APR_INT64_T_FMT " executing file \"%s\", line %d, in %s", getpid(), thread_id, filename, lineno, name); } else { #if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 9) if (PyFrame_GetBack(current)) { #else if (current->f_back) { #endif ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): called from file " "\"%s\", line %d, in %s,", getpid(), filename, lineno, name); } else { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): called from file " "\"%s\", line %d, in %s.", getpid(), filename, lineno, name); } } #if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 9) current = PyFrame_GetBack(current); #else current = current->f_back; #endif } } } else { ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server, "mod_wsgi (pid=%d): Failed to iterate over " "current frames for active threads.", getpid()); PyErr_Print(); PyErr_Clear(); } } else { ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server, "mod_wsgi (pid=%d): Failed to get current frames " "for active threads.", getpid()); PyErr_Print(); PyErr_Clear(); } Py_XDECREF(threads); PyGILState_Release(state); } #endif static void wsgi_daemon_main(apr_pool_t *p, WSGIDaemonProcess *daemon) { apr_threadattr_t *thread_attr; apr_thread_t *reaper = NULL; int i; apr_status_t rv; apr_status_t thread_rv; apr_pollfd_t poll_fd; apr_int32_t poll_count = 0; /* * Setup poll object for listening for shutdown notice from * signal handler. */ poll_fd.desc_type = APR_POLL_FILE; poll_fd.reqevents = APR_POLLIN; poll_fd.desc.f = wsgi_signal_pipe_in; /* Initialise maximum request count for daemon. */ if (daemon->group->maximum_requests) wsgi_request_count = daemon->group->maximum_requests; /* Ensure that threads are joinable. */ apr_threadattr_create(&thread_attr, p); apr_threadattr_detach_set(thread_attr, 0); #if (APR_MAJOR_VERSION >= 1) if (daemon->group->stack_size) { apr_threadattr_stacksize_set(thread_attr, daemon->group->stack_size); } #endif /* Start monitoring thread if required. */ wsgi_startup_timeout = daemon->group->startup_timeout; wsgi_deadlock_timeout = daemon->group->deadlock_timeout; wsgi_idle_timeout = daemon->group->inactivity_timeout; wsgi_request_timeout = daemon->group->request_timeout; wsgi_graceful_timeout = daemon->group->graceful_timeout; wsgi_eviction_timeout = daemon->group->eviction_timeout; wsgi_restart_interval = daemon->group->restart_interval; if (wsgi_deadlock_timeout || wsgi_idle_timeout) { rv = apr_thread_create(&reaper, thread_attr, wsgi_monitor_thread, daemon, p); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ALERT, rv, wsgi_server, "mod_wsgi (pid=%d): Couldn’t create monitor " "thread in daemon process '%s’.", getpid(), daemon->group->name); } } if (wsgi_deadlock_timeout) { if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ALERT, rv, wsgi_server, "mod_wsgi (pid=%d): Couldn’t create deadlock " "thread in daemon process '%s’.", getpid(), daemon->group->name); } rv = apr_thread_create(&reaper, thread_attr, wsgi_deadlock_thread, daemon, p); } /* Initialise worker stack. */ wsgi_worker_stack = (WSGIThreadStack *)apr_palloc(p, sizeof(WSGIThreadStack)); wsgi_worker_stack->state = WSGI_STACK_NO_LISTENER | WSGI_STACK_LAST; /* Start the required number of threads. */ wsgi_worker_threads = (WSGIDaemonThread *)apr_pcalloc(p, daemon->group->threads * sizeof(WSGIDaemonThread)); if (wsgi_server_config->verbose_debugging) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Starting %d threads in daemon " "process '%s’.", getpid(), daemon->group->threads, daemon->group->name); } for (i=0; i<daemon->group->threads; i++) { WSGIDaemonThread *thread = &wsgi_worker_threads[i]; if (wsgi_server_config->verbose_debugging) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Starting thread %d in daemon " "process '%s’.", getpid(), i+1, daemon->group->name); } /* Create the mutex and condition variable for this thread. */ rv = apr_thread_cond_create(&thread->condition, p); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ALERT, rv, wsgi_server, "mod_wsgi (pid=%d): Couldn’t create worker " "thread %d state condition variable in daemon " "process '%s’.", getpid(), i, daemon->group->name); /* * Try to force an exit of the process if fail * to create the worker threads. */ kill(getpid(), SIGTERM); sleep(5); } rv = apr_thread_mutex_create(&thread->mutex, APR_THREAD_MUTEX_DEFAULT, p); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ALERT, rv, wsgi_server, "mod_wsgi (pid=%d): Couldn’t create worker " "thread %d state mutex variable in daemon " "process '%s’.", getpid(), i, daemon->group->name); /* * Try to force an exit of the process if fail * to create the worker threads. */ kill(getpid(), SIGTERM); sleep(5); } /* Now create the actual thread. */ thread->id = i; thread->process = daemon; thread->running = 0; thread->request = 0; rv = apr_thread_create(&thread->thread, thread_attr, wsgi_daemon_thread, thread, p); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ALERT, rv, wsgi_server, "mod_wsgi (pid=%d): Couldn’t create worker " "thread %d in daemon process '%s’.", getpid(), i, daemon->group->name); /* * Try to force an exit of the process if fail * to create the worker threads. */ kill(getpid(), SIGTERM); sleep(5); } } /* Block until we get a process shutdown signal. */ while (1) { char buf[1]; apr_size_t nbytes = 1; rv = apr_poll(&poll_fd, 1, &poll_count, -1); if (APR_STATUS_IS_EINTR(rv)) continue; rv = apr_file_read(wsgi_signal_pipe_in, buf, &nbytes); if (rv != APR_SUCCESS || nbytes != 1) { ap_log_error(APLOG_MARK, APLOG_ALERT, 0, wsgi_server, "mod_wsgi (pid=%d): Failed read on signal pipe '%s’.", getpid(), daemon->group->name); break; } if (buf[0] == ‘C’) { if (!wsgi_daemon_graceful) { wsgi_shutdown_reason = "cpu_time_limit"; if (wsgi_active_requests) { wsgi_daemon_graceful++; apr_thread_mutex_lock(wsgi_monitor_lock); wsgi_graceful_shutdown_time = apr_time_now(); wsgi_graceful_shutdown_time += wsgi_graceful_timeout; apr_thread_mutex_unlock(wsgi_monitor_lock); ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Exceeded CPU time " "limit, waiting for requests to complete " "’%s’.", getpid(), daemon->group->name); } else { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Exceeded CPU time " "limit, triggering immediate shutdown " "’%s’.", getpid(), daemon->group->name); wsgi_daemon_shutdown++; kill(getpid(), SIGINT); } } } else if (buf[0] == ‘G’) { if (!wsgi_daemon_graceful) { wsgi_shutdown_reason = "graceful_signal"; if (wsgi_active_requests) { wsgi_daemon_graceful++; apr_thread_mutex_lock(wsgi_monitor_lock); wsgi_graceful_shutdown_time = apr_time_now(); if (wsgi_eviction_timeout) wsgi_graceful_shutdown_time += wsgi_eviction_timeout; else wsgi_graceful_shutdown_time += wsgi_graceful_timeout; apr_thread_mutex_unlock(wsgi_monitor_lock); ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Process eviction " "requested, waiting for requests to complete " "’%s’.", getpid(), daemon->group->name); } else { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Process eviction " "requested, triggering immediate shutdown " "’%s’.", getpid(), daemon->group->name); wsgi_daemon_shutdown++; kill(getpid(), SIGINT); } } } else break; } ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Shutdown requested '%s’.", getpid(), daemon->group->name); /* * Create a reaper thread to abort process if graceful * shutdown takes too long. Not recommended to disable * this unless external process is controlling shutdown. */ if (daemon->group->shutdown_timeout) { rv = apr_thread_create(&reaper, thread_attr, wsgi_reaper_thread, daemon, p); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ALERT, rv, wsgi_server, "mod_wsgi (pid=%d): Couldn’t create reaper " "thread in daemon process '%s’.", getpid(), daemon->group->name); } } /* * If shutting down process due to reaching request time * limit, then try and dump out stack traces of any threads * which are running as a debugging aid. */ wsgi_publish_process_stopping(wsgi_shutdown_reason); #if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 5) if (wsgi_dump_stack_traces) wsgi_log_stack_traces(); #endif /* * Attempt a graceful shutdown by waiting for any * threads which were processing a request at the time * of shutdown. In some respects this is a bit pointless * as even though we allow the requests to be completed, * the Apache child process which proxied the request * through to this daemon process could get killed off * before the daemon process and so the response gets * cut off or lost. */ wsgi_worker_shutdown(); for (i=0; i<daemon->group->threads; i++) { if (wsgi_worker_threads[i].thread && wsgi_worker_threads[i].running) { rv = apr_thread_join(&thread_rv, wsgi_worker_threads[i].thread); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, wsgi_server, "mod_wsgi (pid=%d): Couldn’t join with " "worker thread %d in daemon process '%s’.", getpid(), i, daemon->group->name); } } } } static apr_status_t wsgi_cleanup_process(void *data) { WSGIProcessGroup *group = (WSGIProcessGroup *)data; /* Only do cleanup if in Apache parent process. */ if (wsgi_parent_pid != getpid()) return APR_SUCCESS; if (group->listener_fd != -1) { if (close(group->listener_fd) < 0) { ap_log_error(APLOG_MARK, APLOG_ERR, errno, wsgi_server, "mod_wsgi (pid=%d): " "Couldn’t close unix domain socket '%s’.", getpid(), group->socket_path); } if (unlink(group->socket_path) < 0 && errno != ENOENT) { ap_log_error(APLOG_MARK, APLOG_ERR, errno, wsgi_server, "mod_wsgi (pid=%d): " "Couldn’t unlink unix domain socket '%s’.", getpid(), group->socket_path); } } return APR_SUCCESS; } static int wsgi_start_process(apr_pool_t *p, WSGIDaemonProcess *daemon) { apr_status_t status; ap_listen_rec *lr; WSGIProcessGroup *entries = NULL; WSGIProcessGroup *entry = NULL; int i = 0; if ((status = apr_proc_fork(&daemon->process, p)) < 0) { ap_log_error(APLOG_MARK, APLOG_ALERT, errno, wsgi_server, "mod_wsgi: Couldn’t spawn process '%s’.", daemon->group->name); return DECLINED; } else if (status == APR_INCHILD) { if (!geteuid()) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Starting process ‘%s’ with " "uid=%ld, gid=%u and threads=%d.", getpid(), daemon->group->name, (long)daemon->group->uid, (unsigned)daemon->group->gid, daemon->group->threads); } else { ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Starting process ‘%s’ with " "threads=%d.", getpid(), daemon->group->name, daemon->group->threads); } #ifdef HAVE_BINDPROCESSOR /* * By default, AIX binds to a single processor. This * bit unbinds children which will then bind to another * CPU. */ status = bindprocessor(BINDPROCESS, (int)getpid(), PROCESSOR_CLASS_ANY); if (status != OK) { ap_log_error(APLOG_MARK, APLOG_ERR, errno, wsgi_server, "mod_wsgi (pid=%d): Failed to unbind processor.", getpid()); } #endif /* Setup daemon process name displayed by 'ps’. */ wsgi_setup_daemon_name(daemon, p); /* Adjust CPU priority if overridden. */ if (daemon->group->cpu_priority != 0) { if (setpriority(PRIO_PROCESS, 0, daemon->group->cpu_priority) == -1) { ap_log_error(APLOG_MARK, APLOG_ERR, errno, wsgi_server, "mod_wsgi (pid=%d): Couldn’t set CPU priority " "in daemon process '%d’.", getpid(), daemon->group->cpu_priority); } } /* Setup daemon process user/group/umask etc. */ if (wsgi_setup_access(daemon) == -1) { /* * If we get any failure from setting up the appropriate * permissions or working directory for the daemon process * then we exit the process. Don’t die immediately to avoid * a fork bomb. */ ap_log_error(APLOG_MARK, APLOG_ALERT, 0, wsgi_server, "mod_wsgi (pid=%d): Failure to configure the " "daemon process correctly and process left in " "unspecified state. Restarting daemon process " "after delay.", getpid()); sleep(20); wsgi_exit_daemon_process(-1); } /* Reinitialise accept mutex in daemon process. */ if (daemon->group->mutex) { status = apr_proc_mutex_child_init(&daemon->group->mutex, daemon->group->mutex_path, p); if (status != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, 0, wsgi_server, "mod_wsgi (pid=%d): Couldn’t initialise accept " "mutex in daemon process '%s’.", getpid(), daemon->group->mutex_path); /* Don’t die immediately to avoid a fork bomb. */ sleep(20); wsgi_exit_daemon_process(-1); } } /* * Create a lookup table of listener socket address * details so can use it later in daemon when trying * to map request to correct virtual host server. */ wsgi_daemon_listeners = apr_hash_make§; for (lr = ap_listeners; lr; lr = lr->next) { char *key; char *host; apr_port_t port; host = lr->bind_addr->hostname; port = lr->bind_addr->port; if (!host) host = ""; key = apr_psprintf(p, "%s|%d", host, port); apr_hash_set(wsgi_daemon_listeners, key, APR_HASH_KEY_STRING, lr->bind_addr); } /* * Close child copy of the listening sockets for the * Apache parent process so we don’t interfere with * the parent process. */ ap_close_listeners(); /* * Cleanup the Apache scoreboard to ensure that any * shared memory segments or memory mapped files not * available to code in daemon processes. */ /* * XXX If this is closed, under Apache 2.4 then daemon * mode processes will crash. Not much choice but to * leave it open. Daemon mode really needs to be * rewritten not to use normal Apache request object and * output bucket chain to avoid potential for problems. */ #if 0 ap_cleanup_scoreboard(0); #endif /* * Wipe out random value used in magic token so that not * possible for user code running in daemon process to * discover this value for other daemon process groups. * In other words, wipe out all but our own. */ entries = (WSGIProcessGroup *)wsgi_daemon_list->elts; for (i = 0; i < wsgi_daemon_list->nelts; ++i) { entry = &entries[i]; if (entry != daemon->group) entry->random = 0; } /* * Close listener socket for daemon processes for other * daemon process groups. In other words, close all but * our own. */ entries = (WSGIProcessGroup *)wsgi_daemon_list->elts; for (i = 0; i < wsgi_daemon_list->nelts; ++i) { entry = &entries[i]; if (entry != daemon->group && entry->listener_fd != -1) { close(entry->listener_fd); entry->listener_fd = -1; } } /* * Register signal handler to receive shutdown signal * from Apache parent process. We need to first create * pipe by which signal handler can notify the main * thread that signal has arrived indicating that * process needs to shutdown. */ status = apr_file_pipe_create(&wsgi_signal_pipe_in, &wsgi_signal_pipe_out, p); if (status != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_EMERG, status, wsgi_server, "mod_wsgi (pid=%d): Couldn’t initialise signal " "pipe in daemon process '%s’.", getpid(), daemon->group->name); /* Don’t die immediately to avoid a fork bomb. */ sleep(20); wsgi_exit_daemon_process(-1); } wsgi_daemon_shutdown = 0; wsgi_daemon_pid = getpid(); apr_signal(SIGINT, wsgi_signal_handler); apr_signal(SIGTERM, wsgi_signal_handler); apr_signal(AP_SIG_GRACEFUL, wsgi_signal_handler); #ifdef SIGXCPU apr_signal(SIGXCPU, wsgi_signal_handler); #endif /* Set limits on amount of CPU time that can be used. */ if (daemon->group->cpu_time_limit > 0) { struct rlimit limit; int result = -1; errno = ENOSYS; limit.rlim_cur = daemon->group->cpu_time_limit; limit.rlim_max = daemon->group->cpu_time_limit + 1; limit.rlim_max += daemon->group->shutdown_timeout; #if defined(RLIMIT_CPU) result = setrlimit(RLIMIT_CPU, &limit); #endif if (result == -1) { ap_log_error(APLOG_MARK, APLOG_CRIT, errno, wsgi_server, "mod_wsgi (pid=%d): Couldn’t set CPU time " "limit of %d seconds for process '%s’.", getpid(), daemon->group->cpu_time_limit, daemon->group->name); } } /* * Set limits on amount of date segment memory that can * be used. Although this is done, some platforms * doesn’t actually support it. */ if (daemon->group->memory_limit > 0) { struct rlimit limit; int result = -1; errno = ENOSYS; limit.rlim_cur = daemon->group->memory_limit; limit.rlim_max = daemon->group->memory_limit; #if defined(RLIMIT_DATA) result = setrlimit(RLIMIT_DATA, &limit); #endif if (result == -1) { ap_log_error(APLOG_MARK, APLOG_CRIT, errno, wsgi_server, "mod_wsgi (pid=%d): Couldn’t set memory " "limit of %ld for process '%s’.", getpid(), (long)daemon->group->memory_limit, daemon->group->name); } } /* * Set limits on amount of virtual memory that can be used. * Although this is done, some platforms doesn’t actually * support it. */ if (daemon->group->virtual_memory_limit > 0) { struct rlimit limit; int result = -1; errno = ENOSYS; limit.rlim_cur = daemon->group->virtual_memory_limit; limit.rlim_max = daemon->group->virtual_memory_limit; #if defined(RLIMIT_AS) result = setrlimit(RLIMIT_AS, &limit); #elif defined(RLIMIT_VMEM) result = setrlimit(RLIMIT_VMEM, &limit); #endif if (result == -1) { ap_log_error(APLOG_MARK, APLOG_CRIT, errno, wsgi_server, "mod_wsgi (pid=%d): Couldn’t set virtual memory " "limit of %ld for process '%s’.", getpid(), (long)daemon->group->virtual_memory_limit, daemon->group->name); } } /* * Flag whether multiple daemon processes or denoted * that requests could be spread across multiple daemon * process groups. */ wsgi_multiprocess = daemon->group->multiprocess; wsgi_multithread = daemon->group->threads != 1; /* * Create a pool for the child daemon process so * we can trigger various events off it at shutdown. */ apr_pool_create(&wsgi_daemon_pool, p); /* * Retain a reference to daemon process details. Do * this here as when doing lazy initialisation of * the interpreter we want to know if in a daemon * process so can pick any daemon process specific * home directory for Python installation. */ wsgi_daemon_group = daemon->group->name; wsgi_daemon_process = daemon; /* Set lang/locale if specified for daemon process. */ if (daemon->group->lang) { char *envvar; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Setting lang to %s for " "daemon process group %s.", getpid(), daemon->group->lang, daemon->group->name); envvar = apr_pstrcat(p, "LANG=", daemon->group->lang, NULL); putenv(envvar); } if (daemon->group->locale) { char *envvar; char *result; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Setting locale to %s for " "daemon process group %s.", getpid(), daemon->group->locale, daemon->group->name); envvar = apr_pstrcat(p, "LC_ALL=", daemon->group->locale, NULL); putenv(envvar); result = setlocale(LC_ALL, daemon->group->locale); if (!result) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server, "mod_wsgi (pid=%d): Unsupported locale setting " "%s specified for daemon process group %s. " "Consider using ‘C.UTF-8’ as fallback setting.", getpid(), daemon->group->locale, daemon->group->name); } } /* Create lock for request monitoring. */ apr_thread_mutex_create(&wsgi_monitor_lock, APR_THREAD_MUTEX_UNNESTED, p); /* * Initialise Python if required to be done in the child * process. Note that it will not be initialised if * mod_python loaded and it has already been done. */ if (wsgi_python_after_fork) wsgi_python_init§; #if PY_MAJOR_VERSION < 3 /* * If mod_python is also being loaded and thus it was * responsible for initialising Python it can leave in * place an active thread state. Under normal conditions * this would be eliminated in Apache child process by * the time that mod_wsgi got to do its own child * initialisation but in daemon process we skip the * mod_python child initialisation so the active thread * state still exists. Thus need to do a bit of a fiddle * to ensure there is no active thread state. Don’t need * to worry about this with Python 3.X as mod_python * only supports Python 2.X. */ if (!wsgi_python_initialized) { PyGILState_STATE state; PyEval_AcquireLock(); state = PyGILState_Ensure(); PyGILState_Release(state); if (state == PyGILState_LOCKED) PyThreadState_Swap(NULL); PyEval_ReleaseLock(); } #endif /* * If the daemon is associated with a virtual host then * we can close all other error logs so long as they * aren’t the same one as being used for the virtual * host. If the virtual host error log is different to * the main server error log, then also tie stderr to * that log file instead. This way any debugging sent * direct to stderr from C code also goes to the virtual * host error log. We close the error logs that aren’t * required as that eliminates possibility that user * code executing in daemon process could maliciously * dump messages into error log for a different virtual * host, as well as stop them being reopened with mode * that would allow seeking back to start of file and * read any information in them. */ if (daemon->group->server->is_virtual) { server_rec *server = NULL; apr_file_t *errfile = NULL; /* * Iterate over all servers and close any error * logs different to that for virtual host. Note that * if errors are being redirected to syslog, then * the server error log reference will actually be * a null pointer, so need to ensure that check for * that and don’t attempt to close it in that case. */ server = wsgi_server; while (server != NULL) { if (server->error_log && server->error_log != daemon->group->server->error_log) { apr_file_close(server->error_log); } server = server->next; } /* * Reassociate stderr output with error log from the * virtual host the daemon is associated with. Close * the virtual host error log and point it at stderr * log instead. Do the latter so don’t get two * references to same open file. Just in case * anything still accesses error log of main server, * map main server error log to that of the virtual * host. Note that cant do this if errors are being * redirected to syslog, as indicated by virtual * host error log being a null pointer. In that case * just leave everything as it was. Also can’t remap * the error log for main server if it was being * redirected to syslog but virtual host wasn’t. */ if (daemon->group->server->error_log && daemon->group->server->error_log != wsgi_server->error_log) { apr_file_t *oldfile = NULL; apr_file_open_stderr(&errfile, wsgi_server->process->pool); apr_file_dup2(errfile, daemon->group->server->error_log, wsgi_server->process->pool); oldfile = daemon->group->server->error_log; server = wsgi_server; while (server != NULL) { if (server->error_log == oldfile) server->error_log = errfile; server = server->next; } apr_file_close(oldfile); if (wsgi_server->error_log) wsgi_server->error_log = errfile; } } /* * Update reference to server object in case daemon * process is actually associated with a virtual host. * This way all logging actually goes into the virtual * hosts log file. */ if (daemon->group->server) { if (wsgi_server_config->verbose_debugging) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Process ‘%s’ logging to " "’%s’.", getpid(), daemon->group->name, daemon->group->server->server_hostname); } wsgi_server = daemon->group->server; } else { if (wsgi_server_config->verbose_debugging) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Process ‘%s’ forced to log " "to '%s’.", getpid(), daemon->group->name, wsgi_server->server_hostname); } } /* Time daemon process started waiting for requests. */ wsgi_restart_time = apr_time_now(); /* * Setup Python in the child daemon process. Note that * we ensure that we are now marked as the original * initialiser of the Python interpreter even though * mod_python might have done it, as we will be the one * to cleanup the child daemon process and not * mod_python. We also need to perform the special * Python setup which has to be done after a fork. */ wsgi_python_initialized = 1; wsgi_python_path = daemon->group->python_path; wsgi_python_eggs = daemon->group->python_eggs; wsgi_newrelic_config_file = daemon->group->newrelic_config_file; wsgi_newrelic_environment = daemon->group->newrelic_environment; wsgi_python_child_init(wsgi_daemon_pool); /* * Create socket wrapper for listener file descriptor * and mutex for controlling which thread gets to * perform the accept() when a connection is ready. */ apr_os_sock_put(&daemon->listener, &daemon->group->listener_fd, p); /* * Run the main routine for the daemon process if there * is a non zero number of threads. When number of threads * is zero we actually go on and shutdown immediately. */ if (daemon->group->threads != 0) wsgi_daemon_main(p, daemon); /* * Destroy the pool for the daemon process. This will * have the side affect of also destroying Python. */ ap_log_error(APLOG_MARK, APLOG_INFO, 0, wsgi_server, "mod_wsgi (pid=%d): Stopping process ‘%s’.", getpid(), daemon->group->name); apr_pool_destroy(wsgi_daemon_pool); /* Exit the daemon process when being shutdown. */ wsgi_exit_daemon_process(0); } #ifdef HAVE_FORK if (wsgi_python_initialized) { #if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 7) #if 0 /* * XXX Appears to be wrong to call this at this point especially * since we haven’t acquired the GIL. It wouldn’t have been possible * for any user code to have registered a Python callback to run * in parent after fork either. Leave in code for now but disabled. */ PyOS_AfterFork_Parent(); #endif #endif } #endif apr_pool_note_subprocess(p, &daemon->process, APR_KILL_AFTER_TIMEOUT); apr_proc_other_child_register(&daemon->process, wsgi_manage_process, daemon, NULL, p); return OK; } static int wsgi_start_daemons(apr_pool_t *p) { WSGIProcessGroup *entries = NULL; WSGIProcessGroup *entry = NULL; WSGIDaemonProcess *process = NULL; int mpm_generation = 0; int i, j; /* Do we need to create any daemon processes. */ if (!wsgi_daemon_list) return OK; /* What server generation is this. */ #if defined(AP_MPMQ_GENERATION) ap_mpm_query(AP_MPMQ_GENERATION, &mpm_generation); #else mpm_generation = ap_my_generation; #endif /* * Cache references to root server and pool as will need * to access these when restarting daemon process when * they die. */ wsgi_parent_pool = p; /* * Startup in turn the required number of daemon processes * for each of the named process groups. */ wsgi_daemon_index = apr_hash_make§; entries = (WSGIProcessGroup *)wsgi_daemon_list->elts; for (i = 0; i < wsgi_daemon_list->nelts; ++i) { int status; entry = &entries[i]; /* * Check for whether the daemon process user and * group are the default Apache values. If they are * then reset them to the current values configured for * Apache. This is to work around where the User/Group * directives had not been set before the WSGIDaemonProcess * directive was used in configuration file. In this case, * where no ‘user’ and ‘group’ options were provided, * the default values would have been used, but these * were later overridden thus why we need to update it. */ if (entry->uid == ap_uname2id(DEFAULT_USER)) { entry->uid = ap_unixd_config.user_id; entry->user = ap_unixd_config.user_name; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Reset default user for " "daemon process group ‘%s’ to uid=%ld.", getpid(), entry->name, (long)entry->uid); } if (entry->gid == ap_gname2id(DEFAULT_GROUP)) { entry->gid = ap_unixd_config.group_id; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Reset default group for " "daemon process group ‘%s’ to gid=%ld.", getpid(), entry->name, (long)entry->gid); } /* * Calculate path for socket to accept requests on and * create the socket. */ entry->socket_rotation = wsgi_server_config->socket_rotation; if (entry->socket_rotation) { entry->socket_path = apr_psprintf(p, "%s.%d.%d.%d.sock", wsgi_server_config->socket_prefix, getpid(), mpm_generation, entry->id); } else { entry->socket_path = apr_psprintf(p, "%s.%d.u%d.%d.sock", wsgi_server_config->socket_prefix, getpid(), entry->uid, entry->id); } apr_hash_set(wsgi_daemon_index, entry->name, APR_HASH_KEY_STRING, entry); entry->listener_fd = wsgi_setup_socket(entry); if (entry->listener_fd == -1) return DECLINED; /* * Register cleanup so that listener socket is cleaned * up properly on a restart and on shutdown. */ apr_pool_cleanup_register(p, entry, wsgi_cleanup_process, apr_pool_cleanup_null); /* * If there is more than one daemon process in the group * then need to create an accept mutex for the daemon * processes to use so they don’t interfere with each * other. */ if (entry->processes > 1) { entry->mutex_path = apr_psprintf(p, "%s.%d.%d.%d.lock", wsgi_server_config->socket_prefix, getpid(), mpm_generation, entry->id); status = apr_proc_mutex_create(&entry->mutex, entry->mutex_path, wsgi_server_config->lock_mechanism, p); if (status != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, errno, wsgi_server, "mod_wsgi (pid=%d): Couldn’t create accept " "lock ‘%s’ (%d).", getpid(), entry->mutex_path, wsgi_server_config->lock_mechanism); return DECLINED; } /* * Depending on the locking mechanism being used * need to change the permissions of the lock. Can’t * use unixd_set_proc_mutex_perms() as it uses the * default Apache child process uid/gid where the * daemon process uid/gid can be different. */ if (!geteuid()) { #if APR_HAS_SYSVSEM_SERIALIZE if (!strcmp(apr_proc_mutex_name(entry->mutex), “sysvsem”)) { apr_os_proc_mutex_t ospmutex; #if !APR_HAVE_UNION_SEMUN union semun { long val; struct semid_ds *buf; unsigned short *array; }; #endif union semun ick; struct semid_ds buf; apr_os_proc_mutex_get(&ospmutex, entry->mutex); buf.sem_perm.uid = entry->uid; buf.sem_perm.gid = entry->gid; buf.sem_perm.mode = 0600; ick.buf = &buf; if (semctl(ospmutex.crossproc, 0, IPC_SET, ick) < 0) { ap_log_error(APLOG_MARK, APLOG_CRIT, errno, wsgi_server, "mod_wsgi (pid=%d): " "Couldn’t set permissions on accept " "mutex ‘%s’ (sysvsem).", getpid(), entry->mutex_path); return DECLINED; } } #endif #if APR_HAS_FLOCK_SERIALIZE if (!strcmp(apr_proc_mutex_name(entry->mutex), “flock”)) { if (chown(entry->mutex_path, entry->uid, -1) < 0) { ap_log_error(APLOG_MARK, APLOG_CRIT, errno, wsgi_server, "mod_wsgi (pid=%d): " "Couldn’t set permissions on accept " "mutex ‘%s’ (flock).", getpid(), entry->mutex_path); return DECLINED; } } #endif } } /* Create the actual required daemon processes. */ for (j = 1; j <= entry->processes; j++) { process = (WSGIDaemonProcess *)apr_pcalloc(p, sizeof( WSGIDaemonProcess)); process->group = entry; process->instance = j; status = wsgi_start_process(p, process); if (status != OK) return status; } } return OK; } static apr_pool_t *wsgi_pconf_pool = NULL; static int wsgi_deferred_start_daemons(apr_pool_t *p, ap_scoreboard_e sb_type) { return wsgi_start_daemons(wsgi_pconf_pool); } static apr_status_t wsgi_socket_connect_un(apr_socket_t *sock, struct sockaddr_un *sa) { apr_status_t rv; apr_os_sock_t rawsock; apr_interval_time_t t; rv = apr_os_sock_get(&rawsock, sock); if (rv != APR_SUCCESS) { return rv; } rv = apr_socket_timeout_get(sock, &t); if (rv != APR_SUCCESS) { return rv; } do { rv = connect(rawsock, (struct sockaddr*)sa, APR_OFFSETOF(struct sockaddr_un, sun_path) + strlen(sa->sun_path) + 1); } while (rv == -1 && errno == EINTR); if ((rv == -1) && (errno == EINPROGRESS || errno == EALREADY) && (t > 0)) { #if APR_MAJOR_VERSION < 2 rv = apr_wait_for_io_or_timeout(NULL, sock, 0); #else rv = apr_socket_wait(sock, APR_WAIT_WRITE); #endif if (rv != APR_SUCCESS) { return rv; } } if (rv == -1 && errno != EISCONN) { return errno; } return APR_SUCCESS; } static int wsgi_connect_daemon(request_rec *r, WSGIDaemonSocket *daemon) { WSGIRequestConfig *config = NULL; apr_status_t rv; struct sockaddr_un addr; int retries = 0; apr_interval_time_t timer = 0; apr_interval_time_t total_time = 0; apr_time_t start_time = 0; /* Grab request configuration. */ config = (WSGIRequestConfig *)ap_get_module_config(r->request_config, &wsgi_module); memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; apr_cpystrn(addr.sun_path, daemon->socket_path, sizeof(addr.sun_path)); start_time = apr_time_now(); while (1) { retries++; config->daemon_connects++; rv = apr_socket_create(&daemon->socket, AF_UNIX, SOCK_STREAM, 0, r->pool); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, "mod_wsgi (pid=%d): Unable to create socket to " "connect to WSGI daemon process.", getpid()); return HTTP_INTERNAL_SERVER_ERROR; } /* * Apply timeout before issuing the socket connection in * case this hangs for some reason. Would have to be an extreme * event for a UNIX socket connect to hang, but have had some * unexplained situations which look exactly like that. */ if (daemon->socket_timeout) apr_socket_timeout_set(daemon->socket, daemon->socket_timeout); else apr_socket_timeout_set(daemon->socket, r->server->timeout); rv = wsgi_socket_connect_un(daemon->socket, &addr); if (rv != APR_SUCCESS) { /* * We need to check for both connection refused and * connection unavailable as Linux systems when * connecting to a UNIX listener socket in non * blocking mode, where the listener backlog is full * will return the error EAGAIN rather than returning * ECONNREFUSED as is supposedly dictated by POSIX. */ if (APR_STATUS_IS_ECONNREFUSED(rv) || APR_STATUS_IS_EAGAIN(rv)) { if ((apr_time_now()-start_time) < daemon->connect_timeout) { if (wsgi_server_config->verbose_debugging) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, "mod_wsgi (pid=%d): Connection attempt " "#%d to WSGI daemon process ‘%s’ on " "’%s’ failed, sleeping before retrying " "again.", getpid(), retries, daemon->name, daemon->socket_path); } apr_socket_close(daemon->socket); /* * Progressively increase time we wait between * connection attempts. Start at 0.125 second, but * back off to 1 second interval after 2 seconds. */ if (total_time < apr_time_make(2, 0)) timer = apr_time_make(0, 125000); else timer = apr_time_make(1, 0); apr_sleep(timer); total_time += timer; } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "mod_wsgi (pid=%d): Unable to connect to " "WSGI daemon process ‘%s’ on ‘%s’ after " "multiple attempts as listener backlog " "limit was exceeded or the socket does " "not exist.", getpid(), daemon->name, daemon->socket_path); apr_socket_close(daemon->socket); return HTTP_SERVICE_UNAVAILABLE; } } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "mod_wsgi (pid=%d): Unable to connect to " "WSGI daemon process ‘%s’ on ‘%s’ as user " "with uid=%ld.", getpid(), daemon->name, daemon->socket_path, (long)geteuid()); apr_socket_close(daemon->socket); return HTTP_SERVICE_UNAVAILABLE; } } else break; } return OK; } static apr_status_t wsgi_socket_send(apr_socket_t *sock, const char *buf, size_t buf_size) { apr_status_t rv; apr_size_t len; while (buf_size > 0) { len = buf_size; rv = apr_socket_send(sock, buf, &len); if (rv != APR_SUCCESS) return rv; buf += len; buf_size -= len; } return APR_SUCCESS; } static apr_status_t wsgi_socket_sendv_limit(apr_socket_t *sock, struct iovec *vec, size_t nvec) { apr_status_t rv; apr_size_t written = 0; apr_size_t to_write = 0; size_t i, offset; /* Calculate how much has to be sent. */ for (i = 0; i < nvec; i++) { to_write += vec[i].iov_len; } /* Loop until all data has been sent. */ offset = 0; while (to_write) { apr_size_t n = 0; rv = apr_socket_sendv(sock, vec+offset, nvec-offset, &n); if (rv != APR_SUCCESS) return rv; if (n > 0) { /* Bail out of all data has been sent. */ written += n; if (written >= to_write) break; /* * Not all data was sent, so ween need to try * again with the remainder of the data. We * first need to work out where to start from. */ for (i = offset; i < nvec; ) { if (n >= vec[i].iov_len) { offset++; n -= vec[i++].iov_len; } else { vec[i].iov_len -= n; vec[i].iov_base = (char *) vec[i].iov_base + n; break; } } } } return APR_SUCCESS; } static apr_status_t wsgi_socket_sendv(apr_socket_t *sock, struct iovec *vec, size_t nvec) { #if defined(_SC_IOV_MAX) static size_t iov_max = 0; if (iov_max == 0) iov_max = sysconf(_SC_IOV_MAX); #else static size_t iov_max = APR_MAX_IOVEC_SIZE; #endif if (nvec > iov_max) { int offset = 0; while (nvec != 0) { apr_status_t rv; rv = wsgi_socket_sendv_limit(sock, &vec[offset], (nvec < iov_max ? nvec : (int)iov_max)); if (rv != APR_SUCCESS) return rv; if (nvec > iov_max) { nvec -= iov_max; offset += iov_max; } else { nvec = 0; } } return APR_SUCCESS; } else return wsgi_socket_sendv_limit(sock, vec, nvec); } static apr_status_t wsgi_send_request(request_rec *r, WSGIRequestConfig *config, WSGIDaemonSocket *daemon) { int rv; const apr_array_header_t *env_arr; const apr_table_entry_t *elts; int i; struct iovec *vec; struct iovec *vec_start; struct iovec *vec_next; apr_size_t total = 0; apr_size_t count = 0; apr_table_setn(r->subprocess_env, "mod_wsgi.daemon_connects", apr_psprintf(r->pool, "%d", config->daemon_connects)); apr_table_setn(r->subprocess_env, "mod_wsgi.daemon_restarts", apr_psprintf(r->pool, "%d", config->daemon_restarts)); /* Send subprocess environment from request object. */ env_arr = apr_table_elts(r->subprocess_env); elts = (const apr_table_entry_t *)env_arr->elts; /* * Sending total amount of data, followed by count of separate * strings and then each null terminated string. The total is * inclusive of the bytes used for the count of the strings. */ vec = (struct iovec *)apr_palloc(r->pool, (2+(2*env_arr->nelts))* sizeof(struct iovec)); vec_start = &vec[2]; vec_next = vec_start; for (i=0; i<env_arr->nelts; ++i) { if (!elts[i].key) continue; vec_next->iov_base = (void*)elts[i].key; vec_next->iov_len = strlen(elts[i].key) + 1; total += vec_next->iov_len; vec_next++; if (elts[i].val) { vec_next->iov_base = (void*)elts[i].val; vec_next->iov_len = strlen(elts[i].val) + 1; } else { vec_next->iov_base = (void*)""; vec_next->iov_len = 1; } total += vec_next->iov_len; vec_next++; } count = vec_next - vec_start; vec[1].iov_base = (void*)&count; vec[1].iov_len = sizeof(count); total += vec[1].iov_len; vec[0].iov_base = (void*)&total; vec[0].iov_len = sizeof(total); rv = wsgi_socket_sendv(daemon->socket, vec, (int)(vec_next-vec)); if (rv != APR_SUCCESS) return rv; return APR_SUCCESS; } static void wsgi_discard_output(apr_bucket_brigade *bb) { apr_bucket *e; const char *buf; apr_size_t len; apr_status_t rv; for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) { if (APR_BUCKET_IS_EOS(e)) { break; } rv = apr_bucket_read(e, &buf, &len, APR_BLOCK_READ); if (rv != APR_SUCCESS) { break; } } } static int wsgi_copy_header(void *v, const char *key, const char *val) { apr_table_addn(v, key, val); return 1; } #define HTTP_UNSET (-HTTP_OK) static int wsgi_scan_headers(request_rec *r, char *buffer, int buflen, int (*getsfunc) (char *, int, void *), void *getsfunc_data) { char x[32768]; char *w, *l; size_t p; int cgi_status = HTTP_UNSET; apr_table_t *merge; apr_table_t *cookie_table; apr_table_t *authen_table; WSGIRequestConfig *config = NULL; config = (WSGIRequestConfig *)ap_get_module_config(r->request_config, &wsgi_module); /* * Default to internal fixed size buffer for reading headers if one * is not supplied explicitly with the call. */ if (buffer) *buffer = '\0’; w = buffer ? buffer : x; buflen = buffer ? buflen : sizeof(x); /* Temporary place to hold headers as we read them. */ merge = apr_table_make(r->pool, 10); /* * The HTTP specification says that it is legal to merge duplicate * headers into one. Some browsers don’t like certain headers being * merged however. These headers are Set-Cookie and WWW-Authenticate. * We will therefore keep these separate and merge them back in * independently at the end. Before we start though, we need to make * sure we save away any instances of these headers which may already * be listed in the request structure for some reason. */ cookie_table = apr_table_make(r->pool, 2); apr_table_do(wsgi_copy_header, cookie_table, r->headers_out, "Set-Cookie", NULL); authen_table = apr_table_make(r->pool, 2); apr_table_do(wsgi_copy_header, authen_table, r->err_headers_out, "WWW-Authenticate", NULL); while (1) { int rv = (*getsfunc) (w, buflen - 1, getsfunc_data); if (rv == 0) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Truncated or " "oversized response headers received from " "daemon process '%s’", config->process_group), r->filename); r->status_line = NULL; return HTTP_INTERNAL_SERVER_ERROR; } else if (rv == -1) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Timeout when " "reading response headers from daemon " "process '%s’", config->process_group), r->filename); r->status_line = NULL; return HTTP_GATEWAY_TIME_OUT; } /* * Delete any trailing (CR?)LF. Indeed, the host’s '\n’: * ‘\012’ for UNIX; ‘\015’ for MacOS; ‘\025’ for OS/390. */ p = strlen(w); if (p > 0 && w[p - 1] == ‘\n’) { if (p > 1 && w[p - 2] == CR) { w[p - 2] = '\0’; } else { w[p - 1] = '\0’; } } /* * If we’ve finished reading the headers, check to make sure * any HTTP/1.1 conditions are met. If so, we’re done; normal * processing will handle the script’s output. If not, just * return the error. */ if (w[0] == ‘\0’) { int cond_status = OK; /* * This fails because it gets confused when a CGI Status * header overrides ap_meets_conditions. * * We can fix that by dropping ap_meets_conditions when * Status has been set. Since this is the only place * cgi_status gets used, let’s test it explicitly. * * The alternative would be to ignore CGI Status when * ap_meets_conditions returns anything interesting. That * would be safer wrt HTTP, but would break CGI. */ if ((cgi_status == HTTP_UNSET) && (r->method_number == M_GET)) { cond_status = ap_meets_conditions®; } /* * Merge the headers received back into the request * structure. There should only be one per header with * values combined for these. */ apr_table_overlap(r->headers_out, merge, APR_OVERLAP_TABLES_MERGE); /* * Now add in the special headers which we can’t merge * because it gives certain browsers problems. */ if (!apr_is_empty_table(cookie_table)) { apr_table_unset(r->headers_out, “Set-Cookie”); r->headers_out = apr_table_overlay(r->pool, r->headers_out, cookie_table); } if (!apr_is_empty_table(authen_table)) { apr_table_unset(r->err_headers_out, “WWW-Authenticate”); r->err_headers_out = apr_table_overlay(r->pool, r->err_headers_out, authen_table); } return cond_status; } /* If we see a bogus header don’t ignore it. Shout and scream. */ if (!(l = strchr(w, ‘:’))) { char malformed[32]; strncpy(malformed, w, sizeof(malformed)-1); malformed[sizeof(malformed)-1] = '\0’; if (!buffer) { /* Soak up all the script output. */ while ((*getsfunc)(w, buflen - 1, getsfunc_data) > 0) { continue; } } wsgi_log_script_error(r, apr_psprintf(r->pool, "Malformed " "header ‘%s’ found when reading script " "headers from daemon process '%s’", malformed, config->process_group), r->filename); r->status_line = NULL; return HTTP_INTERNAL_SERVER_ERROR; } /* Strip leading white space from header value. */ *l++ = '\0’; while (*l && apr_isspace(*l)) { ++l; } if (!strcasecmp(w, “Content-type”)) { char *tmp; /* Nuke trailing whitespace. */ char *endp = l + strlen(l) - 1; while (endp > l && apr_isspace(*endp)) { *endp-- = '\0’; } tmp = apr_pstrdup(r->pool, l); ap_content_type_tolower(tmp); ap_set_content_type(r, tmp); } else if (!strcasecmp(w, “Status”)) { /* * If the script returned a specific status, that’s what * we’ll use, otherwise we assume 200 OK. */ r->status = cgi_status = atoi(l); r->status_line = apr_pstrdup(r->pool, l); } else if (!strcasecmp(w, “Location”)) { apr_table_set(r->headers_out, w, l); } else if (!strcasecmp(w, “Content-Length”)) { apr_table_set(r->headers_out, w, l); } else if (!strcasecmp(w, “Content-Range”)) { apr_table_set(r->headers_out, w, l); } else if (!strcasecmp(w, “Transfer-Encoding”)) { apr_table_set(r->headers_out, w, l); } else if (!strcasecmp(w, “Last-Modified”)) { /* * If the script gave us a Last-Modified header, we can’t just * pass it on blindly because of restrictions on future values. */ ap_update_mtime(r, apr_date_parse_http(l)); ap_set_last_modified®; } else if (!strcasecmp(w, “Set-Cookie”)) { apr_table_add(cookie_table, w, l); } else if (!strcasecmp(w, “WWW-Authenticate”)) { apr_table_add(authen_table, w, l); } else { apr_table_add(merge, w, l); } } return OK; } static int wsgi_getsfunc_brigade(char *buf, int len, void *arg) { apr_bucket_brigade *bb = (apr_bucket_brigade *)arg; const char *dst_end = buf + len - 1; char *dst = buf; apr_bucket *e = APR_BRIGADE_FIRST(bb); apr_status_t rv; int done = 0; while ((dst < dst_end) && !done && e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e)) { const char *bucket_data; apr_size_t bucket_data_len; const char *src; const char *src_end; apr_bucket * next; rv = apr_bucket_read(e, &bucket_data, &bucket_data_len, APR_BLOCK_READ); if (rv != APR_SUCCESS || (bucket_data_len == 0)) { *dst = '\0’; return APR_STATUS_IS_TIMEUP(rv) ? -1 : 0; } src = bucket_data; src_end = bucket_data + bucket_data_len; while ((src < src_end) && (dst < dst_end) && !done) { if (*src == ‘\n’) { done = 1; } else if (*src != ‘\r’) { *dst++ = *src; } src++; } if (src < src_end) { apr_bucket_split(e, src - bucket_data); } next = APR_BUCKET_NEXT(e); APR_BUCKET_REMOVE(e); apr_bucket_destroy(e); e = next; } *dst = '\0’; return done; } static int wsgi_scan_headers_brigade(request_rec *r, apr_bucket_brigade *bb, char *buffer, int buflen) { return wsgi_scan_headers(r, buffer, buflen, wsgi_getsfunc_brigade, bb); } static int wsgi_transfer_response(request_rec *r, apr_bucket_brigade *bb, apr_size_t buffer_size, apr_time_t timeout) { apr_bucket *e; apr_read_type_e mode = APR_NONBLOCK_READ; apr_bucket_brigade *tmpbb; const char *data = NULL; apr_size_t length = 0; apr_size_t bytes_transfered = 0; int bucket_count = 0; apr_status_t rv; #if AP_MODULE_MAGIC_AT_LEAST(20110605, 2) apr_socket_t *sock; apr_interval_time_t existing_timeout = 0; #endif if (buffer_size == 0) buffer_size = 65536; /* * Override the socket timeout for writing back data to the * client. If that wasn’t defined this will be the same as * the timeout for the socket used in communicating with the * daemon, or left as the overall server timeout if that * isn’t specified. Just to be safe we remember the existing * timeout and restore it at the end of a successful request * in case the same connection if kept alive and used for a * subsequent request with a different handler. */ #if AP_MODULE_MAGIC_AT_LEAST(20110605, 2) sock = ap_get_conn_socket(r->connection); rv = apr_socket_timeout_get(sock, &existing_timeout); if (rv != APR_SUCCESS) { existing_timeout = 0; } else { if (timeout) apr_socket_timeout_set(sock, timeout); } #endif /* * Transfer any response content. We want to avoid the * problem where the core output filter has no flow control * to deal with slow HTTP clients and can actually buffer up * excessive amounts of response content in memory. A fix * for this was only introduced in Apache 2.3.3, with * possible further tweaks in Apache 2.4.1. To avoid issue of * what version it was implemented in, just employ a * strategy of forcing a flush every time we pass through * more than a certain amount of data. */ tmpbb = apr_brigade_create(r->pool, r->connection->bucket_alloc); while ((e = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) { /* If we have reached end of stream, we need to pass it on */ if (APR_BUCKET_IS_EOS(e)) { /* * Probably do not need to force a flush as EOS should * do that, but do it just in case when we potentially * have pending data to be written out. */ if (bytes_transfered != 0) { APR_BRIGADE_INSERT_TAIL(tmpbb, apr_bucket_flush_create( r->connection->bucket_alloc)); } APR_BRIGADE_INSERT_TAIL(tmpbb, apr_bucket_eos_create( r->connection->bucket_alloc)); rv = ap_pass_brigade(r->output_filters, tmpbb); apr_brigade_cleanup(tmpbb); if (rv != APR_SUCCESS) { apr_brigade_destroy(bb); /* * Don’t flag error if client connection was aborted * so that access log still records the original HTTP * response code returned by the WSGI application. */ if (r->connection->aborted) return OK; return HTTP_INTERNAL_SERVER_ERROR; } break; } /* * Force the reading in of next block of data to be * transfered if necessary. If the bucket is a heap * bucket, then it will be whatever data is in it. If it * is a socket bucket, this will result in the bucket * being converted to a heap bucket with some amount of * data and the socket bucket added back in after it. Any * non data buckets should be skipped and discarded. The * result should always be that the first bucket is a * heap bucket. */ rv = apr_bucket_read(e, &data, &length, mode); /* * If we would have blocked if not in non blocking mode * we send a flush bucket to ensure that all buffered * data is sent out before we block waiting for more. */ if (rv == APR_EAGAIN && mode == APR_NONBLOCK_READ) { APR_BRIGADE_INSERT_TAIL(tmpbb, apr_bucket_flush_create( r->connection->bucket_alloc)); rv = ap_pass_brigade(r->output_filters, tmpbb); apr_brigade_cleanup(tmpbb); if (rv == APR_TIMEUP) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "mod_wsgi (pid=%d): Failed to proxy response " "to client.", getpid()); } if (rv != APR_SUCCESS) { apr_brigade_destroy(bb); /* * Don’t flag error if client connection was aborted * so that access log still records the original HTTP * response code returned by the WSGI application. */ if (r->connection->aborted) return OK; return HTTP_INTERNAL_SERVER_ERROR; } bytes_transfered = 0; bucket_count = 0; /* * Retry read from daemon using a blocking read. We do * not delete the bucket as we want to operate on the * same one as we would have blocked. */ mode = APR_BLOCK_READ; continue; } else if (rv != APR_SUCCESS) { apr_brigade_destroy(bb); ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "mod_wsgi (pid=%d): Failed to proxy response " "from daemon.", getpid()); /* * Don’t flag error if couldn’t read from daemon * so that access log still records the original HTTP * response code returned by the WSGI application. */ return OK; } /* * We had some data to transfer. Next time round we need to * always be try a non-blocking read first. */ mode = APR_NONBLOCK_READ; /* * Now we don’t actually work with the data which was * read direct and instead simply remove what should be a * heap bucket from the start of the bucket brigade and * then place in a new bucket brigade to be pushed out to * the client. By passing down the bucket, it avoids the * need to create a transient bucket holding a reference * to the data from the first bucket. */ APR_BUCKET_REMOVE(e); APR_BRIGADE_INSERT_TAIL(tmpbb, e); /* * If we have reached the buffer size threshold, we want * to flush the data so that we aren’t buffering too much * in memory and blowing out memory size. We also have a * check on the number of buckets we have accumulated as * a large number of buckets with very small amounts of * data will also accumulate a lot of memory. Apache’s * own flow control doesn’t cope with such a situation. * Right now hard wire the max number of buckets at 16 * which equates to worst case number of separate data * blocks can be written by a writev() call on systems * such as Solaris. */ bytes_transfered += length; bucket_count += 1; if (bytes_transfered > buffer_size || bucket_count >= 16) { APR_BRIGADE_INSERT_TAIL(tmpbb, apr_bucket_flush_create( r->connection->bucket_alloc)); bytes_transfered = 0; bucket_count = 0; /* * Since we flushed the data out to the client, it is * okay to go back and do a blocking read the next time. */ mode = APR_BLOCK_READ; } /* Pass the heap bucket and any flush bucket on. */ rv = ap_pass_brigade(r->output_filters, tmpbb); apr_brigade_cleanup(tmpbb); if (rv == APR_TIMEUP) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "mod_wsgi (pid=%d): Failed to proxy response " "to client.", getpid()); } if (rv != APR_SUCCESS) { apr_brigade_destroy(bb); /* * Don’t flag error if client connection was aborted * so that access log still records the original HTTP * response code returned by the WSGI application. */ if (r->connection->aborted) return OK; return HTTP_INTERNAL_SERVER_ERROR; } } #if AP_MODULE_MAGIC_AT_LEAST(20110605, 2) if (existing_timeout) apr_socket_timeout_set(sock, existing_timeout); #endif apr_brigade_destroy(bb); return OK; } #define ASCII_CRLF “\015\012” #define ASCII_ZERO “\060” static int wsgi_execute_remote(request_rec *r) { WSGIRequestConfig *config = NULL; WSGIDaemonSocket *daemon = NULL; WSGIProcessGroup *group = NULL; char *key = NULL; const char *hash = NULL; int status; apr_status_t rv; int seen_eos; int child_stopped_reading; apr_bucket_brigade *bbout; apr_bucket_brigade *bbin; apr_bucket *b; const char *location = NULL; char *header_buffer = NULL; int header_buflen = 0; /* Grab request configuration. */ config = (WSGIRequestConfig *)ap_get_module_config(r->request_config, &wsgi_module); /* * Only allow the process group to match against a restricted * set of processes if such a restricted set has been defined. */ if (config->restrict_process) { if (!apr_table_get(config->restrict_process, config->process_group)) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Daemon " "process called ‘%s’ cannot be " "accessed by this WSGI application " "as not a member of allowed groups", config->process_group), r->filename); return HTTP_INTERNAL_SERVER_ERROR; } } /* * Do not process request as remote if actually targeted at * the main Apache processes. */ if (!*config->process_group) return DECLINED; /* Grab details of matching process group. */ if (!wsgi_daemon_index) { wsgi_log_script_error(r, apr_psprintf(r->pool, "No WSGI daemon " "process called ‘%s’ has been configured", config->process_group), r->filename); return HTTP_INTERNAL_SERVER_ERROR; } group = (WSGIProcessGroup *)apr_hash_get(wsgi_daemon_index, config->process_group, APR_HASH_KEY_STRING); if (!group) { wsgi_log_script_error(r, apr_psprintf(r->pool, "No WSGI daemon " "process called ‘%s’ has been configured", config->process_group), r->filename); return HTTP_INTERNAL_SERVER_ERROR; } /* * Only allow the process group to match against a daemon * process defined within a virtual host with the same * server name or a daemon process defined at global server * scope. */ if (group->server != r->server && group->server != wsgi_server) { if (strcmp(group->server->server_hostname, r->server->server_hostname) != 0) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Daemon " "process called ‘%s’ cannot be " "accessed by this WSGI application", config->process_group), r->filename); return HTTP_INTERNAL_SERVER_ERROR; } } /* * Check restrictions related to the group of the WSGI * script file and who has write access to the directory it * is contained in. If not satisfied forbid access. */ if (group->script_group) { apr_uid_t gid; struct group *grent = NULL; const char *grname = NULL; apr_finfo_t finfo; const char *path = NULL; if (!(r->finfo.valid & APR_FINFO_GROUP)) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Group " "information not available for WSGI " “script file”), r->filename); return HTTP_FORBIDDEN; } gid = r->finfo.group; if ((grent = getgrgid(gid)) == NULL) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Couldn’t " "determine group of WSGI script file, " "gid=%ld", (long)gid), r->filename); return HTTP_FORBIDDEN; } grname = grent->gr_name; if (strcmp(group->script_group, grname)) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Group of WSGI " "script file does not match required group " "for daemon process, group=%s", grname), r->filename); return HTTP_FORBIDDEN; } if (!(r->finfo.valid & APR_FINFO_WPROT)) { wsgi_log_script_error(r, apr_psprintf(r->pool, "World " "permissions not available for WSGI " “script file”), r->filename); return HTTP_FORBIDDEN; } if (r->finfo.protection & APR_FPROT_WWRITE) { wsgi_log_script_error(r, apr_psprintf(r->pool, "WSGI script " “file is writable to world”), r->filename); return HTTP_FORBIDDEN; } path = ap_make_dirstr_parent(r->pool, r->filename); if (apr_stat(&finfo, path, APR_FINFO_NORM, r->pool) != APR_SUCCESS) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Unable to stat " “parent directory of WSGI script”), path); return HTTP_FORBIDDEN; } gid = finfo.group; if ((grent = getgrgid(gid)) == NULL) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Couldn’t " "determine group of parent directory of " "WSGI script file, gid=%ld", (long)gid), r->filename); return HTTP_FORBIDDEN; } grname = grent->gr_name; if (strcmp(group->script_group, grname)) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Group of parent " "directory of WSGI script file does not " "match required group for daemon process, " "group=%s", grname), r->filename); return HTTP_FORBIDDEN; } if (finfo.protection & APR_FPROT_WWRITE) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Parent directory " “of WSGI script file is writable to world”), r->filename); return HTTP_FORBIDDEN; } } /* * Check restrictions related to who can be the owner of * the WSGI script file and who has write access to the * directory it is contained in. If not satisfied forbid * access. */ if (group->script_user) { apr_uid_t uid; struct passwd *pwent = NULL; const char *pwname = NULL; apr_finfo_t finfo; const char *path = NULL; if (!(r->finfo.valid & APR_FINFO_USER)) { wsgi_log_script_error(r, apr_psprintf(r->pool, "User " "information not available for WSGI " “script file”), r->filename); return HTTP_FORBIDDEN; } uid = r->finfo.user; if ((pwent = getpwuid(uid)) == NULL) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Couldn’t " "determine owner of WSGI script file, " "uid=%ld", (long)uid), r->filename); return HTTP_FORBIDDEN; } pwname = pwent->pw_name; if (strcmp(group->script_user, pwname)) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Owner of WSGI " "script file does not match required user " "for daemon process, user=%s", pwname), r->filename); return HTTP_FORBIDDEN; } if (!(r->finfo.valid & APR_FINFO_GPROT)) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Group " "permissions not available for WSGI " “script file”), r->filename); return HTTP_FORBIDDEN; } if (r->finfo.protection & APR_FPROT_GWRITE) { wsgi_log_script_error(r, apr_psprintf(r->pool, "WSGI script " “file is writable to group”), r->filename); return HTTP_FORBIDDEN; } if (!(r->finfo.valid & APR_FINFO_WPROT)) { wsgi_log_script_error(r, apr_psprintf(r->pool, "World " "permissions not available for WSGI " “script file”), r->filename); return HTTP_FORBIDDEN; } if (r->finfo.protection & APR_FPROT_WWRITE) { wsgi_log_script_error(r, apr_psprintf(r->pool, "WSGI script " “file is writable to world”), r->filename); return HTTP_FORBIDDEN; } path = ap_make_dirstr_parent(r->pool, r->filename); if (apr_stat(&finfo, path, APR_FINFO_NORM, r->pool) != APR_SUCCESS) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Unable to stat " “parent directory of WSGI script”), path); return HTTP_FORBIDDEN; } uid = finfo.user; if ((pwent = getpwuid(uid)) == NULL) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Couldn’t " "determine owner of parent directory of " "WSGI script file, uid=%ld", (long)uid), r->filename); return HTTP_FORBIDDEN; } pwname = pwent->pw_name; if (strcmp(group->script_user, pwname)) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Owner of parent " "directory of WSGI script file does not " "match required user for daemon process, " "user=%s", pwname), r->filename); return HTTP_FORBIDDEN; } if (finfo.protection & APR_FPROT_WWRITE) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Parent directory " “of WSGI script file is writable to world”), r->filename); return HTTP_FORBIDDEN; } if (finfo.protection & APR_FPROT_GWRITE) { wsgi_log_script_error(r, apr_psprintf(r->pool, "Parent directory " “of WSGI script file is writable to group”), r->filename); return HTTP_FORBIDDEN; } } /* * Add magic marker into request environment so that daemon * process can verify that request is from a sender that can * be trusted. Wipe out original key to make it a bit harder * for rogue code in Apache child processes to trawl through * memory looking for unhashed string. */ key = apr_psprintf(r->pool, "%ld|%s|%s|%s", group->random, group->socket_path, r->filename, config->handler_script); hash = ap_md5(r->pool, (const unsigned char *)key); memset(key, '\0’, strlen(key)); apr_table_setn(r->subprocess_env, "mod_wsgi.magic", hash); /* Create connection to the daemon process. */ apr_table_setn(r->subprocess_env, "mod_wsgi.queue_start", apr_psprintf(r->pool, “%” APR_TIME_T_FMT, apr_time_now())); daemon = (WSGIDaemonSocket *)apr_pcalloc(r->pool, sizeof(WSGIDaemonSocket)); daemon->name = config->process_group; daemon->socket_path = group->socket_path; daemon->connect_timeout = group->connect_timeout; daemon->socket_timeout = group->socket_timeout; if ((status = wsgi_connect_daemon(r, daemon)) != OK) return status; /* Send request details and subprocess environment. */ if (wsgi_server_config->verbose_debugging) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Request server was " "’%s|%d’.", getpid(), r->server->server_hostname, r->server->port); } if ((rv = wsgi_send_request(r, config, daemon)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "mod_wsgi (pid=%d): Unable to send request details " "to WSGI daemon process ‘%s’ on '%s’.", getpid(), daemon->name, daemon->socket_path); return HTTP_INTERNAL_SERVER_ERROR; } /* Setup bucket brigade for reading response from daemon. */ bbin = apr_brigade_create(r->pool, r->connection->bucket_alloc); b = apr_bucket_socket_create(daemon->socket, r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bbin, b); b = apr_bucket_eos_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bbin, b); /* Create alternate buffer for reading in response header values. */ if (group->header_buffer_size != 0) { header_buflen = group->header_buffer_size; header_buffer = apr_pcalloc(r->pool, header_buflen); } /* * If process reload mechanism enabled, or a queue timeout is * specified, then we need to look for marker indicating it * is okay to transfer content, or whether process is being * restarted and that we should therefore create a * connection to daemon process again. */ if (*config->process_group && (config->script_reloading || group->queue_timeout != 0)) { int retries = 0; int maximum = (2*group->processes)+1; /* * While special header indicates a restart is being * done, then keep trying to reconnect. Cap the number * of retries to at most about 2 times the number of * daemon processes in the process group. If still being * told things are being restarted, then we will error * indicating service is unavailable. */ while (retries < maximum) { /* Scan the CGI script like headers from daemon. */ status = wsgi_scan_headers_brigade(r, bbin, header_buffer, header_buflen); if (status != OK) return status; /* * Status must be 200 for our special headers. Ideally * we would use 0 as did in the past but Apache 2.4 * complains if use 0 as not a valid status value. */ if (r->status != 200) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): Unexpected status from " "WSGI daemon process '%d’.", getpid(), r->status); r->status_line = NULL; return HTTP_INTERNAL_SERVER_ERROR; } if (!strcmp(r->status_line, “200 Continue”)) { r->status_line = NULL; break; } if (!strcmp(r->status_line, “200 Timeout”)) { r->status_line = NULL; return HTTP_GATEWAY_TIME_OUT; } if (strcmp(r->status_line, “200 Rejected”)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): Unexpected status from " "WSGI daemon process '%d’.", getpid(), r->status); r->status_line = NULL; return HTTP_INTERNAL_SERVER_ERROR; } r->status_line = NULL; /* Need to close previous socket connection first. */ apr_socket_close(daemon->socket); /* Has maximum number of attempts been reached. */ if (retries == maximum) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "mod_wsgi (pid=%d): Maximum number of WSGI " "daemon process restart connects reached '%d’.", getpid(), maximum); return HTTP_SERVICE_UNAVAILABLE; } retries++; config->daemon_restarts++; ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "mod_wsgi (pid=%d): Connect after WSGI daemon " "process restart, attempt #%d.", getpid(), retries); /* Connect and setup connection just like before. */ if ((status = wsgi_connect_daemon(r, daemon)) != OK) return status; if ((rv = wsgi_send_request(r, config, daemon)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "mod_wsgi (pid=%d): Unable to send request " "details to WSGI daemon process ‘%s’ on '%s’.", getpid(), daemon->name, daemon->socket_path); return HTTP_INTERNAL_SERVER_ERROR; } apr_brigade_destroy(bbin); bbin = apr_brigade_create(r->pool, r->connection->bucket_alloc); b = apr_bucket_socket_create(daemon->socket, r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bbin, b); b = apr_bucket_eos_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bbin, b); } } /* * Need to reset request status value to HTTP_OK else it * screws up HTTP input filter when processing a POST * request with 100-continue requirement. */ r->status = HTTP_OK; /* * Transfer any request content which was provided. Note that we * actually frame each data block sent with same format as is used * for chunked transfer encoding. This will be decoded in the * daemon process. This is done so that the EOS can be properly * identified by the daemon process in the absence of a value for * CONTENT_LENGTH that can be relied on. The CONTENT_LENGTH is * dodgy when have mutating input filters and none will be present * at all if chunked request content was used. */ seen_eos = 0; child_stopped_reading = 0; bbout = apr_brigade_create(r->pool, r->connection->bucket_alloc); do { apr_bucket *bucket; rv = ap_get_brigade(r->input_filters, bbout, AP_MODE_READBYTES, APR_BLOCK_READ, HUGE_STRING_LEN); if (rv != APR_SUCCESS) { char status_buffer[512]; const char *error_message; error_message = apr_psprintf(r->pool, "Request data read " "error when proxying data to daemon process: %s", apr_strerror(rv, status_buffer, sizeof( status_buffer)-1)); ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): %s.", getpid(), error_message); if (APR_STATUS_IS_TIMEUP(rv)) return HTTP_REQUEST_TIME_OUT; return HTTP_INTERNAL_SERVER_ERROR; } for (bucket = APR_BRIGADE_FIRST(bbout); bucket != APR_BRIGADE_SENTINEL(bbout); bucket = APR_BUCKET_NEXT(bucket)) { const char *data; apr_size_t len; char chunk_hdr[20]; apr_size_t hdr_len; struct iovec vec[3]; if (APR_BUCKET_IS_EOS(bucket)) { /* Send closing frame for chunked content. */ rv = wsgi_socket_send(daemon->socket, ASCII_ZERO ASCII_CRLF ASCII_CRLF, 5); if (rv != APR_SUCCESS) { char status_buffer[512]; const char *error_message; error_message = apr_psprintf(r->pool, "Request data write " "error when proxying data to daemon process: %s", apr_strerror(rv, status_buffer, sizeof( status_buffer)-1)); ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): %s.", getpid(), error_message); } seen_eos = 1; break; } /* We can’t do much with this. */ if (APR_BUCKET_IS_FLUSH(bucket)) { continue; } /* If the child stopped, we still must read to EOS. */ if (child_stopped_reading) { continue; } /* Read block. */ rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ); if (rv != APR_SUCCESS) { char status_buffer[512]; const char *error_message; error_message = apr_psprintf(r->pool, "Request data read " "error when proxying data to daemon process: %s", apr_strerror(rv, status_buffer, sizeof( status_buffer)-1)); ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): %s.", getpid(), error_message); break; } /* * Keep writing data to the child until done or too * much time elapses with no progress or an error * occurs. Frame the data being sent with format used * for chunked transfer encoding. */ hdr_len = apr_snprintf(chunk_hdr, sizeof(chunk_hdr), “%” APR_UINT64_T_HEX_FMT ASCII_CRLF, (apr_uint64_t)len); vec[0].iov_base = (void *)chunk_hdr; vec[0].iov_len = hdr_len; vec[1].iov_base = (void *)data; vec[1].iov_len = len; vec[2].iov_base = (void *)ASCII_CRLF; vec[2].iov_len = 2; rv = wsgi_socket_sendv(daemon->socket, vec, 3); if (rv != APR_SUCCESS) { char status_buffer[512]; const char *error_message; error_message = apr_psprintf(r->pool, "Request data write " "error when proxying data to daemon process: %s", apr_strerror(rv, status_buffer, sizeof( status_buffer)-1)); ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): %s.", getpid(), error_message); /* Daemon stopped reading, discard remainder. */ child_stopped_reading = 1; } } apr_brigade_cleanup(bbout); } while (!seen_eos); /* * Close socket for writing so that daemon detects end of * request content. */ apr_socket_shutdown(daemon->socket, APR_SHUTDOWN_WRITE); /* Scan the CGI script like headers from daemon. */ status = wsgi_scan_headers_brigade(r, bbin, header_buffer, header_buflen); if (status != OK) return status; /* * Look for the special case of status being 200 but the * status line indicating an error and translate it into a * 500 error so that error document processing will occur * for those cases where WSGI application wouldn’t have * supplied their own error document. We used to use 0 * here for status but Apache 2.4 prohibits it now. */ if (r->status == 200 && !strcmp(r->status_line, “200 Error”)) { r->status_line = NULL; return HTTP_INTERNAL_SERVER_ERROR; } /* * Look for ‘Location’ header and if an internal * redirect, execute the redirect. This behaviour is * consistent with how mod_cgi and mod_cgid work and * what is permitted by the CGI specification. */ location = apr_table_get(r->headers_out, “Location”); if (location && location[0] == ‘/’ && r->status == 200) { /* * Discard all response content returned from * the daemon process. */ wsgi_discard_output(bbin); apr_brigade_destroy(bbin); /* * The internal redirect needs to be a GET no * matter what the original method was. */ r->method = apr_pstrdup(r->pool, “GET”); r->method_number = M_GET; /* * We already read the message body (if any), so * don’t allow the redirected request to think * it has one. Not sure if we need to worry * about removing ‘Transfer-Encoding’ header. */ apr_table_unset(r->headers_in, “Content-Length”); ap_internal_redirect_handler(location, r); return OK; } /* * Allow the web server to override any error * page produced by the WSGI application. */ if (config->error_override && ap_is_HTTP_ERROR(r->status)) { status = r->status; r->status = HTTP_OK; r->status_line = NULL; /* * Discard all response content returned from * the daemon process if any expected. */ if (!r->header_only && /* not HEAD request */ (status != HTTP_NO_CONTENT) && /* not 204 */ (status != HTTP_NOT_MODIFIED)) { /* not 304 */ wsgi_discard_output(bbin); apr_brigade_destroy(bbin); } return status; } /* Transfer any response content. */ return wsgi_transfer_response(r, bbin, group->response_buffer_size, group->response_socket_timeout); } static apr_status_t wsgi_socket_read(apr_socket_t *sock, void *vbuf, apr_size_t size) { char *buf = vbuf; apr_status_t rv; apr_size_t count = 0; apr_size_t len = 0; do { len = size - count; if ((rv = apr_socket_recv(sock, buf + count, &len)) != APR_SUCCESS) return rv; count += len; } while (count < size); return APR_SUCCESS; } static apr_status_t wsgi_read_strings(apr_socket_t *sock, char ***s, apr_pool_t *p) { apr_status_t rv; apr_size_t total; apr_size_t n; apr_size_t i; apr_size_t l; char *buffer; char *offset; if ((rv = wsgi_socket_read(sock, &total, sizeof(total))) != APR_SUCCESS) return rv; buffer = apr_palloc(p, total); offset = buffer; if ((rv = wsgi_socket_read(sock, buffer, total)) != APR_SUCCESS) return rv; memcpy(&n, offset, sizeof(n)); offset += sizeof(n); *s = apr_pcalloc(p, (n+1)*sizeof(**s)); for (i = 0; i < n; i++) { l = strlen(offset) + 1; (*s)[i] = offset; offset += l; } return APR_SUCCESS; } static apr_status_t wsgi_read_request(apr_socket_t *sock, request_rec *r) { int rv; char **vars; /* Read subprocess environment from request object. */ rv = wsgi_read_strings(sock, &vars, r->pool); if (rv != APR_SUCCESS) return rv; while (*vars) { char *key = *vars++; apr_table_setn(r->subprocess_env, key, *vars++); } return APR_SUCCESS; } static ap_filter_rec_t *wsgi_header_filter_handle; static apr_status_t wsgi_header_filter(ap_filter_t *f, apr_bucket_brigade *b) { request_rec *r = f->r; struct iovec vec1[4]; apr_bucket_brigade *b2; char crlf[] = CRLF; apr_size_t buflen; const apr_array_header_t *elts; const apr_table_entry_t *t_elt; const apr_table_entry_t *t_end; struct iovec *vec2; struct iovec *vec2_next; /* Output status line. */ vec1[0].iov_base = (void *)"Status:"; vec1[0].iov_len = strlen(“Status:”); vec1[1].iov_base = (void *)" "; vec1[1].iov_len = sizeof(" ") - 1; vec1[2].iov_base = (void *)(r->status_line); vec1[2].iov_len = strlen(r->status_line); vec1[3].iov_base = (void *)CRLF; vec1[3].iov_len = sizeof(CRLF) - 1; b2 = apr_brigade_create(r->pool, r->connection->bucket_alloc); apr_brigade_writev(b2, NULL, NULL, vec1, 4); /* Merge response header tables together. */ if (!apr_is_empty_table(r->err_headers_out)) { r->headers_out = apr_table_overlay(r->pool, r->err_headers_out, r->headers_out); } /* Override the content type for response. */ if (r->content_type) apr_table_setn(r->headers_out, "Content-Type", r->content_type); /* Formt the response headers for output. */ elts = apr_table_elts(r->headers_out); if (elts->nelts != 0) { t_elt = (const apr_table_entry_t *)(elts->elts); t_end = t_elt + elts->nelts; vec2 = (struct iovec *)apr_palloc(r->pool, 4 * elts->nelts * sizeof(struct iovec)); vec2_next = vec2; do { vec2_next->iov_base = (void*)(t_elt->key); vec2_next->iov_len = strlen(t_elt->key); vec2_next++; vec2_next->iov_base = ": "; vec2_next->iov_len = sizeof(": ") - 1; vec2_next++; vec2_next->iov_base = (void*)(t_elt->val); vec2_next->iov_len = strlen(t_elt->val); vec2_next++; vec2_next->iov_base = CRLF; vec2_next->iov_len = sizeof(CRLF) - 1; vec2_next++; t_elt++; } while (t_elt < t_end); apr_brigade_writev(b2, NULL, NULL, vec2, vec2_next - vec2); } /* Format terminating blank line for response headers. */ buflen = strlen(crlf); apr_brigade_write(b2, NULL, NULL, crlf, buflen); /* Output the response headers. */ ap_pass_brigade(f->next, b2); /* Remove ourselves from filter chain so we aren’t called again. */ ap_remove_output_filter(f); /* Output the partial response content. */ return ap_pass_brigade(f->next, b); } typedef struct cve_2013_5704_fields cve_2013_5704_fields; typedef struct cve_2013_5704_apache22 cve_2013_5704_apache22; typedef struct cve_2013_5704_apache24 cve_2013_5704_apache24; struct cve_2013_5704_fields { apr_table_t *trailers_in; apr_table_t *trailers_out; }; struct cve_2013_5704_apache22 { struct ap_filter_t *proto_input_filters; int eos_sent; cve_2013_5704_fields fields; }; struct cve_2013_5704_apache24 { apr_sockaddr_t *useragent_addr; char *useragent_ip; cve_2013_5704_fields fields; }; static int wsgi_hook_daemon_handler(conn_rec *c) { apr_socket_t *csd; request_rec *r; apr_pool_t *p; apr_status_t rv; char *key; apr_sockaddr_t *addr; const char *filename; const char *script; const char *magic; const char *hash; WSGIRequestConfig *config; apr_bucket *e; apr_bucket_brigade *bb; core_request_config *req_cfg; ap_filter_t *current = NULL; ap_filter_t *next = NULL; const char *item; int queue_timeout_occurred = 0; apr_time_t daemon_start = 0; #if ! (AP_MODULE_MAGIC_AT_LEAST(20120211, 37) || \ (AP_SERVER_MAJORVERSION_NUMBER == 2 && \ AP_SERVER_MINORVERSION_NUMBER <= 2 && \ AP_MODULE_MAGIC_AT_LEAST(20051115, 36))) apr_size_t size = 0; #endif /* Don’t do anything if not in daemon process. */ if (!wsgi_daemon_pool) return DECLINED; /* * Mark this as start of daemon process even though connection * setup has already been done. Otherwise need to carry through * a time value somehow. */ daemon_start = apr_time_now(); /* * Remove all input/output filters except the core filters. * This will ensure that any SSL filters we don’t want are * removed. This is a bit of a hack. Only other option is to * duplicate the code for core input/output filters so can * avoid full Apache connection processing, which is what is * installed the SSL filters and possibly other filters for * logging etc. */ current = c->input_filters; next = current->next; while (current) { if (current->frec == ap_core_input_filter_handle) { current = next; if (!current) break; next = current->next; continue; } ap_remove_input_filter(current); current = next; if (current) next = current->next; } current = c->output_filters; next = current->next; while (current) { if (current->frec == ap_core_output_filter_handle) { current = next; if (!current) break; next = current->next; continue; } ap_remove_output_filter(current); current = next; if (current) next = current->next; } /* * Create and populate our own request object. We allocate more * memory than we require here for the request_rec in order to * implement an opimistic hack for the case where mod_wsgi is built * against an Apache version prior to CVE-2013-6704 being applied to * it. If that Apache is upgraded but mod_wsgi not recompiled then * it will crash in daemon mode. We therefore use the extra space to * set the structure members which are added by CVE-2013-6704 to try * and avoid that situation. Note that this is distinct from the * hack down below to deal with where mod_wsgi was compiled against * an Apache version which had CVE-2013-6704 backported. */ apr_pool_create(&p, c->pool); r = apr_pcalloc(p, sizeof(request_rec)+sizeof(cve_2013_5704_fields)); r->pool = p; r->connection = c; r->server = c->base_server; r->user = NULL; r->ap_auth_type = NULL; r->allowed_methods = ap_make_method_list(p, 2); r->headers_in = apr_table_make(r->pool, 25); r->subprocess_env = apr_table_make(r->pool, 25); r->headers_out = apr_table_make(r->pool, 12); r->err_headers_out = apr_table_make(r->pool, 5); r->notes = apr_table_make(r->pool, 5); r->request_config = ap_create_request_config(r->pool); r->proto_output_filters = c->output_filters; r->output_filters = r->proto_output_filters; r->proto_input_filters = c->input_filters; r->input_filters = r->proto_input_filters; #if AP_MODULE_MAGIC_AT_LEAST(20120211, 37) || \ (AP_SERVER_MAJORVERSION_NUMBER == 2 && \ AP_SERVER_MINORVERSION_NUMBER <= 2 && \ AP_MODULE_MAGIC_AT_LEAST(20051115, 36)) /* * New request_rec fields were added to Apache because of changes * related to CVE-2013-5704. The change means that mod_wsgi version * 4.4.0-4.4.5 will crash if run on the Apache versions with the * addition fields if mod_wsgi daemon mode is used. If we are using * Apache 2.2.29 or 2.4.11, we set the fields direct against the * new structure members. */ r->trailers_in = apr_table_make(r->pool, 5); r->trailers_out = apr_table_make(r->pool, 5); #else /* * We use a huge hack here to try and identify when CVE-2013-5704 * has been back ported to older Apache version. This is necessary * as when backported the Apache module magic number will not be * updated and it isn’t possible to determine from that at compile * time if the new structure members exist and so that they should * be set. We therefore try and work out whether the extra structure * members exist through looking at the size of request_rec and * whether memory has been allocated above what is known to be the * last member in the structure before the new members were added. */ #if AP_SERVER_MINORVERSION_NUMBER <= 2 size = offsetof(request_rec, eos_sent); size += sizeof(r->eos_sent); #else size = offsetof(request_rec, useragent_ip); size += sizeof(r->useragent_ip); #endif /* * Check whether request_rec is at least as large as minimal size * plus the size of the extra fields. If it is, then we need to * set the additional fields. */ if (sizeof(request_rec) >= size + sizeof(cve_2013_5704_fields)) { #if AP_SERVER_MINORVERSION_NUMBER <= 2 cve_2013_5704_apache22 *rext; rext = (cve_2013_5704_apache22 *)&r->proto_input_filters; #else cve_2013_5704_apache24 *rext; rext = (cve_2013_5704_apache24 *)&r->useragent_addr; #endif rext->fields.trailers_in = apr_table_make(r->pool, 5); rext->fields.trailers_out = apr_table_make(r->pool, 5); } else { /* * Finally, to allow forward portability of a compiled mod_wsgi * binary from an Apache version without the CVE-2013-5704 * change to one where it is, without needing to recompile * mod_wsgi, we set fields in the extra memory we added before * the actual request_rec. */ cve_2013_5704_fields *rext; rext = (cve_2013_5704_fields *)(r+1); rext->trailers_in = apr_table_make(r->pool, 5); rext->trailers_out = apr_table_make(r->pool, 5); } #endif r->per_dir_config = r->server->lookup_defaults; r->sent_bodyct = 0; r->read_length = 0; r->read_body = REQUEST_NO_BODY; r->status = HTTP_OK; r->status_line = NULL; r->the_request = NULL; r->used_path_info = AP_REQ_DEFAULT_PATH_INFO; /* * Install our own output filter for writing back headers in * CGI script style. */ ap_add_output_filter_handle(wsgi_header_filter_handle, NULL, r, r->connection); /* Create and install the WSGI request config. */ config = (WSGIRequestConfig *)apr_pcalloc(r->pool, sizeof(WSGIRequestConfig)); ap_set_module_config(r->request_config, &wsgi_module, (void *)config); /* Grab the socket from the connection core config. */ csd = ap_get_module_config(c->conn_config, &core_module); /* * Fake up parts of the internal per request core * configuration. If we don’t do this then when Apache is * compiled with the symbol AP_DEBUG, internal checks made * by Apache will result in process crashing. */ req_cfg = apr_pcalloc(r->pool, sizeof(core_request_config)); req_cfg->bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); ap_set_module_config(r->request_config, &core_module, req_cfg); /* Read in the request details and setup request object. */ if ((rv = wsgi_read_request(csd, r)) != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, wsgi_server, "mod_wsgi (pid=%d): Unable to read WSGI request.", getpid()); apr_pool_destroy§; return HTTP_INTERNAL_SERVER_ERROR; } /* Check magic marker used to validate origin of request. */ filename = apr_table_get(r->subprocess_env, “SCRIPT_FILENAME”); script = apr_table_get(r->subprocess_env, “mod_wsgi.handler_script”); magic = apr_table_get(r->subprocess_env, “mod_wsgi.magic”); if (!magic) { ap_log_error(APLOG_MARK, APLOG_ALERT, rv, wsgi_server, "mod_wsgi (pid=%d): Request origin could not be " "validated.", getpid()); apr_pool_destroy§; return HTTP_INTERNAL_SERVER_ERROR; } key = apr_psprintf(r->pool, "%ld|%s|%s|%s", wsgi_daemon_process->group->random, wsgi_daemon_process->group->socket_path, filename, script); hash = ap_md5(r->pool, (const unsigned char *)key); memset(key, '\0’, strlen(key)); if (strcmp(magic, hash) != 0) { ap_log_error(APLOG_MARK, APLOG_ALERT, rv, wsgi_server, "mod_wsgi (pid=%d): Request origin could not be " "validated.", getpid()); apr_pool_destroy§; return HTTP_INTERNAL_SERVER_ERROR; } apr_table_unset(r->subprocess_env, “mod_wsgi.magic”); /* * If we are executing in a chroot environment, we need to * adjust SCRIPT_FILENAME to remove leading portion of path * that corresponds to the location of the chroot directory. * Also need to adjust DOCUMENT_ROOT as well, although in * that case if it doesn’t actually fall within the choot * directory, we just delete it outright as would be incorrect * if that directory lay outside of the chroot directory. */ if (wsgi_daemon_process->group->root) { const char *root; const char *path; root = wsgi_daemon_process->group->root; path = filename; if (strstr(path, root) == path && path[strlen(root)] == ‘/’) { path += strlen(root); apr_table_set(r->subprocess_env, "SCRIPT_FILENAME", path); filename = path; } else { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, wsgi_server, "mod_wsgi (pid=%d): WSGI script ‘%s’ not located " "within chroot directory '%s’.", getpid(), path, root); return HTTP_INTERNAL_SERVER_ERROR; } path = (char *)apr_table_get(r->subprocess_env, “DOCUMENT_ROOT”); if (strstr(path, root) == path) { path += strlen(root); apr_table_set(r->subprocess_env, "DOCUMENT_ROOT", path); } else { apr_table_unset(r->subprocess_env, “DOCUMENT_ROOT”); } } r->filename = (char *)filename; /* Recalculate WSGI script or handler script modification time. */ if (script && *script) { if ((rv = apr_stat(&r->finfo, script, APR_FINFO_NORM, r->pool)) != APR_SUCCESS) { /* * Don’t fail at this point. Allow the lack of file to * be detected later when trying to load the script file. */ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, wsgi_server, "mod_wsgi (pid=%d): Unable to stat target handler " "script '%s’.", getpid(), script); r->finfo.mtime = 0; } } else { if ((rv = apr_stat(&r->finfo, filename, APR_FINFO_NORM, r->pool)) != APR_SUCCESS) { /* * Don’t fail at this point. Allow the lack of file to * be detected later when trying to load the script file. */ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, wsgi_server, "mod_wsgi (pid=%d): Unable to stat target WSGI " "script '%s’.", getpid(), filename); r->finfo.mtime = 0; } } /* * Trigger mapping of host information to server configuration * so that when logging errors they go to the correct error log * file for the host. */ #if AP_MODULE_MAGIC_AT_LEAST(20111130,0) r->connection->client_ip = (char *)apr_table_get(r->subprocess_env, “REMOTE_ADDR”); r->connection->client_addr->port = atoi(apr_table_get(r->subprocess_env, “REMOTE_PORT”)); #else r->connection->remote_ip = (char *)apr_table_get(r->subprocess_env, “REMOTE_ADDR”); r->connection->remote_addr->port = atoi(apr_table_get(r->subprocess_env, “REMOTE_PORT”)); #endif #if AP_MODULE_MAGIC_AT_LEAST(20111130,0) r->useragent_addr = c->client_addr; r->useragent_ip = c->client_ip; #endif key = apr_psprintf(p, "%s|%s", apr_table_get(r->subprocess_env, “mod_wsgi.listener_host”), apr_table_get(r->subprocess_env, “mod_wsgi.listener_port”)); if (wsgi_server_config->verbose_debugging) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Server listener address '%s’.", getpid(), key); } addr = (apr_sockaddr_t *)apr_hash_get(wsgi_daemon_listeners, key, APR_HASH_KEY_STRING); if (wsgi_server_config->verbose_debugging) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, “mod_wsgi (pid=%d): Server listener address ‘%s’ was” “%s found.", getpid(), key, addr ? “” : " not”); } if (addr) { c->local_addr = addr; } ap_update_vhost_given_ip(r->connection); if (wsgi_server_config->verbose_debugging) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Connection server matched was " "’%s|%d’.", getpid(), c->base_server->server_hostname, c->base_server->port); } r->server = c->base_server; if (apr_table_get(r->subprocess_env, “HTTP_HOST”)) { apr_table_setn(r->headers_in, "Host", apr_table_get(r->subprocess_env, “HTTP_HOST”)); } ap_update_vhost_from_headers®; if (wsgi_server_config->verbose_debugging) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wsgi_server, "mod_wsgi (pid=%d): Request server matched was '%s|%d’.", getpid(), r->server->server_hostname, r->server->port); } /* * Set content length of any request content and add the * standard HTTP input filter so that standard input routines * for request content will work. */ item = apr_table_get(r->subprocess_env, “CONTENT_LENGTH”); if (item) apr_table_setn(r->headers_in, "Content-Length", item); /* Set details of WSGI specific request config. */ config->process_group = apr_table_get(r->subprocess_env, “mod_wsgi.process_group”); config->application_group = apr_table_get(r->subprocess_env, “mod_wsgi.application_group”); config->callable_object = apr_table_get(r->subprocess_env, “mod_wsgi.callable_object”); config->handler_script = apr_table_get(r->subprocess_env, “mod_wsgi.handler_script”); config->script_reloading = atoi(apr_table_get(r->subprocess_env, “mod_wsgi.script_reloading”)); item = apr_table_get(r->subprocess_env, “mod_wsgi.enable_sendfile”); if (item && !strcasecmp(item, “1”)) config->enable_sendfile = 1; else config->enable_sendfile = 0; item = apr_table_get(r->subprocess_env, “mod_wsgi.ignore_activity”); if (item && !strcasecmp(item, “1”)) config->ignore_activity = 1; else config->ignore_activity = 0; config->daemon_connects = atoi(apr_table_get(r->subprocess_env, “mod_wsgi.daemon_connects”)); config->daemon_restarts = atoi(apr_table_get(r->subprocess_env, “mod_wsgi.daemon_restarts”)); item = apr_table_get(r->subprocess_env, “mod_wsgi.request_start”); if (item) { errno = 0; config->request_start = apr_strtoi64(item, (char **)&item, 10); if (!*item && errno != ERANGE) r->request_time = config->request_start; else config->request_start = 0.0; } item = apr_table_get(r->subprocess_env, “mod_wsgi.queue_start”); if (item) { errno = 0; config->queue_start = apr_strtoi64(item, (char **)&item, 10); if (!(!*item && errno != ERANGE)) config->queue_start = 0.0; } config->daemon_start = daemon_start; apr_table_setn(r->subprocess_env, "mod_wsgi.daemon_start", apr_psprintf(r->pool, “%” APR_TIME_T_FMT, config->daemon_start)); #if AP_MODULE_MAGIC_AT_LEAST(20100923,2) item = apr_table_get(r->subprocess_env, “mod_wsgi.request_id”); if (item) r->log_id = item; item = apr_table_get(r->subprocess_env, “mod_wsgi.connection_id”); if (item) r->connection->log_id = item; #endif /* * Install the standard HTTP input filter and set header for * chunked transfer encoding to force it to dechunk the input. * This is necessary as we chunk the data that is proxied to * the daemon processes so that we can determining whether we * actually receive all input or it was truncated. * * Note that the subprocess_env table that gets passed to the * WSGI environ dictionary has already been populated, so the * Transfer-Encoding header will not be passed in the WSGI * environ dictionary as a result of this. */ apr_table_setn(r->headers_in, "Transfer-Encoding", “chunked”); ap_add_input_filter("HTTP_IN", NULL, r, r->connection); /* Check for queue timeout. */ r->status = HTTP_OK; if (wsgi_daemon_process->group->queue_timeout) { if (config->request_start) { apr_time_t queue_time = 0; queue_time = config->daemon_start - config->request_start; if (queue_time > wsgi_daemon_process->group->queue_timeout) { queue_timeout_occurred = 1; r->status = HTTP_INTERNAL_SERVER_ERROR; r->status_line = "200 Timeout"; ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): Queue timeout expired " "for WSGI daemon process '%s’.", getpid(), wsgi_daemon_process->group->name); } } } /* * Execute the actual target WSGI application. In * normal cases OK should always be returned. If * however an error occurs in importing or executing * the script or the Python code raises an exception * which is not caught and handled, then an internal * server error can be returned. As we don’t want to * be triggering any error document handlers in the * daemon process we use a fake status line with 0 * as the status value. This will be picked up in * the Apache child process which will translate it * back to a 500 error so that normal error document * processing occurs. */ if (!queue_timeout_occurred) { if (wsgi_execute_script® != OK) { r->status = HTTP_INTERNAL_SERVER_ERROR; r->status_line = "200 Error"; } } /* * Ensure that request is finalised and any response * is flushed out. This will as a side effect read * any input data which wasn’t consumed, thus * ensuring that the Apache child process isn’t hung * waiting to send the request content and can * therefore process the response correctly. */ ap_finalize_request_protocol®; bb = apr_brigade_create(r->pool, c->bucket_alloc); e = apr_bucket_flush_create(c->bucket_alloc); APR_BRIGADE_INSERT_HEAD(bb, e); ap_pass_brigade(r->connection->output_filters, bb); apr_pool_destroy§; return OK; } #endif /* * Apache 2.X module initialisation functions. */ static int wsgi_hook_init(apr_pool_t *pconf, apr_pool_t *ptemp, apr_pool_t *plog, server_rec *s) { void *data = NULL; const char *userdata_key; char package[128]; char interpreter[256]; int status = OK; /* * No longer support using mod_python at the same time as * mod_wsgi as becoming too painful to hack around * mod_python’s broken usage of threading APIs when align * code to the stricter API requirements of Python 3.2. */ userdata_key = "python_init"; apr_pool_userdata_get(&data, userdata_key, s->process->pool); if (data) { ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, "mod_wsgi (pid=%d): The mod_python module can " "not be used in conjunction with mod_wsgi 4.0+. " "Remove the mod_python module from the Apache " "configuration.", getpid()); return HTTP_INTERNAL_SERVER_ERROR; } /* * Init function gets called twice during startup, we only * need to actually do anything on the second time it is * called. This avoids unecessarily initialising and then * destroying Python for no reason. We also though have to * deal with a special case when a graceful restart is done. * For that we are only called once, which is generally okay * as the ‘wsgi_init’ key will be set from initial start up * of the server. The exception to this is where the module * is only loaded into Apache when the server is already * running. In this case we have to detect that it is not * the initial startup, but a subsequent restart. We can do * this by looking at whether the scoreboard has been * initialised yet. That is probably enough, but to be safe, * also check what generation it is. */ userdata_key = "wsgi_init"; apr_pool_userdata_get(&data, userdata_key, s->process->pool); if (!data) { apr_pool_userdata_set((const void *)1, userdata_key, apr_pool_cleanup_null, s->process->pool); /* * Check for the special case of a graceful restart and * the module being loaded for the first time. In this * case we still go onto perform initialisation as the * initialisation routine for the module will not be * called a second time. */ if (!ap_scoreboard_image || ap_get_scoreboard_global()->running_generation == 0) { return OK; } } /* Setup module version information. */ sprintf(package, "mod_wsgi/%s", MOD_WSGI_VERSION_STRING); ap_add_version_component(pconf, package); /* Record Python version string with Apache. */ sprintf(interpreter, "Python/%d.%d", PY_MAJOR_VERSION, PY_MINOR_VERSION); ap_add_version_component(pconf, interpreter); /* Retain reference to base server. */ wsgi_server = s; /* Retain record of parent process ID. */ wsgi_parent_pid = getpid(); /* Determine whether multiprocess and/or multithread. */ ap_mpm_query(AP_MPMQ_IS_THREADED, &wsgi_multithread); if (wsgi_multithread != AP_MPMQ_NOT_SUPPORTED) { ap_mpm_query(AP_MPMQ_MAX_THREADS, &wsgi_multithread); wsgi_multithread = (wsgi_multithread != 1); } ap_mpm_query(AP_MPMQ_IS_FORKED, &wsgi_multiprocess); if (wsgi_multiprocess != AP_MPMQ_NOT_SUPPORTED) { ap_mpm_query(AP_MPMQ_MAX_DAEMONS, &wsgi_multiprocess); wsgi_multiprocess = (wsgi_multiprocess != 1); } /* Retain reference to main server config. */ wsgi_server_config = ap_get_module_config(s->module_config, &wsgi_module); /* * Check that the version of Python found at * runtime is what was used at compilation. * * XXX Can’t do this as will cause Anaconda * Python to fail as not safe to call the * Py_GetVersion() function before one calls * the Py_Initialize() function when using * Anaconda Python. */ #if 0 wsgi_python_version(); #endif /* * Initialise Python if required to be done in * the parent process. Note that it will not be * initialised if mod_python loaded and it has * already been done. */ if (wsgi_python_required == -1) wsgi_python_required = 1; if (!wsgi_python_after_fork) wsgi_python_init(pconf); /* * Startup separate named daemon processes. This is * a bit tricky as we only want to do this after the * scoreboard has been created. On the initial server * startup though, this hook function is called prior * to the MPM being run, which means the scoreboard * hasn’t been created yet. In that case we need to * defer process creation until after that, which we * can only do by hooking into the pre_mpm hook after * scoreboard creation has been done. On a server * restart, the scoreboard will be preserved, so we * can do it here, which is just as well as the pre_mpm * hook isn’t run on a restart. */ #if defined(MOD_WSGI_WITH_DAEMONS) if (!ap_scoreboard_image) { /* * Need to remember the pool we were given here as * the pre_mpm hook functions get given a different * pool which isn’t the one we want and if we use * that then Apache will crash when it is being * shutdown. So our pre_mpm hook will use the pool * we have remembered here. */ wsgi_pconf_pool = pconf; ap_hook_pre_mpm(wsgi_deferred_start_daemons, NULL, NULL, APR_HOOK_REALLY_LAST); } else status = wsgi_start_daemons(pconf); #endif return status; } static void wsgi_hook_child_init(apr_pool_t *p, server_rec *s) { #if defined(MOD_WSGI_WITH_DAEMONS) WSGIProcessGroup *entries = NULL; WSGIProcessGroup *entry = NULL; int i; /* Close listener sockets for daemon processes. */ if (wsgi_daemon_list) { entries = (WSGIProcessGroup *)wsgi_daemon_list->elts; for (i = 0; i < wsgi_daemon_list->nelts; ++i) { entry = &entries[i]; if (entry->listener_fd != -1) { close(entry->listener_fd); entry->listener_fd = -1; } } } #endif /* Remember worker process ID. */ wsgi_worker_pid = getpid(); /* Time child process started waiting for requests. */ wsgi_restart_time = apr_time_now(); /* Create lock for request monitoring. */ apr_thread_mutex_create(&wsgi_monitor_lock, APR_THREAD_MUTEX_UNNESTED, p); if (wsgi_python_required) { /* * Initialise Python if required to be done in * the child process. Note that it will not be * initialised if mod_python loaded and it has * already been done. */ if (wsgi_python_after_fork) wsgi_python_init§; /* * Now perform additional initialisation steps * always done in child process. */ wsgi_python_child_init§; } } #include “apr_lib.h” static char *wsgi_original_uri(request_rec *r) { char *first, *last; if (r->the_request == NULL) { return (char *) apr_pcalloc(r->pool, 1); } first = r->the_request; /* use the request-line */ while (*first && !apr_isspace(*first)) { ++first; /* skip over the method */ } while (apr_isspace(*first)) { ++first; /* and the space(s) */ } last = first; while (*last && !apr_isspace(*last)) { ++last; /* end at next whitespace */ } return apr_pstrmemdup(r->pool, first, last - first); } static int wsgi_http_invalid_header(const char *w) { char c; while ((c = *w++) != 0) { if (!apr_isalnum© && c != '-') return 1; } return 0; } static void wsgi_drop_invalid_headers(request_rec *r) { /* * Apache 2.2 when converting headers for CGI variables, doesn’t * ignore headers with invalid names. That is, any which use any * characters besides alphanumerics and the '-' character. This * opens us up to header spoofing whereby something can inject * multiple headers which differ by using non alphanumeric * characters in the same position, which would then encode to same * value. Since not easy to cleanup after the fact, as a workaround, * is easier to simply remove the invalid headers. This will make * things end up being the same as Apache 2.4. Doing this could * annoy some users of Apache 2.2 who were using invalid headers, * but things will break for them under Apache 2.4 anyway. */ apr_array_header_t *to_delete = NULL; const apr_array_header_t *hdrs_arr; const apr_table_entry_t *hdrs; int i; hdrs_arr = apr_table_elts(r->headers_in); hdrs = (const apr_table_entry_t *) hdrs_arr->elts; for (i = 0; i < hdrs_arr->nelts; ++i) { if (!hdrs[i].key) { continue; } if (wsgi_http_invalid_header(hdrs[i].key)) { char **new; if (!to_delete) to_delete = apr_array_make(r->pool, 1, sizeof(char *)); new = (char **)apr_array_push(to_delete); *new = hdrs[i].key; } } if (to_delete) { char *key; for (i = 0; i < to_delete->nelts; i++) { key = ((char **)to_delete->elts)[i]; apr_table_unset(r->headers_in, key); } } } static const char *wsgi_proxy_client_headers[] = { "HTTP_X_FORWARDED_FOR", "HTTP_X_CLIENT_IP", "HTTP_X_REAL_IP", NULL, }; static const char *wsgi_proxy_scheme_headers[] = { "HTTP_X_FORWARDED_HTTPS", "HTTP_X_FORWARDED_PROTO", "HTTP_X_FORWARDED_SCHEME", "HTTP_X_FORWARDED_SSL", "HTTP_X_HTTPS", "HTTP_X_SCHEME", NULL, }; static const char *wsgi_proxy_host_headers[] = { "HTTP_X_FORWARDED_HOST", "HTTP_X_HOST", NULL, }; static const char *wsgi_proxy_script_name_headers[] = { "HTTP_X_SCRIPT_NAME", "HTTP_X_FORWARDED_SCRIPT_NAME", NULL, }; static int wsgi_ip_is_in_array(apr_sockaddr_t *client_ip, apr_array_header_t *proxy_ips) { int i; apr_ipsubnet_t **subs = (apr_ipsubnet_t **)proxy_ips->elts; for (i = 0; i < proxy_ips->nelts; i++) { if (apr_ipsubnet_test(subs[i], client_ip)) { return 1; } } return 0; } static void wsgi_process_forwarded_for(request_rec *r, WSGIRequestConfig *config, const char *value ) { if (config->trusted_proxies) { /* * A potentially comma separated list where client we are * interested in will be that immediately before the last * trusted proxy working from the end forwards. If there * are no trusted proxies then we use the last. */ apr_array_header_t *arr; arr = apr_array_make(r->pool, 3, sizeof(char *)); while (*value != ‘\0’) { /* Skip leading whitespace for item. */ while (*value != ‘\0’ && apr_isspace(*value)) value++; if (*value != ‘\0’) { const char *end = NULL; const char *next = NULL; char **entry = NULL; end = value; while (*end != ‘\0’ && *end != ‘,’) end++; if (*end == ‘\0’) next = end; else if (*end == ‘,’) next = end+1; /* Need deal with trailing whitespace. */ while (end != value) { if (!apr_isspace(*(end-1))) break; end–; } entry = (char **)apr_array_push(arr); *entry = apr_pstrndup(r->pool, value, (end-value)); value = next; } } if (arr->nelts != 0) { /* HTTP_X_FORDWARDED_FOR wasn’t just an empty string. */ char **items; int first = -1; int i; items = (char **)arr->elts; /* * Work out the position of the IP closest to the start * that we actually trusted. */ for (i=arr->nelts; i>0; ) { apr_sockaddr_t *sa; apr_status_t rv; i–; rv = apr_sockaddr_info_get(&sa, items[i], APR_UNSPEC, 0, 0, r->pool); if (rv == APR_SUCCESS) { if (!wsgi_ip_is_in_array(sa, config->trusted_proxies)) break; first = i; } else { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, 0, r, “mod_wsgi (pid=%d): Forwarded IP of \"%s\” is " "not a valid IP address.", getpid(), items[i]); break; } } if (first >= 0) { /* * We found at least one trusted IP. We use the * IP that may have appeared before that as * REMOTE_ADDR. We rewrite HTTP_X_FORWARDED_FOR * to record only from REMOTE_ADDR onwards. */ char *list; i = first-1; if (i<0) i = 0; apr_table_setn(r->subprocess_env, "REMOTE_ADDR", items[i]); list = items[i]; i++; while (arr->nelts != i) { list = apr_pstrcat(r->pool, list, ", ", items[i], NULL); i++; } apr_table_setn(r->subprocess_env, "HTTP_X_FORWARDED_FOR", list); } else { /* * No trusted IP. Use the last for REMOTE_ADDR. * We rewrite HTTP_X_FORWARDED_FOR to record only * the last. */ apr_table_setn(r->subprocess_env, "REMOTE_ADDR", items[arr->nelts-1]); apr_table_setn(r->subprocess_env, "HTTP_X_FORWARDED_FOR", items[arr->nelts-1]); } } } else { /* * We do not need to validate the proxies. We will have a * potentially comma separated list where the client we * are interested in will be listed first. */ const char *end = NULL; /* Skip leading whitespace for item. */ while (*value != ‘\0’ && apr_isspace(*value)) value++; if (*value != ‘\0’) { end = value; while (*end != ‘\0’ && *end != ‘,’) end++; /* Need deal with trailing whitespace. */ while (end != value) { if (!apr_isspace(*(end-1))) break; end–; } /* Override REMOTE_ADDR. Leave HTTP_X_FORWARDED_FOR. */ apr_table_setn(r->subprocess_env, "REMOTE_ADDR", apr_pstrndup(r->pool, value, (end-value))); } } } static void wsgi_process_proxy_headers(request_rec *r) { WSGIRequestConfig *config = NULL; apr_array_header_t *trusted_proxy_headers = NULL; int match_client_header = 0; int match_host_header = 0; int match_script_name_header = 0; int match_scheme_header = 0; const char *trusted_client_header = NULL; const char *trusted_host_header = NULL; const char *trusted_script_name_header = NULL; const char *trusted_scheme_header = NULL; int i = 0; int trusted_proxy = 1; const char *client_ip = NULL; apr_status_t rv; config = (WSGIRequestConfig *)ap_get_module_config(r->request_config, &wsgi_module); trusted_proxy_headers = config->trusted_proxy_headers; /* Nothing to do if no trusted headers have been specified. */ if (!trusted_proxy_headers) return; /* * Check for any special processing required for each trusted * header which has been specified. We should only do this if * there was no list of trusted proxies, or if the client IP * was that of a trusted proxy. */ if (config->trusted_proxies) { client_ip = apr_table_get(r->subprocess_env, “REMOTE_ADDR”); if (client_ip) { apr_sockaddr_t *sa; rv = apr_sockaddr_info_get(&sa, client_ip, APR_UNSPEC, 0, 0, r->pool); if (rv == APR_SUCCESS) { if (!wsgi_ip_is_in_array(sa, config->trusted_proxies)) trusted_proxy = 0; } else { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, “mod_wsgi (pid=%d): REMOTE_ADDR of \"%s\” is " "not a valid IP address.", getpid(), client_ip); trusted_proxy = 0; } } else trusted_proxy = 0; } if (trusted_proxy) { for (i=0; i<trusted_proxy_headers->nelts; i++) { const char *name; const char *value; name = ((const char**)trusted_proxy_headers->elts)[i]; value = apr_table_get(r->subprocess_env, name); if (!strcmp(name, “HTTP_X_FORWARDED_FOR”)) { match_client_header = 1; if (value) { wsgi_process_forwarded_for(r, config, value); trusted_client_header = name; } } else if (!strcmp(name, “HTTP_X_CLIENT_IP”) || !strcmp(name, “HTTP_X_REAL_IP”)) { match_client_header = 1; if (value) { /* Use the value as is. */ apr_table_setn(r->subprocess_env, "REMOTE_ADDR", value); trusted_client_header = name; } } else if (!strcmp(name, “HTTP_X_FORWARDED_HOST”) || !strcmp(name, “HTTP_X_HOST”)) { match_host_header = 1; if (value) { /* Use the value as is. May include a port. */ trusted_host_header = name; apr_table_setn(r->subprocess_env, "HTTP_HOST", value); } } else if (!strcmp(name, “HTTP_X_FORWARDED_SERVER”)) { if (value) { /* Use the value as is. */ apr_table_setn(r->subprocess_env, "SERVER_NAME", value); } } else if (!strcmp(name, “HTTP_X_FORWARDED_PORT”)) { if (value) { /* Use the value as is. */ apr_table_setn(r->subprocess_env, "SERVER_PORT", value); } } else if (!strcmp(name, “HTTP_X_SCRIPT_NAME”) || !strcmp(name, “HTTP_X_FORWARDED_SCRIPT_NAME”)) { match_script_name_header = 1; if (value) { /* * Use the value as is. We want to remember what the * original value for SCRIPT_NAME was though. */ apr_table_setn(r->subprocess_env, "mod_wsgi.mount_point", value); trusted_script_name_header = name; apr_table_setn(r->subprocess_env, "SCRIPT_NAME", value); } } else if (!strcmp(name, “HTTP_X_FORWARDED_PROTO”) || !strcmp(name, “HTTP_X_FORWARDED_SCHEME”) || !strcmp(name, “HTTP_X_SCHEME”)) { match_scheme_header = 1; if (value) { trusted_scheme_header = name; /* Value can be either ‘http’ or 'https’. */ if (!strcasecmp(value, “https”)) apr_table_setn(r->subprocess_env, "HTTPS", “1”); else if (!strcasecmp(value, “http”)) apr_table_unset(r->subprocess_env, “HTTPS”); } } else if (!strcmp(name, “HTTP_X_FORWARDED_HTTPS”) || !strcmp(name, “HTTP_X_FORWARDED_SSL”) || !strcmp(name, “HTTP_X_HTTPS”)) { match_scheme_header = 1; if (value) { trusted_scheme_header = name; /* * Value can be a boolean like flag such as 'On’, * 'Off’, 'true’, 'false’, ‘1’ or '0’. */ if (!strcasecmp(value, “On”) || !strcasecmp(value, “true”) || !strcasecmp(value, “1”)) { apr_table_setn(r->subprocess_env, "HTTPS", “1”); } else if (!strcasecmp(value, “Off”) || !strcasecmp(value, “false”) || !strcasecmp(value, “0”)) { apr_table_unset(r->subprocess_env, “HTTPS”); } } } } } else { /* * If it isn’t a trusted proxy, we still need to knock * out any headers for categories we were interested in. */ for (i=0; i<trusted_proxy_headers->nelts; i++) { const char *name; name = ((const char**)trusted_proxy_headers->elts)[i]; if (!strcmp(name, “HTTP_X_FORWARDED_FOR”) || !strcmp(name, “HTTP_X_REAL_IP”)) { match_client_header = 1; } else if (!strcmp(name, “HTTP_X_FORWARDED_HOST”) || !strcmp(name, “HTTP_X_HOST”)) { match_host_header = 1; } else if (!strcmp(name, “HTTP_X_SCRIPT_NAME”) || !strcmp(name, “HTTP_X_FORWARDED_SCRIPT_NAME”)) { match_script_name_header = 1; } else if (!strcmp(name, “HTTP_X_FORWARDED_PROTO”) || !strcmp(name, “HTTP_X_FORWARDED_SCHEME”) || !strcmp(name, “HTTP_X_SCHEME”) || !strcmp(name, “HTTP_X_FORWARDED_HTTPS”) || !strcmp(name, “HTTP_X_FORWARDED_SSL”) || !strcmp(name, “HTTP_X_HTTPS”)) { match_scheme_header = 1; } } } /* * Remove all client IP headers from request environment which * weren’t matched as being trusted. */ if (match_client_header) { const char *name = NULL; for (i=0; (name=wsgi_proxy_client_headers[i]); i++) { if (!trusted_client_header || strcmp(name, trusted_client_header)) { apr_table_unset(r->subprocess_env, name); } } } /* * Remove all proxy scheme headers from request environment * which weren’t matched as being trusted. */ if (match_scheme_header) { const char *name = NULL; for (i=0; (name=wsgi_proxy_scheme_headers[i]); i++) { if (!trusted_scheme_header || strcmp(name, trusted_scheme_header)) { apr_table_unset(r->subprocess_env, name); } } } /* * Remove all proxy host from request environment which weren’t * matched as being trusted. */ if (match_host_header) { const char *name = NULL; for (i=0; (name=wsgi_proxy_host_headers[i]); i++) { if (!trusted_host_header || strcmp(name, trusted_host_header)) apr_table_unset(r->subprocess_env, name); } } /* * Remove all proxy script name headers from request environment * which weren’t matched as being trusted. */ if (match_script_name_header) { const char *name = NULL; for (i=0; (name=wsgi_proxy_script_name_headers[i]); i++) { if (!trusted_script_name_header || strcmp(name, trusted_script_name_header)) { apr_table_unset(r->subprocess_env, name); } } } } static char *wsgi_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©) { *cp++ = apr_toupper©; } else if (c == '-') { *cp++ = '_’; } else return NULL; } *cp = 0; return res; } typedef struct { PyObject_HEAD request_rec *r; WSGIRequestConfig *config; PyObject *log; } AuthObject; static AuthObject *newAuthObject(request_rec *r, WSGIRequestConfig *config) { AuthObject *self; self = PyObject_New(AuthObject, &Auth_Type); if (self == NULL) return NULL; self->config = config; self->r = r; self->log = newLogObject(r, APLOG_ERR, NULL, 0); return self; } static void Auth_dealloc(AuthObject *self) { Py_DECREF(self->log); PyObject_Del(self); } static PyObject *Auth_environ(AuthObject *self, const char *group) { PyObject *vars; PyObject *object; request_rec *r = self->r; server_rec *s = r->server; conn_rec *c = r->connection; apr_port_t rport; const apr_array_header_t *hdrs_arr; const apr_table_entry_t *hdrs; const char *value = NULL; int i; vars = PyDict_New(); hdrs_arr = apr_table_elts(r->headers_in); hdrs = (const apr_table_entry_t *) hdrs_arr->elts; for (i = 0; i < hdrs_arr->nelts; ++i) { if (!hdrs[i].key) { continue; } if (!strcasecmp(hdrs[i].key, “Content-type”)) { #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(hdrs[i].val, strlen(hdrs[i].val), NULL); #else object = PyString_FromString(hdrs[i].val); #endif PyDict_SetItemString(vars, "CONTENT_TYPE", object); Py_DECREF(object); } else if (!strcasecmp(hdrs[i].key, “Content-length”)) { #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(hdrs[i].val, strlen(hdrs[i].val), NULL); #else object = PyString_FromString(hdrs[i].val); #endif PyDict_SetItemString(vars, "CONTENT_LENGTH", object); Py_DECREF(object); } else if (!strcasecmp(hdrs[i].key, “Authorization”) || !strcasecmp(hdrs[i].key, “Proxy-Authorization”)) { continue; } else { if (hdrs[i].val) { char *header = wsgi_http2env(r->pool, hdrs[i].key); if (header) { #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(hdrs[i].val, strlen(hdrs[i].val), NULL); #else object = PyString_FromString(hdrs[i].val); #endif PyDict_SetItemString(vars, header, object); Py_DECREF(object); } } } } value = ap_psignature("", r); #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "SERVER_SIGNATURE", object); Py_DECREF(object); #if AP_MODULE_MAGIC_AT_LEAST(20060905,0) value = ap_get_server_banner(); #else value = ap_get_server_version(); #endif #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "SERVER_SOFTWARE", object); Py_DECREF(object); value = ap_escape_html(r->pool, ap_get_server_name®); #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "SERVER_NAME", object); Py_DECREF(object); if (r->connection->local_ip) { value = r->connection->local_ip; #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "SERVER_ADDR", object); Py_DECREF(object); } value = apr_psprintf(r->pool, "%u", ap_get_server_port®); #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "SERVER_PORT", object); Py_DECREF(object); value = ap_get_remote_host(c, r->per_dir_config, REMOTE_HOST, NULL); if (value) { #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "REMOTE_HOST", object); Py_DECREF(object); } #if AP_MODULE_MAGIC_AT_LEAST(20111130,0) if (r->useragent_ip) { value = r->useragent_ip; #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "REMOTE_ADDR", object); Py_DECREF(object); } #else if (c->remote_ip) { value = c->remote_ip; #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "REMOTE_ADDR", object); Py_DECREF(object); } #endif #if PY_MAJOR_VERSION >= 3 value = ap_document_root®; object = PyUnicode_Decode(value, strlen(value), Py_FileSystemDefaultEncoding, “surrogateescape”); #else object = PyString_FromString(ap_document_root®); #endif PyDict_SetItemString(vars, "DOCUMENT_ROOT", object); Py_DECREF(object); if (s->server_admin) { value = s->server_admin; #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "SERVER_ADMIN", object); Py_DECREF(object); } #if AP_MODULE_MAGIC_AT_LEAST(20111130,0) rport = c->client_addr->port; value = apr_itoa(r->pool, rport); #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "REMOTE_PORT", object); Py_DECREF(object); #else rport = c->remote_addr->port; value = apr_itoa(r->pool, rport); #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "REMOTE_PORT", object); Py_DECREF(object); #endif value = r->protocol; #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "SERVER_PROTOCOL", object); Py_DECREF(object); value = r->method; #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "REQUEST_METHOD", object); Py_DECREF(object); value = r->args ? r->args : ""; #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "QUERY_STRING", object); Py_DECREF(object); value = wsgi_original_uri®; #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "REQUEST_URI", object); Py_DECREF(object); /* * XXX Apparently webdav does actually do modifications to * the uri and path_info attributes of request and they * could be used as part of authorisation. */ if (!strcmp(r->protocol, “INCLUDED”)) { value = r->uri; #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "SCRIPT_NAME", object); Py_DECREF(object); value = r->path_info ? r->path_info : ""; #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "PATH_INFO", object); Py_DECREF(object); } else if (!r->path_info || !*r->path_info) { value = r->uri; #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "SCRIPT_NAME", object); Py_DECREF(object); value = ""; #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "PATH_INFO", object); Py_DECREF(object); } else { int path_info_start = ap_find_path_info(r->uri, r->path_info); value = apr_pstrndup(r->pool, r->uri, path_info_start); #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "SCRIPT_NAME", object); Py_DECREF(object); value = r->path_info ? r->path_info : ""; #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else object = PyString_FromString(value); #endif PyDict_SetItemString(vars, "PATH_INFO", object); Py_DECREF(object); } object = Py_BuildValue("(iii)", AP_SERVER_MAJORVERSION_NUMBER, AP_SERVER_MINORVERSION_NUMBER, AP_SERVER_PATCHLEVEL_NUMBER); PyDict_SetItemString(vars, "apache.version", object); Py_DECREF(object); object = Py_BuildValue("(iii)", MOD_WSGI_MAJORVERSION_NUMBER, MOD_WSGI_MINORVERSION_NUMBER, MOD_WSGI_MICROVERSION_NUMBER); PyDict_SetItemString(vars, "mod_wsgi.version", object); Py_DECREF(object); #if PY_MAJOR_VERSION >= 3 object = PyUnicode_FromString(“”); #else object = PyString_FromString(“”); #endif PyDict_SetItemString(vars, "mod_wsgi.process_group", object); Py_DECREF(object); #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(group, strlen(group), NULL); #else object = PyString_FromString(group); #endif PyDict_SetItemString(vars, "mod_wsgi.application_group", object); Py_DECREF(object); object = PyLong_FromLong(self->config->script_reloading); PyDict_SetItemString(vars, "mod_wsgi.script_reloading", object); Py_DECREF(object); /* * Setup log object for WSGI errors. Don’t decrement * reference to log object as keep reference to it. */ object = (PyObject *)self->log; PyDict_SetItemString(vars, "wsgi.errors", object); /* * If Apache extensions are enabled add a CObject reference * to the Apache request_rec structure instance. */ if (!wsgi_daemon_pool && self->config->pass_apache_request) { #if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 2) || \ (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 7) object = PyCapsule_New(self->r, 0, 0); #else object = PyCObject_FromVoidPtr(self->r, 0); #endif PyDict_SetItemString(vars, "apache.request_rec", object); Py_DECREF(object); } /* * Extensions for accessing SSL certificate information from * mod_ssl when in use. */ object = PyObject_GetAttrString((PyObject *)self, “ssl_is_https”); PyDict_SetItemString(vars, "mod_ssl.is_https", object); Py_DECREF(object); object = PyObject_GetAttrString((PyObject *)self, “ssl_var_lookup”); PyDict_SetItemString(vars, "mod_ssl.var_lookup", object); Py_DECREF(object); return vars; } static PyObject *Auth_ssl_is_https(AuthObject *self, PyObject *args) { APR_OPTIONAL_FN_TYPE(ssl_is_https) *ssl_is_https = 0; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, “request object has expired”); return NULL; } if (!PyArg_ParseTuple(args, “:ssl_is_https”)) return NULL; ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); if (ssl_is_https == 0) return Py_BuildValue("i", 0); return Py_BuildValue("i", ssl_is_https(self->r->connection)); } static PyObject *Auth_ssl_var_lookup(AuthObject *self, PyObject *args) { APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *ssl_var_lookup = 0; PyObject *item = NULL; PyObject *latin_item = NULL; char *name = 0; char *value = 0; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, “request object has expired”); return NULL; } if (!PyArg_ParseTuple(args, "O:ssl_var_lookup", &item)) return NULL; #if PY_MAJOR_VERSION >= 3 if (PyUnicode_Check(item)) { latin_item = PyUnicode_AsLatin1String(item); if (!latin_item) { PyErr_Format(PyExc_TypeError, "byte string value expected, " “value containing non ‘latin-1’ characters found”); return NULL; } item = latin_item; } #endif if (!PyString_Check(item)) { PyErr_Format(PyExc_TypeError, "byte string value expected, value " "of type %.200s found", item->ob_type->tp_name); Py_XDECREF(latin_item); return NULL; } name = PyString_AsString(item); ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); if (ssl_var_lookup == 0) { Py_XDECREF(latin_item); Py_INCREF(Py_None); return Py_None; } value = ssl_var_lookup(self->r->pool, self->r->server, self->r->connection, self->r, name); Py_XDECREF(latin_item); if (!value) { Py_INCREF(Py_None); return Py_None; } #if PY_MAJOR_VERSION >= 3 return PyUnicode_DecodeLatin1(value, strlen(value), NULL); #else return PyString_FromString(value); #endif } static PyMethodDef Auth_methods[] = { { "ssl_is_https", (PyCFunction)Auth_ssl_is_https, METH_VARARGS, 0 }, { "ssl_var_lookup", (PyCFunction)Auth_ssl_var_lookup, METH_VARARGS, 0 }, { NULL, NULL} }; static PyTypeObject Auth_Type = { PyVarObject_HEAD_INIT(NULL, 0) "mod_wsgi.Auth", /*tp_name*/ sizeof(AuthObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)Auth_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ 0, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ 0, /*tp_iter*/ 0, /*tp_iternext*/ Auth_methods, /*tp_methods*/ 0, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ 0, /*tp_init*/ 0, /*tp_alloc*/ 0, /*tp_new*/ 0, /*tp_free*/ 0, /*tp_is_gc*/ }; #if defined(MOD_WSGI_WITH_AUTHN_PROVIDER) static authn_status wsgi_check_password(request_rec *r, const char *user, const char *password) { WSGIRequestConfig *config; InterpreterObject *interp = NULL; PyObject *modules = NULL; PyObject *module = NULL; char *name = NULL; int exists = 0; const char *script; const char *group; authn_status status; config = wsgi_create_req_config(r->pool, r); if (!config->auth_user_script) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server, "mod_wsgi (pid=%d): Location of WSGI user " "authentication script not provided.", getpid()); return AUTH_GENERAL_ERROR; } /* * Acquire the desired python interpreter. Once this is done * it is safe to start manipulating python objects. */ script = config->auth_user_script->handler_script; group = wsgi_server_group(r, config->auth_user_script->application_group); interp = wsgi_acquire_interpreter(group); if (!interp) { ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, "mod_wsgi (pid=%d): Cannot acquire interpreter ‘%s’.", getpid(), group); return AUTH_GENERAL_ERROR; } /* Calculate the Python module name to be used for script. */ name = wsgi_module_name(r->pool, script); /* * Use a lock around the check to see if the module is * already loaded and the import of the module to prevent * two request handlers trying to import the module at the * same time. */ #if APR_HAS_THREADS if (config->script_reloading) { Py_BEGIN_ALLOW_THREADS apr_thread_mutex_lock(wsgi_module_lock); Py_END_ALLOW_THREADS } #endif modules = PyImport_GetModuleDict(); module = PyDict_GetItemString(modules, name); Py_XINCREF(module); if (module) exists = 1; /* * If script reloading is enabled and the module for it has * previously been loaded, see if it has been modified since * the last time it was accessed. */ if (module && config->script_reloading) { if (wsgi_reload_required(r->pool, r, script, module, NULL)) { /* * Script file has changed. Only support module * reloading for authentication scripts. Remove the * module from the modules dictionary before * reloading it again. If code is executing within * the module at the time, the callers reference * count on the module should ensure it isn’t * actually destroyed until it is finished. */ Py_DECREF(module); module = NULL; PyDict_DelItemString(modules, name); } } if (!module) { module = wsgi_load_source(r->pool, r, name, exists, script, "", group, 0); } /* Safe now to release the module lock. */ #if APR_HAS_THREADS if (config->script_reloading) apr_thread_mutex_unlock(wsgi_module_lock); #endif /* Log any details of exceptions if import failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); /* Assume an internal server error unless everything okay. */ status = AUTH_GENERAL_ERROR; /* Determine if script exists and execute it. */ if (module) { PyObject *module_dict = NULL; PyObject *object = NULL; module_dict = PyModule_GetDict(module); object = PyDict_GetItemString(module_dict, “check_password”); if (object) { PyObject *vars = NULL; PyObject *args = NULL; PyObject *result = NULL; PyObject *method = NULL; AuthObject *adapter = NULL; adapter = newAuthObject(r, config); if (adapter) { vars = Auth_environ(adapter, group); Py_INCREF(object); args = Py_BuildValue("(Oss)", vars, user, password); result = PyObject_CallObject(object, args); Py_DECREF(args); Py_DECREF(object); Py_DECREF(vars); if (result) { if (result == Py_None) { status = AUTH_USER_NOT_FOUND; } else if (result == Py_True) { status = AUTH_GRANTED; } else if (result == Py_False) { status = AUTH_DENIED; } #if PY_MAJOR_VERSION >= 3 else if (PyUnicode_Check(result)) { PyObject *str = NULL; str = PyUnicode_AsUTF8String(result); if (str) { adapter->r->user = apr_pstrdup(adapter->r->pool, PyString_AsString(str)); status = AUTH_GRANTED; } } #else else if (PyString_Check(result)) { adapter->r->user = apr_pstrdup(adapter->r->pool, PyString_AsString(result)); status = AUTH_GRANTED; } #endif else { PyErr_SetString(PyExc_TypeError, "Basic auth " "provider must return True, False " “None or user name as string”); } Py_DECREF(result); } /* * Wipe out references to Apache request object * held by Python objects, so can detect when an * application holds on to the transient Python * objects beyond the life of the request and * thus raise an exception if they are used. */ adapter->r = NULL; /* Log any details of exceptions if execution failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); /* Close the log object so data is flushed. */ method = PyObject_GetAttrString(adapter->log, “close”); if (!method) { PyErr_Format(PyExc_AttributeError, "’%s’ object has no attribute ‘close’", adapter->log->ob_type->tp_name); } else { result = PyObject_CallObject(method, NULL); Py_XDECREF(result); } /* Log any details of exceptions if execution failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); Py_XDECREF(method); /* No longer need adapter object. */ Py_DECREF((PyObject *)adapter); } } else { Py_BEGIN_ALLOW_THREADS ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): Target WSGI user " "authentication script ‘%s’ does not provide " "’Basic’ auth provider.", getpid(), script); Py_END_ALLOW_THREADS } } /* Cleanup and release interpreter, */ Py_XDECREF(module); wsgi_release_interpreter(interp); return status; } static authn_status wsgi_get_realm_hash(request_rec *r, const char *user, const char *realm, char **rethash) { WSGIRequestConfig *config; InterpreterObject *interp = NULL; PyObject *modules = NULL; PyObject *module = NULL; char *name = NULL; int exists = 0; const char *script; const char *group; authn_status status; config = wsgi_create_req_config(r->pool, r); if (!config->auth_user_script) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server, "mod_wsgi (pid=%d): Location of WSGI user " "authentication script not provided.", getpid()); return AUTH_GENERAL_ERROR; } /* * Acquire the desired python interpreter. Once this is done * it is safe to start manipulating python objects. */ script = config->auth_user_script->handler_script; group = wsgi_server_group(r, config->auth_user_script->application_group); interp = wsgi_acquire_interpreter(group); if (!interp) { ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, "mod_wsgi (pid=%d): Cannot acquire interpreter ‘%s’.", getpid(), group); return AUTH_GENERAL_ERROR; } /* Calculate the Python module name to be used for script. */ name = wsgi_module_name(r->pool, script); /* * Use a lock around the check to see if the module is * already loaded and the import of the module to prevent * two request handlers trying to import the module at the * same time. */ #if APR_HAS_THREADS Py_BEGIN_ALLOW_THREADS apr_thread_mutex_lock(wsgi_module_lock); Py_END_ALLOW_THREADS #endif modules = PyImport_GetModuleDict(); module = PyDict_GetItemString(modules, name); Py_XINCREF(module); if (module) exists = 1; /* * If script reloading is enabled and the module for it has * previously been loaded, see if it has been modified since * the last time it was accessed. */ if (module && config->script_reloading) { if (wsgi_reload_required(r->pool, r, script, module, NULL)) { /* * Script file has changed. Only support module * reloading for authentication scripts. Remove the * module from the modules dictionary before * reloading it again. If code is executing within * the module at the time, the callers reference * count on the module should ensure it isn’t * actually destroyed until it is finished. */ Py_DECREF(module); module = NULL; PyDict_DelItemString(modules, name); } } if (!module) { module = wsgi_load_source(r->pool, r, name, exists, script, "", group, 0); } /* Safe now to release the module lock. */ #if APR_HAS_THREADS apr_thread_mutex_unlock(wsgi_module_lock); #endif /* Log any details of exceptions if import failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); /* Assume an internal server error unless everything okay. */ status = AUTH_GENERAL_ERROR; /* Determine if script exists and execute it. */ if (module) { PyObject *module_dict = NULL; PyObject *object = NULL; module_dict = PyModule_GetDict(module); object = PyDict_GetItemString(module_dict, “get_realm_hash”); if (object) { PyObject *vars = NULL; PyObject *args = NULL; PyObject *result = NULL; PyObject *method = NULL; AuthObject *adapter = NULL; adapter = newAuthObject(r, config); if (adapter) { vars = Auth_environ(adapter, group); Py_INCREF(object); args = Py_BuildValue("(Oss)", vars, user, realm); result = PyObject_CallObject(object, args); Py_DECREF(args); Py_DECREF(object); Py_DECREF(vars); if (result) { if (result == Py_None) { status = AUTH_USER_NOT_FOUND; } else if (PyString_Check(result)) { *rethash = PyString_AsString(result); *rethash = apr_pstrdup(r->pool, *rethash); status = AUTH_USER_FOUND; } #if PY_MAJOR_VERSION >= 3 else if (PyUnicode_Check(result)) { PyObject *latin_item; latin_item = PyUnicode_AsLatin1String(result); if (!latin_item) { PyErr_SetString(PyExc_TypeError, "Digest auth " "provider must return None " "or string object, value " "containing non ‘latin-1’ " “characters found”); } else { Py_DECREF(result); result = latin_item; *rethash = PyString_AsString(result); *rethash = apr_pstrdup(r->pool, *rethash); status = AUTH_USER_FOUND; } } #endif else { PyErr_SetString(PyExc_TypeError, "Digest auth " "provider must return None " “or string object”); } Py_DECREF(result); } /* * Wipe out references to Apache request object * held by Python objects, so can detect when an * application holds on to the transient Python * objects beyond the life of the request and * thus raise an exception if they are used. */ adapter->r = NULL; /* Log any details of exceptions if execution failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); /* Close the log object so data is flushed. */ method = PyObject_GetAttrString(adapter->log, “close”); if (!method) { PyErr_Format(PyExc_AttributeError, "’%s’ object has no attribute ‘close’", adapter->log->ob_type->tp_name); } else { args = PyTuple_New(0); result = PyObject_CallObject(method, args); Py_XDECREF(result); Py_DECREF(args); } /* Log any details of exceptions if execution failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); Py_XDECREF(method); /* No longer need adapter object. */ Py_DECREF((PyObject *)adapter); } } else { Py_BEGIN_ALLOW_THREADS ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): Target WSGI user " "authentication script ‘%s’ does not provide " "’Digest’ auth provider.", getpid(), script); Py_END_ALLOW_THREADS } } /* Cleanup and release interpreter, */ Py_XDECREF(module); wsgi_release_interpreter(interp); return status; } static const authn_provider wsgi_authn_provider = { &wsgi_check_password, &wsgi_get_realm_hash }; #endif static int wsgi_groups_for_user(request_rec *r, WSGIRequestConfig *config, apr_table_t **grpstatus) { apr_table_t *grps = apr_table_make(r->pool, 15); InterpreterObject *interp = NULL; PyObject *modules = NULL; PyObject *module = NULL; char *name = NULL; int exists = 0; const char *script; const char *group; int status = HTTP_INTERNAL_SERVER_ERROR; if (!config->auth_group_script) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server, "mod_wsgi (pid=%d): Location of WSGI group " "authentication script not provided.", getpid()); return HTTP_INTERNAL_SERVER_ERROR; } /* * Acquire the desired python interpreter. Once this is done * it is safe to start manipulating python objects. */ script = config->auth_group_script->handler_script; group = wsgi_server_group(r, config->auth_group_script->application_group); interp = wsgi_acquire_interpreter(group); if (!interp) { ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, "mod_wsgi (pid=%d): Cannot acquire interpreter ‘%s’.", getpid(), group); return HTTP_INTERNAL_SERVER_ERROR; } /* Calculate the Python module name to be used for script. */ name = wsgi_module_name(r->pool, script); /* * Use a lock around the check to see if the module is * already loaded and the import of the module to prevent * two request handlers trying to import the module at the * same time. */ #if APR_HAS_THREADS Py_BEGIN_ALLOW_THREADS apr_thread_mutex_lock(wsgi_module_lock); Py_END_ALLOW_THREADS #endif modules = PyImport_GetModuleDict(); module = PyDict_GetItemString(modules, name); Py_XINCREF(module); if (module) exists = 1; /* * If script reloading is enabled and the module for it has * previously been loaded, see if it has been modified since * the last time it was accessed. */ if (module && config->script_reloading) { if (wsgi_reload_required(r->pool, r, script, module, NULL)) { /* * Script file has changed. Only support module * reloading for authentication scripts. Remove the * module from the modules dictionary before * reloading it again. If code is executing within * the module at the time, the callers reference * count on the module should ensure it isn’t * actually destroyed until it is finished. */ Py_DECREF(module); module = NULL; PyDict_DelItemString(modules, name); } } if (!module) { module = wsgi_load_source(r->pool, r, name, exists, script, "", group, 0); } /* Safe now to release the module lock. */ #if APR_HAS_THREADS apr_thread_mutex_unlock(wsgi_module_lock); #endif /* Log any details of exceptions if import failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); /* Assume an internal server error unless everything okay. */ status = HTTP_INTERNAL_SERVER_ERROR; /* Determine if script exists and execute it. */ if (module) { PyObject *module_dict = NULL; PyObject *object = NULL; module_dict = PyModule_GetDict(module); object = PyDict_GetItemString(module_dict, “groups_for_user”); if (object) { PyObject *vars = NULL; PyObject *args = NULL; PyObject *result = NULL; PyObject *method = NULL; AuthObject *adapter = NULL; adapter = newAuthObject(r, config); if (adapter) { vars = Auth_environ(adapter, group); Py_INCREF(object); args = Py_BuildValue("(Os)", vars, r->user); result = PyObject_CallObject(object, args); Py_DECREF(args); Py_DECREF(object); Py_DECREF(vars); if (result) { PyObject *iterator; iterator = PyObject_GetIter(result); if (iterator) { PyObject *item; const char *name; status = OK; while ((item = PyIter_Next(iterator))) { #if PY_MAJOR_VERSION >= 3 if (PyUnicode_Check(item)) { PyObject *latin_item; latin_item = PyUnicode_AsLatin1String(item); if (!latin_item) { Py_BEGIN_ALLOW_THREADS ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): " "Groups for user returned " "from ‘%s’ must be an " "iterable sequence of " "byte strings, value " "containing non ‘latin-1’ " "characters found", getpid(), script); Py_END_ALLOW_THREADS Py_DECREF(item); status = HTTP_INTERNAL_SERVER_ERROR; break; } else { Py_DECREF(item); item = latin_item; } } #endif if (!PyString_Check(item)) { Py_BEGIN_ALLOW_THREADS ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): Groups for " "user returned from ‘%s’ must " "be an iterable sequence of " "byte strings.", getpid(), script); Py_END_ALLOW_THREADS Py_DECREF(item); status = HTTP_INTERNAL_SERVER_ERROR; break; } name = PyString_AsString(item); apr_table_setn(grps, apr_pstrdup(r->pool, name), “1”); Py_DECREF(item); } Py_DECREF(iterator); } else { Py_BEGIN_ALLOW_THREADS ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): Groups for user " "returned from ‘%s’ must be an iterable " "sequence of byte strings.", getpid(), script); Py_END_ALLOW_THREADS } Py_DECREF(result); } /* * Wipe out references to Apache request object * held by Python objects, so can detect when an * application holds on to the transient Python * objects beyond the life of the request and * thus raise an exception if they are used. */ adapter->r = NULL; /* Log any details of exceptions if execution failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); /* Close the log object so data is flushed. */ method = PyObject_GetAttrString(adapter->log, “close”); if (!method) { PyErr_Format(PyExc_AttributeError, "’%s’ object has no attribute 'close’", adapter->log->ob_type->tp_name); } else { args = PyTuple_New(0); result = PyObject_CallObject(method, args); Py_XDECREF(result); Py_DECREF(args); } /* Log any details of exceptions if execution failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); Py_XDECREF(method); /* No longer need adapter object. */ Py_DECREF((PyObject *)adapter); } } else { Py_BEGIN_ALLOW_THREADS ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): Target WSGI group " "authentication script ‘%s’ does not provide " "group provider.", getpid(), script); Py_END_ALLOW_THREADS } } /* Cleanup and release interpreter, */ Py_XDECREF(module); wsgi_release_interpreter(interp); if (status == OK) *grpstatus = grps; return status; } static int wsgi_allow_access(request_rec *r, WSGIRequestConfig *config, const char *host) { InterpreterObject *interp = NULL; PyObject *modules = NULL; PyObject *module = NULL; char *name = NULL; int exists = 0; const char *script; const char *group; int allow = 0; if (!config->access_script) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server, "mod_wsgi (pid=%d): Location of WSGI host " "access script not provided.", getpid()); return 0; } /* * Acquire the desired python interpreter. Once this is done * it is safe to start manipulating python objects. */ script = config->access_script->handler_script; group = wsgi_server_group(r, config->access_script->application_group); interp = wsgi_acquire_interpreter(group); if (!interp) { ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, "mod_wsgi (pid=%d): Cannot acquire interpreter ‘%s’.", getpid(), group); return 0; } /* Calculate the Python module name to be used for script. */ name = wsgi_module_name(r->pool, script); /* * Use a lock around the check to see if the module is * already loaded and the import of the module to prevent * two request handlers trying to import the module at the * same time. */ #if APR_HAS_THREADS Py_BEGIN_ALLOW_THREADS apr_thread_mutex_lock(wsgi_module_lock); Py_END_ALLOW_THREADS #endif modules = PyImport_GetModuleDict(); module = PyDict_GetItemString(modules, name); Py_XINCREF(module); if (module) exists = 1; /* * If script reloading is enabled and the module for it has * previously been loaded, see if it has been modified since * the last time it was accessed. */ if (module && config->script_reloading) { if (wsgi_reload_required(r->pool, r, script, module, NULL)) { /* * Script file has changed. Only support module * reloading for authentication scripts. Remove the * module from the modules dictionary before * reloading it again. If code is executing within * the module at the time, the callers reference * count on the module should ensure it isn’t * actually destroyed until it is finished. */ Py_DECREF(module); module = NULL; PyDict_DelItemString(modules, name); } } if (!module) { module = wsgi_load_source(r->pool, r, name, exists, script, "", group, 0); } /* Safe now to release the module lock. */ #if APR_HAS_THREADS apr_thread_mutex_unlock(wsgi_module_lock); #endif /* Log any details of exceptions if import failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); /* Assume not allowed unless everything okay. */ allow = 0; /* Determine if script exists and execute it. */ if (module) { PyObject *module_dict = NULL; PyObject *object = NULL; module_dict = PyModule_GetDict(module); object = PyDict_GetItemString(module_dict, “allow_access”); if (object) { PyObject *vars = NULL; PyObject *args = NULL; PyObject *result = NULL; PyObject *method = NULL; AuthObject *adapter = NULL; adapter = newAuthObject(r, config); if (adapter) { vars = Auth_environ(adapter, group); Py_INCREF(object); args = Py_BuildValue("(Oz)", vars, host); result = PyObject_CallObject(object, args); Py_DECREF(args); Py_DECREF(object); Py_DECREF(vars); if (result) { if (result == Py_None) { allow = -1; } else if (PyBool_Check(result)) { if (result == Py_True) allow = 1; } else { Py_BEGIN_ALLOW_THREADS ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): Indicator of " "host accessibility returned from ‘%s’ " "must a boolean or None.", getpid(), script); Py_END_ALLOW_THREADS } Py_DECREF(result); } /* * Wipe out references to Apache request object * held by Python objects, so can detect when an * application holds on to the transient Python * objects beyond the life of the request and * thus raise an exception if they are used. */ adapter->r = NULL; /* Log any details of exceptions if execution failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); /* Close the log object so data is flushed. */ method = PyObject_GetAttrString(adapter->log, “close”); if (!method) { PyErr_Format(PyExc_AttributeError, "’%s’ object has no attribute 'close’", adapter->log->ob_type->tp_name); } else { args = PyTuple_New(0); result = PyObject_CallObject(method, args); Py_XDECREF(result); Py_DECREF(args); } /* Log any details of exceptions if execution failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); Py_XDECREF(method); /* No longer need adapter object. */ Py_DECREF((PyObject *)adapter); } } else { Py_BEGIN_ALLOW_THREADS ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): Target WSGI host " "access script ‘%s’ does not provide " "host validator.", getpid(), script); Py_END_ALLOW_THREADS } } /* Cleanup and release interpreter, */ Py_XDECREF(module); wsgi_release_interpreter(interp); return allow; } static int wsgi_hook_access_checker(request_rec *r) { WSGIRequestConfig *config; int allow = 0; const char *host = NULL; config = wsgi_create_req_config(r->pool, r); if (!config->access_script) return DECLINED; host = ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_HOST, NULL); #if AP_MODULE_MAGIC_AT_LEAST(20111130,0) if (!host) host = r->useragent_ip; #else if (!host) host = r->connection->remote_ip; #endif allow = wsgi_allow_access(r, config, host); if (allow < 0) return DECLINED; else if (allow) return OK; if (ap_satisfies® != SATISFY_ANY || !ap_some_auth_required®) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): " "Client denied by server configuration: '%s’.", getpid(), r->filename); } return HTTP_FORBIDDEN; } #if !defined(MOD_WSGI_WITH_AUTHN_PROVIDER) static int wsgi_hook_check_user_id(request_rec *r) { WSGIRequestConfig *config; int status = -1; const char *password; InterpreterObject *interp = NULL; PyObject *modules = NULL; PyObject *module = NULL; char *name = NULL; int exists = 0; const char *script; const char *group; if ((status = ap_get_basic_auth_pw(r, &password))) return status; config = wsgi_create_req_config(r->pool, r); if (!config->auth_user_script) return DECLINED; /* * Acquire the desired python interpreter. Once this is done * it is safe to start manipulating python objects. */ script = config->auth_user_script->handler_script; group = wsgi_server_group(r, config->auth_user_script->application_group); interp = wsgi_acquire_interpreter(group); if (!interp) { ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, "mod_wsgi (pid=%d): Cannot acquire interpreter '%s’.", getpid(), group); return HTTP_INTERNAL_SERVER_ERROR; } /* Calculate the Python module name to be used for script. */ name = wsgi_module_name(r->pool, script); /* * Use a lock around the check to see if the module is * already loaded and the import of the module to prevent * two request handlers trying to import the module at the * same time. */ #if APR_HAS_THREADS Py_BEGIN_ALLOW_THREADS apr_thread_mutex_lock(wsgi_module_lock); Py_END_ALLOW_THREADS #endif modules = PyImport_GetModuleDict(); module = PyDict_GetItemString(modules, name); Py_XINCREF(module); if (module) exists = 1; /* * If script reloading is enabled and the module for it has * previously been loaded, see if it has been modified since * the last time it was accessed. */ if (module && config->script_reloading) { if (wsgi_reload_required(r->pool, r, script, module, NULL)) { /* * Script file has changed. Only support module * reloading for authentication scripts. Remove the * module from the modules dictionary before * reloading it again. If code is executing within * the module at the time, the callers reference * count on the module should ensure it isn’t * actually destroyed until it is finished. */ Py_DECREF(module); module = NULL; PyDict_DelItemString(modules, name); } } if (!module) { module = wsgi_load_source(r->pool, r, name, exists, script, "", group, 0); } /* Safe now to release the module lock. */ #if APR_HAS_THREADS apr_thread_mutex_unlock(wsgi_module_lock); #endif /* Log any details of exceptions if import failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); /* Assume an internal server error unless everything okay. */ status = HTTP_INTERNAL_SERVER_ERROR; /* Determine if script exists and execute it. */ if (module) { PyObject *module_dict = NULL; PyObject *object = NULL; module_dict = PyModule_GetDict(module); object = PyDict_GetItemString(module_dict, “check_password”); if (object) { PyObject *vars = NULL; PyObject *args = NULL; PyObject *result = NULL; PyObject *method = NULL; AuthObject *adapter = NULL; adapter = newAuthObject(r, config); if (adapter) { vars = Auth_environ(adapter, group); Py_INCREF(object); args = Py_BuildValue("(Oss)", vars, r->user, password); result = PyObject_CallObject(object, args); Py_DECREF(args); Py_DECREF(object); Py_DECREF(vars); if (result) { if (result == Py_None) { if (config->user_authoritative) { ap_note_basic_auth_failure®; status = HTTP_UNAUTHORIZED; ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): User ‘%s’ not " "found in executing authentication " "script '%s’, for uri '%s’.", getpid(), r->user, script, r->uri); } else status = DECLINED; } else if (result == Py_True) { status = OK; } else if (result == Py_False) { ap_note_basic_auth_failure®; status = HTTP_UNAUTHORIZED; ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): Password mismatch " "for user ‘%s’ in executing " "authentication script ‘%s’, for uri " "’%s’.", getpid(), r->user, script, r->uri); } else { PyErr_SetString(PyExc_TypeError, "Basic auth " "provider must return True, False " “or None”); } Py_DECREF(result); } /* * Wipe out references to Apache request object * held by Python objects, so can detect when an * application holds on to the transient Python * objects beyond the life of the request and * thus raise an exception if they are used. */ adapter->r = NULL; /* Log any details of exceptions if execution failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); /* Close the log object so data is flushed. */ method = PyObject_GetAttrString(adapter->log, “close”); if (!method) { PyErr_Format(PyExc_AttributeError, "’%s’ object has no attribute ‘close’", adapter->log->ob_type->tp_name); } else { result = PyObject_CallObject(method, NULL); Py_XDECREF(result); } /* Log any details of exceptions if execution failed. */ if (PyErr_Occurred()) wsgi_log_python_error(r, NULL, script, 0); Py_XDECREF(method); /* No longer need adapter object. */ Py_DECREF((PyObject *)adapter); } } else { Py_BEGIN_ALLOW_THREADS ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): Target WSGI user " "authentication script ‘%s’ does not provide " "’Basic’ auth provider.", getpid(), script); Py_END_ALLOW_THREADS } } /* Cleanup and release interpreter, */ Py_XDECREF(module); wsgi_release_interpreter(interp); return status; } #endif #if defined(MOD_WSGI_WITH_AUTHZ_PROVIDER) #if MOD_WSGI_WITH_AUTHZ_PROVIDER_PARSED static authz_status wsgi_check_authorization(request_rec *r, const char *require_args, const void *parsed_require_line) #else static authz_status wsgi_check_authorization(request_rec *r, const char *require_args) #endif { WSGIRequestConfig *config; apr_table_t *grpstatus = NULL; const char *t, *w; int status; #if AP_MODULE_MAGIC_AT_LEAST(20100714,0) if (!r->user) return AUTHZ_DENIED_NO_USER; #endif config = wsgi_create_req_config(r->pool, r); if (!config->auth_group_script) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server, "mod_wsgi (pid=%d): Location of WSGI group " "authorization script not provided.", getpid()); return AUTHZ_DENIED; } status = wsgi_groups_for_user(r, config, &grpstatus); if (status != OK) return AUTHZ_DENIED; if (apr_table_elts(grpstatus)->nelts == 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): " "Authorization of user ‘%s’ to access ‘%s’ failed. " "User is not a member of any groups.", getpid(), r->user, r->uri); return AUTHZ_DENIED; } t = require_args; while ((w = ap_getword_conf(r->pool, &t)) && w[0]) { if (apr_table_get(grpstatus, w)) { return AUTHZ_GRANTED; } } ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): " "Authorization of user ‘%s’ to access ‘%s’ failed. " "User is not a member of designated groups.", getpid(), r->user, r->uri); return AUTHZ_DENIED; } static const authz_provider wsgi_authz_provider = { &wsgi_check_authorization, #if MOD_WSGI_WITH_AUTHZ_PROVIDER_PARSED NULL, #endif }; #else static int wsgi_hook_auth_checker(request_rec *r) { WSGIRequestConfig *config; int m = r->method_number; const apr_array_header_t *reqs_arr; require_line *reqs; int required_group = 0; register int x; const char *t, *w; apr_table_t *grpstatus = NULL; char *reason = NULL; config = wsgi_create_req_config(r->pool, r); if (!config->auth_group_script) return DECLINED; reqs_arr = ap_requires®; if (!reqs_arr) return DECLINED; reqs = (require_line *)reqs_arr->elts; for (x = 0; x < reqs_arr->nelts; x++) { if (!(reqs[x].method_mask & (AP_METHOD_BIT << m))) { continue; } t = reqs[x].requirement; w = ap_getword_white(r->pool, &t); #if AP_MODULE_MAGIC_AT_LEAST(20100714,0) if (!strcasecmp(w, “wsgi-group”)) { #else if (!strcasecmp(w, “group”) || !strcasecmp(w, “wsgi-group”)) { #endif required_group = 1; if (!grpstatus) { int status; status = wsgi_groups_for_user(r, config, &grpstatus); if (status != OK) return status; if (apr_table_elts(grpstatus)->nelts == 0) { reason = "User is not a member of any groups"; break; } } while (t[0]) { w = ap_getword_conf(r->pool, &t); if (apr_table_get(grpstatus, w)) { return OK; } } } } if (!required_group || !config->group_authoritative) return DECLINED; ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_wsgi (pid=%d): " "Authorization of user ‘%s’ to access ‘%s’ failed. %s.", getpid(), r->user, r->uri, reason ? reason : "User is not " “a member of designated groups”); ap_note_auth_failure®; return HTTP_UNAUTHORIZED; } #endif APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *wsgi_logio_add_bytes_out; static void ap_logio_add_bytes_out(conn_rec *c, apr_off_t bytes) { if (!wsgi_daemon_pool && wsgi_logio_add_bytes_out) wsgi_logio_add_bytes_out(c, bytes); } static int wsgi_hook_logio(apr_pool_t *pconf, apr_pool_t *ptemp, apr_pool_t *plog, server_rec *s) { /* * This horrible fiddle is to insert a proxy function before * the normal ap_logio_add_bytes_out() function so that the * call to it can be disabled when mod_wsgi running in daemon * mode. If this is not done, then daemon process will crash * when mod_logio has been loaded. */ wsgi_logio_add_bytes_out = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_out); APR_REGISTER_OPTIONAL_FN(ap_logio_add_bytes_out); return OK; } static void wsgi_register_hooks(apr_pool_t *p) { static const char * const p1[] = { "mod_alias.c", NULL }; static const char * const n1[]= { "mod_userdir.c", "mod_vhost_alias.c", NULL }; static const char * const n2[] = { "core.c", NULL }; #if !defined(MOD_WSGI_WITH_AUTHN_PROVIDER) static const char * const p3[] = { "mod_auth.c", NULL }; #endif #if !defined(MOD_WSGI_WITH_AUTHZ_PROVIDER) static const char * const n4[] = { "mod_authz_user.c", NULL }; #endif static const char * const n5[] = { "mod_authz_host.c", NULL }; static const char * const p6[] = { "mod_python.c", NULL }; static const char * const p7[] = { "mod_ssl.c", NULL }; ap_hook_post_config(wsgi_hook_init, p6, NULL, APR_HOOK_MIDDLE); ap_hook_child_init(wsgi_hook_child_init, p6, NULL, APR_HOOK_MIDDLE); ap_hook_translate_name(wsgi_hook_intercept, p1, n1, APR_HOOK_MIDDLE); ap_hook_handler(wsgi_hook_handler, NULL, NULL, APR_HOOK_MIDDLE); #if defined(MOD_WSGI_WITH_DAEMONS) ap_hook_post_config(wsgi_hook_logio, NULL, n2, APR_HOOK_REALLY_FIRST); wsgi_header_filter_handle = ap_register_output_filter("WSGI_HEADER", wsgi_header_filter, NULL, AP_FTYPE_PROTOCOL); #endif #if !defined(MOD_WSGI_WITH_AUTHN_PROVIDER) ap_hook_check_user_id(wsgi_hook_check_user_id, p3, NULL, APR_HOOK_MIDDLE); #else ap_register_provider(p, AUTHN_PROVIDER_GROUP, "wsgi", AUTHN_PROVIDER_VERSION, &wsgi_authn_provider); #endif #if !defined(MOD_WSGI_WITH_AUTHZ_PROVIDER) ap_hook_auth_checker(wsgi_hook_auth_checker, NULL, n4, APR_HOOK_MIDDLE); #else ap_register_provider(p, AUTHZ_PROVIDER_GROUP, "wsgi-group", AUTHZ_PROVIDER_VERSION, &wsgi_authz_provider); #endif ap_hook_access_checker(wsgi_hook_access_checker, p7, n5, APR_HOOK_MIDDLE); } static const command_rec wsgi_commands[] = { AP_INIT_RAW_ARGS("WSGIScriptAlias", wsgi_add_script_alias, NULL, RSRC_CONF, “Map location to target WSGI script file.”), AP_INIT_RAW_ARGS("WSGIScriptAliasMatch", wsgi_add_script_alias, "*", RSRC_CONF, “Map location pattern to target WSGI script file.”), #if defined(MOD_WSGI_WITH_DAEMONS) AP_INIT_RAW_ARGS("WSGIDaemonProcess", wsgi_add_daemon_process, NULL, RSRC_CONF, “Specify details of daemon processes to start.”), AP_INIT_TAKE1("WSGISocketPrefix", wsgi_set_socket_prefix, NULL, RSRC_CONF, “Path prefix for the daemon process sockets.”), AP_INIT_TAKE1("WSGISocketRotation", wsgi_set_socket_rotation, NULL, RSRC_CONF, “Enable/Disable rotation of daemon process sockets.”), AP_INIT_TAKE1("WSGIAcceptMutex", wsgi_set_accept_mutex, NULL, RSRC_CONF, “Set accept mutex type for daemon processes.”), AP_INIT_TAKE1("WSGILazyInitialization", wsgi_set_lazy_initialization, NULL, RSRC_CONF, “Enable/Disable lazy Python initialization.”), #endif AP_INIT_TAKE1("WSGIVerboseDebugging", wsgi_set_verbose_debugging, NULL, RSRC_CONF, “Enable/Disable verbose debugging messages.”), #if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6 AP_INIT_TAKE1("WSGIPy3kWarningFlag", wsgi_set_py3k_warning_flag, NULL, RSRC_CONF, “Enable/Disable Python 3.0 warnings.”), #endif #if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 3) || \ (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6) AP_INIT_TAKE1("WSGIDontWriteBytecode", wsgi_set_dont_write_bytecode, NULL, RSRC_CONF, “Enable/Disable writing of byte code.”), #endif AP_INIT_TAKE1("WSGIPythonWarnings", wsgi_add_python_warnings, NULL, RSRC_CONF, “Control Python warning messages.”), AP_INIT_TAKE1("WSGIPythonOptimize", wsgi_set_python_optimize, NULL, RSRC_CONF, “Set level of Python compiler optimisations.”), AP_INIT_TAKE1("WSGIPythonHome", wsgi_set_python_home, NULL, RSRC_CONF, “Python prefix/exec_prefix absolute path names.”), AP_INIT_TAKE1("WSGIPythonPath", wsgi_set_python_path, NULL, RSRC_CONF, “Python module search path.”), AP_INIT_TAKE1("WSGIPythonEggs", wsgi_set_python_eggs, NULL, RSRC_CONF, “Python eggs cache directory.”), AP_INIT_TAKE1("WSGIPythonHashSeed", wsgi_set_python_hash_seed, NULL, RSRC_CONF, “Python hash seed.”), AP_INIT_TAKE1("WSGIDestroyInterpreter", wsgi_set_destroy_interpreter, NULL, RSRC_CONF, “Enable/Disable destruction of Python interpreter.”), #if defined(MOD_WSGI_WITH_DAEMONS) AP_INIT_TAKE1("WSGIRestrictEmbedded", wsgi_set_restrict_embedded, NULL, RSRC_CONF, “Enable/Disable use of embedded mode.”), #endif AP_INIT_TAKE1("WSGIRestrictStdin", wsgi_set_restrict_stdin, NULL, RSRC_CONF, “Enable/Disable restrictions on use of STDIN.”), AP_INIT_TAKE1("WSGIRestrictStdout", wsgi_set_restrict_stdout, NULL, RSRC_CONF, “Enable/Disable restrictions on use of STDOUT.”), AP_INIT_TAKE1("WSGIRestrictSignal", wsgi_set_restrict_signal, NULL, RSRC_CONF, “Enable/Disable restrictions on use of signal().”), AP_INIT_TAKE1("WSGICaseSensitivity", wsgi_set_case_sensitivity, NULL, RSRC_CONF, “Define whether file system is case sensitive.”), #if defined(MOD_WSGI_WITH_DAEMONS) AP_INIT_RAW_ARGS("WSGIRestrictProcess", wsgi_set_restrict_process, NULL, ACCESS_CONF|RSRC_CONF, “Limit selectable WSGI process groups.”), AP_INIT_TAKE1("WSGIProcessGroup", wsgi_set_process_group, NULL, ACCESS_CONF|RSRC_CONF, “Name of the WSGI process group.”), #endif AP_INIT_TAKE1("WSGIApplicationGroup", wsgi_set_application_group, NULL, ACCESS_CONF|RSRC_CONF, “Application interpreter group.”), AP_INIT_TAKE1("WSGICallableObject", wsgi_set_callable_object, NULL, OR_FILEINFO, “Name of entry point in WSGI script file.”), AP_INIT_RAW_ARGS("WSGIImportScript", wsgi_add_import_script, NULL, RSRC_CONF, “Location of WSGI import script.”), AP_INIT_RAW_ARGS("WSGIDispatchScript", wsgi_set_dispatch_script, NULL, ACCESS_CONF|RSRC_CONF, “Location of WSGI dispatch script.”), AP_INIT_TAKE1("WSGIPassApacheRequest", wsgi_set_pass_apache_request, NULL, ACCESS_CONF|RSRC_CONF, “Enable/Disable Apache request object.”), AP_INIT_TAKE1("WSGIPassAuthorization", wsgi_set_pass_authorization, NULL, OR_FILEINFO, “Enable/Disable WSGI authorization.”), AP_INIT_TAKE1("WSGIScriptReloading", wsgi_set_script_reloading, NULL, OR_FILEINFO, “Enable/Disable script reloading mechanism.”), AP_INIT_TAKE1("WSGIErrorOverride", wsgi_set_error_override, NULL, OR_FILEINFO, “Enable/Disable overriding of error pages.”), AP_INIT_TAKE1("WSGIChunkedRequest", wsgi_set_chunked_request, NULL, OR_FILEINFO, “Enable/Disable support for chunked requests.”), AP_INIT_TAKE1("WSGIMapHEADToGET", wsgi_set_map_head_to_get, NULL, OR_FILEINFO, “Enable/Disable mapping of HEAD to GET.”), AP_INIT_TAKE1("WSGIIgnoreActivity", wsgi_set_ignore_activity, NULL, OR_FILEINFO, “Enable/Disable reset of inactvity timeout.”), AP_INIT_RAW_ARGS("WSGITrustedProxyHeaders", wsgi_set_trusted_proxy_headers, NULL, OR_FILEINFO, “Specify a list of trusted proxy headers.”), AP_INIT_RAW_ARGS("WSGITrustedProxies", wsgi_set_trusted_proxies, NULL, OR_FILEINFO, “Specify a list of trusted proxies.”), #ifndef WIN32 AP_INIT_TAKE1("WSGIEnableSendfile", wsgi_set_enable_sendfile, NULL, OR_FILEINFO, “Enable/Disable support for kernel sendfile.”), #endif AP_INIT_RAW_ARGS("WSGIAccessScript", wsgi_set_access_script, NULL, OR_AUTHCFG, “Location of WSGI host access script file.”), AP_INIT_RAW_ARGS("WSGIAuthUserScript", wsgi_set_auth_user_script, NULL, OR_AUTHCFG, “Location of WSGI user auth script file.”), AP_INIT_RAW_ARGS("WSGIAuthGroupScript", wsgi_set_auth_group_script, NULL, OR_AUTHCFG, “Location of WSGI group auth script file.”), #if !defined(MOD_WSGI_WITH_AUTHN_PROVIDER) AP_INIT_TAKE1("WSGIUserAuthoritative", wsgi_set_user_authoritative, NULL, OR_AUTHCFG, “Enable/Disable as being authoritative on users.”), #endif AP_INIT_TAKE1("WSGIGroupAuthoritative", wsgi_set_group_authoritative, NULL, OR_AUTHCFG, “Enable/Disable as being authoritative on groups.”), AP_INIT_RAW_ARGS("WSGIHandlerScript", wsgi_add_handler_script, NULL, ACCESS_CONF|RSRC_CONF, “Location of WSGI handler script file.”), AP_INIT_TAKE1("WSGIServerMetrics", wsgi_set_server_metrics, NULL, RSRC_CONF, “Enabled/Disable access to server metrics.”), AP_INIT_TAKE1("WSGINewRelicConfigFile", wsgi_set_newrelic_config_file, NULL, RSRC_CONF, “New Relic monitoring agent configuration file.”), AP_INIT_TAKE1("WSGINewRelicEnvironment", wsgi_set_newrelic_environment, NULL, RSRC_CONF, “New Relic monitoring agent environment.”), { NULL } }; /* Dispatch list for API hooks */ module AP_MODULE_DECLARE_DATA wsgi_module = { STANDARD20_MODULE_STUFF, wsgi_create_dir_config, /* create per-dir config structures */ wsgi_merge_dir_config, /* merge per-dir config structures */ wsgi_create_server_config, /* create per-server config structures */ wsgi_merge_server_config, /* merge per-server config structures */ wsgi_commands, /* table of config file commands */ wsgi_register_hooks /* register hooks */ }; /* ------------------------------------------------------------------------- */ #if defined(_WIN32) #if PY_MAJOR_VERSION < 3 PyMODINIT_FUNC initmod_wsgi(void) { } #else PyMODINIT_FUNC PyInit_mod_wsgi(void) { return NULL; } #endif #endif /* ------------------------------------------------------------------------- */ /* vi: set sw=4 expandtab : */

Related news

GHSA-7527-8855-9cf8: Incorrect header handling in mod-wsgi

A vulnerability was found in mod_wsgi. The X-Client-IP header is not removed from a request from an untrusted proxy, allowing an attacker to pass the X-Client-IP header to the target WSGI application because the condition to remove it is missing.

Ubuntu Security Notice USN-5551-1

Ubuntu Security Notice 5551-1 - It was discovered that mod-wsgi did not correctly remove the X-Client-IP header when processing requests from untrusted proxies. A remote attacker could use this issue to pass the header to WSGI applications, contrary to expectations.

CVE-2022-22721: Apache HTTP Server 2.4 vulnerabilities

If LimitXMLRequestBody is set to allow request bodies larger than 350MB (defaults to 1M) on 32 bit systems an integer overflow happens which later causes out of bounds writes. This issue affects Apache HTTP Server 2.4.52 and earlier.

CVE-2021-44790: Apache HTTP Server 2.4 vulnerabilities

A carefully crafted request body can cause a buffer overflow in the mod_lua multipart parser (r:parsebody() called from Lua scripts). The Apache httpd team is not aware of an exploit for the vulnerabilty though it might be possible to craft one. This issue affects Apache HTTP Server 2.4.51 and earlier.

CVE-2020-9490: Apache HTTP Server 2.4 vulnerabilities

Apache HTTP Server versions 2.4.20 to 2.4.43. A specially crafted value for the 'Cache-Digest' header in a HTTP/2 request would result in a crash when the server actually tries to HTTP/2 PUSH a resource afterwards. Configuring the HTTP/2 feature via "H2Push off" will mitigate this vulnerability for unpatched servers.

CVE-2016-0502: Oracle Critical Patch Update - January 2016

Unspecified vulnerability in Oracle MySQL 5.5.31 and earlier and 5.6.11 and earlier allows remote authenticated users to affect availability via unknown vectors related to Optimizer.

CVE-2015-2590: Oracle Critical Patch Update Advisory - July 2015

Unspecified vulnerability in Oracle Java SE 6u95, 7u80, and 8u45, and Java SE Embedded 7u75 and 8u33 allows remote attackers to affect confidentiality, integrity, and availability via unknown vectors related to Libraries, a different vulnerability than CVE-2015-4732.

CVE-2015-2582: Oracle Critical Patch Update Advisory - July 2015

Unspecified vulnerability in Oracle MySQL Server 5.5.43 and earlier and 5.6.24 and earlier allows remote authenticated users to affect availability via vectors related to GIS.

CVE-2015-0391: Oracle Critical Patch Update Advisory - January 2015

Unspecified vulnerability in Oracle MySQL Server 5.5.38 and earlier, and 5.6.19 and earlier, allows remote authenticated users to affect availability via vectors related to DDL.

CVE-2015-0395: Oracle Critical Patch Update Advisory - January 2015

Unspecified vulnerability in Oracle Java SE 5.0u75, 6u85, 7u72, and 8u25 allows remote attackers to affect confidentiality, integrity, and availability via unknown vectors related to Hotspot.

CVE-2012-0053: Apache HTTP Server 2.2 vulnerabilities

protocol.c in the Apache HTTP Server 2.2.x through 2.2.21 does not properly restrict header information during construction of Bad Request (aka 400) error documents, which allows remote attackers to obtain the values of HTTPOnly cookies via vectors involving a (1) long or (2) malformed header in conjunction with crafted web script.

CVE: Latest News

CVE-2023-50976: Transactions API Authorization by oleiman · Pull Request #14969 · redpanda-data/redpanda
CVE-2023-6905
CVE-2023-6903
CVE-2023-6904
CVE-2023-3907