diff options
author | Graham Dumpleton <Graham.Dumpleton@gmail.com> | 2015-02-21 21:53:15 +1100 |
---|---|---|
committer | Graham Dumpleton <Graham.Dumpleton@gmail.com> | 2015-02-21 21:53:15 +1100 |
commit | 61572d5706182d9d2ecf2ff7711be29de49240e9 (patch) | |
tree | 242e428d6ac0ddf8f4b61010bc4590ab67d92717 | |
parent | 3cc8bba0658bb448ac197cd5517b363ec3dbc400 (diff) | |
parent | 683acabbb3acd7d60015a1b84878e24f8d9a6278 (diff) | |
download | mod_wsgi-4.4.9.tar.gz |
Merge branch 'release/4.4.9'4.4.9
32 files changed, 678 insertions, 75 deletions
diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..e268341 --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +Graham Dumpleton <Graham.Dumpleton@gmail.com> Graham.Dumpleton <devnull@localhost> diff --git a/configure.ac b/configure.ac index f3f1d09..ab6145d 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ dnl vim: set sw=4 expandtab : dnl -dnl Copyright 2007-2014 GRAHAM DUMPLETON +dnl Copyright 2007-2015 GRAHAM DUMPLETON dnl dnl Licensed under the Apache License, Version 2.0 (the "License"); dnl you may not use this file except in compliance with the License. diff --git a/docs/conf.py b/docs/conf.py index 36cbae5..402a603 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -41,7 +41,7 @@ master_doc = 'index' # General information about the project. project = u'mod_wsgi' -copyright = u'2014, Graham Dumpleton' +copyright = u'2007-2015, Graham Dumpleton' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/index.rst b/docs/index.rst index b826a23..ac08d10 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,7 +19,7 @@ host any Python application which supports the Python WSGI_ interface. Status ====== -No mod_wsgi is not dead, it's just been resting. +No mod_wsgi is not dead, it was just resting. Renewed development on mod_wsgi began early 2014, with a considerable amount of new development work and fixes being performed. This included the @@ -27,7 +27,9 @@ ability to install mod_wsgi using 'pip', along with an admin command called ``mod_wsgi-express`` which provides a really simple way of starting up Apache/mod_wsgi with an automatically generated configuration. -Completely revised documentation will progressively be incorporated here. +Completely revised documentation will eventually be incorporated here. +Right now though I am having too much fun working on all the new features. + In the mean time keep referring to the older documentation at: http://www.modwsgi.org/ diff --git a/docs/release-notes/index.rst b/docs/release-notes/index.rst index 9b7b42b..0321696 100644 --- a/docs/release-notes/index.rst +++ b/docs/release-notes/index.rst @@ -5,6 +5,7 @@ Release Notes .. toctree:: :maxdepth: 2 + version-4.4.9.rst version-4.4.8.rst version-4.4.7.rst version-4.4.6.rst diff --git a/docs/release-notes/version-4.4.9.rst b/docs/release-notes/version-4.4.9.rst new file mode 100644 index 0000000..a4828c3 --- /dev/null +++ b/docs/release-notes/version-4.4.9.rst @@ -0,0 +1,107 @@ +============= +Version 4.4.9 +============= + +Version 4.4.9 of mod_wsgi can be obtained from: + + https://codeload.github.com/GrahamDumpleton/mod_wsgi/tar.gz/4.4.9 + +For details on the availability of Windows binaries see: + + https://github.com/GrahamDumpleton/mod_wsgi/tree/master/win32 + +Features Changed +---------------- + +1. The ``--proxy-url-alias`` option of ``mod_wsgi-express`` has been +superseded by the ``--proxy-mount-point`` option. This option now should +only be used to proxy to a whole site or sub site and not individual file +resources. If the mount point URL for what should be proxied doesn't have a +trailing slash, the trailing slash redirection will first be performed on +the proxy for the mount point rather than simply passing it through to +the backend. + +2. The signal handler intercept will now be removed automatically from a +Python child process forked from either an Apache child process or a daemon +process. This avoids the requirement of setting ``WSGIRestrictSignal`` to +``Off`` if wanting to setup new signal handlers from a forked child process. + +3. The signal handler registrations setup in daemon processes to manage +process shutdown, will now revert to exiting the process when invoked from +a Python process forked from a daemon process. This avoids the need to set +new signal handlers in forked processes to override what was inherited. + +Note that this only applies to processes forked from daemon mode processes. +If you are forking processes when your WSGI application is running in +embedded mode, it is still a good idea to set signal handles for ``SIGINT``, +``SIGTERM`` and ``SIGUSR1`` back to ``SIG_DFL`` using ``signal.signal()`` +if you want to avoid the possibility of strange behaviour due to the +inherited Apache child worker process signal registrations. + +New Features +------------ + +1. Added ``--hsts-policy`` option to ``mod_wsgi-express`` to allow a HSTS +(``Strict-Transport-Security``) policy response header to be specified which +should be included when the ``--https-only`` option is used to ensure that +the site only accepts HTTPS connections. + +2. Added ``WSGITrustedProxyHeaders`` directive. This allows you to specify +a space separated list of inbound HTTP headers used to transfer client +connection information from a proxy to a backend server, that are trusted. +When the specified headers are seen in a request, the values passed via +them will be used to fix up the values in the WSGI ``environ`` dictionary +to reflect client information as was seen by the proxy. + +Only the specific headers you are expecting and which is guaranteed to have +only been set by the proxy should be listed. Whether it exists or not, all +other headers in a category will be removed so as to avoid an issue with +a forged header getting through to a WSGI middleware which is looking for a +different header and subsequently overriding whatever the trusted header +specified. This applies to the following as well when more than one +convention is used for the header name. + +The header names which are accepted for specifying the HTTP scheme used are +``X-Forwarded-Proto``, ``X-Forwarded-Scheme`` and ``X-Scheme``. It is +expected that the value these supply will be ``http`` or ``https``. When it +is ``https``, the ``wsgi.url_scheme`` value in the WSGI ``environ`` +dictionary will be overridden to be ``https``. + +Alternate headers accepted are ``X-Forwarded-HTTPS``, ``X-Forwarded-SSL`` +and ``X-HTTPS``. If these are passed, the value needs to be ``On``, +``true`` or ``1``. A case insensitive match is performed. When matched, the +``wsgi.url_scheme`` value in the WSGI ``environ`` dictionary will be +overridden to be ``https``. + +The header names which are accepted for specifying the target host are +``X-Forwarded-Host`` and ``X-Host``. When found, the value will be used +to override the ``HTTP_HOST`` value in the WSGI ``environ`` dictionary. + +The sole header name accepted for specifying the front end proxy server +name is ``X-Fowarded-Server``. When found, the value will be used to +override the ``SERVER_NAME`` value in the WSGI ``environ`` dictionary. + +The sole header name accepted for specifying the front end proxy server +port is ``X-Fowarded-Port``. When found, the value will be used to +override the ``SERVER_PORT`` value in the WSGI ``environ`` dictionary. + +The header names accepted for specifying the client IP address are +``X-Forwarded-For`` and ``X-Real-IP``. When ``X-Forwarded-For`` is used +then the first IP address listed in the header value will be used. For +``X-Real-IP`` only one IP address should be given. When found, the value +will be used to override the ``REMOTE_ADDR`` value in the WSGI ``environ`` +dictionary. + +Note that at present there is no facility for specifying a list of trusted +IP addresses to be specified for front end proxies. This will be a feature +added in a future version. When that is available and ``X-Forwarded-For`` +is used, then the IP address preceding the furthest away trusted proxy IP +address will instead be used, even if not the first in the list. + +The header names accepted for specifying the application mount point are +``X-Script-Name`` and ``X-Forwarded-Script-Name``. When found, the value +will override the ``SCRIPT_NAME`` value in the ``WSGI`` environ dictionary. + +When using ``mod_wsgi-express`` the equivalent command line option is +``--trust-proxy-header``. The option can be used multiple times to specify +more than one header. diff --git a/src/server/__init__.py b/src/server/__init__.py index 2e06db4..ea2eaac 100644 --- a/src/server/__init__.py +++ b/src/server/__init__.py @@ -169,6 +169,9 @@ LoadModule dir_module '${HTTPD_MODULES_DIRECTORY}/mod_dir.so' <IfModule !env_module> LoadModule env_module '${HTTPD_MODULES_DIRECTORY}/mod_env.so' </IfModule> +<IfModule !headers_module> +LoadModule headers_module '${HTTPD_MODULES_DIRECTORY}/mod_headers.so' +</IfModule> <IfVersion >= 2.2.15> <IfModule !reqtimeout_module> @@ -369,6 +372,10 @@ CustomLog "%(access_log_file)s" %(log_format_nickname)s WSGIChunkedRequest On </IfDefine> +<IfDefine WSGI_WITH_PROXY_HEADERS> +WSGITrustedProxyHeaders %(trusted_proxy_headers)s +</IfDefine> + <IfDefine WSGI_WITH_HTTPS> <IfModule !ssl_module> LoadModule ssl_module ${HTTPD_MODULES_DIRECTORY}/mod_ssl.so @@ -530,6 +537,11 @@ ServerAlias %(server_aliases)s SSLEngine On SSLCertificateFile %(ssl_certificate)s.crt SSLCertificateKeyFile %(ssl_certificate)s.key +<IfDefine WSGI_HTTPS_ONLY> +<IfDefine WSGI_HSTS_POLICY> +Header set Strict-Transport-Security %(hsts_policy)s +</IfDefine> +</IfDefine> </VirtualHost> <IfDefine WSGI_REDIRECT_WWW> <VirtualHost *:%(https_port)s> @@ -623,14 +635,25 @@ WSGIImportScript '%(server_root)s/handler.wsgi' \\ </IfDefine> """ -APACHE_PROXY_PASS_CONFIG = """ +APACHE_PROXY_PASS_MOUNT_POINT_CONFIG = """ ProxyPass '%(mount_point)s' '%(url)s' +ProxyPassReverse '%(mount_point)s' '%(url)s' +""" + +APACHE_PROXY_PASS_MOUNT_POINT_SLASH_CONFIG = """ +ProxyPass '%(mount_point)s/' '%(url)s/' +ProxyPassReverse '%(mount_point)s/' '%(url)s/' +<LocationMatch '^%(mount_point)s$'> +RewriteEngine On +RewriteRule - http://%%{HTTP_HOST}%%{REQUEST_URI}/ [R=302,L] +</LocationMatch> """ APACHE_PROXY_PASS_HOST_CONFIG = """ <VirtualHost *:%(port)s> ServerName %(host)s ProxyPass / '%(url)s' +ProxyPassReverse / '%(url)s' </VirtualHost> """ @@ -715,10 +738,14 @@ def generate_apache_config(options): with open(options['httpd_conf'], 'w') as fp: print(APACHE_GENERAL_CONFIG % options, file=fp) - if options['proxy_url_aliases']: - for mount_point, url in options['proxy_url_aliases']: - print(APACHE_PROXY_PASS_CONFIG % dict( - mount_point=mount_point, url=url), file=fp) + if options['proxy_mount_points']: + for mount_point, url in options['proxy_mount_points']: + if mount_point.endswith('/'): + print(APACHE_PROXY_PASS_MOUNT_POINT_CONFIG % dict( + mount_point=mount_point, url=url), file=fp) + else: + print(APACHE_PROXY_PASS_MOUNT_POINT_SLASH_CONFIG % dict( + mount_point=mount_point, url=url), file=fp) if options['proxy_virtual_hosts']: for host, url in options['proxy_virtual_hosts']: @@ -1138,16 +1165,35 @@ class ApplicationHandler(object): # Strip out the leading component due to internal redirect in # Apache when using web application as fallback resource. + mount_point = environ.get('mod_wsgi.mount_point') + script_name = environ.get('SCRIPT_NAME') path_info = environ.get('PATH_INFO') - environ['SCRIPT_NAME'] = '' - environ['PATH_INFO'] = script_name + path_info + if mount_point is not None: + # If this is set then it means that SCRIPT_NAME was + # overridden by a trusted proxy header. In this case + # we want to ignore any local mount point, simply + # stripping it from the path. + + script_name = environ['mod_wsgi.script_name'] - if self.mount_point != '/': - if environ['PATH_INFO'].startswith(self.mount_point): - environ['SCRIPT_NAME'] = self.mount_point - environ['PATH_INFO'] = environ['PATH_INFO'][len(self.mount_point):] + environ['PATH_INFO'] = script_name + path_info + + if self.mount_point != '/': + if environ['PATH_INFO'].startswith(self.mount_point): + environ['PATH_INFO'] = environ['PATH_INFO'][len( + self.mount_point):] + + else: + environ['SCRIPT_NAME'] = '' + environ['PATH_INFO'] = script_name + path_info + + if self.mount_point != '/': + if environ['PATH_INFO'].startswith(self.mount_point): + environ['SCRIPT_NAME'] = self.mount_point + environ['PATH_INFO'] = environ['PATH_INFO'][len( + self.mount_point):] return self.application(environ, start_response) @@ -1535,9 +1581,12 @@ option_list = ( 'the extension.'), optparse.make_option('--https-only', action='store_true', default=False, help='Flag indicating whether any requests ' - 'made using a HTTP request over the non connection connection ' + 'made using a HTTP request over the non secure connection ' 'should be redirected automatically to use a HTTPS request ' 'over the secure connection.'), + optparse.make_option('--hsts-policy', default=None, metavar='PARAMS', + help='Specify the HTST policy that should be applied when ' + 'HTTPS only connections are being enforced.'), optparse.make_option('--server-name', default=None, metavar='HOSTNAME', help='The primary host name of the web server. If this name ' @@ -1782,15 +1831,25 @@ option_list = ( default=False, help='Flag indicating whether Apache error ' 'documents will override application error responses.'), - optparse.make_option('--proxy-url-alias', action='append', nargs=2, - dest='proxy_url_aliases', metavar='URL-PATH URL', + optparse.make_option('--proxy-mount-point', action='append', nargs=2, + dest='proxy_mount_points', metavar='URL-PATH URL', help='Map a sub URL such that any requests against it will be ' - 'proxied to the specified URL.'), + 'proxied to the specified URL. This is only for proxying to a ' + 'site as a whole, or a sub site, not individual resources.'), + optparse.make_option('--proxy-url-alias', action='append', nargs=2, + dest='proxy_mount_points', metavar='URL-PATH URL', + help=optparse.SUPPRESS_HELP), + optparse.make_option('--proxy-virtual-host', action='append', nargs=2, dest='proxy_virtual_hosts', metavar='HOSTNAME URL', help='Proxy any requests for the specified host name to the ' 'remote URL.'), + optparse.make_option('--trust-proxy-header', action='append', default=[], + dest='trusted_proxy_headers', metavar='HEADER-NAME', + help='The name of any trusted HTTP header providing details ' + 'of the front end client request when proxying.'), + optparse.make_option('--keep-alive-timeout', type='int', default=0, metavar='SECONDS', help='The number of seconds which a client ' 'connection will be kept alive to allow subsequent requests ' @@ -2191,6 +2250,9 @@ def _cmd_setup_server(command, args, options): if not options['mount_point'].startswith('/'): options['mount_point'] = os.path.normpath('/' + options['mount_point']) + # Create subdirectories for mount points in document directory + # so that fallback resource rewrite rule will work. + if options['mount_point'] != '/': parts = options['mount_point'].rstrip('/').split('/')[1:] subdir = options['document_root'] @@ -2463,6 +2525,9 @@ def _cmd_setup_server(command, args, options): options['httpd_arguments_list'] = [] + options['trusted_proxy_headers'] = ' '.join( + options['trusted_proxy_headers']) + if options['startup_log']: if not options['log_to_terminal']: options['startup_log_file'] = os.path.join( @@ -2558,6 +2623,8 @@ def _cmd_setup_server(command, args, options): options['httpd_arguments_list'].append('-DWSGI_WITH_HTTPS') if options['https_only']: options['httpd_arguments_list'].append('-DWSGI_HTTPS_ONLY') + if options['hsts_policy']: + options['httpd_arguments_list'].append('-DWSGI_HSTS_POLICY') if options['server_aliases']: options['httpd_arguments_list'].append('-DWSGI_SERVER_ALIAS') @@ -2600,8 +2667,10 @@ def _cmd_setup_server(command, args, options): options['httpd_arguments_list'].append('-DWSGI_CHUNKED_REQUEST') if options['with_php5']: options['httpd_arguments_list'].append('-DWSGI_WITH_PHP5') - if options['proxy_url_aliases'] or options['proxy_virtual_hosts']: + if options['proxy_mount_points'] or options['proxy_virtual_hosts']: options['httpd_arguments_list'].append('-DWSGI_WITH_PROXY') + if options['trusted_proxy_headers']: + options['httpd_arguments_list'].append('-DWSGI_WITH_PROXY_HEADERS') options['httpd_arguments_list'].extend( _mpm_module_defines(options['modules_directory'], diff --git a/src/server/mod_wsgi.c b/src/server/mod_wsgi.c index c60f422..ff2a51d 100644 --- a/src/server/mod_wsgi.c +++ b/src/server/mod_wsgi.c @@ -1,7 +1,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -184,6 +184,11 @@ static void *wsgi_merge_server_config(apr_pool_t *p, void *base_conf, else config->map_head_to_get = parent->map_head_to_get; + if (child->trusted_proxy_headers) + config->trusted_proxy_headers = child->trusted_proxy_headers; + else + config->trusted_proxy_headers = parent->trusted_proxy_headers; + if (child->enable_sendfile != -1) config->enable_sendfile = child->enable_sendfile; else @@ -219,6 +224,8 @@ typedef struct { int chunked_request; int map_head_to_get; + apr_array_header_t *trusted_proxy_headers; + int enable_sendfile; WSGIScriptFile *access_script; @@ -251,6 +258,8 @@ static WSGIDirectoryConfig *newWSGIDirectoryConfig(apr_pool_t *p) object->chunked_request = -1; object->map_head_to_get = -1; + object->trusted_proxy_headers = NULL; + object->enable_sendfile = -1; object->access_script = NULL; @@ -338,6 +347,11 @@ static void *wsgi_merge_dir_config(apr_pool_t *p, void *base_conf, else config->map_head_to_get = parent->map_head_to_get; + if (child->trusted_proxy_headers) + config->trusted_proxy_headers = child->trusted_proxy_headers; + else + config->trusted_proxy_headers = parent->trusted_proxy_headers; + if (child->enable_sendfile != -1) config->enable_sendfile = child->enable_sendfile; else @@ -398,6 +412,8 @@ typedef struct { int chunked_request; int map_head_to_get; + apr_array_header_t *trusted_proxy_headers; + int enable_sendfile; WSGIScriptFile *access_script; @@ -833,6 +849,11 @@ static WSGIRequestConfig *wsgi_create_req_config(apr_pool_t *p, request_rec *r) config->map_head_to_get = 2; } + config->trusted_proxy_headers = dconfig->trusted_proxy_headers; + + if (!config->trusted_proxy_headers) + config->trusted_proxy_headers = sconfig->trusted_proxy_headers; + config->enable_sendfile = dconfig->enable_sendfile; if (config->enable_sendfile < 0) { @@ -3869,6 +3890,8 @@ static void wsgi_python_child_init(apr_pool_t *p) 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 @@ -4971,6 +4994,40 @@ static const char *wsgi_set_map_head_to_get(cmd_parms *cmd, void *mconfig, 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; + + headers = apr_array_make(cmd->pool, 3, sizeof(char*)); + dconfig->trusted_proxy_headers = headers; + } + else { + WSGIServerConfig *sconfig = NULL; + sconfig = ap_get_module_config(cmd->server->module_config, + &wsgi_module); + + headers = apr_array_make(cmd->pool, 3, sizeof(char*)); + sconfig->trusted_proxy_headers = 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 const char *wsgi_set_enable_sendfile(cmd_parms *cmd, void *mconfig, const char *f) { @@ -5424,6 +5481,7 @@ static int wsgi_hook_intercept(request_rec *r) /* 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) { @@ -5441,21 +5499,22 @@ static void wsgi_build_environment(request_rec *r) &wsgi_module); /* - * Populate environment with standard CGI variables. Before - * we do this though, we ensure that we delete any 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. + * 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(r); + /* Populate environment with standard CGI variables. */ + ap_add_cgi_vars(r); ap_add_common_vars(r); @@ -5492,14 +5551,6 @@ static void wsgi_build_environment(request_rec *r) apr_table_setn(r->subprocess_env, "REQUEST_METHOD", "GET"); } - /* Determine whether connection uses HTTPS protocol. */ - - 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"); - /* * If enabled, pass along authorisation headers which Apache * leaves out of CGI environment. WSGI still needs to see @@ -5538,7 +5589,7 @@ static void wsgi_build_environment(request_rec *r) script_name = apr_table_get(r->subprocess_env, "SCRIPT_NAME"); - if (*script_name) { + if (*script_name == '/') { while (*script_name && (*(script_name+1) == '/')) script_name++; script_name = apr_pstrdup(r->pool, script_name); @@ -5548,7 +5599,7 @@ static void wsgi_build_environment(request_rec *r) path_info = apr_table_get(r->subprocess_env, "PATH_INFO"); - if (*path_info) { + if (*path_info == '/') { while (*path_info && (*(path_info+1) == '/')) path_info++; path_info = apr_pstrdup(r->pool, path_info); @@ -5557,6 +5608,34 @@ static void wsgi_build_environment(request_rec *r) } /* + * 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(r); + + /* + * 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 @@ -7225,6 +7304,9 @@ 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); @@ -9116,6 +9198,8 @@ static int wsgi_start_process(apr_pool_t *p, WSGIDaemonProcess *daemon) wsgi_daemon_shutdown = 0; + wsgi_daemon_pid = getpid(); + apr_signal(SIGINT, wsgi_signal_handler); apr_signal(SIGTERM, wsgi_signal_handler); @@ -12180,6 +12264,10 @@ static void wsgi_hook_child_init(apr_pool_t *p, server_rec *s) } #endif + /* Remember worker process ID. */ + + wsgi_worker_pid = getpid(); + /* Create lock for request monitoring. */ apr_thread_mutex_create(&wsgi_monitor_lock, @@ -12297,6 +12385,244 @@ static void wsgi_drop_invalid_headers(request_rec *r) } } +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 void wsgi_process_proxy_headers(request_rec *r) +{ + WSGIRequestConfig *config = NULL; + + apr_array_header_t *trusted_proxy_headers = NULL; + + int match_scheme_header = 0; + int match_host_header = 0; + int match_script_name_header = 0; + + const char *trusted_scheme_header = NULL; + const char *trusted_host_header = NULL; + const char *trusted_script_name_header = NULL; + + int i = 0; + + 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. + */ + + for (i=0; i<trusted_proxy_headers->nelts; i++) { + const char *name = NULL; + const char *value = NULL; + + name = ((const char**)trusted_proxy_headers->elts)[i]; + value = apr_table_get(r->subprocess_env, name); + + if (!strcmp(name, "HTTP_X_FORWARDED_FOR")) { + if (value) { + /* + * A potentially comma separated list where client + * we are interested in will be listed first. + */ + + const char *end = NULL; + + while (*value != '\0' && apr_isspace(*value)) + value++; + + if (*value != '\0') { + end = value; + + while (*end != '\0' && *end != ',') + end++; + + /* Need to deal with trailing whitespace. */ + + while (end != value) { + if (!apr_isspace(*(end-1))) + break; + + end--; + } + + apr_table_setn(r->subprocess_env, "REMOTE_ADDR", + apr_pstrndup(r->pool, value, (end-value))); + } + } + } + else if (!strcmp(name, "HTTP_X_REAL_IP")) { + if (value) { + /* Use the value as is. */ + + apr_table_setn(r->subprocess_env, "REMOTE_ADDR", value); + } + } + 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"); + } + } + } + } + + /* + * 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)); @@ -14428,6 +14754,9 @@ static const command_rec wsgi_commands[] = AP_INIT_TAKE1("WSGIMapHEADToGET", wsgi_set_map_head_to_get, NULL, OR_FILEINFO, "Enable/Disable mapping of HEAD to GET."), + AP_INIT_RAW_ARGS("WSGITrustedProxyHeaders", wsgi_set_trusted_proxy_headers, + NULL, OR_FILEINFO, "Specify a list of trusted proxy headers."), + #ifndef WIN32 AP_INIT_TAKE1("WSGIEnableSendfile", wsgi_set_enable_sendfile, NULL, OR_FILEINFO, "Enable/Disable support for kernel sendfile."), diff --git a/src/server/wsgi_apache.c b/src/server/wsgi_apache.c index afa9162..504cbc3 100644 --- a/src/server/wsgi_apache.c +++ b/src/server/wsgi_apache.c @@ -1,7 +1,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_apache.h b/src/server/wsgi_apache.h index aa54c0f..bd2f2b9 100644 --- a/src/server/wsgi_apache.h +++ b/src/server/wsgi_apache.h @@ -4,7 +4,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_buckets.c b/src/server/wsgi_buckets.c index 94573a9..a1c9c1f 100644 --- a/src/server/wsgi_buckets.c +++ b/src/server/wsgi_buckets.c @@ -1,7 +1,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_buckets.h b/src/server/wsgi_buckets.h index c0f6c37..4a34da6 100644 --- a/src/server/wsgi_buckets.h +++ b/src/server/wsgi_buckets.h @@ -4,7 +4,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_convert.c b/src/server/wsgi_convert.c index c344cf2..d55bec9 100644 --- a/src/server/wsgi_convert.c +++ b/src/server/wsgi_convert.c @@ -1,7 +1,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_convert.h b/src/server/wsgi_convert.h index c14966b..b1f5d6e 100644 --- a/src/server/wsgi_convert.h +++ b/src/server/wsgi_convert.h @@ -4,7 +4,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_daemon.c b/src/server/wsgi_daemon.c index d1392ae..5a4bd25 100644 --- a/src/server/wsgi_daemon.c +++ b/src/server/wsgi_daemon.c @@ -1,7 +1,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_daemon.h b/src/server/wsgi_daemon.h index 999fca2..3b83de5 100644 --- a/src/server/wsgi_daemon.h +++ b/src/server/wsgi_daemon.h @@ -4,7 +4,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_interp.c b/src/server/wsgi_interp.c index 60851d0..5ac9296 100644 --- a/src/server/wsgi_interp.c +++ b/src/server/wsgi_interp.c @@ -1,7 +1,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,13 +42,39 @@ /* Function to restrict access to use of signal(). */ -static PyObject *wsgi_signal_intercept(PyObject *self, PyObject *args) +static void SignalIntercept_dealloc(SignalInterceptObject *self) +{ + Py_DECREF(self->wrapped); +} + +static SignalInterceptObject *newSignalInterceptObject(PyObject *wrapped) +{ + SignalInterceptObject *self = NULL; + + self = PyObject_New(SignalInterceptObject, &SignalIntercept_Type); + if (self == NULL) + return NULL; + + Py_INCREF(wrapped); + self->wrapped = wrapped; + + return self; +} + +static PyObject *SignalIntercept_call( + SignalInterceptObject *self, PyObject *args, PyObject *kwds) { PyObject *h = NULL; int n = 0; PyObject *m = NULL; + if (wsgi_daemon_pid != 0 && wsgi_daemon_pid != getpid()) + return PyObject_Call(self->wrapped, args, kwds); + + if (wsgi_worker_pid != 0 && wsgi_worker_pid != getpid()) + return PyObject_Call(self->wrapped, args, kwds); + if (!PyArg_ParseTuple(args, "iO:signal", &n, &h)) return NULL; @@ -87,9 +113,48 @@ static PyObject *wsgi_signal_intercept(PyObject *self, PyObject *args) return h; } -static PyMethodDef wsgi_signal_method[] = { - { "signal", (PyCFunction)wsgi_signal_intercept, METH_VARARGS, 0 }, - { NULL, NULL } +PyTypeObject SignalIntercept_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "mod_wsgi.SignalIntercept", /*tp_name*/ + sizeof(SignalInterceptObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)SignalIntercept_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*/ + (ternaryfunc)SignalIntercept_call, /*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*/ + 0, /*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*/ }; /* Wrapper around Python interpreter instances. */ @@ -504,10 +569,26 @@ InterpreterObject *newInterpreterObject(const char *name) */ if (wsgi_server_config->restrict_signal != 0) { + module = PyImport_ImportModule("signal"); - PyModule_AddObject(module, "signal", PyCFunction_New( - &wsgi_signal_method[0], NULL)); - Py_DECREF(module); + + if (module) { + PyObject *dict = NULL; + PyObject *func = NULL; + + dict = PyModule_GetDict(module); + func = PyDict_GetItemString(dict, "signal"); + + if (func) { + PyObject *wrapper = NULL; + + wrapper = (PyObject *)newSignalInterceptObject(func); + PyDict_SetItemString(dict, "signal", wrapper); + Py_DECREF(wrapper); + } + } + + Py_XDECREF(module); } /* diff --git a/src/server/wsgi_interp.h b/src/server/wsgi_interp.h index c788173..7da1e09 100644 --- a/src/server/wsgi_interp.h +++ b/src/server/wsgi_interp.h @@ -4,7 +4,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,13 @@ typedef struct { PyObject_HEAD PyObject *wrapped; +} SignalInterceptObject; + +extern PyTypeObject SignalIntercept_Type; + +typedef struct { + PyObject_HEAD + PyObject *wrapped; } ShutdownInterpreterObject; extern PyTypeObject ShutdownInterpreter_Type; diff --git a/src/server/wsgi_logger.c b/src/server/wsgi_logger.c index 5f29704..28f0728 100644 --- a/src/server/wsgi_logger.c +++ b/src/server/wsgi_logger.c @@ -1,7 +1,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_logger.h b/src/server/wsgi_logger.h index 33b2247..319a295 100644 --- a/src/server/wsgi_logger.h +++ b/src/server/wsgi_logger.h @@ -4,7 +4,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_metrics.c b/src/server/wsgi_metrics.c index 4b0ccb0..e888fcf 100644 --- a/src/server/wsgi_metrics.c +++ b/src/server/wsgi_metrics.c @@ -1,7 +1,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_metrics.h b/src/server/wsgi_metrics.h index 2143806..06c5079 100644 --- a/src/server/wsgi_metrics.h +++ b/src/server/wsgi_metrics.h @@ -4,7 +4,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_python.h b/src/server/wsgi_python.h index 8c3841a..0137eab 100644 --- a/src/server/wsgi_python.h +++ b/src/server/wsgi_python.h @@ -4,7 +4,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_restrict.c b/src/server/wsgi_restrict.c index 8267abc..630ced3 100644 --- a/src/server/wsgi_restrict.c +++ b/src/server/wsgi_restrict.c @@ -1,7 +1,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_restrict.h b/src/server/wsgi_restrict.h index a5c8a59..33def94 100644 --- a/src/server/wsgi_restrict.h +++ b/src/server/wsgi_restrict.h @@ -4,7 +4,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_server.c b/src/server/wsgi_server.c index 0d602ca..b6bdf82 100644 --- a/src/server/wsgi_server.c +++ b/src/server/wsgi_server.c @@ -1,7 +1,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,8 @@ const char *wsgi_daemon_group = ""; /* Process information. */ pid_t wsgi_parent_pid = 0; +pid_t wsgi_worker_pid = 0; +pid_t wsgi_daemon_pid = 0; /* New Relic monitoring agent. */ diff --git a/src/server/wsgi_server.h b/src/server/wsgi_server.h index a9fd74d..d7a27b1 100644 --- a/src/server/wsgi_server.h +++ b/src/server/wsgi_server.h @@ -4,7 +4,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,8 @@ extern server_rec *wsgi_server; extern pid_t wsgi_parent_pid; +extern pid_t wsgi_worker_pid; +extern pid_t wsgi_daemon_pid; extern const char *wsgi_daemon_group; /* New Relic monitoring agent. */ @@ -104,6 +106,8 @@ typedef struct { int chunked_request; int map_head_to_get; + apr_array_header_t *trusted_proxy_headers; + int enable_sendfile; apr_hash_t *handler_scripts; diff --git a/src/server/wsgi_stream.c b/src/server/wsgi_stream.c index 3513745..5be4757 100644 --- a/src/server/wsgi_stream.c +++ b/src/server/wsgi_stream.c @@ -1,7 +1,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_stream.h b/src/server/wsgi_stream.h index 2c306fe..ea083e1 100644 --- a/src/server/wsgi_stream.h +++ b/src/server/wsgi_stream.h @@ -4,7 +4,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_validate.c b/src/server/wsgi_validate.c index c72728a..df74b57 100644 --- a/src/server/wsgi_validate.c +++ b/src/server/wsgi_validate.c @@ -1,7 +1,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_validate.h b/src/server/wsgi_validate.h index 33b6922..67fe2cb 100644 --- a/src/server/wsgi_validate.h +++ b/src/server/wsgi_validate.h @@ -4,7 +4,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/server/wsgi_version.h b/src/server/wsgi_version.h index 5f1e2ce..08a5cbc 100644 --- a/src/server/wsgi_version.h +++ b/src/server/wsgi_version.h @@ -4,7 +4,7 @@ /* ------------------------------------------------------------------------- */ /* - * Copyright 2007-2014 GRAHAM DUMPLETON + * Copyright 2007-2015 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,8 +25,8 @@ #define MOD_WSGI_MAJORVERSION_NUMBER 4 #define MOD_WSGI_MINORVERSION_NUMBER 4 -#define MOD_WSGI_MICROVERSION_NUMBER 8 -#define MOD_WSGI_VERSION_STRING "4.4.8" +#define MOD_WSGI_MICROVERSION_NUMBER 9 +#define MOD_WSGI_VERSION_STRING "4.4.9" /* ------------------------------------------------------------------------- */ |