httpd-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From minf...@apache.org
Subject svn commit: r645160 - in /httpd/httpd/trunk: CHANGES docs/manual/mod/mod_session_dbd.xml modules/session/config.m4 modules/session/mod_session_dbd.c
Date Sat, 05 Apr 2008 18:59:46 GMT
Author: minfrin
Date: Sat Apr  5 11:59:40 2008
New Revision: 645160

URL: http://svn.apache.org/viewvc?rev=645160&view=rev
Log:
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.

Added:
    httpd/httpd/trunk/docs/manual/mod/mod_session_dbd.xml
    httpd/httpd/trunk/modules/session/mod_session_dbd.c
Modified:
    httpd/httpd/trunk/CHANGES
    httpd/httpd/trunk/modules/session/config.m4

Modified: httpd/httpd/trunk/CHANGES
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/CHANGES?rev=645160&r1=645159&r2=645160&view=diff
==============================================================================
--- httpd/httpd/trunk/CHANGES [utf-8] (original)
+++ httpd/httpd/trunk/CHANGES [utf-8] Sat Apr  5 11:59:40 2008
@@ -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

Added: httpd/httpd/trunk/docs/manual/mod/mod_session_dbd.xml
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/manual/mod/mod_session_dbd.xml?rev=645160&view=auto
==============================================================================
--- httpd/httpd/trunk/docs/manual/mod/mod_session_dbd.xml (added)
+++ httpd/httpd/trunk/docs/manual/mod/mod_session_dbd.xml Sat Apr  5 11:59:40 2008
@@ -0,0 +1,315 @@
+<?xml version="1.0"?>
+<!DOCTYPE modulesynopsis SYSTEM "../style/modulesynopsis.dtd">
+<?xml-stylesheet type="text/xsl" href="../style/manual.en.xsl"?>
+<!-- $LastChangedRevision: 634760 $ -->
+
+<!--
+ 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.
+-->
+
+<modulesynopsis metafile="mod_session_dbd.xml.meta">
+
+<name>mod_session_dbd</name>
+<description>DBD/SQL based session support</description>
+<status>Extension</status>
+<sourcefile>mod_session_dbd.c</sourcefile>
+<identifier>session_dbd_module</identifier>
+
+<summary>
+    <note type="warning"><title>Warning</title>
+      <p>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.</p>
+    </note>
+
+    <p>This submodule of <module>mod_session</module> provides support
for the
+    storage of user sessions within a SQL database using the
+    <module>mod_dbd</module> module.</p>
+
+    <p>Sessions can either be <strong>anonymous</strong>, where the session
is
+    keyed by a unique UUID string stored on the browser in a cookie, or
+    <strong>per user</strong>, where the session is keyed against the userid
of
+    the logged in user.</p>
+
+    <p>SQL based sessions are hidden from the browser, and so offer a measure of
+    privacy without the need for encryption.</p>
+    
+    <p>Different webservers within a server farm may choose to share a database,
+    and so share sessions with one another.</p>
+    
+    <p>For more details on the session interface, see the documentation for
+    the <module>mod_session</module> module.</p>
+    
+</summary>
+<seealso><module>mod_session</module></seealso>
+<seealso><module>mod_session_crypto</module></seealso>
+<seealso><module>mod_session_cookie</module></seealso>
+<seealso><module>mod_dbd</module></seealso>
+
+    <section id="anonymous"><title>DBD Configuration</title>
+
+      <p>Before the <module>mod_session_dbd</module> module can be configured
to maintain a
+      session, the <module>mod_dbd</module> module must be configured to make
the various database queries
+      available to the server.</p>
+      
+      <p>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.</p>
+
+      <example><title>Sample DBD configuration</title>
+        DBDriver pgsql<br />
+        DBDParams "dbname=apachesession user=apache password=xxxxx host=localhost"<br
/>
+        DBDPrepareSQL "delete from session where key = %s" deletesession<br />
+        DBDPrepareSQL "update session set value = %s, expiry = %lld where key = %s" updatesession<br
/>
+        DBDPrepareSQL "insert into session (value, expiry, key) values (%s, %lld, %s)" insertsession<br
/>
+        DBDPrepareSQL "select value from session where key = %s and (expiry = 0 or expiry
&gt; %lld)" selectsession<br />
+        DBDPrepareSQL "delete from session where expiry != 0 and expiry &lt; %lld" cleansession<br
/>
+      </example>
+
+    </section>
+
+    <section id="anonymous"><title>Anonymous Sessions</title>
+    
+      <p>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.</p>
+    
+      <p>To create a simple anonymous session and store it in a postgres database
+      table called <var>apachesession</var>, and save the session ID in a cookie
+      called <var>session</var>, configure the session as follows:</p>
+      
+      <example><title>SQL based anonymous session</title>
+        Session On<br />
+        SessionDBDCookieName session path=/<br />
+      </example>
+      
+      <p>For more examples on how the session can be configured to be read
+      from and written to by a CGI application, see the
+      <module>mod_session</module> examples section.</p>
+      
+      <p>For documentation on how the session can be used to store username
+      and password details, see the <module>mod_auth_form</module> module.</p>
+
+    </section>
+
+    <section id="peruser"><title>Per User Sessions</title>
+    
+      <p>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.</p>
+      
+      <p>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
+      <module>mod_auth_form</module>.</p>
+    
+      <p>To create a simple per user session and store it in a postgres database
+      table called <var>apachesession</var>, and with the session keyed to the
+      userid, configure the session as follows:</p>
+      
+      <example><title>SQL based per user session</title>
+        Session On<br />
+        SessionDBDPerUser On<br />
+      </example>
+      
+    </section>
+
+    <section id="housekeeping"><title>Database Housekeeping</title>
+      <p>Over the course of time, the database can be expected to start accumulating
+      expired sessions. At this point, the <module>mod_session_dbd</module> module
+      is not yet able to handle session expiry automatically.</p>
+      
+      <note type="warning"><title>Warning</title>
+      <p>The administrator will need to set up an external process via cron to clean
+      out expired sessions.</p>
+      </note>
+
+    </section>
+
+<directivesynopsis>
+<name>SessionDBDCookieName</name>
+<description>Name and attributes for the RFC2109 cookie storing the session ID</description>
+<syntax>SessionDBDCookieName <var>name</var> <var>attributes</var></syntax>
+<default>none</default>
+<contextlist><context>directory</context>
+</contextlist>
+<compatibility>Available in Apache 2.3.0 and later</compatibility>
+
+<usage>
+    <p>The <directive>SessionDBDCookieName</directive> 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 <code>Set-Cookie</code> HTTP
header.
+    </p>
+
+    <p>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.
+    </p>
+
+    <example><title>Cookie with attributes</title>
+      Session On<br />
+      SessionDBDCookieName session path=/private;domain=example.com;httponly;secure;version=1;<br
/>
+    </example>
+
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>SessionDBDCookieName2</name>
+<description>Name and attributes for the RFC2965 cookie storing the session ID</description>
+<syntax>SessionDBDCookieName2 <var>name</var> <var>attributes</var></syntax>
+<default>none</default>
+<contextlist><context>directory</context>
+</contextlist>
+<compatibility>Available in Apache 2.3.0 and later</compatibility>
+
+<usage>
+    <p>The <directive>SessionDBDCookieName2</directive> 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 <code>Set-Cookie2</code> HTTP
header.
+    </p>
+    
+    <p>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.
+    </p>
+    
+    <example><title>Cookie2 with attributes</title>
+      Session On<br />
+      SessionDBDCookieName2 session path=/private;domain=example.com;httponly;secure;version=1;<br
/>
+    </example>
+
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>SessionDBDCookieRemove</name>
+<description>Control for whether session ID cookies should be removed from incoming
HTTP headers</description>
+<syntax>SessionDBDCookieRemove On|Off</syntax>
+<default>SessionDBDCookieRemove On</default>
+<contextlist><context>directory</context>
+</contextlist>
+<compatibility>Available in Apache 2.3.0 and later</compatibility>
+
+<usage>
+    <p>The <directive>SessionDBDCookieRemove</directive> flag controls
whether the cookies
+    containing the session ID will be removed from the headers during request processing.</p>
+
+    <p>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.</p>
+
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>SessionDBDPerUser</name>
+<description>Enable a per user session</description>
+<syntax>SessionDBDPerUser On|Off</syntax>
+<default>SessionDBDPerUser Off</default>
+<contextlist><context>directory</context>
+</contextlist>
+<compatibility>Available in Apache 2.3.0 and later</compatibility>
+
+<usage>
+    <p>The <directive>SessionDBDPerUser</directive> 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.</p>
+
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>SessionDBDSelectLabel</name>
+<description>The SQL query to use to select sessions from the database</description>
+<syntax>SessionDBDSelectLabel <var>label</var></syntax>
+<default>SessionDBDSelectLabel selectsession</default>
+<contextlist><context>directory</context>
+</contextlist>
+<compatibility>Available in Apache 2.3.0 and later</compatibility>
+
+<usage>
+    <p>The <directive>SessionDBDSelectLabel</directive> directive sets
the default select
+    query label to be used to load in a session. This label must have been previously defined
using the
+    <directive module="mod_dbd">DBDPrepareSQL</directive> directive.</p>
+
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>SessionDBDInsertLabel</name>
+<description>The SQL query to use to insert sessions into the database</description>
+<syntax>SessionDBDInsertLabel <var>label</var></syntax>
+<default>SessionDBDInsertLabel insertsession</default>
+<contextlist><context>directory</context>
+</contextlist>
+<compatibility>Available in Apache 2.3.0 and later</compatibility>
+
+<usage>
+    <p>The <directive>SessionDBDInsertLabel</directive> directive sets
the default insert
+    query label to be used to load in a session. This label must have been previously defined
using the
+    <directive module="mod_dbd">DBDPrepareSQL</directive> directive.</p>
+
+    <p>If an attempt to update the session affects no rows, this query will be called
to insert the
+    session into the database.</p>
+
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>SessionDBDUpdateLabel</name>
+<description>The SQL query to use to update existing sessions in the database</description>
+<syntax>SessionDBDUpdateLabel <var>label</var></syntax>
+<default>SessionDBDUpdateLabel updatesession</default>
+<contextlist><context>directory</context>
+</contextlist>
+<compatibility>Available in Apache 2.3.0 and later</compatibility>
+
+<usage>
+    <p>The <directive>SessionDBDUpdateLabel</directive> directive sets
the default update
+    query label to be used to load in a session. This label must have been previously defined
using the
+    <directive module="mod_dbd">DBDPrepareSQL</directive> directive.</p>
+
+    <p>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.</p>
+
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>SessionDBDDeleteLabel</name>
+<description>The SQL query to use to remove sessions from the database</description>
+<syntax>SessionDBDDeleteLabel <var>label</var></syntax>
+<default>SessionDBDDeleteLabel deletesession</default>
+<contextlist><context>directory</context>
+</contextlist>
+<compatibility>Available in Apache 2.3.0 and later</compatibility>
+
+<usage>
+    <p>The <directive>SessionDBDDeleteLabel</directive> 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 <directive module="mod_dbd">DBDPrepareSQL</directive> directive.</p>
+
+</usage>
+</directivesynopsis>
+
+</modulesynopsis>

Modified: httpd/httpd/trunk/modules/session/config.m4
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/session/config.m4?rev=645160&r1=645159&r2=645160&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/session/config.m4 (original)
+++ httpd/httpd/trunk/modules/session/config.m4 Sat Apr  5 11:59:40 2008
@@ -12,7 +12,7 @@
 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

Added: httpd/httpd/trunk/modules/session/mod_session_dbd.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/session/mod_session_dbd.c?rev=645160&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/session/mod_session_dbd.c (added)
+++ httpd/httpd/trunk/modules/session/mod_session_dbd.c Sat Apr  5 11:59:40 2008
@@ -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 */
+};



Mime
View raw message