summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGraham Dumpleton <Graham.Dumpleton@gmail.com>2014-09-15 08:00:29 +1000
committerGraham Dumpleton <Graham.Dumpleton@gmail.com>2014-09-15 08:00:29 +1000
commit1242f836f8fbcbf9abc674c30774d4377fa09154 (patch)
treefc40bddaa699f4baa673550fbb4964858d35b4f4
parentcc3c0395d551bfc9a54de56d0120d87ac53fb5d7 (diff)
parentef1117a85e84c70cac03e829a8c133c74184cc62 (diff)
downloadmod_wsgi-4.3.0.tar.gz
Merge branch 'release/4.3.0'4.3.0
-rw-r--r--docs/release-notes/index.rst2
-rw-r--r--docs/release-notes/version-4.3.0.rst220
-rw-r--r--setup.py33
-rw-r--r--src/server/__init__.py538
-rw-r--r--src/server/management/commands/runmodwsgi.py11
-rw-r--r--src/server/mod_wsgi.c243
-rw-r--r--src/server/wsgi_interp.c21
-rw-r--r--src/server/wsgi_server.h1
-rw-r--r--src/server/wsgi_version.h6
-rw-r--r--tests/auth.wsgi29
-rw-r--r--tests/environ.wsgi3
11 files changed, 1032 insertions, 75 deletions
diff --git a/docs/release-notes/index.rst b/docs/release-notes/index.rst
index 433b8f7..ef4d671 100644
--- a/docs/release-notes/index.rst
+++ b/docs/release-notes/index.rst
@@ -5,6 +5,8 @@ Release Notes
.. toctree::
:maxdepth: 2
+ version-4.3.0.rst
+
version-4.2.8.rst
version-4.2.7.rst
version-4.2.6.rst
diff --git a/docs/release-notes/version-4.3.0.rst b/docs/release-notes/version-4.3.0.rst
new file mode 100644
index 0000000..8f62be3
--- /dev/null
+++ b/docs/release-notes/version-4.3.0.rst
@@ -0,0 +1,220 @@
+=============
+Version 4.3.0
+=============
+
+Version 4.3.0 of mod_wsgi can be obtained from:
+
+ https://github.com/GrahamDumpleton/mod_wsgi/archive/4.3.0.tar.gz
+
+Known Issues
+------------
+
+1. The makefiles for building mod_wsgi on Windows are currently broken and
+need updating. As most new changes relate to mod_wsgi daemon mode, which is
+not supported under Windows, you should keep using the last available
+binary for version 3.X on Windows instead.
+
+Bugs Fixed
+----------
+
+1. Performing authorization using the ``WSGIAuthGroupScript`` was not
+working correctly on Apache 2.4 due to changes in how auth providers
+and authentication/authorization works. The result could be that a user
+could gain access to a resource even though they were not in the
+required group.
+
+2. Under Apache 2.4, when creating the ``environ`` dictionary for
+passing into access/authentication/authorisation handlers, the behvaiour
+of Apache 2.4 as it pertained to the WSGI application, whereby it
+blocked the passing of any HTTP headers with a name which did not contain
+just alphanumerics or '-', was not being mirrored. This created the
+possibility of HTTP header spoofing in certain circumstances. Such headers
+are now being ignored.
+
+3. When ``home`` option was used with ``WSGIDaemonProcess`` directive an
+empty string was added to ``sys.path``. This meant current working directory
+would be searched. This was fine so long as the current working directory
+wasn't changed, but if it was, it would no longer look in the home
+directory. Need to use the actual home directory instead.
+
+4. Fixed Django management command integration so would work for versions
+of Django prior to 1.6 where ``BASE_DIR`` didn't exist in Django settings
+module.
+
+Features Changed
+----------------
+
+1. In Apache 2.4, any headers with a name which does not include only
+alphanumerics or '-' are blocked from being passed into a WSGI application
+when the CGI like WSGI ``environ`` dictionary is created. This is a
+mechanism to prevent header spoofing when there are multiple headers where
+the only difference is the use of non alphanumerics in a specific character
+position.
+
+This protection mechanism from Apache 2.4 is now being restrospectively
+applied even when Apache 2.2 is being used and even though Apache itself
+doesn't do it. This may technically result in headers that were previously
+being passed, no longer being passed. The change is also technically
+against what the HTTP RFC says is allowed for HTTP header names, but such
+blocking would occur in Apache 2.4 anyway due to changes in Apache. It is
+also understood that other web servers such as nginx also perform the same
+type of blocking. Reliance on HTTP headers which use characters other
+than alphanumerics and '-' is therefore dubious as many servers will now
+discard them when needing to be passed into a system which requires the
+headers to be passed as CGI like variables such as is the case for WSGI.
+
+2. In Apache 2.4, only ``wsgi-group`` is allowed when using the ``Require``
+directive for group authorisation. In prior Apache versions ``group`` would
+also be accepted and matched by the ``wsgi`` auth provider. The inability
+to use ``group`` is due to a change in Apache itself and not mod_wsgi. To
+avoid any issues going forward though, the mod_wsgi code will now no longer
+check for ``group`` even if for some reason Apache still decides to pass
+the authorisation check off to mod_wsgi even when it shouldn't.
+
+New Features
+------------
+
+1. The value of the ``REMOTE_USER`` variable for an authenticated user
+when user ``Basic`` authentication can now be overridden from an
+authentication handler specified using the ``WSGIAuthUserScript``. To
+override the name used to identify the user, instead of returning ``True``
+when indicating that the user is allowed, return the name to be used for
+that user as a string. That value will then be passed through in
+``REMOTE_USER`` in place of any original value::
+
+ def check_password(environ, user, password):
+ if user == 'spy':
+ if password == 'secret':
+ return 'grumpy'
+ return False
+ return None
+
+2. Added the ``--debug-mode`` option to ``mod_wsgi-express`` which results
+in Apache and the WSGI application being run in a single process which is
+left attached to stdin/stdout of the shell where the script was run. Only a
+single thread will be used to handle any requests.
+
+This feature enables the ability to interactively debug a Python WSGI
+application using the Python debugger (``pdb``). The simplest way to
+break into the Python debugger is by adding to your WSGI application code::
+
+ import pdb; pdb.set_trace()
+
+3. Added the ``--application-type`` option to ``mod_wsgi-express``. This
+defaults to ``script`` indicating that the target WSGI application provided
+to ``mod_wsgi-express`` is a WSGI script file defined by a relative or
+absolute file system path.
+
+In addition to ``script``, it is also possible to supply for the application
+type ``module`` and ``paste``.
+
+For the case of ``module``, the target WSGI application will be taken to
+reside in a Python module with the specified name. This module will be
+loaded using the standard Python module import system and so must reside
+on the Python module search path.
+
+For the case of ``paste``, the target WSGI application will be taken to be
+a Paste deployment configuration file. In loading the Paste deployment
+configuration file, any WSGI application pipeline specified by the
+configuration will be constructed and the resulting top level WSGI
+application entry point returned used as the WSGI application.
+
+Note that the code file for the WSGI script file, Python module, or Paste
+deployment configuration file, if modified, will all result in the WSGI
+application being automatically reloaded on the next web request.
+
+4. Added the ``--auth-user-script`` and ``--auth-type`` options to
+``mod_wsgi-express`` to enable the hosted site to implement user
+authentication using either HTTP ``Basic`` or ``Digest`` authentication
+mechanisms. The ``check_password()`` or ``get_realm_hash()`` functions
+should follow the same form as if using the ``WSGIAuthUserScript`` direct
+with mod_wsgi when using manual configuration.
+
+5. Added the ``--auth-group-script`` and ``--auth-group`` options to
+``mod_wsgi-express`` to enable group authorization to be performed using a
+group authorization script, in conjunction with a user authentication
+script. The ``groups_for_user()`` function should follow the same form as
+if using the ``WSGIAuthGroupScript`` direct with mod_wsgi when using manual
+configuration.
+
+By default any users must be a member of the ``wsgi`` group. The name of
+this group though can be overridden using the ``--auth-group`` option.
+It is recommended that this be overridden rather than changing your own
+application to use the ``wsgi`` group.
+
+6. Added the ``--directory-index`` option to ``mod_wsgi-express`` to enable
+a index resource to be added to the document root directory which would
+take precedence over the WSGI application for the root page for the site.
+
+7. Added the ``--with-php5`` option to ``mod_wsgi-express`` to enable the
+concurrent hosting of a PHP web application in conjunction with the WSGI
+application. Due to the limitations of PHP, this is currently only
+supported if using prefork MPM.
+
+8. Added the ``--server-name`` option to ``mod_wsgi-express``. When this is
+used and set to the host name for the web site, a virtual host will be
+created to ensure that the server only accepts web requests for that host
+name.
+
+If the host name starts with ``www.`` then web requests will also be
+accepted against the parent domain, that is the host name without the
+``www.``, but those requests will be automatically redirected to the
+specified host name on the same port as that used for the original request.
+
+When the ``--server-name`` option is being used, the ``--server-alias``
+option can also be specified, multiple times if need be, to setup alternate
+names for the web site on which web requests should also be accepted.
+Wildcard aliases may be used in the name if wishing to match multiple
+sub domains in one go.
+
+If for some reason you do still need to be able to access the server via
+``localhost`` when a virtual host for a set server name is being used, you
+can supply the ``--allow-localhost`` option.
+
+9. Added the ``--rotate-logs`` option to ``mod_wsgi-express`` to enable log
+file rotation. By default the error log and access log, if enabled, will be
+rotated when they reach 5MB in size. To change the size at which the log
+files will be rotated, use the ``--max-log-size`` option. If the
+``rotatelogs`` command is not being found properly, its location can be
+specified using the ``--rotatelogs-executable`` option.
+
+10. Added the ``--ssl-port`` and ``--ssl-certificate`` options to
+``mod_wsgi-express``. When both are set, with the latter being the stub
+path for the SSL certificate ``.crt`` and ``.key`` file, then HTTPS
+requests will be handled over the designated SSL port.
+
+When ``--https-only`` is supplied, any requests made over HTTP to the non
+SSL port will be automatically redirected so as to use a HTTPS connection
+over the SSL connection.
+
+Note that if using the ``--allow-localhost`` option, redirection from a
+HTTP to HTTPS connection will not occur when access via ``localhost``.
+
+11. Added the ``--setenv`` option to ``mod_wsgi-express`` to enable request
+specific name/value pairs to be added to the WSGI environ dictionary. The
+values are restricted to string values.
+
+Also added a companion ``--passenv`` option to ``mod_wsgi-express`` to
+indicate the names of normal process environment variables which should
+be added to the per request WSGI environ dictionary.
+
+12. Added the ``WSGIMapHEADToGET`` directive for overriding the previous
+behaviour of automatically mapping any ``HEAD`` request to a ``GET`` request
+when an Apache output filter was registered that may want to see the complete
+response in order to generate correct response headers.
+
+The directive can be set to be either ``Auto`` (the default), ``On`` which
+will always map a ``HEAD`` to ``GET`` even if no output filters detected and
+``Off`` to always preserve the original request method type.
+
+The original behaviour was to avoid problems with users trying to optimise
+for ``HEAD`` requests and then breaking caching mechanisms because the
+response headers for a ``HEAD`` request for a resource didn't match a ``GET``
+request against the same resource as required by HTTP.
+
+If using mod_wsgi-express, the ``--map-head-to-get`` option can be used with
+the same values.
+
+12. Added the ``--compress-responses`` option to ``mod_wsgi-express`` to
+enable compression of common text based responses such as plain text, HTML,
+XML, CSS and Javascript.
diff --git a/setup.py b/setup.py
index 55c846e..69ff1e5 100644
--- a/setup.py
+++ b/setup.py
@@ -75,9 +75,17 @@ elif os.path.exists(os.path.join(BINDIR, PROGNAME)):
else:
HTTPD = PROGNAME
+if os.path.exists(os.path.join(SBINDIR, 'rotatelogs')):
+ ROTATELOGS = os.path.join(SBINDIR, 'rotatelogs')
+elif os.path.exists(os.path.join(BINDIR, 'rotatelogs')):
+ ROTATELOGS = os.path.join(BINDIR, 'rotatelogs')
+else:
+ ROTATELOGS = 'rotatelogs'
+
with open(os.path.join(os.path.dirname(__file__),
'src/server/apxs_config.py'), 'w') as fp:
print('HTTPD = "%s"' % HTTPD, file=fp)
+ print('ROTATELOGS = "%s"' % ROTATELOGS, file=fp)
print('BINDIR = "%s"' % BINDIR, file=fp)
print('SBINDIR = "%s"' % SBINDIR, file=fp)
print('PROGNAME = "%s"' % PROGNAME, file=fp)
@@ -122,13 +130,24 @@ LD_RUN_PATH = LD_RUN_PATH.lstrip(':')
os.environ['LD_RUN_PATH'] = LD_RUN_PATH
-# If using Python 3.4, then minimum MacOS X version you can use is 10.8.
-# We have to force this with the compiler otherwise Python 3.4 sets it
-# to 10.6 which screws up Apache APR % formats for apr_time_t, which
-# breaks daemon mode queue time.
-
-if sys.version_info >= (3, 4):
- os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.8'
+# On MacOS X, recent versions of Apple's Apache do not support compiling
+# Apache modules with a target older than 10.8. This is because it
+# screws up Apache APR % formats for apr_time_t, which breaks daemon
+# mode queue time. For the target to be 10.8 or newer for now if Python
+# installation supports older versions. This means that things will not
+# build for older MacOS X versions. Deal with these when they occur.
+
+if sys.platform == 'darwin':
+ target = os.environ.get('MACOSX_DEPLOYMENT_TARGET')
+ if target is None:
+ target = get_python_config('MACOSX_DEPLOYMENT_TARGET')
+
+ if target:
+ target_version = tuple(map(int, target.split('.')))
+ #assert target_version >= (10, 8), \
+ # 'Minimum of 10.8 for MACOSX_DEPLOYMENT_TARGET'
+ if target_version < (10, 8):
+ os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.8'
# Now add the definitions to build everything.
diff --git a/src/server/__init__.py b/src/server/__init__.py
index 7f5e46c..53f7391 100644
--- a/src/server/__init__.py
+++ b/src/server/__init__.py
@@ -97,16 +97,30 @@ LockFile '%(server_root)s/accept.lock'
</IfVersion>
<IfVersion >= 2.4>
+<IfDefine WSGI_WITH_PHP5>
<IfModule !mpm_event_module>
<IfModule !mpm_worker_module>
<IfModule !mpm_prefork_module>
-<IfDefine WSGI_MPM_EVENT_MODULE>
+<IfDefine WSGI_MPM_EXISTS_PREFORK_MODULE>
+LoadModule mpm_prefork_module '%(modules_directory)s/mod_mpm_prefork.so'
+</IfDefine>
+</IfModule>
+</IfModule>
+</IfModule>
+</IfDefine>
+</IfVersion>
+
+<IfVersion >= 2.4>
+<IfModule !mpm_event_module>
+<IfModule !mpm_worker_module>
+<IfModule !mpm_prefork_module>
+<IfDefine WSGI_MPM_ENABLE_EVENT_MODULE>
LoadModule mpm_event_module '%(modules_directory)s/mod_mpm_event.so'
</IfDefine>
-<IfDefine WSGI_MPM_WORKER_MODULE>
+<IfDefine WSGI_MPM_ENABLE_WORKER_MODULE>
LoadModule mpm_worker_module '%(modules_directory)s/mod_mpm_worker.so'
</IfDefine>
-<IfDefine WSGI_MPM_PREFORK_MODULE>
+<IfDefine WSGI_MPM_ENABLE_PREFORK_MODULE>
LoadModule mpm_prefork_module '%(modules_directory)s/mod_mpm_prefork.so'
</IfDefine>
</IfModule>
@@ -144,6 +158,36 @@ LoadModule alias_module '%(modules_directory)s/mod_alias.so'
<IfModule !dir_module>
LoadModule dir_module '%(modules_directory)s/mod_dir.so'
</IfModule>
+<IfModule !env_module>
+LoadModule env_module '%(modules_directory)s/mod_env.so'
+</IfModule>
+
+<IfDefine WSGI_COMPRESS_RESPONSES>
+<IfModule !deflate_module>
+LoadModule deflate_module '%(modules_directory)s/mod_deflate.so'
+</IfModule>
+</IfDefine>
+
+<IfDefine WSGI_AUTH_USER>
+<IfModule !auth_basic_module>
+LoadModule auth_basic_module '%(modules_directory)s/mod_auth_basic.so'
+</IfModule>
+<IfModule !auth_digest_module>
+LoadModule auth_digest_module '%(modules_directory)s/mod_auth_digest.so'
+</IfModule>
+<IfModule !authz_user_module>
+LoadModule authz_user_module '%(modules_directory)s/mod_authz_user.so'
+</IfModule>
+</IfDefine>
+
+<IfModule mpm_prefork_module>
+<IfDefine WSGI_WITH_PHP5>
+<IfModule !php5_module>
+Loadmodule php5_module '%(modules_directory)s/libphp5.so'
+</IfModule>
+AddHandler application/x-httpd-php .php
+</IfDefine>
+</IfModule>
LoadModule wsgi_module '%(mod_wsgi_so)s'
@@ -173,6 +217,8 @@ LimitRequestBody %(limit_request_body)s
</Directory>
WSGIPythonHome '%(python_home)s'
+
+<IfDefine !ONE_PROCESS>
WSGIRestrictEmbedded On
WSGISocketPrefix %(server_root)s/wsgi
<IfDefine WSGI_MULTIPROCESS>
@@ -222,8 +268,15 @@ WSGIDaemonProcess %(host)s:%(port)s \\
header-buffer-size=%(header_buffer_size)s \\
server-metrics=%(daemon_server_metrics_flag)s
</IfDefine>
+</IfDefine>
+
WSGICallableObject '%(callable_object)s'
WSGIPassAuthorization On
+WSGIMapHEADToGET %(map_head_to_get)s
+
+<IfDefine ONE_PROCESS>
+WSGIRestrictStdin Off
+</IfDefine>
<IfDefine WSGI_SERVER_METRICS>
ExtendedStatus On
@@ -246,7 +299,23 @@ KeepAliveTimeout %(keep_alive_timeout)s
KeepAlive Off
</IfDefine>
+<IfDefine WSGI_COMPRESS_RESPONSES>
+AddOutputFilterByType DEFLATE text/plain
+AddOutputFilterByType DEFLATE text/html
+AddOutputFilterByType DEFLATE text/xml
+AddOutputFilterByType DEFLATE text/css
+AddOutputFilterByType DEFLATE text/javascript
+AddOutputFilterByType DEFLATE application/xhtml+xml
+AddOutputFilterByType DEFLATE application/javascript
+</IfDefine>
+
+<IfDefine WSGI_ROTATE_LOGS>
+ErrorLog "|%(rotatelogs_executable)s \\
+ %(error_log)s.%%Y-%%m-%%d-%%H_%%M_%%S %(max_log_size)sM"
+</IfDefine>
+<IfDefine !WSGI_ROTATE_LOGS>
ErrorLog '%(error_log)s'
+</IfDefine>
LogLevel %(log_level)s
<IfDefine WSGI_ACCESS_LOG>
@@ -254,19 +323,41 @@ LogLevel %(log_level)s
LoadModule log_config_module %(modules_directory)s/mod_log_config.so
</IfModule>
LogFormat "%%h %%l %%u %%t \\"%%r\\" %%>s %%b" common
+<IfDefine WSGI_ROTATE_LOGS>
+CustomLog "|%(rotatelogs_executable)s \\
+ %(log_directory)s/access_log.%%Y-%%m-%%d-%%H_%%M_%%S %(max_log_size)sM" common
+</IfDefine>
+<IfDefine !WSGI_ROTATE_LOGS>
CustomLog "%(log_directory)s/access_log" common
</IfDefine>
+</IfDefine>
+
+<IfDefine WSGI_WITH_SSL>
+<IfModule !ssl_module>
+LoadModule ssl_module %(modules_directory)s/mod_ssl.so
+</IfModule>
+</IfDefine>
<IfModule mpm_prefork_module>
+<IfDefine !ONE_PROCESS>
ServerLimit %(prefork_server_limit)s
StartServers %(prefork_start_servers)s
MaxClients %(prefork_max_clients)s
MinSpareServers %(prefork_min_spare_servers)s
MaxSpareServers %(prefork_max_spare_servers)s
+</IfDefine>
+<IfDefine ONE_PROCESS>
+ServerLimit 1
+StartServers 1
+MaxClients 1
+MinSpareServers 1
+MaxSpareServers 1
+</IfDefine>
MaxRequestsPerChild 0
</IfModule>
<IfModule mpm_worker_module>
+<IfDefine !ONE_PROCESS>
ServerLimit %(worker_server_limit)s
ThreadLimit %(worker_thread_limit)s
StartServers %(worker_start_servers)s
@@ -274,11 +365,22 @@ MaxClients %(worker_max_clients)s
MinSpareThreads %(worker_min_spare_threads)s
MaxSpareThreads %(worker_max_spare_threads)s
ThreadsPerChild %(worker_threads_per_child)s
+</IfDefine>
+<IfDefine ONE_PROCESS>
+ServerLimit 1
+ThreadLimit 1
+StartServers 1
+MaxClients 1
+MinSpareThreads 1
+MaxSpareThreads 1
+ThreadsPerChild 1
+</IfDefine>
MaxRequestsPerChild 0
ThreadStackSize 262144
</IfModule>
<IfModule mpm_event_module>
+<IfDefine !ONE_PROCESS>
ServerLimit %(worker_server_limit)s
ThreadLimit %(worker_thread_limit)s
StartServers %(worker_start_servers)s
@@ -286,10 +388,117 @@ MaxClients %(worker_max_clients)s
MinSpareThreads %(worker_min_spare_threads)s
MaxSpareThreads %(worker_max_spare_threads)s
ThreadsPerChild %(worker_threads_per_child)s
+</IfDefine>
+<IfDefine ONE_PROCESS>
+ServerLimit 1
+ThreadLimit 1
+StartServers 1
+MaxClients 1
+MinSpareThreads 1
+MaxSpareThreads 1
+ThreadsPerChild 1
+</IfDefine>
MaxRequestsPerChild 0
ThreadStackSize 262144
</IfModule>
+<IfDefine WSGI_VIRTUAL_HOST>
+
+<IfVersion < 2.4>
+NameVirtualHost *:%(port)s
+</IfVersion>
+<VirtualHost _default_:%(port)s>
+<Location />
+Order deny,allow
+Deny from all
+<IfDefine WSGI_ALLOW_LOCALHOST>
+Allow from localhost
+</IfDefine>
+</Location>
+</VirtualHost>
+<IfDefine !WSGI_HTTPS_ONLY>
+<VirtualHost *:%(port)s>
+ServerName %(server_name)s
+<IfDefine WSGI_SERVER_ALIAS>
+ServerAlias %(server_aliases)s
+</IfDefine>
+</VirtualHost>
+<IfDefine WSGI_REDIRECT_WWW>
+<VirtualHost *:%(port)s>
+ServerName %(parent_domain)s
+Redirect permanent / http://%(server_name)s:%(port)s/
+</VirtualHost>
+</IfDefine>
+</IfDefine>
+
+<IfDefine WSGI_HTTPS_ONLY>
+<VirtualHost *:%(port)s>
+ServerName %(server_name)s
+<IfDefine WSGI_SERVER_ALIAS>
+ServerAlias %(server_aliases)s
+RewriteEngine On
+RewriteCond %%{HTTPS} off
+RewriteRule (.*) https://%%{HTTP_HOST}%%{REQUEST_URI}
+</IfDefine>
+</VirtualHost>
+<IfDefine WSGI_REDIRECT_WWW>
+<VirtualHost *:%(port)s>
+ServerName %(parent_domain)s
+RewriteEngine On
+RewriteCond %%{HTTPS} off
+RewriteRule (.*) https://%%{HTTP_HOST}%%{REQUEST_URI}
+</VirtualHost>
+</IfDefine>
+</IfDefine>
+
+</IfDefine>
+
+<IfDefine WSGI_VIRTUAL_HOST>
+
+<IfDefine WSGI_WITH_SSL>
+<IfDefine WSGI_LISTENER_HOST>
+Listen %(host)s:%(ssl_port)s
+</IfDefine>
+<IfDefine !WSGI_LISTENER_HOST>
+Listen %(ssl_port)s
+</IfDefine>
+<IfVersion < 2.4>
+NameVirtualHost *:%(ssl_port)s
+</IfVersion>
+<VirtualHost _default_:%(ssl_port)s>
+<Location />
+Order deny,allow
+Deny from all
+<IfDefine WSGI_ALLOW_LOCALHOST>
+Allow from localhost
+</IfDefine>
+</Location>
+SSLEngine On
+SSLCertificateFile %(ssl_certificate)s.crt
+SSLCertificateKeyFile %(ssl_certificate)s.key
+</VirtualHost>
+<VirtualHost *:%(ssl_port)s>
+ServerName %(server_name)s
+<IfDefine WSGI_SERVER_ALIAS>
+ServerAlias %(server_aliases)s
+</IfDefine>
+SSLEngine On
+SSLCertificateFile %(ssl_certificate)s.crt
+SSLCertificateKeyFile %(ssl_certificate)s.key
+</VirtualHost>
+<IfDefine WSGI_REDIRECT_WWW>
+<VirtualHost *:%(ssl_port)s>
+ServerName %(parent_domain)s
+Redirect permanent / https://%(server_name)s:%(ssl_port)s/
+SSLEngine On
+SSLCertificateFile %(ssl_certificate)s.crt
+SSLCertificateKeyFile %(ssl_certificate)s.key
+</VirtualHost>
+</IfDefine>
+</IfDefine>
+
+</IfDefine>
+
DocumentRoot '%(document_root)s'
<Directory '%(server_root)s'>
@@ -300,8 +509,14 @@ DocumentRoot '%(document_root)s'
</Directory>
<Directory '%(document_root)s%(mount_point)s'>
+<IfDefine WSGI_DIRECTORY_INDEX>
+ DirectoryIndex %(directory_index)s
+</IfDefine>
RewriteEngine On
RewriteCond %%{REQUEST_FILENAME} !-f
+<IfDefine WSGI_DIRECTORY_INDEX>
+ RewriteCond %%{REQUEST_FILENAME} !-d
+</IfDefine>
<IfDefine WSGI_SERVER_STATUS>
RewriteCond %%{REQUEST_URI} !/server-status
</IfDefine>
@@ -314,10 +529,45 @@ DocumentRoot '%(document_root)s'
WSGIErrorOverride On
</IfDefine>
+<IfDefine WSGI_AUTH_USER>
+<Location />
+ AuthType %(auth_type)s
+ AuthName '%(host)s:%(port)s'
+ Auth%(auth_type)sProvider wsgi
+ WSGIAuthUserScript '%(auth_user_script)s'
+<IfDefine WSGI_AUTH_GROUP>
+ WSGIAuthGroupScript '%(auth_group_script)s'
+</IfDefine>
+<IfVersion < 2.4>
+ Require valid-user
+<IfDefine WSGI_AUTH_GROUP>
+ Require wsgi-group '%(auth_group)s'
+</IfDefine>
+</IfVersion>
+<IfVersion >= 2.4>
+ <RequireAll>
+ Require valid-user
+<IfDefine WSGI_AUTH_GROUP>
+ Require wsgi-group '%(auth_group)s'
+</IfDefine>
+ </RequireAll>
+</IfVersion>
+</Location>
+</IfDefine>
+
+<IfDefine !ONE_PROCESS>
WSGIHandlerScript wsgi-handler '%(server_root)s/handler.wsgi' \\
process-group='%(host)s:%(port)s' application-group=%%{GLOBAL}
WSGIImportScript '%(server_root)s/handler.wsgi' \\
process-group='%(host)s:%(port)s' application-group=%%{GLOBAL}
+</IfDefine>
+
+<IfDefine ONE_PROCESS>
+WSGIHandlerScript wsgi-handler '%(server_root)s/handler.wsgi' \\
+ process-group='%%{GLOBAL}' application-group=%%{GLOBAL}
+WSGIImportScript '%(server_root)s/handler.wsgi' \\
+ process-group='%%{GLOBAL}' application-group=%%{GLOBAL}
+</IfDefine>
"""
APACHE_ALIAS_DIRECTORY_CONFIG = """
@@ -360,6 +610,14 @@ APACHE_ERROR_DOCUMENT_CONFIG = """
ErrorDocument '%(status)s' '%(document)s'
"""
+APACHE_SETENV_CONFIG = """
+SetEnv '%(name)s' '%(value)s'
+"""
+
+APACHE_PASSENV_CONFIG = """
+PassEnv '%(name)s'
+"""
+
APACHE_INCLUDE_CONFIG = """
Include '%(filename)s'
"""
@@ -410,6 +668,15 @@ def generate_apache_config(options):
print(APACHE_ERROR_DOCUMENT_CONFIG % dict(status=status,
document=document.replace("'", "\\'")), file=fp)
+ if options['setenv_variables']:
+ for name, value in options['setenv_variables']:
+ print(APACHE_SETENV_CONFIG % dict(name=name, value=value),
+ file=fp)
+
+ if options['passenv_variables']:
+ for name in options['passenv_variables']:
+ print(APACHE_PASSENV_CONFIG % dict(name=name), file=fp)
+
if options['include_files']:
for filename in options['include_files']:
filename = os.path.abspath(filename)
@@ -535,26 +802,44 @@ def start_reloader(interval=1.0):
class ApplicationHandler(object):
- def __init__(self, script, callable_object='application', mount_point='/',
- with_newrelic=False, with_wdb=False):
+ def __init__(self, entry_point, application_type='script',
+ callable_object='application', mount_point='/',
+ with_newrelic=False, with_wdb=False, debug_mode=False):
- self.script = script
+ self.entry_point = entry_point
+ self.application_type = application_type
self.callable_object = callable_object
self.mount_point = mount_point
- self.module = imp.new_module('__wsgi__')
- self.module.__file__ = script
-
- with open(script, 'r') as fp:
- code = compile(fp.read(), script, 'exec', dont_inherit=True)
- exec(code, self.module.__dict__)
-
- self.application = getattr(self.module, callable_object)
-
- sys.modules['__wsgi__'] = self.module
+ if application_type == 'module':
+ __import__(entry_point)
+ self.module = sys.modules[entry_point]
+ self.application = getattr(self.module, callable_object)
+ self.target = self.module.__file__
+ parts = os.path.splitext(self.target)[-1]
+ if parts[-1].lower() in ('.pyc', '.pyd', '.pyd'):
+ self.target = parts[0] + '.py'
+
+ elif application_type == 'paste':
+ from paste.deploy import loadapp
+ self.application = loadapp('config:%s' % entry_point)
+ self.target = entry_point
+
+ else:
+ self.module = imp.new_module('__wsgi__')
+ self.module.__file__ = entry_point
+
+ with open(entry_point, 'r') as fp:
+ code = compile(fp.read(), entry_point, 'exec',
+ dont_inherit=True)
+ exec(code, self.module.__dict__)
+
+ sys.modules['__wsgi__'] = self.module
+ self.application = getattr(self.module, callable_object)
+ self.target = entry_point
try:
- self.mtime = os.path.getmtime(script)
+ self.mtime = os.path.getmtime(self.target)
except Exception:
self.mtime = None
@@ -564,6 +849,8 @@ class ApplicationHandler(object):
if with_wdb:
self.setup_wdb()
+ self.debug_mode = debug_mode
+
def setup_newrelic(self):
import newrelic.agent
@@ -585,8 +872,11 @@ class ApplicationHandler(object):
self.application = WdbMiddleware(self.application)
def reload_required(self, environ):
+ if self.debug_mode:
+ return False
+
try:
- mtime = os.path.getmtime(self.script)
+ mtime = os.path.getmtime(self.target)
except Exception:
mtime = None
@@ -615,19 +905,24 @@ class ApplicationHandler(object):
WSGI_HANDLER_SCRIPT = """
import mod_wsgi.server
-script = '%(script)s'
+entry_point = '%(entry_point)s'
+application_type = '%(application_type)s'
callable_object = '%(callable_object)s'
mount_point = '%(mount_point)s'
with_newrelic = %(with_newrelic_agent)s
with_wdb = %(with_wdb)s
+reload_on_changes = %(reload_on_changes)s
+debug_mode = %(debug_mode)s
-handler = mod_wsgi.server.ApplicationHandler(script, callable_object,
- mount_point, with_newrelic=with_newrelic, with_wdb=with_wdb)
+handler = mod_wsgi.server.ApplicationHandler(entry_point,
+ application_type=application_type, callable_object=callable_object,
+ mount_point=mount_point, with_newrelic=with_newrelic,
+ with_wdb=with_wdb, debug_mode=debug_mode)
reload_required = handler.reload_required
handle_request = handler.handle_request
-if %(reload_on_changes)s:
+if reload_on_changes and not debug_mode:
mod_wsgi.server.start_reloader()
"""
@@ -742,6 +1037,8 @@ def generate_wdb_server_script(options):
WSGI_CONTROL_SCRIPT = """
#!/bin/sh
+# %(sys_argv)s
+
HTTPD="%(httpd_executable)s %(httpd_arguments)s"
WSGI_RUN_USER="${WSGI_RUN_USER:-%(user)s}"
@@ -750,6 +1047,12 @@ WSGI_RUN_GROUP="${WSGI_RUN_GROUP:-%(group)s}"
export WSGI_RUN_USER
export WSGI_RUN_GROUP
+LANG='%(lang)s'
+LC_ALL='%(locale)s'
+
+export LANG
+export LOCALE
+
ACMD="$1"
ARGV="$@"
@@ -807,6 +1110,16 @@ def check_percentage(option, opt_str, value, parser):
setattr(parser.values, option.dest, value)
option_list = (
+ optparse.make_option('--application-type', default='script',
+ metavar='TYPE', help='The type of WSGI application entry point '
+ 'that was provided. Defaults to \'script\', indicating the '
+ 'traditional mod_wsgi style WSGI script file specified by a '
+ 'filesystem path. Alternatively one can supply \'module\', '
+ 'indicating that the provided entry point is a Python module '
+ 'which should be imported using the standard Python import '
+ 'mechanism, or \'paste\' indicating that the provided entry '
+ 'point is a Paste deployment configuration file.'),
+
optparse.make_option('--host', default=None, metavar='IP-ADDRESS',
help='The specific host (IP address) interface on which '
'requests are to be accepted. Defaults to listening on '
@@ -815,6 +1128,33 @@ option_list = (
metavar='NUMBER', help='The specific port to bind to and '
'on which requests are to be accepted. Defaults to port 8000.'),
+ optparse.make_option('--ssl-port', type='int', metavar='NUMBER',
+ help='The specific port to bind to and on which requests are '
+ 'to be accepted for SSL connections.'),
+ optparse.make_option('--ssl-certificate', default=None,
+ metavar='FILE-PATH', help='Specify the path to the SSL '
+ 'certificate files. It is expected that the files have \'.crt\' '
+ 'and \'.key\' extensions. This option should refer to the '
+ 'common part of the names for both files which appears before '
+ '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 SSL connection should '
+ 'be redirected automatically to use a HTTPS request over the '
+ 'SSL connection.'),
+
+ optparse.make_option('--server-name', default=None, metavar='HOSTNAME',
+ help='The primary host name of the web server. If this name '
+ 'starts with \'www.\' then an automatic redirection from the '
+ 'parent domain name to the \'www.\' server name will created.'),
+ optparse.make_option('--server-alias', action='append',
+ dest='server_aliases', metavar='HOSTNAME', help='A secondary '
+ 'host name for the web server. May include wilcard patterns.'),
+ optparse.make_option('--allow-localhost', action='store_true',
+ default=False, help='Flag indicating whether access via '
+ 'localhost should still be allowed when a server name has been '
+ 'specified and a name based virtual host has been configured.'),
+
optparse.make_option('--processes', type='int', metavar='NUMBER',
help='The number of worker processes (instances of the WSGI '
'application) to be started up and which will handle requests '
@@ -956,9 +1296,22 @@ option_list = (
'application within the WSGI script file. Defaults to '
'the name \'application\'.'),
+ optparse.make_option('--map-head-to-get', default='Auto',
+ metavar='OFF|ON|AUTO', help='Flag indicating whether HEAD '
+ 'requests should be mapped to a GET request. By default a HEAD '
+ 'request will be automatically mapped to a GET request when an '
+ 'Apache output filter is detected that may want to see the '
+ 'entire response in order to set up response headers correctly '
+ 'for a HEAD request. This can be disable by setting to \'Off\'.'),
+
optparse.make_option('--document-root', metavar='DIRECTORY-PATH',
help='The directory which should be used as the document root '
'and which contains any static files.'),
+ optparse.make_option('--directory-index', metavar='FILE-NAME',
+ help='The name of a directory index resource to be found in the '
+ 'document root directory. Requests mapping to the directory '
+ 'will be mapped to this resource rather than being passed '
+ 'through to the WSGI application.'),
optparse.make_option('--mount-point', metavar='URL-PATH', default='/',
help='The URL path at which the WSGI application will be '
@@ -983,6 +1336,11 @@ option_list = (
'to be made over the same connection. Defaults to 0, indicating '
'that keep alive connections are disabled.'),
+ optparse.make_option('--compress-responses', action='store_true',
+ default=False, help='Flag indicating whether responses for '
+ 'common text based responses, such as plain text, HTML, XML, '
+ 'CSS and Javascript should be compressed.'),
+
optparse.make_option('--server-metrics', action='store_true',
default=False, help='Flag indicating whether internal server '
'metrics will be available within the WSGI application. '
@@ -992,6 +1350,25 @@ option_list = (
'will be available at the /server-status sub URL. Defaults to '
'being disabled.'),
+ optparse.make_option('--auth-user-script', metavar='SCRIPT-PATH',
+ default=None, help='Specify a Python script file for '
+ 'performing user authentication.'),
+ optparse.make_option('--auth-type', metavar='TYPE',
+ default='Basic', help='Specify the type of authentication '
+ 'scheme used when authenticating users. Defaults to using '
+ '\'Basic\'. Alternate schemes available are \'Digest\'.'),
+
+ optparse.make_option('--auth-group-script', metavar='SCRIPT-PATH',
+ default=None, help='Specify a Python script file for '
+ 'performing group based authorization in conjunction with '
+ 'a user authentication script.'),
+ optparse.make_option('--auth-group', metavar='SCRIPT-PATH',
+ default='wsgi', help='Specify the group which users should '
+ 'be a member of when using a group based authorization script. '
+ 'Defaults to \'wsgi\' as a place holder but should be '
+ 'overridden to be the actual group you use rather than '
+ 'making your group name match the default.'),
+
optparse.make_option('--include-file', action='append',
dest='include_files', metavar='FILE-PATH', help='Specify the '
'path to an additional web server configuration file to be '
@@ -1010,6 +1387,16 @@ option_list = (
'as normally defined by the LC_ALL environment variable. '
'Defaults to \'en_US.UTF-8\'.'),
+ optparse.make_option('--setenv', action='append', nargs=2,
+ dest='setenv_variables', metavar='KEY VALUE', help='Specify '
+ 'a name/value pairs to be added to the per request WSGI environ '
+ 'dictionary'),
+ optparse.make_option('--passenv', action='append',
+ dest='passenv_variables', metavar='KEY', help='Specify the '
+ 'names of any process level environment variables which should '
+ 'be passed as a name/value pair in the per request WSGI '
+ 'environ dictionary.'),
+
optparse.make_option('--working-directory', metavar='DIRECTORY-PATH',
help='Specify the directory which should be used as the '
'current working directory of the WSGI application. This '
@@ -1038,6 +1425,16 @@ option_list = (
help='Flag indicating whether the web server startup log should '
'be enabled. Defaults to being disabled.'),
+ optparse.make_option('--rotate-logs', action='store_true', default=False,
+ help='Flag indicating whether log rotation should be performed.'),
+ optparse.make_option('--max-log-size', default=5, type='int',
+ metavar='MB', help='The maximum size in MB the log file should '
+ 'be allowed to reach before log file rotation is performed.'),
+
+ optparse.make_option('--rotatelogs-executable',
+ default=apxs_config.ROTATELOGS, metavar='FILE-PATH',
+ help='Override the path to the rotatelogs executable.'),
+
optparse.make_option('--python-eggs', metavar='DIRECTORY-PATH',
help='Specify an alternate directory which should be used for '
'unpacking of Python eggs. Defaults to a sub directory of '
@@ -1070,10 +1467,21 @@ option_list = (
help='Flag indicating whether the wdb interactive debugger '
'should be enabled for the WSGI application.'),
+ optparse.make_option('--with-php5', action='store_true', default=False,
+ help='Flag indicating whether PHP 5 support should be enabled.'),
+
optparse.make_option('--enable-docs', action='store_true', default=False,
help='Flag indicating whether the mod_wsgi documentation should '
'be made available at the /__wsgi__/docs sub URL.'),
+ optparse.make_option('--debug-mode', action='store_true', default=False,
+ help='Flag indicating whether to run in single process mode '
+ 'to allow the running of an interactive Python debugger. This '
+ 'will override all options related to processes, threads and '
+ 'communication with workers. All forms of source code reloading '
+ 'will also be disabled. Both stdin and stdout will be attached '
+ 'to the console to allow interaction with the Python debugger.'),
+
optparse.make_option('--setup-only', action='store_true', default=False,
help='Flag indicating that after the configuration files have '
'been setup, that the command should then exit and not go on '
@@ -1098,14 +1506,19 @@ def cmd_setup_server(params):
def _mpm_module_defines(modules_directory):
result = []
workers = ['event', 'worker', 'prefork']
+ found = False
for name in workers:
if os.path.exists(os.path.join(modules_directory,
'mod_mpm_%s.so' % name)):
- result.append('-DWSGI_MPM_%s_MODULE' % name.upper())
- break
+ if not found:
+ result.append('-DWSGI_MPM_ENABLE_%s_MODULE' % name.upper())
+ found = True
+ result.append('-DWSGI_MPM_EXISTS_%s_MODULE' % name.upper())
return result
def _cmd_setup_server(command, args, options):
+ options['sys_argv'] = repr(sys.argv)
+
options['mod_wsgi_so'] = where()
options['working_directory'] = options['working_directory'] or os.getcwd()
@@ -1128,12 +1541,27 @@ def _cmd_setup_server(command, args, options):
except Exception:
pass
+ if options['ssl_certificate']:
+ options['ssl_certificate'] = os.path.abspath(
+ options['ssl_certificate'])
+
if not args:
- options['script'] = os.path.join(options['server_root'],
+ options['entry_point'] = os.path.join(options['server_root'],
'default.wsgi')
+ options['application_type'] = 'script'
options['enable_docs'] = True
+ elif options['application_type'] in ('script', 'paste'):
+ options['entry_point'] = os.path.abspath(args[0])
else:
- options['script'] = os.path.abspath(args[0])
+ options['entry_point'] = args[0]
+
+ if options['auth_user_script']:
+ options['auth_user_script'] = os.path.abspath(
+ options['auth_user_script'])
+
+ if options['auth_group_script']:
+ options['auth_group_script'] = os.path.abspath(
+ options['auth_group_script'])
options['documentation_directory'] = os.path.join(os.path.dirname(
os.path.dirname(__file__)), 'docs')
@@ -1146,9 +1574,6 @@ def _cmd_setup_server(command, args, options):
else:
options['documentation_url'] = 'http://www.modwsgi.org/'
- options['script_directory'] = os.path.dirname(options['script'])
- options['script_filename'] = os.path.basename(options['script'])
-
if not os.path.isabs(options['server_root']):
options['server_root'] = os.path.abspath(options['server_root'])
@@ -1336,27 +1761,73 @@ def _cmd_setup_server(command, args, options):
options['httpd_arguments_list'].append(
options['startup_log_filename'])
+ if options['server_name']:
+ host = options['server_name']
+ else:
+ host = options['host']
+
if options['port'] == 80:
- options['url'] = 'http://%s/' % options['host']
+ options['url'] = 'http://%s/' % host
+ else:
+ options['url'] = 'http://%s:%s/' % (host, options['port'])
+
+ if options['ssl_port'] == 443:
+ options['ssl_url'] = 'https://%s/' % host
+ elif options['ssl_port'] is not None:
+ options['ssl_url'] = 'https://%s:%s/' % (host, options['ssl_port'])
else:
- options['url'] = 'http://%s:%s/' % (options['host'],
- options['port'])
+ options['ssl_url'] = None
+
+ if options['debug_mode']:
+ options['httpd_arguments_list'].append('-DONE_PROCESS')
+
+ options['parent_domain'] = 'unspecified'
+
+ if options['server_name']:
+ options['httpd_arguments_list'].append('-DWSGI_VIRTUAL_HOST')
+ if options['server_name'].lower().startswith('www.'):
+ options['httpd_arguments_list'].append('-DWSGI_REDIRECT_WWW')
+ options['parent_domain'] = options['server_name'][4:]
+
+ if options['ssl_port'] and options['ssl_certificate']:
+ options['httpd_arguments_list'].append('-DWSGI_WITH_SSL')
+ if options['https_only']:
+ options['httpd_arguments_list'].append('-DWSGI_HTTPS_ONLY')
+
+ if options['server_aliases']:
+ options['httpd_arguments_list'].append('-DWSGI_SERVER_ALIAS')
+ options['server_aliases'] = ' '.join(options['server_aliases'])
+
+ if options['allow_localhost']:
+ options['httpd_arguments_list'].append('-DWSGI_ALLOW_LOCALHOST')
if options['server_metrics']:
options['httpd_arguments_list'].append('-DWSGI_SERVER_METRICS')
if options['server_status']:
options['httpd_arguments_list'].append('-DWSGI_SERVER_METRICS')
options['httpd_arguments_list'].append('-DWSGI_SERVER_STATUS')
+ if options['directory_index']:
+ options['httpd_arguments_list'].append('-DWSGI_DIRECTORY_INDEX')
if options['access_log']:
options['httpd_arguments_list'].append('-DWSGI_ACCESS_LOG')
+ if options['rotate_logs']:
+ options['httpd_arguments_list'].append('-DWSGI_ROTATE_LOGS')
if options['keep_alive'] != 0:
options['httpd_arguments_list'].append('-DWSGI_KEEP_ALIVE')
+ if options['compress_responses'] != 0:
+ options['httpd_arguments_list'].append('-DWSGI_COMPRESS_RESPONSES')
if options['multiprocess']:
options['httpd_arguments_list'].append('-DWSGI_MULTIPROCESS')
if options['listener_host']:
options['httpd_arguments_list'].append('-DWSGI_LISTENER_HOST')
if options['error_override']:
options['httpd_arguments_list'].append('-DWSGI_ERROR_OVERRIDE')
+ if options['auth_user_script']:
+ options['httpd_arguments_list'].append('-DWSGI_AUTH_USER')
+ if options['auth_group_script']:
+ options['httpd_arguments_list'].append('-DWSGI_AUTH_GROUP')
+ if options['with_php5']:
+ options['httpd_arguments_list'].append('-DWSGI_WITH_PHP5')
options['httpd_arguments_list'].extend(
_mpm_module_defines(options['modules_directory']))
@@ -1371,6 +1842,9 @@ def _cmd_setup_server(command, args, options):
print('Server URL :', options['url'])
+ if options['ssl_url']:
+ print('Server URL (SSL) :', options['ssl_url'])
+
if options['server_status']:
print('Server Status :', '%sserver-status' % options['url'])
diff --git a/src/server/management/commands/runmodwsgi.py b/src/server/management/commands/runmodwsgi.py
index b80541e..ad7cd8e 100644
--- a/src/server/management/commands/runmodwsgi.py
+++ b/src/server/management/commands/runmodwsgi.py
@@ -29,7 +29,16 @@ class Command(BaseCommand):
args = [script_file]
options['callable_object'] = callable_object
- options['working_directory'] = settings.BASE_DIR
+ # If there is no BASE_DIR in Django settings, assume that
+ # the current working directory is the parent directory of
+ # the directory the settings module is in.
+
+ if hasattr(settings, 'BASE_DIR'):
+ options['working_directory'] = settings.BASE_DIR
+ else:
+ settings_mod = sys.modules[os.environ['DJANGO_SETTINGS_MODULE']]
+ parent = os.path.dirname(os.path.dirname(settings_mod.__file__))
+ options['working_directory'] = parent
url_aliases = options.setdefault('url_aliases') or []
diff --git a/src/server/mod_wsgi.c b/src/server/mod_wsgi.c
index 1dcd01f..332ff94 100644
--- a/src/server/mod_wsgi.c
+++ b/src/server/mod_wsgi.c
@@ -178,6 +178,11 @@ static void *wsgi_merge_server_config(apr_pool_t *p, void *base_conf,
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->enable_sendfile != -1)
config->enable_sendfile = child->enable_sendfile;
else
@@ -211,6 +216,7 @@ typedef struct {
int script_reloading;
int error_override;
int chunked_request;
+ int map_head_to_get;
int enable_sendfile;
@@ -242,6 +248,7 @@ static WSGIDirectoryConfig *newWSGIDirectoryConfig(apr_pool_t *p)
object->script_reloading = -1;
object->error_override = -1;
object->chunked_request = -1;
+ object->map_head_to_get = -1;
object->enable_sendfile = -1;
@@ -325,6 +332,11 @@ static void *wsgi_merge_dir_config(apr_pool_t *p, void *base_conf,
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->enable_sendfile != -1)
config->enable_sendfile = child->enable_sendfile;
else
@@ -383,6 +395,7 @@ typedef struct {
int script_reloading;
int error_override;
int chunked_request;
+ int map_head_to_get;
int enable_sendfile;
@@ -732,6 +745,14 @@ static WSGIRequestConfig *wsgi_create_req_config(apr_pool_t *p, request_rec *r)
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->enable_sendfile = dconfig->enable_sendfile;
if (config->enable_sendfile < 0) {
@@ -4739,6 +4760,40 @@ static const char *wsgi_set_chunked_request(cmd_parms *cmd, void *mconfig,
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_enable_sendfile(cmd_parms *cmd, void *mconfig,
const char *f)
{
@@ -5203,6 +5258,8 @@ static void wsgi_log_script_error(request_rec *r, const char *e, const char *n)
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", message);
}
+static void wsgi_drop_invalid_headers(request_rec *r);
+
static void wsgi_build_environment(request_rec *r)
{
WSGIRequestConfig *config = NULL;
@@ -5218,7 +5275,21 @@ static void wsgi_build_environment(request_rec *r)
config = (WSGIRequestConfig *)ap_get_module_config(r->request_config,
&wsgi_module);
- /* Populate environment with standard CGI variables. */
+ /*
+ * 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.
+ */
+
+ wsgi_drop_invalid_headers(r);
ap_add_cgi_vars(r);
ap_add_common_vars(r);
@@ -5237,11 +5308,24 @@ static void wsgi_build_environment(request_rec *r)
* 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 (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");
+ 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");
+ }
/* Determine whether connection uses HTTPS protocol. */
@@ -6907,6 +6991,17 @@ static void wsgi_signal_handler(int signum)
}
}
+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)
@@ -7229,7 +7324,7 @@ static int wsgi_setup_access(WSGIDaemonProcess *daemon)
sleep(20);
- exit(-1);
+ wsgi_exit_daemon_process(-1);
}
/*
@@ -7895,7 +7990,7 @@ static void *wsgi_reaper_thread(apr_thread_t *thd, void *data)
"mod_wsgi (pid=%d): Aborting process '%s'.",
getpid(), daemon->group->name);
- exit(-1);
+ wsgi_exit_daemon_process(-1);
return NULL;
}
@@ -8612,7 +8707,7 @@ static int wsgi_start_process(apr_pool_t *p, WSGIDaemonProcess *daemon)
sleep(20);
- exit(-1);
+ wsgi_exit_daemon_process(-1);
}
/* Reinitialise accept mutex in daemon process. */
@@ -8631,7 +8726,7 @@ static int wsgi_start_process(apr_pool_t *p, WSGIDaemonProcess *daemon)
sleep(20);
- exit(-1);
+ wsgi_exit_daemon_process(-1);
}
}
@@ -8740,7 +8835,7 @@ static int wsgi_start_process(apr_pool_t *p, WSGIDaemonProcess *daemon)
sleep(20);
- exit(-1);
+ wsgi_exit_daemon_process(-1);
}
wsgi_daemon_shutdown = 0;
@@ -9076,7 +9171,7 @@ static int wsgi_start_process(apr_pool_t *p, WSGIDaemonProcess *daemon)
/* Exit the daemon process when being shutdown. */
- exit(-1);
+ wsgi_exit_daemon_process(0);
}
apr_pool_note_subprocess(p, &daemon->process, APR_KILL_AFTER_TIMEOUT);
@@ -11421,6 +11516,71 @@ static char *wsgi_original_uri(request_rec *r)
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) && 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 char *wsgi_http2env(apr_pool_t *a, const char *w)
{
char *res = (char *)apr_palloc(a, sizeof("HTTP_") + strlen(w));
@@ -11434,12 +11594,14 @@ static char *wsgi_http2env(apr_pool_t *a, const char *w)
*cp++ = '_';
while ((c = *w++) != 0) {
- if (!apr_isalnum(c)) {
- *cp++ = '_';
- }
- else {
+ if (apr_isalnum(c)) {
*cp++ = apr_toupper(c);
}
+ else if (c == '-') {
+ *cp++ = '_';
+ }
+ else
+ return NULL;
}
*cp = 0;
@@ -11529,15 +11691,22 @@ static PyObject *Auth_environ(AuthObject *self, const char *group)
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);
+ object = PyUnicode_DecodeLatin1(hdrs[i].val,
+ strlen(hdrs[i].val), NULL);
#else
- object = PyString_FromString(hdrs[i].val);
+ object = PyString_FromString(hdrs[i].val);
#endif
- PyDict_SetItemString(vars, wsgi_http2env(r->pool, hdrs[i].key),
- object);
- Py_DECREF(object);
+
+ PyDict_SetItemString(vars, header, object);
+
+ Py_DECREF(object);
+ }
+ }
}
}
@@ -12134,10 +12303,31 @@ static authn_status wsgi_check_password(request_rec *r, const char *user,
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 "
- "or None");
+ "None or user name as string");
}
Py_DECREF(result);
@@ -13229,6 +13419,11 @@ static authz_status wsgi_check_authorization(request_rec *r,
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) {
@@ -13311,7 +13506,11 @@ static int wsgi_hook_auth_checker(request_rec *r)
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) {
@@ -13510,6 +13709,8 @@ static const command_rec wsgi_commands[] =
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."),
#ifndef WIN32
AP_INIT_TAKE1("WSGIEnableSendfile", wsgi_set_enable_sendfile,
diff --git a/src/server/wsgi_interp.c b/src/server/wsgi_interp.c
index 98f011c..6d8e765 100644
--- a/src/server/wsgi_interp.c
+++ b/src/server/wsgi_interp.c
@@ -907,29 +907,30 @@ InterpreterObject *newInterpreterObject(const char *name)
/*
* If running in daemon mode and a home directory was set then
- * insert an empty string at the start of the Python module search
- * path so the current working directory will be searched. This
- * makes things similar to when using the Python interpreter on the
- * command line. If the current working directory changes then where
- * it looks follows, so doesn't always look in home.
+ * insert the home directory at the start of the Python module
+ * search path. This makes things similar to when using the Python
+ * interpreter on the command line with a script.
*/
#if defined(MOD_WSGI_WITH_DAEMONS)
if (wsgi_daemon_process && wsgi_daemon_process->group->home) {
PyObject *path = NULL;
+ const char *home = wsgi_daemon_process->group->home;
path = PySys_GetObject("path");
if (module && path) {
- PyObject *empty;
+ PyObject *item;
#if PY_MAJOR_VERSION >= 3
- empty = PyUnicode_DecodeLatin1("", strlen(""), NULL);
+ item = PyUnicode_Decode(home, strlen(home),
+ Py_FileSystemDefaultEncoding,
+ "surrogateescape");
#else
- empty = PyString_FromString("");
+ item = PyString_FromString(home);
#endif
- PyList_Insert(path, 0, empty);
- Py_DECREF(empty);
+ PyList_Insert(path, 0, item);
+ Py_DECREF(item);
}
Py_XDECREF(module);
diff --git a/src/server/wsgi_server.h b/src/server/wsgi_server.h
index c58b980..a9fd74d 100644
--- a/src/server/wsgi_server.h
+++ b/src/server/wsgi_server.h
@@ -102,6 +102,7 @@ typedef struct {
int script_reloading;
int error_override;
int chunked_request;
+ int map_head_to_get;
int enable_sendfile;
diff --git a/src/server/wsgi_version.h b/src/server/wsgi_version.h
index ff2ae73..2ec827a 100644
--- a/src/server/wsgi_version.h
+++ b/src/server/wsgi_version.h
@@ -24,9 +24,9 @@
/* Module version information. */
#define MOD_WSGI_MAJORVERSION_NUMBER 4
-#define MOD_WSGI_MINORVERSION_NUMBER 2
-#define MOD_WSGI_MICROVERSION_NUMBER 8
-#define MOD_WSGI_VERSION_STRING "4.2.8"
+#define MOD_WSGI_MINORVERSION_NUMBER 3
+#define MOD_WSGI_MICROVERSION_NUMBER 0
+#define MOD_WSGI_VERSION_STRING "4.3.0"
/* ------------------------------------------------------------------------- */
diff --git a/tests/auth.wsgi b/tests/auth.wsgi
new file mode 100644
index 0000000..37a90cb
--- /dev/null
+++ b/tests/auth.wsgi
@@ -0,0 +1,29 @@
+def check_password(environ, user, password):
+ print('USER', user, environ['REQUEST_URI'])
+ if user == 'spy':
+ if password == 'secret':
+ return True
+ return False
+ elif user == 'witness':
+ if password == 'secret':
+ return 'protected'
+ return False
+ return None
+
+import md5
+
+def get_realm_hash(environ, user, realm):
+ print('USER', user, environ['REQUEST_URI'])
+ if user == 'spy':
+ value = md5.new()
+ # user:realm:password
+ value.update('%s:%s:%s' % (user, realm, 'secret'))
+ hash = value.hexdigest()
+ return hash
+ return None
+
+def groups_for_user(environ, user):
+ print('GROUP', user, environ['REQUEST_URI'])
+ if user == 'spy':
+ return ['secret-agents']
+ return ['']
diff --git a/tests/environ.wsgi b/tests/environ.wsgi
index 6ee1723..c26eae7 100644
--- a/tests/environ.wsgi
+++ b/tests/environ.wsgi
@@ -13,7 +13,7 @@ import apache
def application(environ, start_response):
headers = []
- headers.append(('Content-Type', 'text/plain'))
+ headers.append(('Content-Type', 'text/plain; charset="UTF-8"'))
write = start_response('200 OK', headers)
input = environ['wsgi.input']
@@ -24,6 +24,7 @@ def application(environ, start_response):
print('GID: %s' % os.getgid(), file=output)
print(file=output)
+ print('python.version: %r' % (sys.version,), file=output)
print('apache.version: %r' % (apache.version,), file=output)
print('mod_wsgi.version: %r' % (mod_wsgi.version,), file=output)
print(file=output)