From: Graham Leggett Date: Fri, 4 Apr 2008 15:58:15 +0000 (+0000) Subject: mod_session: Add a generic session interface to unify the different X-Git-Tag: 2.3.0~818 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=2b7c89aab4ade1c2c82cb2affccc45e65a342026;p=thirdparty%2Fapache%2Fhttpd.git mod_session: Add a generic session interface to unify the different attempts at saving persistent sessions across requests. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@644746 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index 516cf225b65..2fd656d3496 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,10 @@ Changes with Apache 2.3.0 [ When backported to 2.2.x, remove entry from this file ] + *) mod_session: Add a generic session interface to unify the different + attempts at saving persistent sessions across requests. + [Graham Leggett] + *) core, authn/z: Avoid calling access control hooks for internal requests with configurations which match those of initial request. Revert to original behaviour (call access control hooks for internal requests diff --git a/configure.in b/configure.in index 65bad7c0244..10450911b45 100644 --- a/configure.in +++ b/configure.in @@ -204,7 +204,7 @@ if test "$abs_builddir" != "$abs_srcdir"; then APR_ADDTO(INCLUDES, [-I\$(top_builddir)/include]) fi -APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/os/\$(OS_DIR) -I\$(top_srcdir)/server/mpm/\$(MPM_SUBDIR_NAME) -I\$(top_srcdir)/modules/http -I\$(top_srcdir)/modules/filters -I\$(top_srcdir)/modules/proxy -I\$(top_srcdir)/include -I\$(top_srcdir)/modules/generators -I\$(top_srcdir)/modules/mappers -I\$(top_srcdir)/modules/database]) +APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/os/\$(OS_DIR) -I\$(top_srcdir)/server/mpm/\$(MPM_SUBDIR_NAME) -I\$(top_srcdir)/modules/http -I\$(top_srcdir)/modules/filters -I\$(top_srcdir)/modules/proxy -I\$(top_srcdir)/modules/session -I\$(top_srcdir)/include -I\$(top_srcdir)/modules/generators -I\$(top_srcdir)/modules/mappers -I\$(top_srcdir)/modules/database]) # apr/apr-util --includes may pick up system paths for dependent # libraries, so ensure these are later in INCLUDES than local source diff --git a/docs/manual/mod/mod_session.xml b/docs/manual/mod/mod_session.xml new file mode 100644 index 00000000000..3aa9d3da8f9 --- /dev/null +++ b/docs/manual/mod/mod_session.xml @@ -0,0 +1,393 @@ + + + + + + + + + +mod_session +Session support +Extension +mod_session.c +session_module + + + Warning +

The session modules make use of HTTP cookies, and as such can fall + victim to Cross Site Scripting attacks, or expose potentially private + information to clients. Please ensure that the relevant risks have + been taken into account before enabling the session functionality on + your server.

+
+ +

This module provides support for a server wide per user session + interface. Sessions can be used for keeping track of whether a user + has been logged in, or for other per user information that should + be kept available across requests.

+ +

Sessions may be stored on the server, or may be stored on the + browser. Sessions may also be optionally encrypted for added security. + These features are divided into several modules in addition to + mod_session: mod_session_crypto, + mod_session_cookie and mod_session_dbd. + Depending on the server requirements, load the appropriate modules + into the server (either statically at compile time or dynamically + via the LoalModule directive).

+ +

Sessions may be manipulated from other modules that depend on the + session, or the session may be read from and written to using + environment variables and HTTP headers, as appropriate.

+ +
+mod_session_cookie +mod_session_crypto +mod_session_dbd + +
What is a session? +

At the core of the session interface is a table of key and value pairs + that are made accessible across browser requests.

+ +

These pairs can be set any valid set of strings, as needed by the + application making use of the session.

+ +
+
Who can use a session? +

The session interface is primarily developed for the use by other + server modules, such as mod_auth_form, however CGI + based applications can optionally be granted access to the contents + of the session via the HTTP_SESSION environment variable. Sessions + have the option to be modified and/or updated by inserting an HTTP + response header containing the new session parameters.

+ +
+
Keeping sessions on the server +

Apache can be configured to keep track of per user sessions stored + on a particular server or group of servers. This functionality is + similar to the sessions available in typical application servers.

+ +

If configured, sessions are tracked through the use of a session ID that + is stored inside a cookie, or extracted from the parameters embedded + within the URL query string, as found in a typical GET request.

+ +

As the contents of the session are stored exclusively on the server, + there is an expectation of privacy of the contents of the session. This + does have performance and resource implications should a large number + of sessions be present, or where a large number of webservers have to + share sessions with one another.

+ +

The mod_session_dbd module allows the storage of user + sessions within a SQL database via mod_dbd.

+ +
+ +
Keeping sessions on the browser +

Where keeping track of a session on a server is too resource + intensive or inconvenient, the option exists to store the contents + of the session within a cookie on the client browser instead.

