From 5b822119d075f16f1a5b2dcade320f43cbe78daa Mon Sep 17 00:00:00 2001 From: Graham Leggett Date: Sat, 5 Apr 2008 18:59:40 +0000 Subject: [PATCH] mod_session_dbd: Add a session implementation capable of storing session information in a SQL database via the dbd interface. Useful for sites where session privacy is important. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@645160 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 4 + docs/manual/mod/mod_session_dbd.xml | 315 ++++++++++++++ modules/session/config.m4 | 2 +- modules/session/mod_session_dbd.c | 639 ++++++++++++++++++++++++++++ 4 files changed, 959 insertions(+), 1 deletion(-) create mode 100644 docs/manual/mod/mod_session_dbd.xml create mode 100644 modules/session/mod_session_dbd.c diff --git a/CHANGES b/CHANGES index b1e275b8e2..87dc3cfeb0 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_dbd: Add a session implementation capable of storing + session information in a SQL database via the dbd interface. Useful + for sites where session privacy is important. [Graham Leggett] + *) mod_session_crypto: Add a session encoding implementation capable of encrypting and decrypting sessions wherever they may be stored. Introduces a level of privacy when sessions are stored on the diff --git a/docs/manual/mod/mod_session_dbd.xml b/docs/manual/mod/mod_session_dbd.xml new file mode 100644 index 0000000000..0380b90c0a --- /dev/null +++ b/docs/manual/mod/mod_session_dbd.xml @@ -0,0 +1,315 @@ + + + + + + + + + +mod_session_dbd +DBD/SQL based session support +Extension +mod_session_dbd.c +session_dbd_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 submodule of mod_session provides support for the + storage of user sessions within a SQL database using the + mod_dbd module.

+ +

Sessions can either be anonymous, where the session is + keyed by a unique UUID string stored on the browser in a cookie, or + per user, where the session is keyed against the userid of + the logged in user.

+ +

SQL based sessions are hidden from the browser, and so offer a measure of + privacy without the need for encryption.

+ +

Different webservers within a server farm may choose to share a database, + and so share sessions with one another.

+ +

For more details on the session interface, see the documentation for + the mod_session module.

+ +
+mod_session +mod_session_crypto +mod_session_cookie +mod_dbd + +
DBD Configuration + +

Before the mod_session_dbd module can be configured to maintain a + session, the mod_dbd module must be configured to make the various database queries + available to the server.

+ +

There are four queries required to keep a session maintained, to select an existing session, + to update an existing session, to insert a new session, and to delete an expired or empty + session. These queries are configured as per the example below.

+ + Sample DBD configuration + DBDriver pgsql
+ DBDParams "dbname=apachesession user=apache password=xxxxx host=localhost"
+ DBDPrepareSQL "delete from session where key = %s" deletesession
+ DBDPrepareSQL "update session set value = %s, expiry = %lld where key = %s" updatesession
+ DBDPrepareSQL "insert into session (value, expiry, key) values (%s, %lld, %s)" insertsession
+ DBDPrepareSQL "select value from session where key = %s and (expiry = 0 or expiry > %lld)" selectsession
+ DBDPrepareSQL "delete from session where expiry != 0 and expiry < %lld" cleansession
+
+ +
+ +
Anonymous Sessions + +

Anonymous sessions are keyed against a unique UUID, and stored on the + browser within an HTTP cookie. This method is similar to that used by most + application servers to store session information.

+ +

To create a simple anonymous session and store it in a postgres database + table called apachesession, and save the session ID in a cookie + called session, configure the session as follows:

+ + SQL based anonymous session + Session On
+ SessionDBDCookieName session path=/
+
+ +

For more examples on how the session can be configured to be read + from and written to by a CGI application, see the + mod_session examples section.

+ +

For documentation on how the session can be used to store username + and password details, see the mod_auth_form module.

+ +
+ +
Per User Sessions + +

Per user sessions are keyed against the username of a successfully + authenticated user. It offers the most privacy, as no external handle + to the session exists outside of the authenticated realm.