+ +

This has the advantage that minimal resources are required on the + server to keep track of sessions, and multiple servers within a server + farm have no need to share session information.

+ +

The contents of the session however are exposed to the client, with a + corresponding risk of a loss of privacy. The + mod_session_crypto module can be configured to encrypt the + contents of the session before writing the session to the client.

+ +

The mod_session_cookie allows the storage of user + sessions on the browser within an HTTP cookie.

+ +
+ +
Basic Examples + +

Creating a session is as simple as turning the session on, and deciding + where the session will be stored. In this example, the session will be + stored on the browser, in a cookie called session.

+ + Browser based session + Session On
+ SessionCookieName session path=/
+
+ +

The session is not useful unless it can be written to or read from. The + following example shows how values can be injected into the session through + the use of a predetermined HTTP response header called + X-Replace-Session.

+ + Writing to a session + Session On
+ SessionCookieName session path=/
+ SessionHeader X-Replace-Session
+
+ +

The header should contain name value pairs expressed in the same format + as a query string in a URL, as in the example below. Setting a key to the + empty string has the effect of removing that key from the session.

+ + CGI to write to a session + #!/bin/bash
+ echo "Content-Type: text/plain"
+ echo "X-Replace-Session: key1=foo&key2=&key3=bar"
+ echo
+ env
+
+ +

If configured, the session can be read back from the HTTP_SESSION + environment variable. By default, the session is kept private, so this + has to be explicitly turned on with the + SessionEnv directive.

+ + Read from a session + Session On
+ SessionEnv On
+ SessionCookieName session path=/
+ SessionHeader X-Replace-Session
+
+ +

Once read, the CGI variable HTTP_SESSION should contain + the value key1=foo&key3=bar.

+ +
+
Session Privacy + +

Using the "show cookies" feature of your browser, you would have seen + a clear text representation of the session. This could potentially be a + problem should the end user need to be kept unaware of the contents of + the session, or where a third party could gain unauthorised access to the + data within the session.

+ +

The contents of the session can be optionally encrypted before being + placed on the browser using the mod_session_crypto + module.

+ + Browser based encrypted session + Session On
+ SessionCryptoPassphrase secret
+ SessionCookieName session path=/
+
+ +

The session will be automatically decrypted on load, and encrypted on + save by Apache, the underlying application using the session need have + no knowledge that encryption is taking place.

+ +

Sessions stored on the server rather than on the browser can also be + encrypted as needed, offering privacy where potentially sensitive + information is being shared between webservers in a server farm using + the mod_session_dbd module.

+ +
+
Cookie Privacy + +

The HTTP cookie mechanism also offers privacy features, such as the + ability to restrict cookie transport to SSL protected pages only, or + to prevent browser based javascript from gaining access to the contents + of the cookie.

+ + Warning +

Some of the HTTP cookie privacy features are either non standard, or + are not implemented consistently across browsers. The session modules + allow you to set cookie parameters, but it makes no guarantee that privacy + will be respected by the browser. If security is a concern, use the + mod_session_crypto to encrypt the contents of the session, + or store the session on the server using the mod_session_dbd + module.

+
+ +

Standard cookie parameters can be specified after the name of the cookie, + as in the example below.

+ + Setting cookie parameters + Session On
+ SessionCryptoPassphrase secret
+ SessionCookieName session path=/private;domain=example.com;httponly;secure;
+
+ +

In cases where the Apache server forms the frontend for backend origin servers, + it is possible to have the session cookies removed from the incoming HTTP headers using + the SessionCookieRemove directive. + This keeps the contents of the session cookies from becoming accessible from the + backend server. +

+ +
+
Session Support for Authentication + +

As is possible within many application servers, authentication modules can use + a session for storing the username and password after login. The + mod_auth_form saves the user's login name and password within + the session.

+ + Form based authentication + Session On
+ SessionCryptoPassphrase secret
+ SessionCookieName session path=/
+ AuthFormProvider file
+ AuthUserFile conf/passwd
+ AuthType form
+ AuthName realm
+ ...
+
+ +

See the mod_auth_form module for documentation and complete + examples.

+ +
+ + +Session +Enables a session for the current directory or location +Session On|Off +Session Off +directory + +Available in Apache 2.3.0 and later + + +

The Session directive enables a session for the + directory or location container. Further directives control where the + session will be stored and how privacy is maintained.

+
+
+ + +SessionMaxAge +Define a maximum age in seconds for a session +SessionMaxAge maxage +SessionMaxAge 0 +directory + +Available in Apache 2.3.0 and later + + +

The SessionMaxAge directive defines a time limit + for which a session will remain valid. When a session is saved, this time + limit is reset and an existing session can be continued. If a session + becomes older than this limit without a request to the server to refresh + the session, the session will time out and be removed. Where a session is + used to stored user login details, this has the effect of logging the user + out automatically after the given time.

+ +

Setting the maxage to zero disables session expiry.

+
+
+ + +SessionEnv +Control whether the contents of the session are written to the +HTTP_SESSION environment variable +SessionEnv On|Off +SessionEnv Off +directory + +Available in Apache 2.3.0 and later + + +

If set to On, the SessionEnv directive + causes the contents of the session to be written to a CGI environment + variable called HTTP_SESSION.

+ +

The string is written in the URL query format, for example:

+ + + key1=foo&key3=bar + + +
+
+ + +SessionHeader +Import session updates from a given HTTP response header +SessionHeader header +none +directory + +Available in Apache 2.3.0 and later + + +

The SessionHeader directive defines the name of an + HTTP response header which, if present, will be parsed and written to the + current session.

+ +

The header value is expected to be in the URL query format, for example:

+ + + key1=foo&key2=&key3=bar + + +

Where a key is set to the empty string, that key will be removed from the + session.

+ +
+
+ + +SessionInclude +Define URL prefixes for which a session is valid +SessionInclude path +all URLs +directory + +Available in Apache 2.3.0 and later + + +

The SessionInclude directive allows sessions to + be made valid for specific URL prefixes only. This can be used to make a + website more efficient, by targeting a more precise URL space for which + a session should be maintained. By default, all URLs within the directory + or location are included in the session.

+ + Warning +

This directive has a similar purpose to the path attribute + in HTTP cookies, but should not be confused with this attribute. This + directive does not set the path attribute, which must be + configured separately.

+
+
+ + +SessionExclude +Define URL prefixes for which a session is ignored +SessionExclude path +none +directory + +Available in Apache 2.3.0 and later + + +

The SessionExclude directive allows sessions to + be disabled specific URL prefixes only. This can be used to make a + website more efficient, by targeting a more precise URL space for which + a session should be maintained. By default, all URLs within the directory + or location are included in the session. The + SessionExclude directive takes + precedence over the + SessionInclude directive.

+ + Warning +

This directive has a similar purpose to the path attribute + in HTTP cookies, but should not be confused with this attribute. This + directive does not set the path attribute, which must be + configured separately.