+ +

Per user sessions work within a correctly configured authenticated + environment, be that using basic authentication, digest authentication + or SSL client certificates. Due to the limitations of who came first, + the chicken or the egg, per user sessions cannot be used to store + authentication credentials from a module like + mod_auth_form.

+ +

To create a simple per user session and store it in a postgres database + table called apachesession, and with the session keyed to the + userid, configure the session as follows:

+ + SQL based per user session + Session On
+ SessionDBDPerUser On
+
+ +
+ +
Database Housekeeping +

Over the course of time, the database can be expected to start accumulating + expired sessions. At this point, the mod_session_dbd module + is not yet able to handle session expiry automatically.

+ + Warning +

The administrator will need to set up an external process via cron to clean + out expired sessions.

+
+ +
+ + +SessionDBDCookieName +Name and attributes for the RFC2109 cookie storing the session ID +SessionDBDCookieName name attributes +none +directory + +Available in Apache 2.3.0 and later + + +

The SessionDBDCookieName directive specifies the name and + optional attributes of an RFC2109 compliant cookie inside which the session ID will + be stored. RFC2109 cookies are set using the Set-Cookie HTTP header. +

+ +

An optional list of cookie attributes can be specified, as per the example below. + These attributes are inserted into the cookie as is, and are not interpreted by + Apache. Ensure that your attributes are defined correctly as per the cookie specification. +

+ + Cookie with attributes + Session On
+ SessionDBDCookieName session path=/private;domain=example.com;httponly;secure;version=1;
+
+ +
+
+ + +SessionDBDCookieName2 +Name and attributes for the RFC2965 cookie storing the session ID +SessionDBDCookieName2 name attributes +none +directory + +Available in Apache 2.3.0 and later + + +

The SessionDBDCookieName2 directive specifies the name and + optional attributes of an RFC2965 compliant cookie inside which the session ID will + be stored. RFC2965 cookies are set using the Set-Cookie2 HTTP header. +

+ +

An optional list of cookie attributes can be specified, as per the example below. + These attributes are inserted into the cookie as is, and are not interpreted by + Apache. Ensure that your attributes are defined correctly as per the cookie specification. +

+ + Cookie2 with attributes + Session On
+ SessionDBDCookieName2 session path=/private;domain=example.com;httponly;secure;version=1;
+
+ +
+
+ + +SessionDBDCookieRemove +Control for whether session ID cookies should be removed from incoming HTTP headers +SessionDBDCookieRemove On|Off +SessionDBDCookieRemove On +directory + +Available in Apache 2.3.0 and later + + +

The SessionDBDCookieRemove flag controls whether the cookies + containing the session ID will be removed from the headers during request processing.

+ +

In a reverse proxy situation where the Apache server acts as a server frontend for + a backend origin server, revealing the contents of the session ID cookie to the backend + could be a potential privacy violation. When set to on, the session ID cookie will be + removed from the incoming HTTP headers.

+ +
+
+ + +SessionDBDPerUser +Enable a per user session +SessionDBDPerUser On|Off +SessionDBDPerUser Off +directory + +Available in Apache 2.3.0 and later + + +

The SessionDBDPerUser flag enables a per user session keyed + against the user's login name. If the user is not logged in, this directive will be + ignored.

+ +
+
+ + +SessionDBDSelectLabel +The SQL query to use to select sessions from the database +SessionDBDSelectLabel label +SessionDBDSelectLabel selectsession +directory + +Available in Apache 2.3.0 and later + + +

The SessionDBDSelectLabel directive sets the default select + query label to be used to load in a session. This label must have been previously defined using the + DBDPrepareSQL directive.

+ +
+
+ + +SessionDBDInsertLabel +The SQL query to use to insert sessions into the database +SessionDBDInsertLabel label +SessionDBDInsertLabel insertsession +directory + +Available in Apache 2.3.0 and later + + +

The SessionDBDInsertLabel directive sets the default insert + query label to be used to load in a session. This label must have been previously defined using the + DBDPrepareSQL directive.

+ +

If an attempt to update the session affects no rows, this query will be called to insert the + session into the database.

+ +
+
+ + +SessionDBDUpdateLabel +The SQL query to use to update existing sessions in the database +SessionDBDUpdateLabel label +SessionDBDUpdateLabel updatesession +directory + +Available in Apache 2.3.0 and later + + +

The SessionDBDUpdateLabel directive sets the default update + query label to be used to load in a session. This label must have been previously defined using the + DBDPrepareSQL directive.

+ +

If an attempt to update the session affects no rows, the insert query will be + called to insert the session into the database. If the database supports InsertOrUpdate, + override this query to perform the update in one query instead of two.

+ +
+
+ + +SessionDBDDeleteLabel +The SQL query to use to remove sessions from the database +SessionDBDDeleteLabel label +SessionDBDDeleteLabel deletesession +directory + +Available in Apache 2.3.0 and later + + +

The SessionDBDDeleteLabel directive sets the default delete + query label to be used to delete an expired or empty session. This label must have been previously + defined using the DBDPrepareSQL directive.

+ +
+
+ +
diff --git a/modules/session/config.m4 b/modules/session/config.m4 index 9b1d523ced..bc86486cbe 100644 --- a/modules/session/config.m4 +++ b/modules/session/config.m4 @@ -12,7 +12,7 @@ dnl APACHE_MODULE(session, session module, , , most) APACHE_MODULE(session_cookie, session cookie module, , , most) APACHE_MODULE(session_crypto, session crypto module, , , most) -dnl APACHE_MODULE(session_dbd, session dbd module, , , most) +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_dbd.c b/modules/session/mod_session_dbd.c new file mode 100644 index 0000000000..1ad345769c --- /dev/null +++ b/modules/session/mod_session_dbd.c @@ -0,0 +1,639 @@ +/* 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 "http_log.h" +#include "util_cookies.h" +#include "apr_dbd.h" +#include "mod_dbd.h" +#include "mpm_common.h" + +#define LOG_PREFIX "mod_session_dbd: " +#define MOD_SESSION_DBD "mod_session_dbd" + +module AP_MODULE_DECLARE_DATA session_dbd_module; + +/** + * Structure to carry the per-dir session config. + */ +typedef struct { + const char *name; + int name_set; + const char *name_attrs; + const char *name2; + int name2_set; + const char *name2_attrs; + int peruser; + int peruser_set; + int remove; + int remove_set; + const char *selectlabel; + const char *insertlabel; + const char *updatelabel; + const char *deletelabel; +} session_dbd_dir_conf; + +/* optional function - look it up once in post_config */ +static ap_dbd_t *(*session_dbd_acquire_fn) (request_rec *) = NULL; +static void (*session_dbd_prepare_fn) (server_rec *, const char *, const char *) = NULL; + +/** + * Initialise the database. + * + * If the mod_dbd module is missing, this method will return APR_EGENERAL. + */ +static apr_status_t dbd_init(request_rec *r, const char *query, ap_dbd_t **dbdp, + apr_dbd_prepared_t **statementp) +{ + ap_dbd_t *dbd; + apr_dbd_prepared_t *statement; + + if (!session_dbd_prepare_fn || !session_dbd_acquire_fn) { + session_dbd_prepare_fn = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_prepare); + session_dbd_acquire_fn = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_acquire); + if (!session_dbd_prepare_fn || !session_dbd_acquire_fn) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "You must load mod_dbd to enable AuthDBD functions"); + return APR_EGENERAL; + } + } + + dbd = session_dbd_acquire_fn(r); + if (!dbd) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "failed to acquire database connection"); + return APR_EGENERAL; + } + + statement = apr_hash_get(dbd->prepared, query, APR_HASH_KEY_STRING); + if (!statement) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "failed to find the prepared statement called '%s'", query); + return APR_EGENERAL; + } + + *dbdp = dbd; + *statementp = statement; + + return APR_SUCCESS; +} + +/** + * Load the session by the key specified. + */ +static apr_status_t dbd_load(request_rec * r, const char *key, const char **val) +{ + + apr_status_t rv; + ap_dbd_t *dbd = NULL; + apr_dbd_prepared_t *statement = NULL; + apr_dbd_results_t *res = NULL; + apr_dbd_row_t *row = NULL; + apr_int64_t expiry = (apr_int64_t) apr_time_now(); + + session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config, + &session_dbd_module); + + if (conf->selectlabel == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "no SessionDBDselectlabel has been specified"); + return APR_EGENERAL; + } + + rv = dbd_init(r, conf->selectlabel, &dbd, &statement); + if (rv) { + return rv; + } + rv = apr_dbd_pvbselect(dbd->driver, r->pool, dbd->handle, &res, statement, + 0, key, &expiry, NULL); + if (rv) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "query execution error saving session '%s' " + "in database using query '%s': %s", key, conf->selectlabel, + apr_dbd_error(dbd->driver, dbd->handle, rv)); + return APR_EGENERAL; + } + for (rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1); + rv != -1; + rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1)) { + if (rv != 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, LOG_PREFIX + "error retrieving results while saving '%s' " + "in database using query '%s': %s", key, conf->selectlabel, + apr_dbd_error(dbd->driver, dbd->handle, rv)); + return APR_EGENERAL; + } + if (*val == NULL) { + *val = apr_dbd_get_entry(dbd->driver, row, 0); + } + /* we can't break out here or row won't get cleaned up */ + } + + return APR_SUCCESS; + +} + +/** + * Load the session by firing off a dbd query. + * + * If the session is anonymous, the session key will be extracted from + * the cookie specified. Failing that, the session key will be extracted + * from the GET parameters. + * + * If the session is keyed by the username, the session will be extracted + * by that. + * + * If no session is found, an empty session will be created. + * + * On success, this returns OK. + */ +AP_DECLARE(int) ap_session_dbd_load(request_rec * r, session_rec ** z) +{ + + session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config, + &session_dbd_module); + + apr_status_t ret = APR_SUCCESS; + session_rec *zz = NULL; + const char *name = NULL; + const char *note = NULL; + const char *val = NULL; + const char *key = NULL; + request_rec *m = r->main ? r->main : r; + + /* is our session in a cookie? */ + if (conf->name2_set) { + name = conf->name2; + } + else if (conf->name_set) { + name = conf->name; + } + else if (conf->peruser_set && r->user) { + name = r->user; + } + else { + return DECLINED; + } + + /* first look in the notes */ + note = apr_pstrcat(r->pool, MOD_SESSION_DBD, name, NULL); + zz = (session_rec *)apr_table_get(m->notes, note); + if (zz) { + *z = zz; + return OK; + } + + /* load anonymous sessions */ + if (conf->name_set || conf->name2_set) { + + /* load RFC2109 compliant cookie */ + if (conf->name_set) { + ap_cookie_read(r, conf->name, &key, conf->remove); + } + + /* load RFC2965 compliant cookie */ + if (!key && conf->name2_set) { + ap_cookie_read(r, conf->name2, &key, conf->remove); + } + + if (key) { + ret = dbd_load(r, key, &val); + if (ret != APR_SUCCESS) { + return ret; + } + } + + } + + /* load named session */ + else if (conf->peruser) { + if (r->user) { + ret = dbd_load(r, r->user, &val); + if (ret != APR_SUCCESS) { + return ret; + } + } + } + + /* otherwise not for us */ + else { + return DECLINED; + } + + /* create a new session and return it */ + 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)); + if (key) { + apr_uuid_parse(zz->uuid, key); + } + else { + apr_uuid_get(zz->uuid); + } + zz->encoded = val; + *z = zz; + + /* put the session in the notes so we don't have to parse it again */ + apr_table_setn(m->notes, note, (char *)zz); + + return OK; + +} + +/** + * Save the session by the key specified. + */ +static apr_status_t dbd_save(request_rec * r, const char *key, const char *val, apr_int64_t expiry) +{ + + apr_status_t rv; + ap_dbd_t *dbd = NULL; + apr_dbd_prepared_t *statement; + int rows = 0; + + session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config, + &session_dbd_module); + + if (conf->updatelabel == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "no SessionDBDupdatelabel has been specified"); + return APR_EGENERAL; + } + + rv = dbd_init(r, conf->updatelabel, &dbd, &statement); + if (rv) { + return rv; + } + rv = apr_dbd_pvbquery(dbd->driver, r->pool, dbd->handle, &rows, statement, + val, &expiry, key, NULL); + if (rv) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "query execution error updating session '%s' " + "using database query '%s': %s", key, conf->updatelabel, + apr_dbd_error(dbd->driver, dbd->handle, rv)); + return APR_EGENERAL; + } + + /* + * if some rows were updated it means a session existed and was updated, + * so we are done. + */ + if (rows != 0) { + return APR_SUCCESS; + } + + if (conf->insertlabel == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "no SessionDBDinsertlabel has been specified"); + return APR_EGENERAL; + } + + rv = dbd_init(r, conf->insertlabel, &dbd, &statement); + if (rv) { + return rv; + } + rv = apr_dbd_pvbquery(dbd->driver, r->pool, dbd->handle, &rows, statement, + val, &expiry, key, NULL); + if (rv) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, LOG_PREFIX + "query execution error inserting session '%s' " + "in database with '%s': %s", key, conf->insertlabel, + apr_dbd_error(dbd->driver, dbd->handle, rv)); + return APR_EGENERAL; + } + + /* + * if some rows were inserted it means a session was inserted, so we are + * done. + */ + if (rows != 0) { + return APR_SUCCESS; + } + + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "the session insert query did not cause any rows to be added " + "to the database for session '%s', session not inserted", key); + + return APR_EGENERAL; + +} + +/** + * Remove the session by the key specified. + */ +static apr_status_t dbd_remove(request_rec * r, const char *key) +{ + + apr_status_t rv; + apr_dbd_prepared_t *statement; + int rows = 0; + + session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config, + &session_dbd_module); + ap_dbd_t *dbd = session_dbd_acquire_fn(r); + if (dbd == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "failed to acquire database connection to remove " + "session with key '%s'", key); + return APR_EGENERAL; + } + + if (conf->deletelabel == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "no SessionDBDdeletelabel has been specified"); + return APR_EGENERAL; + } + + statement = apr_hash_get(dbd->prepared, conf->deletelabel, APR_HASH_KEY_STRING); + if (statement == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "prepared statement could not be found for " + "SessionDBDdeletelabel with the label '%s'", conf->deletelabel); + return APR_EGENERAL; + } + rv = apr_dbd_pvbquery(dbd->driver, r->pool, dbd->handle, &rows, statement, + key, NULL); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, LOG_PREFIX + "query execution error removing session '%s' " + "from database", key); + return rv; + } + + return APR_SUCCESS; + +} + +/** + * Clean out expired sessions. + * + * TODO: We need to figure out a way to clean out expired sessions from the database. + * The monitor hook doesn't help us that much, as we have no handle into the + * server, and so we need to come up with a way to do this safely. + */ +static apr_status_t dbd_clean(apr_pool_t *p) +{ + + return APR_ENOTIMPL; + +} + +/** + * Save the session by firing off a dbd query. + * + * If the session is anonymous, save the session and write a cookie + * containing the uuid. + * + * If the session is keyed to the username, save the session using + * the username as a key. + * + * On success, this method will return APR_SUCCESS. + * + * @param r The request pointer. + * @param z A pointer to where the session will be written. + */ +AP_DECLARE(int) ap_session_dbd_save(request_rec * r, session_rec * z) +{ + + apr_status_t ret = APR_SUCCESS; + session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config, + &session_dbd_module); + + /* support anonymous sessions */ + if (conf->name_set || conf->name2_set) { + + /* don't cache pages with a session */ + apr_table_addn(r->headers_out, "Cache-Control", "no-cache"); + + /* must we create a uuid? */ + char *buffer = apr_pcalloc(r->pool, APR_UUID_FORMATTED_LENGTH + 1); + apr_uuid_format(buffer, z->uuid); + + /* save the session with the uuid as key */ + if (z->encoded && z->encoded[0]) { + ret = dbd_save(r, buffer, z->encoded, z->expiry); + } + else { + ret = dbd_remove(r, buffer); + } + if (ret != APR_SUCCESS) { + return ret; + } + + /* create RFC2109 compliant cookie */ + if (conf->name_set) { + ap_cookie_write(r, conf->name, buffer, conf->name_attrs, z->maxage); + } + + /* create RFC2965 compliant cookie */ + if (conf->name2_set) { + ap_cookie_write2(r, conf->name2, buffer, conf->name2_attrs, z->maxage); + } + + return OK; + + } + + /* save named session */ + else if (conf->peruser) { + + /* don't cache pages with a session */ + apr_table_addn(r->headers_out, "Cache-Control", "no-cache"); + + if (r->user) { + ret = dbd_save(r, r->user, z->encoded, z->expiry); + if (ret != APR_SUCCESS) { + return ret; + } + return OK; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "peruser sessions can only be saved if a user is logged in, " + "session not saved: %s", r->uri); + } + } + + return DECLINED; + +} + +/** + * This function performs housekeeping on the database, deleting expired + * sessions. + */ +AP_DECLARE(int) ap_session_dbd_monitor(apr_pool_t *p) +{ + /* TODO handle housekeeping */ + dbd_clean(p); + return OK; +} + + +static void *create_session_dbd_dir_config(apr_pool_t * p, char *dummy) +{ + session_dbd_dir_conf *new = + (session_dbd_dir_conf *) apr_pcalloc(p, sizeof(session_dbd_dir_conf)); + + new->remove = 1; + new->remove_set = 1; + + new->selectlabel = "selectsession"; + new->insertlabel = "insertsession"; + new->updatelabel = "updatesession"; + new->deletelabel = "deletesession"; + + return (void *) new; +} + +static void *merge_session_dbd_dir_config(apr_pool_t * p, void *basev, void *addv) +{ + session_dbd_dir_conf *new = (session_dbd_dir_conf *) apr_pcalloc(p, sizeof(session_dbd_dir_conf)); + session_dbd_dir_conf *add = (session_dbd_dir_conf *) addv; + session_dbd_dir_conf *base = (session_dbd_dir_conf *) basev; + + new->name = (add->name_set == 0) ? base->name : add->name; + new->name_attrs = (add->name_set == 0) ? base->name_attrs : add->name_attrs; + new->name_set = add->name_set || base->name_set; + new->name2 = (add->name2_set == 0) ? base->name2 : add->name2; + new->name2_attrs = (add->name2_set == 0) ? base->name2_attrs : add->name2_attrs; + new->name2_set = add->name2_set || base->name2_set; + new->peruser = (add->peruser_set == 0) ? base->peruser : add->peruser; + new->peruser_set = add->peruser_set || base->peruser_set; + new->remove = (add->remove_set == 0) ? base->remove : add->remove; + new->remove_set = add->remove_set || base->remove_set; + new->selectlabel = (!add->selectlabel) ? base->selectlabel : add->selectlabel; + new->updatelabel = (!add->updatelabel) ? base->updatelabel : add->updatelabel; + new->insertlabel = (!add->insertlabel) ? base->insertlabel : add->insertlabel; + new->deletelabel = (!add->deletelabel) ? base->deletelabel : add->deletelabel; + + return new; +} + +/** + * Sanity check a given string that it exists, is not empty, + * and does not contain special characters. + */ +static const char *check_string(cmd_parms * cmd, const char *string) +{ + if (APR_SUCCESS != ap_cookie_check_string(string)) { + return apr_pstrcat(cmd->pool, cmd->directive->directive, + " cannot be empty, or contain '=', ';' or '&'.", + NULL); + } + return NULL; +} + +static const char * + set_dbd_peruser(cmd_parms * parms, void *dconf, int flag) +{ + session_dbd_dir_conf *conf = dconf; + + conf->peruser = flag; + conf->peruser_set = 1; + + return NULL; +} + +static const char * + set_dbd_cookie_remove(cmd_parms * parms, void *dconf, int flag) +{ + session_dbd_dir_conf *conf = dconf; + + conf->remove = flag; + conf->remove_set = 1; + + return NULL; +} + +static const char *set_cookie_name(cmd_parms * cmd, void *config, const char *args) +{ + char *last; + char *line = apr_pstrdup(cmd->pool, args); + session_dbd_dir_conf *conf = (session_dbd_dir_conf *) config; + char *cookie = apr_strtok(line, " \t", &last); + conf->name = cookie; + conf->name_set = 1; + while (apr_isspace(*last)) { + last++; + } + conf->name_attrs = last; + return check_string(cmd, cookie); +} + +static const char *set_cookie_name2(cmd_parms * cmd, void *config, const char *args) +{ + char *last; + char *line = apr_pstrdup(cmd->pool, args); + session_dbd_dir_conf *conf = (session_dbd_dir_conf *) config; + char *cookie = apr_strtok(line, " \t", &last); + conf->name2 = cookie; + conf->name2_set = 1; + while (apr_isspace(*last)) { + last++; + } + conf->name2_attrs = last; + return check_string(cmd, cookie); +} + +static const command_rec session_dbd_cmds[] = +{ + AP_INIT_TAKE1("SessionDBDSelectLabel", ap_set_string_slot, + (void *) APR_OFFSETOF(session_dbd_dir_conf, selectlabel), OR_AUTHCFG, + "Query label used to select a new session"), + AP_INIT_TAKE1("SessionDBDInsertLabel", ap_set_string_slot, + (void *) APR_OFFSETOF(session_dbd_dir_conf, insertlabel), OR_AUTHCFG, + "Query label used to insert a new session"), + AP_INIT_TAKE1("SessionDBDUpdateLabel", ap_set_string_slot, + (void *) APR_OFFSETOF(session_dbd_dir_conf, updatelabel), OR_AUTHCFG, + "Query label used to update an existing session"), + AP_INIT_TAKE1("SessionDBDDeleteLabel", ap_set_string_slot, + (void *) APR_OFFSETOF(session_dbd_dir_conf, deletelabel), OR_AUTHCFG, + "Query label used to delete an existing session"), + AP_INIT_FLAG("SessionDBDPerUser", set_dbd_peruser, NULL, OR_AUTHCFG, + "Save the session per user"), + AP_INIT_FLAG("SessionDBDCookieRemove", set_dbd_cookie_remove, NULL, RSRC_CONF|OR_AUTHCFG, + "Remove the session cookie after session load. On by default."), + AP_INIT_RAW_ARGS("SessionDBDCookieName", set_cookie_name, NULL, OR_AUTHCFG, + "The name of the RFC2109 cookie carrying the session key"), + AP_INIT_RAW_ARGS("SessionDBDCookieName2", set_cookie_name2, NULL, OR_AUTHCFG, + "The name of the RFC2965 cookie carrying the session key"), + {NULL} +}; + +static void register_hooks(apr_pool_t * p) +{ + ap_hook_session_load(ap_session_dbd_load, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_session_save(ap_session_dbd_save, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_monitor(ap_session_dbd_monitor, NULL, NULL, APR_HOOK_MIDDLE); +} + +module AP_MODULE_DECLARE_DATA session_dbd_module = +{ + STANDARD20_MODULE_STUFF, + create_session_dbd_dir_config, /* dir config creater */ + merge_session_dbd_dir_config, /* dir merger --- default is to + * override */ + NULL, /* server config */ + NULL, /* merge server config */ + session_dbd_cmds, /* command apr_table_t */ + register_hooks /* register hooks */ +}; -- 2.40.0