+
+
+ +
diff --git a/include/httpd.h b/include/httpd.h index 332521446f3..0b631070f92 100644 --- a/include/httpd.h +++ b/include/httpd.h @@ -1434,6 +1434,13 @@ AP_DECLARE(int) ap_find_last_token(apr_pool_t *p, const char *line, const char * */ AP_DECLARE(int) ap_is_url(const char *u); +/** + * Unescape a string + * @param url The string to unescape + * @return 0 on success, non-zero otherwise + */ +AP_DECLARE(int) ap_unescape_all(char *url); + /** * Unescape a URL * @param url The url to unescape @@ -1469,6 +1476,14 @@ AP_DECLARE(void) ap_getparents(char *name); */ AP_DECLARE(char *) ap_escape_path_segment(apr_pool_t *p, const char *s); +/** + * Escape a path segment, as defined in RFC 1808, to a preallocated buffer. + * @param c The preallocated buffer to write to + * @param s The path to convert + * @return The converted URL (c) + */ +AP_DECLARE(char *) ap_escape_path_segment_b(char *c, const char *s); + /** * convert an OS path to a URL in an OS dependant way. * @param p The pool to allocate from diff --git a/modules/session/Makefile.in b/modules/session/Makefile.in new file mode 100644 index 00000000000..fb9dacfe968 --- /dev/null +++ b/modules/session/Makefile.in @@ -0,0 +1,4 @@ +# a modules Makefile has no explicit targets -- they will be defined by +# whatever modules are enabled. just grab special.mk to deal with this. +include $(top_srcdir)/build/special.mk + diff --git a/modules/session/config.m4 b/modules/session/config.m4 new file mode 100644 index 00000000000..f346bfda71c --- /dev/null +++ b/modules/session/config.m4 @@ -0,0 +1,19 @@ +dnl modules enabled in this directory by default + +dnl Session + +dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]]) + +APACHE_MODPATH_INIT(session) + +dnl Session modules; modules that are capable of storing key value pairs in +dnl various places, such as databases, LDAP, or cookies. +dnl +APACHE_MODULE(session, session module, , , most) +dnl APACHE_MODULE(session_cookie, session cookie module, , , most) +dnl APACHE_MODULE(session_crypto, session crypto module, , , most) +dnl APACHE_MODULE(session_dbd, session dbd module, , , most) +dnl APACHE_MODULE(session_ldap, session ldap module, , , most) + +APACHE_MODPATH_FINISH + diff --git a/modules/session/mod_session.c b/modules/session/mod_session.c new file mode 100644 index 00000000000..7e7867a389b --- /dev/null +++ b/modules/session/mod_session.c @@ -0,0 +1,617 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define CORE_PRIVATE + +#include "mod_session.h" +#include "apr_lib.h" +#include "apr_strings.h" +#include "util_filter.h" +#include "http_log.h" +#include "http_request.h" +#include "http_protocol.h" + +#define SESSION_PREFIX "mod_session: " +#define SESSION_EXPIRY "expiry" +#define HTTP_SESSION "HTTP_SESSION" + +APR_HOOK_STRUCT( + APR_HOOK_LINK(session_load) + APR_HOOK_LINK(session_save) + APR_HOOK_LINK(session_encode) + APR_HOOK_LINK(session_decode) +) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, session_load, + (request_rec * r, session_rec ** z), (r, z), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, session_save, + (request_rec * r, session_rec * z), (r, z), DECLINED) +AP_IMPLEMENT_HOOK_RUN_ALL(int, session_encode, + (request_rec * r, session_rec * z), (r, z), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_ALL(int, session_decode, + (request_rec * r, session_rec * z), (r, z), OK, DECLINED) +/** + * Should the session be included within this URL. + * + * This function tests whether a session is valid for this URL. It uses the + * include and exclude arrays to determine whether they should be included. + */ + static int session_included(request_rec * r, session_dir_conf * conf) +{ + + const char **includes = (const char **) conf->includes->elts; + const char **excludes = (const char **) conf->excludes->elts; + int included = 1; /* defaults to included */ + int i; + + if (conf->includes->nelts) { + included = 0; + for (i = 0; !included && i < conf->includes->nelts; i++) { + const char *include = includes[i]; + if (strncmp(r->parsed_uri.path, include, strlen(include))) { + included = 1; + } + } + } + + if (conf->excludes->nelts) { + for (i = 0; included && i < conf->includes->nelts; i++) { + const char *exclude = excludes[i]; + if (strncmp(r->parsed_uri.path, exclude, strlen(exclude))) { + included = 0; + } + } + } + + return included; +} + +/** + * Get a particular value from the session. + * @param r The current request. + * @param z The current session. If this value is NULL, the session will be + * looked up in the request, created if necessary, and saved to the request + * notes. + * @param key The key to get. + * @param value The buffer to write the value to. + */ +AP_DECLARE(void) ap_session_get(request_rec * r, session_rec * z, const char *key, const char **value) +{ + if (!z) { + ap_session_load(r, &z); + } + *value = apr_table_get(z->entries, key); +} + +/** + * Set a particular value to the session. + * + * Using this method ensures that the dirty flag is set correctly, so that + * the session can be saved efficiently. + * @param r The current request. + * @param z The current session. If this value is NULL, the session will be + * looked up in the request, created if necessary, and saved to the request + * notes. + * @param key The key to set. The existing key value will be replaced. + * @param value The value to set. + */ +AP_DECLARE(void) ap_session_set(request_rec * r, session_rec * z, + const char *key, const char *value) +{ + if (!z) { + ap_session_load(r, &z); + } + if (value) { + apr_table_set(z->entries, key, value); + } + else { + apr_table_unset(z->entries, key); + } + z->dirty = 1; +} + +/** + * Load the session. + * + * If the session doesn't exist, a blank one will be created. + * + * @param r The request + * @param z A pointer to where the session will be written. + */ +AP_DECLARE(int) ap_session_load(request_rec * r, session_rec ** z) +{ + + session_dir_conf *dconf = ap_get_module_config(r->per_dir_config, + &session_module); + apr_time_t now; + session_rec *zz = NULL; + + /* is the session enabled? */ + if (!dconf->enabled) { + return APR_SUCCESS; + } + + /* should the session be loaded at all? */ + if (!session_included(r, dconf)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, SESSION_PREFIX + "excluded by configuration for: %s", r->uri); + return APR_SUCCESS; + } + + /* load the session from the session hook */ + int rv = ap_run_session_load(r, &zz); + if (DECLINED == rv) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, SESSION_PREFIX + "session is enabled but no session modules have been configured, " + "session not loaded: %s", r->uri); + return APR_EGENERAL; + } + else if (OK != rv) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, SESSION_PREFIX + "error while loading the session, " + "session not loaded: %s", r->uri); + return rv; + } + + /* found a session that hasn't expired? */ + now = apr_time_now(); + if (!zz || (zz->expiry && zz->expiry < now)) { + + /* no luck, create a blank session */ + zz = (session_rec *) apr_pcalloc(r->pool, sizeof(session_rec)); + zz->pool = r->pool; + zz->entries = apr_table_make(zz->pool, 10); + zz->uuid = (apr_uuid_t *) apr_pcalloc(zz->pool, sizeof(apr_uuid_t)); + apr_uuid_get(zz->uuid); + + } + else { + rv = ap_run_session_decode(r, zz); + if (OK != rv) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, SESSION_PREFIX + "error while decoding the session, " + "session not loaded: %s", r->uri); + return rv; + } + } + + /* make sure the expiry is set, if present */ + if (!zz->expiry && dconf->maxage) { + zz->expiry = now + dconf->maxage * APR_USEC_PER_SEC; + zz->maxage = dconf->maxage; + } + + *z = zz; + + return APR_SUCCESS; + +} + +/** + * Save the session. + * + * In most implementations the session is only saved if the dirty flag is + * true. This prevents the session being saved unnecessarily. + * + * @param r The request + * @param z A pointer to where the session will be written. + */ +AP_DECLARE(int) ap_session_save(request_rec * r, session_rec * z) +{ + if (z) { + apr_time_t now = apr_time_now(); + int rv = 0; + + /* sanity checks, should we try save at all? */ + if (z->written) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, SESSION_PREFIX + "attempt made to save the session twice, " + "session not saved: %s", r->uri); + return APR_EGENERAL; + } + if (z->expiry && z->expiry < now) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, SESSION_PREFIX + "attempt made to save a session when the session had already expired, " + "session not saved: %s", r->uri); + return APR_EGENERAL; + } + + /* encode the session */ + rv = ap_run_session_encode(r, z); + if (OK != rv) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, SESSION_PREFIX + "error while encoding the session, " + "session not saved: %s", r->uri); + return rv; + } + + /* try the save */ + rv = ap_run_session_save(r, z); + if (DECLINED == rv) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, SESSION_PREFIX + "session is enabled but no session modules have been configured, " + "session not saved: %s", r->uri); + return APR_EGENERAL; + } + else if (OK != rv) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, SESSION_PREFIX + "error while saving the session, " + "session not saved: %s", r->uri); + return rv; + } + else { + z->written = 1; + } + } + + return APR_SUCCESS; + +} + +static int identity_count(int *count, const char *key, const char *val) +{ + *count += strlen(key) * 3 + strlen(val) * 3 + 1; + return 1; +} + +static int identity_concat(char *buffer, const char *key, const char *val) +{ + char *slider = buffer; + int length = strlen(slider); + slider += length; + if (length) { + *slider = '&'; + slider++; + } + ap_escape_path_segment_b(slider, key); + slider += strlen(slider); + *slider = '='; + slider++; + ap_escape_path_segment_b(slider, val); + return 1; +} + +/** + * Default identity encoding for the session. + * + * By default, the name value pairs in the session are URLEncoded, separated + * by equals, and then in turn separated by ampersand, in the format of an + * html form. + * + * This was chosen to make it easy for external code to unpack a session, + * should there be a need to do so. + * + * @param r The request pointer. + * @param z A pointer to where the session will be written. + */ +AP_DECLARE(int) ap_session_identity_encode(request_rec * r, session_rec * z) +{ + + char *buffer = NULL; + int length = 0; + if (z->expiry) { + char *expiry = apr_psprintf(r->pool, "%" APR_INT64_T_FMT, z->expiry); + apr_table_set(z->entries, SESSION_EXPIRY, expiry); + } + apr_table_do((int (*) (void *, const char *, const char *)) + identity_count, &length, z->entries, NULL);; + buffer = apr_pcalloc(r->pool, length + 1); + apr_table_do((int (*) (void *, const char *, const char *)) + identity_concat, buffer, z->entries, NULL); + z->encoded = buffer; + return OK; + +} + +/** + * Default identity decoding for the session. + * + * By default, the name value pairs in the session are URLEncoded, separated + * by equals, and then in turn separated by ampersand, in the format of an + * html form. + * + * This was chosen to make it easy for external code to unpack a session, + * should there be a need to do so. + * + * This function reverses that process, and populates the session table. + * + * Name / value pairs that are not encoded properly are ignored. + * + * @param r The request pointer. + * @param z A pointer to where the session will be written. + */ +AP_DECLARE(int) ap_session_identity_decode(request_rec * r, session_rec * z) +{ + + char *last = NULL; + char *encoded, *pair; + const char *sep = "&"; + + /* sanity check - anything to decode? */ + if (!z->encoded) { + return OK; + } + + /* decode what we have */ + encoded = apr_pstrcat(r->pool, z->encoded, NULL); + pair = apr_strtok(encoded, sep, &last); + while (pair && pair[0]) { + char *plast = NULL; + const char *psep = "="; + char *key = apr_strtok(pair, psep, &plast); + char *val = apr_strtok(NULL, psep, &plast); + if (key && *key) { + if (!val || !*val) { + apr_table_unset(z->entries, key); + } + if (!ap_unescape_all(key) && !ap_unescape_all(val)) { + if (!strcmp(SESSION_EXPIRY, key)) { + z->expiry = (apr_time_t) apr_atoi64(val); + } + else { + apr_table_set(z->entries, key, val); + } + } + } + pair = apr_strtok(NULL, sep, &last); + } + z->encoded = NULL; + return OK; + +} + +/** + * Ensure any changes to the session are committed. + * + * This is done in an output filter so that our options for where to + * store the session can include storing the session within a cookie: + * As an HTTP header, the cookie must be set before the output is + * written, but after the handler is run. + * + * NOTE: It is possible for internal redirects to cause more than one + * request to be present, and each request might have a session + * defined. We need to go through each session in turn, and save each + * one. + * + * The same session might appear in more than one request. The first + * attempt to save the session will be called + */ +static apr_status_t ap_session_output_filter(ap_filter_t * f, + apr_bucket_brigade * in) +{ + + /* save all the sessions in all the requests */ + request_rec *r = f->r->main; + if (!r) { + r = f->r; + } + while (r) { + session_rec *z = NULL; + session_dir_conf *conf = ap_get_module_config(r->per_dir_config, + &session_module); + + /* load the session, or create one if necessary */ + ap_session_load(r, &z); + if (!z || z->written) { + r = r->next; + continue; + } + + /* if a header was specified, insert the new values from the header */ + if (conf->header_set) { + const char *override = apr_table_get(r->err_headers_out, conf->header); + if (!override) { + override = apr_table_get(r->headers_out, conf->header); + } + if (override) { + z->encoded = override; + ap_session_identity_decode(r, z); + } + } + + /* save away the session, and we're done */ + ap_session_save(r, z); + + r = r->next; + } + + /* remove ourselves from the filter chain */ + ap_remove_output_filter(f); + + /* send the data up the stack */ + return ap_pass_brigade(f->next, in); + +} + +/** + * Insert the output filter. + */ +static void ap_session_insert_output_filter(request_rec * r) +{ + ap_add_output_filter("MOD_SESSION_OUT", NULL, r, r->connection); +} + +/** + * Fixups hook. + * + * Load the session within a fixup - this ensures that the session is + * properly loaded prior to the handler being called. + * + * The fixup is also responsible for injecting the session into the CGI + * environment, should the admin have configured it so. + * + * @param r The request + */ +AP_DECLARE(int) ap_session_fixups(request_rec * r) +{ + session_dir_conf *conf = ap_get_module_config(r->per_dir_config, + &session_module); + + session_rec *z = NULL; + ap_session_load(r, &z); + + if (conf->env) { + ap_session_identity_encode(r, z); + if (z->encoded) { + apr_table_set(r->subprocess_env, HTTP_SESSION, z->encoded); + z->encoded = NULL; + } + } + + return OK; + +} + + +static void *create_session_dir_config(apr_pool_t * p, char *dummy) +{ + session_dir_conf *new = + (session_dir_conf *) apr_pcalloc(p, sizeof(session_dir_conf)); + + new->includes = apr_array_make(p, 10, sizeof(const char **)); + new->excludes = apr_array_make(p, 10, sizeof(const char **)); + + return (void *) new; +} + +static void *merge_session_dir_config(apr_pool_t * p, void *basev, void *addv) +{ + session_dir_conf *new = (session_dir_conf *) apr_pcalloc(p, sizeof(session_dir_conf)); + session_dir_conf *add = (session_dir_conf *) addv; + session_dir_conf *base = (session_dir_conf *) basev; + + new->enabled = (add->enabled_set == 0) ? base->enabled : add->enabled; + new->enabled_set = add->enabled_set || base->enabled_set; + new->maxage = (add->maxage_set == 0) ? base->maxage : add->maxage; + new->maxage_set = add->maxage_set || base->maxage_set; + new->header = (add->header_set == 0) ? base->header : add->header; + new->header_set = add->header_set || base->header_set; + new->env = (add->env_set == 0) ? base->env : add->env; + new->env_set = add->env_set || base->env_set; + new->includes = apr_array_append(p, base->includes, add->includes); + new->excludes = apr_array_append(p, base->excludes, add->excludes); + + return new; +} + + +static const char * + set_session_enable(cmd_parms * parms, void *dconf, int flag) +{ + session_dir_conf *conf = dconf; + + conf->enabled = flag; + conf->enabled_set = 1; + + return NULL; +} + +static const char * + set_session_maxage(cmd_parms * parms, void *dconf, const char *arg) +{ + session_dir_conf *conf = dconf; + + conf->maxage = atol(arg); + conf->maxage_set = 1; + + return NULL; +} + +static const char * + set_session_header(cmd_parms * parms, void *dconf, const char *arg) +{ + session_dir_conf *conf = dconf; + + conf->header = arg; + conf->header_set = 1; + + return NULL; +} + +static const char * + set_session_env(cmd_parms * parms, void *dconf, int flag) +{ + session_dir_conf *conf = dconf; + + conf->env = flag; + conf->env_set = 1; + + return NULL; +} + +static const char *add_session_include(cmd_parms * cmd, void *dconf, const char *f) +{ + session_dir_conf *conf = dconf; + + const char **new = apr_array_push(conf->includes); + *new = f; + + return NULL; +} + +static const char *add_session_exclude(cmd_parms * cmd, void *dconf, const char *f) +{ + session_dir_conf *conf = dconf; + + const char **new = apr_array_push(conf->excludes); + *new = f; + + return NULL; +} + + +static const command_rec session_cmds[] = +{ + AP_INIT_FLAG("Session", set_session_enable, NULL, OR_AUTHCFG, + "on if a session should be maintained for these URLs"), + AP_INIT_TAKE1("SessionMaxAge", set_session_maxage, NULL, OR_AUTHCFG, + "length of time for which a session should be valid. Zero to disable"), + AP_INIT_TAKE1("SessionHeader", set_session_header, NULL, OR_AUTHCFG, + "output header, if present, whose contents will be injected into the session."), + AP_INIT_FLAG("SessionEnv", set_session_env, NULL, OR_AUTHCFG, + "on if a session should be written to the CGI environment. Defaults to off"), + AP_INIT_TAKE1("SessionInclude", add_session_include, NULL, OR_AUTHCFG, + "URL prefixes to include in the session. Defaults to all URLs"), + AP_INIT_TAKE1("SessionExclude", add_session_exclude, NULL, OR_AUTHCFG, + "URL prefixes to exclude from the session. Defaults to no URLs"), + {NULL} +}; + +static void register_hooks(apr_pool_t * p) +{ + ap_register_output_filter("MOD_SESSION_OUT", ap_session_output_filter, + NULL, AP_FTYPE_CONTENT_SET); + ap_hook_insert_filter(ap_session_insert_output_filter, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_insert_error_filter(ap_session_insert_output_filter, + NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_fixups(ap_session_fixups, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_session_encode(ap_session_identity_encode, NULL, NULL, APR_HOOK_REALLY_FIRST); + ap_hook_session_decode(ap_session_identity_decode, NULL, NULL, APR_HOOK_REALLY_LAST); + APR_REGISTER_OPTIONAL_FN(ap_session_get); + APR_REGISTER_OPTIONAL_FN(ap_session_set); + APR_REGISTER_OPTIONAL_FN(ap_session_load); + APR_REGISTER_OPTIONAL_FN(ap_session_save); +} + +module AP_MODULE_DECLARE_DATA session_module = +{ + STANDARD20_MODULE_STUFF, + create_session_dir_config, /* dir config creater */ + merge_session_dir_config, /* dir merger --- default is to override */ + NULL, /* server config */ + NULL, /* merge server config */ + session_cmds, /* command apr_table_t */ + register_hooks /* register hooks */ +}; diff --git a/modules/session/mod_session.h b/modules/session/mod_session.h new file mode 100644 index 00000000000..54223c2dc35 --- /dev/null +++ b/modules/session/mod_session.h @@ -0,0 +1,206 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MOD_SESSION_H +#define MOD_SESSION_H + +/** + * @file mod_session.h + * @brief Session Module for Apache + * + * @defgroup MOD_SESSION mod_session + * @ingroup APACHE_MODS + * @{ + */ + +#define CORE_PRIVATE + +#include "apr_hooks.h" +#include "apr_optional.h" +#include "apr_tables.h" +#include "apr_uuid.h" +#include "apr_pools.h" +#include "apr_time.h" + +#include "httpd.h" +#include "http_config.h" +#include "ap_config.h" + +#define MOD_SESSION_NOTES_KEY "mod_session_key" + +/** + * Define the name of a username stored in the session, so that modules interested + * in the username can find it in a standard place. + */ +#define MOD_SESSION_USER "user" + +/** + * Define the name of a password stored in the session, so that modules interested + * in the password can find it in a standard place. + */ +#define MOD_SESSION_PW "pw" + +/** + * A session structure. + * + * At the core of the session is a set of name value pairs making up the + * session. + * + * The session might be uniquely identified by an anonymous uuid, or + * a remote_user value, or both. + */ +typedef struct { + apr_pool_t *pool; /* pool to be used for this session */ + apr_uuid_t *uuid; /* anonymous uuid of this particular session */ + const char *remote_user; /* user who owns this particular session */ + apr_table_t *entries; /* key value pairs */ + const char *encoded; /* the encoded version of the key value pairs */ + apr_time_t expiry; /* if > 0, the time of expiry of this session */ + long maxage; /* if > 0, the maxage of the session, from + * which expiry is calculated */ + int dirty; /* dirty flag */ + int cached; /* true if this session was loaded from a + * cache of some kind */ + int written; /* true if this session has already been + * written */ +} session_rec; + +/** + * Structure to carry the per-dir session config. + */ +typedef struct { + int enabled; /* whether the session has been enabled for + * this directory */ + int enabled_set; + long maxage; /* seconds until session expiry */ + int maxage_set; + const char *header; /* header to inject session */ + int header_set; + int env; /* whether the session has been enabled for + * this directory */ + int env_set; + apr_array_header_t *includes; /* URL prefixes to be included. All + * URLs included if empty */ + apr_array_header_t *excludes; /* URL prefixes to be excluded. No + * URLs excluded if empty */ +} session_dir_conf; + +/** + * Get a particular value from the session. + * @param r The current request. + * @param z The current session. If this value is NULL, the session will be + * looked up in the request, created if necessary, and saved to the request + * notes. + * @param key The key to get. + * @param value The buffer to write the value to. + */ +AP_DECLARE(void) ap_session_get(request_rec * r, session_rec * z, const char *key, const char **value); + +/** + * Set a particular value to the session. + * + * Using this method ensures that the dirty flag is set correctly, so that + * the session can be saved efficiently. + * @param r The current request. + * @param z The current session. If this value is NULL, the session will be + * looked up in the request, created if necessary, and saved to the request + * notes. + * @param key The key to set. The existing key value will be replaced. + * @param value The value to set. + */ +AP_DECLARE(void) ap_session_set(request_rec * r, session_rec * z, + const char *key, const char *value); + +/** + * Load the session. + * + * If the session doesn't exist, a blank one will be created. + * + * @param r The request + * @param z A pointer to where the session will be written. + */ +AP_DECLARE(int) ap_session_load(request_rec * r, session_rec ** z); + +/** + * Hook to load the session. + * + * If the session doesn't exist, a blank one will be created. + * + * @param r The request + * @param z A pointer to where the session will be written. + */ +AP_DECLARE_HOOK(int, session_load, (request_rec * r, session_rec ** z)) + +/** + * Save the session. + * + * In most implementations the session is only saved if the dirty flag is + * true. This prevents the session being saved unnecessarily. + * + * @param r The request + * @param z A pointer to where the session will be written. + */ +AP_DECLARE(int) ap_session_save(request_rec * r, session_rec * z); + +/** + * Hook to save the session. + * + * In most implementations the session is only saved if the dirty flag is + * true. This prevents the session being saved unnecessarily. + * + * @param r The request + * @param z A pointer to where the session will be written. + */ +AP_DECLARE_HOOK(int, session_save, (request_rec * r, session_rec * z)) + +/** + * Hook to encode the session. + * + * In the default implementation, the key value pairs are encoded using + * key value pairs separated by equals, in turn separated by ampersand, + * like a web form. + * + * @param r The request + * @param z A pointer to where the session will be written. + */ +AP_DECLARE_HOOK(int, session_encode, (request_rec * r, session_rec * z)) + +/** + * Hook to decode the session. + * + * In the default implementation, the key value pairs are encoded using + * key value pairs separated by equals, in turn separated by ampersand, + * like a web form. + * + * @param r The request + * @param z A pointer to where the session will be written. + */ +AP_DECLARE_HOOK(int, session_decode, (request_rec * r, session_rec * z)) + +APR_DECLARE_OPTIONAL_FN(void, ap_session_get, (request_rec * r, session_rec * z, + const char *key, const char **value)); +APR_DECLARE_OPTIONAL_FN(void, ap_session_set, (request_rec * r, session_rec * z, + const char *key, const char *value)); +APR_DECLARE_OPTIONAL_FN(int, ap_session_load, (request_rec *, session_rec **)); +APR_DECLARE_OPTIONAL_FN(int, ap_session_save, (request_rec *, session_rec *)); + +/** + * The name of the module. + */ +extern module AP_MODULE_DECLARE_DATA session_module; + +#endif /* MOD_SESSION_H */ +/** @} */ diff --git a/server/util.c b/server/util.c index eede0053085..5b6a522b65e 100644 --- a/server/util.c +++ b/server/util.c @@ -1646,6 +1646,11 @@ AP_DECLARE(char *) ap_construct_server(apr_pool_t *p, const char *hostname, } } +AP_DECLARE(int) ap_unescape_all(char *url) +{ + return unescape_url(url, NULL, NULL); +} + /* c2x takes an unsigned, and expects the caller has guaranteed that * 0 <= what < 256... which usually means that you have to cast to * unsigned char first, because (unsigned)(char)(x) first goes through @@ -1685,9 +1690,8 @@ static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix, * something with a '/' in it (and thus does not prefix "./"). */ -AP_DECLARE(char *) ap_escape_path_segment(apr_pool_t *p, const char *segment) +AP_DECLARE(char *) ap_escape_path_segment_b(char *copy, const char *segment) { - char *copy = apr_palloc(p, 3 * strlen(segment) + 1); const unsigned char *s = (const unsigned char *)segment; unsigned char *d = (unsigned char *)copy; unsigned c; @@ -1705,6 +1709,11 @@ AP_DECLARE(char *) ap_escape_path_segment(apr_pool_t *p, const char *segment) return copy; } +AP_DECLARE(char *) ap_escape_path_segment(apr_pool_t *p, const char *segment) +{ + return ap_escape_path_segment_b(apr_palloc(p, 3 * strlen(segment) + 1), segment); +} + AP_DECLARE(char *) ap_os_escape_path(apr_pool_t *p, const char *path, int partial) { char *copy = apr_palloc(p, 3 * strlen(path) + 3);