]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
Session cache interface redesign, Part 8:
authorJoe Orton <jorton@apache.org>
Tue, 8 Apr 2008 10:47:04 +0000 (10:47 +0000)
committerJoe Orton <jorton@apache.org>
Tue, 8 Apr 2008 10:47:04 +0000 (10:47 +0000)
Abstract out the mod_ssl session caching interface into a separate set
of modules, mod_socache_*.

* modules/cache/ap_socache.h: New file.

* modules/cache/config.m4: Copy CHECK_DISTCACHE from ../ssl/config.m4;
  add new socache modules.

* modules/cache/mod_socache_dbm.c: Copied from ../ssl/ssl_scache_dbm.c.
  s/ssl_scache_/socache_/; add module structure and register_hooks.

* modules/cache/mod_socache_shmcb.c: Copied from ../ssl/ssl_scache_shmcb.c.
  s/ssl_scache_/socache_/; add module structure and register_hooks.
  Add SHMCB_MAX_SIZE definition, replacing APR_SHM_MAXSIZE.

* modules/cache/mod_socache_memcache.c: Copied from
  ../ssl/ssl_scache_memcache.c.
  s/ssl_scache_/socache_/; add module structure and register_hooks.
  Enable for APR-Util 1.3.x at compile-time, omitting configure-time
  checks.

* modules/cache/mod_socache_dc.c: Copied from ../ssl/ssl_scache_dc.c.
  s/ssl_scache_/socache_/; add module structure and register_hooks.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@645844 13f79535-47bb-0310-9956-ffa450edef68

modules/cache/ap_socache.h [new file with mode: 0644]
modules/cache/config.m4
modules/cache/mod_socache_dbm.c [new file with mode: 0644]
modules/cache/mod_socache_dc.c [new file with mode: 0644]
modules/cache/mod_socache_memcache.c [new file with mode: 0644]
modules/cache/mod_socache_shmcb.c [new file with mode: 0644]

diff --git a/modules/cache/ap_socache.h b/modules/cache/ap_socache.h
new file mode 100644 (file)
index 0000000..7d89201
--- /dev/null
@@ -0,0 +1,92 @@
+/* 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.
+ */
+
+/**
+ * @file ap_socache.h
+ * @brief Small object cache provider interface.
+ *
+ * @defgroup AP_SOCACHE ap_socache
+ * @ingroup  APACHE_MODS
+ * @{
+ */
+
+#ifndef AP_SOCACHE_H
+#define AP_SOCACHE_H 
+
+#include "httpd.h"
+#include "ap_provider.h"
+#include "apr_pools.h"
+
+/* If this flag is set, the store/retrieve/delete/status interfaces of
+ * the provider are NOT safe to be called concurrently from multiple
+ * processes or threads, and an external global mutex must be used to
+ * serialize access to the provider. */
+#define AP_SOCACHE_FLAG_NOTMPSAFE (0x0001)
+
+typedef struct ap_socache_provider_t {
+    /* Canonical provider name: */
+    const char *name;
+
+    /* Bitmask of AP_SOCACHE_FLAG_* flags: */
+    unsigned int flags;
+
+    /* Create a session cache based on the given configuration string
+     * ARG.  Returns NULL on success, or an error string on failure.
+     * Pool TMP should be used for any temporary allocations, pool P
+     * should be used for any allocations lasting as long as the
+     * lifetime of the return context.
+     *
+     * The context pointer returned in *INSTANCE will be passed as the
+     * first argument to subsequent invocations. */
+    const char *(*create)(void **instance, const char *arg, 
+                          apr_pool_t *tmp, apr_pool_t *p);
+    /* Initialize the cache.  Return APR error code.   */
+    apr_status_t (*init)(void *instance, /* hints, namespace */
+                         server_rec *s, apr_pool_t *pool);
+    /* Destroy a given cache context. */    
+    void (*destroy)(void *instance, server_rec *s);
+    /* Store an object in the cache with key ID of length IDLEN, with
+     * DATA of length DATALEN.  The object expires at abolute time
+     * EXPIRY.  */
+    apr_status_t (*store)(void *instance, server_rec *s, 
+                          const unsigned char *id, unsigned int idlen, 
+                          time_t expiry, 
+                          unsigned char *data, unsigned int datalen);
+    /* Retrieve cached object with key ID of length IDLEN, returning
+     * TRUE on success or FALSE otherwise.  If TRUE, the data must be
+     * placed in DEST, which has length on entry of *DESTLEN.
+     * *DESTLEN must be updated to equal the length of data written on
+     * exit. */
+    apr_status_t (*retrieve)(void *instance, server_rec *s,
+                             const unsigned char *id, unsigned int idlen,
+                             unsigned char *data, unsigned int *datalen,
+                             apr_pool_t *pool);
+    /* Remove an object from the cache with key ID of length IDLEN.
+     * POOL may be used for temporary allocations. */
+    void (*delete)(void *instance, server_rec *s,
+                   const unsigned char *id, unsigned int idlen,
+                   apr_pool_t *pool);
+    /* Dump cache status for mod_status output. */
+    void (*status)(void *instance, request_rec *r, int flags);
+} ap_socache_provider_t;
+
+/* Cache providers are registered using the ap_provider_* interface,
+ * with the following group and version:  */
+#define AP_SOCACHE_PROVIDER_GROUP "socache"
+#define AP_SOCACHE_PROVIDER_VERSION "0"
+
+#endif /* AP_SOCACHE_H */
+/** @} */
index 9eae493c3b5baa0bc9ae7c821fe6046ca3fdceeb..918b79f36401cf93689fceb947e44fae6131df54 100644 (file)
@@ -24,4 +24,86 @@ APACHE_MODULE(cache, dynamic file caching, $cache_objs, , most)
 APACHE_MODULE(disk_cache, disk caching module, , , most)
 APACHE_MODULE(mem_cache, memory caching module, $mem_cache_objs, , )
 
+AC_DEFUN([CHECK_DISTCACHE], [
+  AC_MSG_CHECKING(whether Distcache is required)
+  ap_ssltk_dc="no"
+  tmp_nomessage=""
+  tmp_forced="no"
+  AC_ARG_ENABLE(distcache,
+    APACHE_HELP_STRING(--enable-distcache,Enable distcache support),
+    ap_ssltk_dc="$enableval"
+    tmp_nomessage=""
+    tmp_forced="yes"
+    if test "x$ap_ssltk_dc" = "x"; then
+      ap_ssltk_dc="yes"
+      dnl our "error"s become "tests revealed that..."
+      tmp_forced="no"
+    fi
+    if test "$ap_ssltk_dc" != "yes" -a "$ap_ssltk_dc" != "no"; then
+      tmp_nomessage="--enable-distcache had illegal syntax - disabling"
+      ap_ssltk_dc="no"
+    fi)
+  if test "$tmp_forced" = "no"; then
+    AC_MSG_RESULT($ap_ssltk_dc (default))
+  else
+    AC_MSG_RESULT($ap_ssltk_dc (specified))
+  fi
+  if test "$tmp_forced" = "yes" -a "x$ap_ssltk_dc" = "xno" -a "x$tmp_nomessage" != "x"; then
+    AC_MSG_ERROR(distcache support failed: $tmp_nomessage)
+  fi
+  if test "$ap_ssltk_dc" = "yes"; then
+    AC_CHECK_HEADER(
+      [distcache/dc_client.h],
+      [],
+      [tmp_nomessage="can't include distcache headers"
+      ap_ssltk_dc="no"])
+    if test "$tmp_forced" = "yes" -a "x$ap_ssltk_dc" = "xno"; then
+      AC_MSG_ERROR(distcache support failed: $tmp_nomessage)
+    fi
+  fi
+  if test "$ap_ssltk_dc" = "yes"; then
+    AC_MSG_CHECKING(for Distcache version)
+    AC_TRY_COMPILE(
+[#include <distcache/dc_client.h>],
+[#if DISTCACHE_CLIENT_API != 0x0001
+#error "distcache API version is unrecognised"
+#endif],
+[],
+[tmp_nomessage="distcache has an unsupported API version"
+ap_ssltk_dc="no"])
+    AC_MSG_RESULT($ap_ssltk_dc)
+    if test "$tmp_forced" = "yes" -a "x$ap_ssltk_dc" = "xno"; then
+      AC_MSG_ERROR(distcache support failed: $tmp_nomessage)
+    fi
+  fi
+  if test "$ap_ssltk_dc" = "yes"; then
+    AC_MSG_CHECKING(for Distcache libraries)
+    save_libs=$LIBS
+    LIBS="$LIBS -ldistcache -lnal"
+    AC_TRY_LINK(
+      [#include <distcache/dc_client.h>],
+      [DC_CTX *foo = DC_CTX_new((const char *)0,0);],
+      [],
+      [tmp_no_message="failed to link with distcache libraries"
+      ap_ssltk_dc="no"])
+    LIBS=$save_libs
+    AC_MSG_RESULT($ap_ssltk_dc)
+    if test "$tmp_forced" = "yes" -a "x$ap_ssltk_dc" = "xno"; then
+      AC_MSG_ERROR(distcache support failed: $tmp_nomessage)
+    else
+      APR_ADDTO(MOD_SOCACHE_LDADD, [-ldistcache -lnal])
+      AC_DEFINE(HAVE_DISTCACHE, 1, [Define if distcache support is enabled])
+    fi
+  fi
+])
+
+APACHE_MODULE(socache_shmcb,  shmcb small object cache provider, , most)
+APACHE_MODULE(socache_dbm, dbm small object cache provider, , most)
+APACHE_MODULE(socache_memcache, memcache small object cache provider, , most)
+APACHE_MODULE(socache_dc, distcache small object cache provider, , , no, [
+   CHECK_DISTCACHE
+])
+
+APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
 APACHE_MODPATH_FINISH
diff --git a/modules/cache/mod_socache_dbm.c b/modules/cache/mod_socache_dbm.c
new file mode 100644 (file)
index 0000000..5400d10
--- /dev/null
@@ -0,0 +1,507 @@
+/* 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.
+ */
+
+#include "httpd.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "http_config.h"
+#include "mpm_common.h"
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_time.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+#include "apr_dbm.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "ap_socache.h"
+
+/* Use of the context structure must be thread-safe after the initial
+ * create/init; callers must hold the mutex. */
+struct context {
+    const char *data_file;
+    /* Pool must only be used with the mutex held. */
+    apr_pool_t *pool;
+    time_t last_expiry;
+    time_t timeout;
+};
+
+/**
+ * Support for DBM library
+ */
+#define SSL_DBM_FILE_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD )
+
+/* ### this should use apr_dbm_usednames. */
+#if !defined(SSL_DBM_FILE_SUFFIX_DIR) && !defined(SSL_DBM_FILE_SUFFIX_PAG)
+#if defined(DBM_SUFFIX)
+#define SSL_DBM_FILE_SUFFIX_DIR DBM_SUFFIX
+#define SSL_DBM_FILE_SUFFIX_PAG DBM_SUFFIX
+#elif defined(__FreeBSD__) || (defined(DB_LOCK) && defined(DB_SHMEM))
+#define SSL_DBM_FILE_SUFFIX_DIR ".db"
+#define SSL_DBM_FILE_SUFFIX_PAG ".db"
+#else
+#define SSL_DBM_FILE_SUFFIX_DIR ".dir"
+#define SSL_DBM_FILE_SUFFIX_PAG ".pag"
+#endif
+#endif
+
+
+
+static void socache_dbm_expire(struct context *ctx, server_rec *s);
+
+static void socache_dbm_remove(void *context, server_rec *s, 
+                               const unsigned char *id, unsigned int idlen,
+                               apr_pool_t *p);
+
+static const char *socache_dbm_create(void **context, const char *arg, 
+                                      apr_pool_t *tmp, apr_pool_t *p)
+{
+    struct context *ctx;
+
+    *context = ctx = apr_pcalloc(p, sizeof *ctx);
+
+    ctx->data_file = ap_server_root_relative(p, arg);
+    if (!ctx->data_file) {
+        return apr_psprintf(tmp, "Invalid cache file path %s", arg);
+    }
+    ctx->timeout = 30; /* ### take as hint in _init */
+    apr_pool_create(&ctx->pool, p);
+
+    return NULL;
+}
+
+static apr_status_t socache_dbm_init(void *context, server_rec *s, apr_pool_t *p)
+{
+    struct context *ctx = context;
+    apr_dbm_t *dbm;
+    apr_status_t rv;
+
+    /* for the DBM we need the data file */
+    if (ctx->data_file == NULL) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+                     "SSLSessionCache required");
+        return APR_EINVAL;
+    }
+
+    /* open it once to create it and to make sure it _can_ be created */
+    apr_pool_clear(ctx->pool);
+
+    if ((rv = apr_dbm_open(&dbm, ctx->data_file,
+            APR_DBM_RWCREATE, SSL_DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                     "Cannot create SSLSessionCache DBM file `%s'",
+                     ctx->data_file);
+        return rv;
+    }
+    apr_dbm_close(dbm);
+
+#if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE)
+    /*
+     * We have to make sure the Apache child processes have access to
+     * the DBM file. But because there are brain-dead platforms where we
+     * cannot exactly determine the suffixes we try all possibilities.
+     */
+    if (geteuid() == 0 /* is superuser */) {
+        chown(ctx->data_file, unixd_config.user_id, -1 /* no gid change */);
+        if (chown(apr_pstrcat(p, ctx->data_file, SSL_DBM_FILE_SUFFIX_DIR, NULL),
+                  unixd_config.user_id, -1) == -1) {
+            if (chown(apr_pstrcat(p, ctx->data_file, ".db", NULL),
+                      unixd_config.user_id, -1) == -1)
+                chown(apr_pstrcat(p, ctx->data_file, ".dir", NULL),
+                      unixd_config.user_id, -1);
+        }
+        if (chown(apr_pstrcat(p, ctx->data_file, SSL_DBM_FILE_SUFFIX_PAG, NULL),
+                  unixd_config.user_id, -1) == -1) {
+            if (chown(apr_pstrcat(p, ctx->data_file, ".db", NULL),
+                      unixd_config.user_id, -1) == -1)
+                chown(apr_pstrcat(p, ctx->data_file, ".pag", NULL),
+                      unixd_config.user_id, -1);
+        }
+    }
+#endif
+    socache_dbm_expire(ctx, s);
+
+    return APR_SUCCESS;
+}
+
+static void socache_dbm_kill(void *context, server_rec *s)
+{
+    struct context *ctx = context;
+
+    /* the correct way */
+    unlink(apr_pstrcat(ctx->pool, ctx->data_file, SSL_DBM_FILE_SUFFIX_DIR, NULL));
+    unlink(apr_pstrcat(ctx->pool, ctx->data_file, SSL_DBM_FILE_SUFFIX_PAG, NULL));
+    /* the additional ways to be sure */
+    unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".dir", NULL));
+    unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".pag", NULL));
+    unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".db", NULL));
+    unlink(ctx->data_file);
+
+    return;
+}
+
+static apr_status_t socache_dbm_store(void *context, server_rec *s,
+                                      const unsigned char *id, unsigned int idlen,
+                                      time_t expiry, 
+                                      unsigned char *ucaData, unsigned int nData)
+{
+    struct context *ctx = context;
+    apr_dbm_t *dbm;
+    apr_datum_t dbmkey;
+    apr_datum_t dbmval;
+    apr_status_t rv;
+
+    /* be careful: do not try to store too much bytes in a DBM file! */
+#ifdef PAIRMAX
+    if ((idlen + nData) >= PAIRMAX) {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "data size too large for DBM session cache: %d >= %d",
+                 (idlen + nData), PAIRMAX);
+        return APR_ENOSPC;
+    }
+#else
+    if ((idlen + nData) >= 950 /* at least less than approx. 1KB */) {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "data size too large for DBM session cache: %d >= %d",
+                 (idlen + nData), 950);
+        return APR_ENOSPC;
+    }
+#endif
+
+    /* create DBM key */
+    dbmkey.dptr  = (char *)id;
+    dbmkey.dsize = idlen;
+
+    /* create DBM value */
+    dbmval.dsize = sizeof(time_t) + nData;
+    dbmval.dptr  = (char *)malloc(dbmval.dsize);
+    if (dbmval.dptr == NULL) {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "malloc error creating DBM value");
+        return APR_ENOMEM;
+    }
+    memcpy((char *)dbmval.dptr, &expiry, sizeof(time_t));
+    memcpy((char *)dbmval.dptr+sizeof(time_t), ucaData, nData);
+
+    /* and store it to the DBM file */
+    apr_pool_clear(ctx->pool);
+
+    if ((rv = apr_dbm_open(&dbm, ctx->data_file,
+                           APR_DBM_RWCREATE, SSL_DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                     "Cannot open SSLSessionCache DBM file `%s' for writing "
+                     "(store)",
+                     ctx->data_file);
+        free(dbmval.dptr);
+        return rv;
+    }
+    if ((rv = apr_dbm_store(dbm, dbmkey, dbmval)) != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                     "Cannot store SSL session to DBM file `%s'",
+                     ctx->data_file);
+        apr_dbm_close(dbm);
+        free(dbmval.dptr);
+        return rv;
+    }
+    apr_dbm_close(dbm);
+
+    /* free temporary buffers */
+    free(dbmval.dptr);
+
+    /* allow the regular expiring to occur */
+    socache_dbm_expire(ctx, s);
+
+    return APR_SUCCESS;
+}
+
+static apr_status_t socache_dbm_retrieve(void *context, server_rec *s, 
+                                         const unsigned char *id, unsigned int idlen,
+                                         unsigned char *dest, unsigned int *destlen,
+                                         apr_pool_t *p)
+{
+    struct context *ctx = context;
+    apr_dbm_t *dbm;
+    apr_datum_t dbmkey;
+    apr_datum_t dbmval;
+    unsigned int nData;
+    time_t expiry;
+    time_t now;
+    apr_status_t rc;
+
+    /* allow the regular expiring to occur */
+    socache_dbm_expire(ctx, s);
+
+    /* create DBM key and values */
+    dbmkey.dptr  = (char *)id;
+    dbmkey.dsize = idlen;
+
+    /* and fetch it from the DBM file
+     * XXX: Should we open the dbm against r->pool so the cleanup will
+     * do the apr_dbm_close? This would make the code a bit cleaner.
+     */
+    apr_pool_clear(ctx->pool);
+    if ((rc = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, 
+                           SSL_DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rc, s,
+                     "Cannot open SSLSessionCache DBM file `%s' for reading "
+                     "(fetch)",
+                     ctx->data_file);
+        return rc;
+    }
+    rc = apr_dbm_fetch(dbm, dbmkey, &dbmval);
+    if (rc != APR_SUCCESS) {
+        apr_dbm_close(dbm);
+        return rc;
+    }
+    if (dbmval.dptr == NULL || dbmval.dsize <= sizeof(time_t)) {
+        apr_dbm_close(dbm);
+        return APR_EGENERAL;
+    }
+
+    /* parse resulting data */
+    nData = dbmval.dsize-sizeof(time_t);
+    if (nData > *destlen) {
+        apr_dbm_close(dbm);
+        return APR_ENOSPC;
+    }    
+
+    *destlen = nData;
+    memcpy(&expiry, dbmval.dptr, sizeof(time_t));
+    memcpy(dest, (char *)dbmval.dptr + sizeof(time_t), nData);
+
+    apr_dbm_close(dbm);
+
+    /* make sure the stuff is still not expired */
+    now = time(NULL);
+    if (expiry <= now) {
+        socache_dbm_remove(ctx, s, id, idlen, p);
+        return APR_NOTFOUND;
+    }
+
+    return APR_SUCCESS;
+}
+
+static void socache_dbm_remove(void *context, server_rec *s, 
+                               const unsigned char *id, unsigned int idlen,
+                               apr_pool_t *p)
+{
+    struct context *ctx = context;
+    apr_dbm_t *dbm;
+    apr_datum_t dbmkey;
+    apr_status_t rv;
+
+    /* create DBM key and values */
+    dbmkey.dptr  = (char *)id;
+    dbmkey.dsize = idlen;
+
+    /* and delete it from the DBM file */
+    apr_pool_clear(ctx->pool);
+
+    if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, 
+                           SSL_DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                     "Cannot open SSLSessionCache DBM file `%s' for writing "
+                     "(delete)",
+                     ctx->data_file);
+        return;
+    }
+    apr_dbm_delete(dbm, dbmkey);
+    apr_dbm_close(dbm);
+
+    return;
+}
+
+static void socache_dbm_expire(struct context *ctx, server_rec *s)
+{
+    apr_dbm_t *dbm;
+    apr_datum_t dbmkey;
+    apr_datum_t dbmval;
+    time_t tExpiresAt;
+    int nElements = 0;
+    int nDeleted = 0;
+    int bDelete;
+    apr_datum_t *keylist;
+    int keyidx;
+    int i;
+    time_t tNow;
+    apr_status_t rv;
+
+    /*
+     * make sure the expiration for still not-accessed session
+     * cache entries is done only from time to time
+     */
+    tNow = time(NULL);
+
+    if (tNow < ctx->last_expiry + ctx->timeout) {
+        return;
+    }
+
+    ctx->last_expiry = tNow;
+
+    /*
+     * Here we have to be very carefully: Not all DBM libraries are
+     * smart enough to allow one to iterate over the elements and at the
+     * same time delete expired ones. Some of them get totally crazy
+     * while others have no problems. So we have to do it the slower but
+     * more safe way: we first iterate over all elements and remember
+     * those which have to be expired. Then in a second pass we delete
+     * all those expired elements. Additionally we reopen the DBM file
+     * to be really safe in state.
+     */
+
+#define KEYMAX 1024
+
+    for (;;) {
+        /* allocate the key array in a memory sub pool */
+        apr_pool_clear(ctx->pool);
+
+        if ((keylist = apr_palloc(ctx->pool, sizeof(dbmkey)*KEYMAX)) == NULL) {
+            break;
+        }
+
+        /* pass 1: scan DBM database */
+        keyidx = 0;
+        if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
+                               SSL_DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+            ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                         "Cannot open SSLSessionCache DBM file `%s' for "
+                         "scanning",
+                         ctx->data_file);
+            break;
+        }
+        apr_dbm_firstkey(dbm, &dbmkey);
+        while (dbmkey.dptr != NULL) {
+            nElements++;
+            bDelete = FALSE;
+            apr_dbm_fetch(dbm, dbmkey, &dbmval);
+            if (dbmval.dsize <= sizeof(time_t) || dbmval.dptr == NULL)
+                bDelete = TRUE;
+            else {
+                memcpy(&tExpiresAt, dbmval.dptr, sizeof(time_t));
+                if (tExpiresAt <= tNow)
+                    bDelete = TRUE;
+            }
+            if (bDelete) {
+                if ((keylist[keyidx].dptr = apr_pmemdup(ctx->pool, dbmkey.dptr, dbmkey.dsize)) != NULL) {
+                    keylist[keyidx].dsize = dbmkey.dsize;
+                    keyidx++;
+                    if (keyidx == KEYMAX)
+                        break;
+                }
+            }
+            apr_dbm_nextkey(dbm, &dbmkey);
+        }
+        apr_dbm_close(dbm);
+
+        /* pass 2: delete expired elements */
+        if (apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
+                         SSL_DBM_FILE_MODE, ctx->pool) != APR_SUCCESS) {
+            ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                         "Cannot re-open SSLSessionCache DBM file `%s' for "
+                         "expiring",
+                         ctx->data_file);
+            break;
+        }
+        for (i = 0; i < keyidx; i++) {
+            apr_dbm_delete(dbm, keylist[i]);
+            nDeleted++;
+        }
+        apr_dbm_close(dbm);
+
+        if (keyidx < KEYMAX)
+            break;
+    }
+
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "Inter-Process Session Cache (DBM) Expiry: "
+                 "old: %d, new: %d, removed: %d",
+                 nElements, nElements-nDeleted, nDeleted);
+}
+
+static void socache_dbm_status(void *context, request_rec *r, int flags)
+{
+    struct context *ctx = context;
+    apr_dbm_t *dbm;
+    apr_datum_t dbmkey;
+    apr_datum_t dbmval;
+    int nElem;
+    int nSize;
+    int nAverage;
+    apr_status_t rv;
+
+    nElem = 0;
+    nSize = 0;
+
+    apr_pool_clear(ctx->pool);
+    if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, 
+                           SSL_DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+                     "Cannot open SSLSessionCache DBM file `%s' for status "
+                     "retrival",
+                     ctx->data_file);
+        return;
+    }
+    /*
+     * XXX - Check the return value of apr_dbm_firstkey, apr_dbm_fetch - TBD
+     */
+    apr_dbm_firstkey(dbm, &dbmkey);
+    for ( ; dbmkey.dptr != NULL; apr_dbm_nextkey(dbm, &dbmkey)) {
+        apr_dbm_fetch(dbm, dbmkey, &dbmval);
+        if (dbmval.dptr == NULL)
+            continue;
+        nElem += 1;
+        nSize += dbmval.dsize;
+    }
+    apr_dbm_close(dbm);
+    if (nSize > 0 && nElem > 0)
+        nAverage = nSize / nElem;
+    else
+        nAverage = 0;
+    ap_rprintf(r, "cache type: <b>DBM</b>, maximum size: <b>unlimited</b><br>");
+    ap_rprintf(r, "current sessions: <b>%d</b>, current size: <b>%d</b> bytes<br>", nElem, nSize);
+    ap_rprintf(r, "average session size: <b>%d</b> bytes<br>", nAverage);
+    return;
+}
+
+static const ap_socache_provider_t socache_dbm = {
+    "dbm",
+    AP_SOCACHE_FLAG_NOTMPSAFE,
+    socache_dbm_create,
+    socache_dbm_init,
+    socache_dbm_kill,
+    socache_dbm_store,
+    socache_dbm_retrieve,
+    socache_dbm_remove,
+    socache_dbm_status
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+    ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "dbm", 
+                         AP_SOCACHE_PROVIDER_VERSION,
+                         &socache_dbm);
+}
+
+const module AP_MODULE_DECLARE_DATA socache_dbm_module = {
+    STANDARD20_MODULE_STUFF,
+    NULL, NULL, NULL, NULL, NULL,
+    register_hooks
+};
diff --git a/modules/cache/mod_socache_dc.c b/modules/cache/mod_socache_dc.c
new file mode 100644 (file)
index 0000000..204dd38
--- /dev/null
@@ -0,0 +1,182 @@
+/* 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.
+ */
+
+#include "httpd.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_config.h"
+#include "http_protocol.h"
+
+#include "apr_strings.h"
+#include "apr_time.h"
+
+#include "ap_socache.h"
+
+#include "distcache/dc_client.h"
+
+#if !defined(DISTCACHE_CLIENT_API) || (DISTCACHE_CLIENT_API < 0x0001)
+#error "You must compile with a more recent version of the distcache-base package"
+#endif
+
+struct context {
+    /* Configured target server: */
+    const char *target;
+    /* distcache client context: */
+    DC_CTX *dc;
+};
+
+static const char *socache_dc_create(void **context, const char *arg, 
+                                     apr_pool_t *tmp, apr_pool_t *p)
+{
+    struct context *ctx;
+
+    ctx = *context = apr_palloc(p, sizeof *ctx);
+    
+    ctx->target = apr_pstrdup(p, arg);
+
+    return NULL;
+}
+
+static apr_status_t socache_dc_init(void *context, server_rec *s, apr_pool_t *p)
+{
+    struct context *ctx = ctx;
+
+#if 0
+    /* If a "persistent connection" mode of operation is preferred, you *must*
+     * also use the PIDCHECK flag to ensure fork()'d processes don't interlace
+     * comms on the same connection as each other. */
+#define SESSION_CTX_FLAGS        SESSION_CTX_FLAG_PERSISTENT | \
+                                 SESSION_CTX_FLAG_PERSISTENT_PIDCHECK | \
+                                 SESSION_CTX_FLAG_PERSISTENT_RETRY | \
+                                 SESSION_CTX_FLAG_PERSISTENT_LATE
+#else
+    /* This mode of operation will open a temporary connection to the 'target'
+     * for each cache operation - this makes it safe against fork()
+     * automatically. This mode is preferred when running a local proxy (over
+     * unix domain sockets) because overhead is negligable and it reduces the
+     * performance/stability danger of file-descriptor bloatage. */
+#define SESSION_CTX_FLAGS        0
+#endif
+    ctx->dc = DC_CTX_new(ctx->target, SESSION_CTX_FLAGS);
+    if (!ctx->dc) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "distributed scache failed to obtain context");
+        return APR_EGENERAL;
+    }
+    ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, "distributed scache context initialised");
+
+    return APR_SUCCESS;
+}
+
+static void socache_dc_kill(void *context, server_rec *s)
+{
+    struct context *ctx = context;
+
+    if (ctx && ctx->dc) {
+        DC_CTX_free(ctx->dc);
+        ctx->dc = NULL;
+    }
+}
+
+static apr_status_t socache_dc_store(void *context, server_rec *s, 
+                                     const unsigned char *id, unsigned int idlen,
+                                     time_t timeout,
+                                     unsigned char *der, unsigned int der_len)
+{
+    struct context *ctx = context;
+
+    /* !@#$%^ - why do we deal with *absolute* time anyway??? */
+    timeout -= time(NULL);
+    /* Send the serialised session to the distributed cache context */
+    if (!DC_CTX_add_session(ctx->dc, id, idlen, der, der_len,
+                            (unsigned long)timeout * 1000)) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "distributed scache 'add_session' failed");
+        return APR_EGENERAL;
+    }
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "distributed scache 'add_session' successful");
+    return APR_SUCCESS;
+}
+
+static apr_status_t socache_dc_retrieve(void *context, server_rec *s, 
+                                        const unsigned char *id, unsigned int idlen,
+                                        unsigned char *dest, unsigned int *destlen,
+                                        apr_pool_t *p)
+{
+    unsigned int data_len;
+    struct context *ctx = context;
+
+    /* Retrieve any corresponding session from the distributed cache context */
+    if (!DC_CTX_get_session(ctx->dc, id, idlen, dest, *destlen, &data_len)) {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "distributed scache 'get_session' MISS");
+        return APR_EGENERAL;
+    }
+    if (data_len > *destlen) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "distributed scache 'get_session' OVERFLOW");
+        return APR_ENOSPC;
+    }
+    *destlen = data_len;
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "distributed scache 'get_session' HIT");
+    return APR_SUCCESS;
+}
+
+static void socache_dc_remove(void *context, server_rec *s, 
+                              const unsigned char *id, unsigned int idlen, 
+                              apr_pool_t *p)
+{
+    struct context *ctx = context;
+
+    /* Remove any corresponding session from the distributed cache context */
+    if (!DC_CTX_remove_session(ctx->dc, id, idlen)) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "distributed scache 'remove_session' MISS");
+    } else {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "distributed scache 'remove_session' HIT");
+    }
+}
+
+static void socache_dc_status(void *context, request_rec *r, int flags)
+{
+    struct context *ctx = context;
+
+    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                  "distributed scache 'socache_dc_status'");
+    ap_rprintf(r, "cache type: <b>DC (Distributed Cache)</b>, "
+               " target: <b>%s</b><br>", ctx->target);
+}
+
+static const ap_socache_provider_t socache_dc = {
+    "distcache",
+    0,
+    socache_dc_create,
+    socache_dc_init,
+    socache_dc_kill,
+    socache_dc_store,
+    socache_dc_retrieve,
+    socache_dc_remove,
+    socache_dc_status
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+    ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "dc", 
+                         AP_SOCACHE_PROVIDER_VERSION,
+                         &socache_dc);
+}
+
+const module AP_MODULE_DECLARE_DATA socache_dc_module = {
+    STANDARD20_MODULE_STUFF,
+    NULL, NULL, NULL, NULL, NULL,
+    register_hooks
+};
+
diff --git a/modules/cache/mod_socache_memcache.c b/modules/cache/mod_socache_memcache.c
new file mode 100644 (file)
index 0000000..6c954d1
--- /dev/null
@@ -0,0 +1,313 @@
+/* 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.
+*/
+
+
+#include "httpd.h"
+#include "http_config.h"
+
+#include "apr.h"
+#include "apu_version.h"
+
+/* apr_memcache support requires >= 1.3 */
+#if APU_MAJOR_VERSION > 1 || \
+    (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION > 2)
+#define HAVE_APU_MEMCACHE 1
+#endif
+
+#ifdef HAVE_APU_MEMCACHE
+
+#include "ap_socache.h"
+#include "ap_mpm.h"
+#include "http_log.h"
+#include "apr_memcache.h"
+
+/* The underlying apr_memcache system is thread safe.. */
+#define MC_TAG "mod_ssl:"
+#define MC_TAG_LEN \
+    (sizeof(MC_TAG))
+
+#define MC_KEY_LEN 254
+
+#ifndef MC_DEFAULT_SERVER_PORT
+#define MC_DEFAULT_SERVER_PORT 11211
+#endif
+
+
+#ifndef MC_DEFAULT_SERVER_MIN
+#define MC_DEFAULT_SERVER_MIN 0
+#endif
+
+#ifndef MC_DEFAULT_SERVER_SMAX
+#define MC_DEFAULT_SERVER_SMAX 1
+#endif
+
+#ifndef MC_DEFAULT_SERVER_TTL
+#define MC_DEFAULT_SERVER_TTL 600
+#endif
+
+struct context {
+    const char *servers;
+    apr_memcache_t *mc;
+};
+
+static const char *socache_mc_create(void **context, const char *arg, 
+                                     apr_pool_t *tmp, apr_pool_t *p)
+{
+    struct context *ctx;
+    
+    *context = ctx = apr_palloc(p, sizeof *ctx);
+
+    ctx->servers = apr_pstrdup(p, arg);
+
+    return NULL;
+}
+
+static apr_status_t socache_mc_init(void *context, server_rec *s, apr_pool_t *p)
+{
+    apr_status_t rv;
+    int thread_limit = 0;
+    int nservers = 0;
+    char *cache_config;
+    char *split;
+    char *tok;
+    struct context *ctx = context;
+
+    ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);
+
+    /* Find all the servers in the first run to get a total count */
+    cache_config = apr_pstrdup(p, ctx->servers);
+    split = apr_strtok(cache_config, ",", &tok);
+    while (split) {
+        nservers++;
+        split = apr_strtok(NULL,",", &tok);
+    }
+
+    rv = apr_memcache_create(p, nservers, 0, &ctx->mc);
+    if (rv != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+                     "SSLSessionCache: Failed to create Memcache Object of '%d' size.", 
+                     nservers);
+        return rv;
+    }
+
+    /* Now add each server to the memcache */
+    cache_config = apr_pstrdup(p, ctx->servers);
+    split = apr_strtok(cache_config, ",", &tok);
+    while (split) {
+        apr_memcache_server_t *st;
+        char *host_str;
+        char *scope_id;
+        apr_port_t port;
+
+        rv = apr_parse_addr_port(&host_str, &scope_id, &port, split, p);
+        if (rv != APR_SUCCESS) {
+            ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+                         "SSLSessionCache: Failed to Parse Server: '%s'", split);
+            return rv;
+        }
+
+        if (host_str == NULL) {
+            ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+                         "SSLSessionCache: Failed to Parse Server, "
+                         "no hostname specified: '%s'", split);
+            return APR_EINVAL;
+        }
+
+        if (port == 0) {
+            port = MC_DEFAULT_SERVER_PORT;
+        }
+
+        rv = apr_memcache_server_create(p,
+                                        host_str, port,
+                                        MC_DEFAULT_SERVER_MIN,
+                                        MC_DEFAULT_SERVER_SMAX,
+                                        thread_limit,
+                                        MC_DEFAULT_SERVER_TTL,
+                                        &st);
+        if (rv != APR_SUCCESS) {
+            ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+                         "SSLSessionCache: Failed to Create Server: %s:%d", 
+                         host_str, port);
+            return rv;
+        }
+
+        rv = apr_memcache_add_server(ctx->mc, st);
+        if (rv != APR_SUCCESS) {
+            ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+                         "SSLSessionCache: Failed to Add Server: %s:%d", 
+                         host_str, port);
+            return rv;
+        }
+
+        split = apr_strtok(NULL,",", &tok);
+    }
+
+    return APR_SUCCESS;
+}
+
+static void socache_mc_kill(void *context, server_rec *s)
+{
+    /* noop. */
+}
+
+static char *mc_session_id2sz(const unsigned char *id, unsigned int idlen,
+                              char *str, int strsize)
+{
+    char *cp;
+    int n;
+    int maxlen = (strsize - MC_TAG_LEN)/2;
+
+    cp = apr_cpystrn(str, MC_TAG, MC_TAG_LEN);
+    for (n = 0; n < idlen && n < maxlen; n++) {
+        apr_snprintf(cp, 3, "%02X", (unsigned) id[n]);
+        cp += 2;
+    }
+
+    *cp = '\0';
+
+    return str;
+}
+
+static apr_status_t socache_mc_store(void *context, server_rec *s, 
+                                     const unsigned char *id, unsigned int idlen,
+                                     time_t timeout,
+                                     unsigned char *ucaData, unsigned int nData)
+{
+    struct context *ctx = context;
+    char buf[MC_KEY_LEN];
+    char *strkey = NULL;
+    apr_status_t rv;
+
+    strkey = mc_session_id2sz(id, idlen, buf, sizeof(buf));
+    if(!strkey) {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "scache_mc: Key generation borked.");
+        return APR_EGENERAL;
+    }
+
+    rv = apr_memcache_set(ctx->mc, strkey, (char*)ucaData, nData, timeout, 0);
+
+    if (rv != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+                     "scache_mc: error setting key '%s' "
+                     "with %d bytes of data", strkey, nData);
+        return rv;
+    }
+
+    return APR_SUCCESS;
+}
+
+static apr_status_t socache_mc_retrieve(void *context, server_rec *s, 
+                                        const unsigned char *id, unsigned int idlen,
+                                        unsigned char *dest, unsigned int *destlen,
+                                        apr_pool_t *p)
+{
+    struct context *ctx = context;
+    apr_size_t der_len;
+    char buf[MC_KEY_LEN], *der;
+    char *strkey = NULL;
+    apr_status_t rv;
+
+    strkey = mc_session_id2sz(id, idlen, buf, sizeof(buf));
+
+    if (!strkey) {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                     "scache_mc: Key generation borked.");
+        return APR_EGENERAL;
+    }
+
+    /* ### this could do with a subpool, but _getp looks like it will
+     * eat memory like it's going out of fashion anyway. */
+
+    rv = apr_memcache_getp(ctx->mc, p, strkey,
+                           &der, &der_len, NULL);
+    if (rv) {
+        if (rv != APR_NOTFOUND) {
+            ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                         "scache_mc: 'get_session' FAIL");
+        }
+        return rv;
+    }
+    else if (der_len > *destlen) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                     "scache_mc: 'get_session' OVERFLOW");
+        return rv;
+    }    
+
+    memcpy(dest, der, der_len);
+    *destlen = der_len;
+
+    return APR_SUCCESS;
+}
+
+static void socache_mc_remove(void *context, server_rec *s, 
+                              const unsigned char *id, unsigned int idlen,
+                              apr_pool_t *p)
+{
+    struct context *ctx = context;
+    char buf[MC_KEY_LEN];
+    char* strkey = NULL;
+    apr_status_t rv;
+
+    strkey = mc_session_id2sz(id, idlen, buf, sizeof(buf));
+    if(!strkey) {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "scache_mc: Key generation borked.");
+        return;
+    }
+
+    rv = apr_memcache_delete(ctx->mc, strkey, 0);
+
+    if (rv != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s,
+                     "scache_mc: error deleting key '%s' ",
+                     strkey);
+        return;
+    }
+}
+
+static void socache_mc_status(void *context, request_rec *r, int flags)
+{
+    /* SSLModConfigRec *mc = myModConfig(r->server); */
+    /* TODO: Make a mod_status handler. meh. */
+}
+
+static const ap_socache_provider_t socache_mc = {
+    "memcache",
+    0,
+    socache_mc_create,
+    socache_mc_init,
+    socache_mc_kill,
+    socache_mc_store,
+    socache_mc_retrieve,
+    socache_mc_remove,
+    socache_mc_status
+};
+
+#endif /* HAVE_APU_MEMCACHE */
+
+static void register_hooks(apr_pool_t *p)
+{
+#ifdef HAVE_APU_MEMCACHE
+    ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "mc", 
+                         AP_SOCACHE_PROVIDER_VERSION,
+                         &socache_mc);
+#endif
+}
+
+const module AP_MODULE_DECLARE_DATA socache_memcache_module = {
+    STANDARD20_MODULE_STUFF,
+    NULL, NULL, NULL, NULL, NULL,
+    register_hooks
+};
diff --git a/modules/cache/mod_socache_shmcb.c b/modules/cache/mod_socache_shmcb.c
new file mode 100644 (file)
index 0000000..5226b7f
--- /dev/null
@@ -0,0 +1,851 @@
+/* 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.
+ */
+
+#include "httpd.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "http_config.h"
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_time.h"
+#include "apr_shm.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "ap_socache.h"
+
+#define SHMCB_MAX_SIZE (64 * 1024 * 1024)
+
+/* 
+ * This shared memory based SSL session cache implementation was
+ * originally written by Geoff Thorpe <geoff geoffthorpe.net> for C2Net
+ * Europe as a contribution to Ralf Engelschall's mod_ssl project.
+ *
+ * Since rewritten by GT to not use alignment-fudging memcpys and reduce
+ * complexity.
+ */
+
+/*
+ * Header structure - the start of the shared-mem segment
+ */
+typedef struct {
+    /* Stats for cache operations */
+    unsigned long stat_stores;
+    unsigned long stat_expiries;
+    unsigned long stat_scrolled;
+    unsigned long stat_retrieves_hit;
+    unsigned long stat_retrieves_miss;
+    unsigned long stat_removes_hit;
+    unsigned long stat_removes_miss;
+    /* Number of subcaches */
+    unsigned int subcache_num;
+    /* How many indexes each subcache's queue has */
+    unsigned int index_num;
+    /* How large each subcache is, including the queue and data */
+    unsigned int subcache_size;
+    /* How far into each subcache the data area is (optimisation) */
+    unsigned int subcache_data_offset;
+    /* How large the data area in each subcache is (optimisation) */
+    unsigned int subcache_data_size;
+} SHMCBHeader;
+
+/* 
+ * Subcache structure - the start of each subcache, followed by
+ * indexes then data
+ */
+typedef struct {
+    /* The start position and length of the cyclic buffer of indexes */
+    unsigned int idx_pos, idx_used;
+    /* Same for the data area */
+    unsigned int data_pos, data_used;
+} SHMCBSubcache;
+
+/* 
+ * Index structure - each subcache has an array of these
+ */
+typedef struct {
+    /* absolute time this entry expires */
+    time_t expires;
+    /* location within the subcache's data area */
+    unsigned int data_pos;
+    /* size (most logic ignores this, we keep it only to minimise memcpy) */
+    unsigned int data_used;
+    /* length of the used data which contains the id */
+    unsigned int id_len;
+    /* Used to mark explicitly-removed sessions */
+    unsigned char removed;
+} SHMCBIndex;
+
+struct context {
+    const char *data_file;
+    apr_size_t shm_size;
+    apr_shm_t *shm;
+    SHMCBHeader *header;
+};
+
+/* The SHM data segment is of fixed size and stores data as follows.
+ *
+ *   [ SHMCBHeader | Subcaches ]
+ *
+ * The SHMCBHeader header structure stores metadata concerning the
+ * cache and the contained subcaches.
+ *
+ * Subcaches is a hash table of header->subcache_num SHMCBSubcache
+ * structures.  The hash table is indexed by SHMCB_MASK(id). Each
+ * SHMCBSubcache structure has a fixed size (header->subcache_size),
+ * which is determined at creation time, and looks like the following:
+ *
+ *   [ SHMCBSubcache | Indexes | Data ]
+ *
+ * Each subcache is prefixed by the SHMCBSubcache structure.
+ *
+ * The subcache's "Data" segment is a single cyclic data buffer, of
+ * total size header->subcache_data_size; data inside is referenced
+ * using byte offsets. The offset marking the beginning of the cyclic
+ * buffer is subcache->data_pos the buffer's length is
+ * subcache->data_used.
+ *
+ * "Indexes" is an array of header->index_num SHMCBIndex structures,
+ * which is used as a cyclic queue; subcache->idx_pos gives the array
+ * index of the first in use, subcache->idx_used gives the number in
+ * use.  Both ->idx_* values have a range of [0, header->index_num)
+ *
+ * Each in-use SHMCBIndex structure represents a single SSL session.
+ * The ID and data segment are stored consecutively in the subcache's
+ * cyclic data buffer.  The "Data" segment can thus be seen to 
+ * look like this, for example
+ *
+ * offset:  [ 0     1     2     3     4     5     6    ...
+ * contents:[ ID1   Data1       ID2   Data2       ID3  ...
+ *
+ * where the corresponding indices would look like:
+ *
+ * idx1 = { data_pos = 0, data_used = 3, id_len = 1, ...}
+ * idx2 = { data_pos = 3, data_used = 3, id_len = 1, ...}
+ * ...
+ */
+
+/* This macro takes a pointer to the header and a zero-based index and returns
+ * a pointer to the corresponding subcache. */
+#define SHMCB_SUBCACHE(pHeader, num) \
+                (SHMCBSubcache *)(((unsigned char *)(pHeader)) + \
+                        sizeof(SHMCBHeader) + \
+                        (num) * ((pHeader)->subcache_size))
+
+/* This macro takes a pointer to the header and a session id and returns a
+ * pointer to the corresponding subcache. */
+#define SHMCB_MASK(pHeader, id) \
+                SHMCB_SUBCACHE((pHeader), *(id) & ((pHeader)->subcache_num - 1))
+
+/* This macro takes the same params as the last, generating two outputs for use
+ * in ap_log_error(...). */
+#define SHMCB_MASK_DBG(pHeader, id) \
+                *(id), (*(id) & ((pHeader)->subcache_num - 1))
+
+/* This macro takes a pointer to a subcache and a zero-based index and returns
+ * a pointer to the corresponding SHMCBIndex. */
+#define SHMCB_INDEX(pSubcache, num) \
+                ((SHMCBIndex *)(((unsigned char *)pSubcache) + \
+                                sizeof(SHMCBSubcache)) + num)
+
+/* This macro takes a pointer to the header and a subcache and returns a
+ * pointer to the corresponding data area. */
+#define SHMCB_DATA(pHeader, pSubcache) \
+                ((unsigned char *)(pSubcache) + (pHeader)->subcache_data_offset)
+
+/*
+ * Cyclic functions - assists in "wrap-around"/modulo logic
+ */
+
+/* Addition modulo 'mod' */
+#define SHMCB_CYCLIC_INCREMENT(val,inc,mod) \
+                (((val) + (inc)) % (mod))
+
+/* Subtraction (or "distance between") modulo 'mod' */
+#define SHMCB_CYCLIC_SPACE(val1,val2,mod) \
+                ((val2) >= (val1) ? ((val2) - (val1)) : \
+                        ((val2) + (mod) - (val1)))
+
+/* A "normal-to-cyclic" memcpy. */
+static void shmcb_cyclic_ntoc_memcpy(unsigned int buf_size, unsigned char *data,
+                                     unsigned int dest_offset, const unsigned char *src,
+                                     unsigned int src_len)
+{
+    if (dest_offset + src_len < buf_size)
+        /* It be copied all in one go */
+        memcpy(data + dest_offset, src, src_len);
+    else {
+        /* Copy the two splits */
+        memcpy(data + dest_offset, src, buf_size - dest_offset);
+        memcpy(data, src + buf_size - dest_offset,
+               src_len + dest_offset - buf_size);
+    }
+}
+
+/* A "cyclic-to-normal" memcpy. */
+static void shmcb_cyclic_cton_memcpy(unsigned int buf_size, unsigned char *dest,
+                                     const unsigned char *data, unsigned int src_offset,
+                                     unsigned int src_len)
+{
+    if (src_offset + src_len < buf_size)
+        /* It be copied all in one go */
+        memcpy(dest, data + src_offset, src_len);
+    else {
+        /* Copy the two splits */
+        memcpy(dest, data + src_offset, buf_size - src_offset);
+        memcpy(dest + buf_size - src_offset, data,
+               src_len + src_offset - buf_size);
+    }
+}
+
+/* A memcmp against a cyclic data buffer.  Compares SRC of length
+ * SRC_LEN against the contents of cyclic buffer DATA (which is of
+ * size BUF_SIZE), starting at offset DEST_OFFSET. Got that?  Good. */
+static int shmcb_cyclic_memcmp(unsigned int buf_size, unsigned char *data,
+                               unsigned int dest_offset, 
+                               const unsigned char *src,
+                               unsigned int src_len)
+{
+    if (dest_offset + src_len < buf_size)
+        /* It be compared all in one go */
+        return memcmp(data + dest_offset, src, src_len);
+    else {
+        /* Compare the two splits */
+        int diff;
+        
+        diff = memcmp(data + dest_offset, src, buf_size - dest_offset);
+        if (diff) {
+            return diff;
+        }
+        return memcmp(data, src + buf_size - dest_offset,
+                      src_len + dest_offset - buf_size);
+    }
+}
+
+
+/* Prototypes for low-level subcache operations */
+static void shmcb_subcache_expire(server_rec *, SHMCBHeader *, SHMCBSubcache *);
+/* Returns zero on success, non-zero on failure. */   
+static int shmcb_subcache_store(server_rec *s, SHMCBHeader *header,
+                                SHMCBSubcache *subcache, 
+                                unsigned char *data, unsigned int data_len,
+                                const unsigned char *id, unsigned int id_len,
+                                time_t expiry);
+/* Returns zero on success, non-zero on failure. */   
+static int shmcb_subcache_retrieve(server_rec *, SHMCBHeader *, SHMCBSubcache *,
+                                   const unsigned char *id, unsigned int idlen,
+                                   unsigned char *data, unsigned int *datalen);
+/* Returns zero on success, non-zero on failure. */   
+static int shmcb_subcache_remove(server_rec *, SHMCBHeader *, SHMCBSubcache *,
+                                 const unsigned char *, unsigned int);
+
+/*
+ * High-Level "handlers" as per ssl_scache.c
+ * subcache internals are deferred to shmcb_subcache_*** functions lower down
+ */
+
+static const char *socache_shmcb_create(void **context, const char *arg, 
+                                        apr_pool_t *tmp, apr_pool_t *p)
+{
+    struct context *ctx;
+    char *path, *cp, *cp2;
+
+    /* Allocate the context. */
+    *context = ctx = apr_pcalloc(p, sizeof *ctx);
+    
+    ctx->data_file = path = ap_server_root_relative(p, arg);
+    ctx->shm_size  = 1024*512; /* 512KB */
+
+    cp = strchr(path, '(');
+    if (cp) {
+        *cp++ = '\0';
+
+        if (!(cp2 = strchr(cp, ')'))) {
+            return "Invalid argument: no closing parenthesis";
+        }
+            
+        *cp2 = '\0';
+        
+        ctx->shm_size = atoi(cp);
+        
+        if (ctx->shm_size < 8192) {
+            return "Invalid argument: size has to be >= 8192 bytes";
+            
+        }
+        
+        if (ctx->shm_size >= SHMCB_MAX_SIZE) {
+            return apr_psprintf(tmp,
+                                "Invalid argument: size has "
+                                "to be < %d bytes on this platform", 
+                                SHMCB_MAX_SIZE);
+            
+        }
+    }
+
+    return NULL;
+}
+
+static apr_status_t socache_shmcb_init(void *context, server_rec *s, apr_pool_t *p)
+{
+    void *shm_segment;
+    apr_size_t shm_segsize;
+    apr_status_t rv;
+    SHMCBHeader *header;
+    unsigned int num_subcache, num_idx, loop;
+    struct context *ctx = context;
+
+    /* Create shared memory segment */
+    if (ctx->data_file == NULL) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+                     "SSLSessionCache required");
+        return APR_EINVAL;
+    }
+
+    /* Use anonymous shm by default, fall back on name-based. */
+    rv = apr_shm_create(&ctx->shm, ctx->shm_size, NULL, p);
+    if (APR_STATUS_IS_ENOTIMPL(rv)) {
+        /* For a name-based segment, remove it first in case of a
+         * previous unclean shutdown. */
+        apr_shm_remove(ctx->data_file, p);
+
+        rv = apr_shm_create(&ctx->shm, ctx->shm_size, ctx->data_file, p);
+    }
+
+    if (rv != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                     "could not allocate shared memory for shmcb "
+                     "session cache");
+        return rv;
+    }
+
+    shm_segment = apr_shm_baseaddr_get(ctx->shm);
+    shm_segsize = apr_shm_size_get(ctx->shm);
+    if (shm_segsize < (5 * sizeof(SHMCBHeader))) {
+        /* the segment is ridiculously small, bail out */
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+                     "shared memory segment too small");
+        return APR_ENOSPC;
+    }
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "shmcb_init allocated %" APR_SIZE_T_FMT
+                 " bytes of shared memory",
+                 shm_segsize);
+    /* Discount the header */
+    shm_segsize -= sizeof(SHMCBHeader);
+    /* Select the number of subcaches to create and how many indexes each
+     * should contain based on the size of the memory (the header has already
+     * been subtracted). Typical non-client-auth sslv3/tlsv1 sessions are
+     * around 180 bytes (148 bytes data and 32 bytes for the id), so
+     * erring to division by 150 helps ensure we would exhaust data
+     * storage before index storage (except sslv2, where it's
+     * *slightly* the other way). From there, we select the number of
+     * subcaches to be a power of two, such that the number of indexes
+     * per subcache at least twice the number of subcaches. */
+    num_idx = (shm_segsize) / 150;
+    num_subcache = 256;
+    while ((num_idx / num_subcache) < (2 * num_subcache))
+        num_subcache /= 2;
+    num_idx /= num_subcache;
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "for %" APR_SIZE_T_FMT " bytes (%" APR_SIZE_T_FMT 
+                 " including header), recommending %u subcaches, "
+                 "%u indexes each", shm_segsize,
+                 shm_segsize + sizeof(SHMCBHeader), num_subcache, num_idx);
+    if (num_idx < 5) {
+        /* we're still too small, bail out */
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+                     "shared memory segment too small");
+        return APR_ENOSPC;
+    }
+    /* OK, we're sorted */
+    ctx->header = header = shm_segment;
+    header->stat_stores = 0;
+    header->stat_expiries = 0;
+    header->stat_scrolled = 0;
+    header->stat_retrieves_hit = 0;
+    header->stat_retrieves_miss = 0;
+    header->stat_removes_hit = 0;
+    header->stat_removes_miss = 0;
+    header->subcache_num = num_subcache;
+    /* Convert the subcache size (in bytes) to a value that is suitable for
+     * structure alignment on the host platform, by rounding down if necessary.
+     * This assumes that sizeof(unsigned long) provides an appropriate
+     * alignment unit.  */
+    header->subcache_size = ((size_t)(shm_segsize / num_subcache) &
+                             ~(size_t)(sizeof(unsigned long) - 1));
+    header->subcache_data_offset = sizeof(SHMCBSubcache) +
+                                   num_idx * sizeof(SHMCBIndex);
+    header->subcache_data_size = header->subcache_size -
+                                 header->subcache_data_offset;
+    header->index_num = num_idx;
+
+    /* Output trace info */
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "shmcb_init_memory choices follow");
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "subcache_num = %u", header->subcache_num);
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "subcache_size = %u", header->subcache_size);
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "subcache_data_offset = %u", header->subcache_data_offset);
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "subcache_data_size = %u", header->subcache_data_size);
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "index_num = %u", header->index_num);
+    /* The header is done, make the caches empty */
+    for (loop = 0; loop < header->subcache_num; loop++) {
+        SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop);
+        subcache->idx_pos = subcache->idx_used = 0;
+        subcache->data_pos = subcache->data_used = 0;
+    }
+    ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
+                 "Shared memory session cache initialised");
+    /* Success ... */
+
+    return APR_SUCCESS;
+}
+
+static void socache_shmcb_kill(void *context, server_rec *s)
+{
+    struct context *ctx = context;
+
+    if (ctx && ctx->shm) {
+        apr_shm_destroy(ctx->shm);
+        ctx->shm = NULL;
+    }
+}
+
+static apr_status_t socache_shmcb_store(void *context, server_rec *s, 
+                                        const unsigned char *id, unsigned int idlen,
+                                        time_t timeout, 
+                                        unsigned char *encoded,
+                                        unsigned int len_encoded)
+{
+    struct context *ctx = context;
+    SHMCBHeader *header = ctx->header;
+    SHMCBSubcache *subcache = SHMCB_MASK(header, id);
+
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "socache_shmcb_store (0x%02x -> subcache %d)",
+                 SHMCB_MASK_DBG(header, id));
+    if (idlen < 4) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "unusably short session_id provided "
+                "(%u bytes)", idlen);
+        return APR_EINVAL;
+    }
+    if (shmcb_subcache_store(s, header, subcache, encoded,
+                             len_encoded, id, idlen, timeout)) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+                     "can't store a session!");
+        return APR_ENOSPC;
+    }
+    header->stat_stores++;
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "leaving socache_shmcb_store successfully");
+    return APR_SUCCESS;
+}
+
+static apr_status_t socache_shmcb_retrieve(void *context, server_rec *s, 
+                                              const unsigned char *id, unsigned int idlen,
+                                              unsigned char *dest, unsigned int *destlen,
+                                              apr_pool_t *p)
+{
+    struct context *ctx = context;
+    SHMCBHeader *header = ctx->header;
+    SHMCBSubcache *subcache = SHMCB_MASK(header, id);
+    int rv;
+
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "socache_shmcb_retrieve (0x%02x -> subcache %d)",
+                 SHMCB_MASK_DBG(header, id));
+
+    /* Get the session corresponding to the session_id, if it exists. */
+    rv = shmcb_subcache_retrieve(s, header, subcache, id, idlen,
+                                 dest, destlen);
+    if (rv == 0)
+        header->stat_retrieves_hit++;
+    else
+        header->stat_retrieves_miss++;
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "leaving socache_shmcb_retrieve successfully");
+
+    return rv == 0 ? APR_SUCCESS : APR_EGENERAL;
+}
+
+static void socache_shmcb_remove(void *context, server_rec *s, 
+                                    const unsigned char *id, unsigned int idlen,
+                                    apr_pool_t *p)
+{
+    struct context *ctx = context;
+    SHMCBHeader *header = ctx->header;
+    SHMCBSubcache *subcache = SHMCB_MASK(header, id);
+
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "socache_shmcb_remove (0x%02x -> subcache %d)",
+                 SHMCB_MASK_DBG(header, id));
+    if (idlen < 4) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "unusably short session_id provided "
+                "(%u bytes)", idlen);
+        return;
+    }
+    if (shmcb_subcache_remove(s, header, subcache, id, idlen))
+        header->stat_removes_hit++;
+    else
+        header->stat_removes_miss++;
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "leaving socache_shmcb_remove successfully");
+}
+
+static void socache_shmcb_status(void *context, request_rec *r, int flags)
+{
+    server_rec *s = r->server;
+    struct context *ctx = context;
+    SHMCBHeader *header = ctx->header;
+    unsigned int loop, total = 0, cache_total = 0, non_empty_subcaches = 0;
+    time_t idx_expiry, min_expiry = 0, max_expiry = 0, average_expiry = 0;
+    time_t now = time(NULL);
+    double expiry_total = 0;
+    int index_pct, cache_pct;
+
+    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "inside shmcb_status");
+    /* Perform the iteration inside the mutex to avoid corruption or invalid
+     * pointer arithmetic. The rest of our logic uses read-only header data so
+     * doesn't need the lock. */
+    /* Iterate over the subcaches */
+    for (loop = 0; loop < header->subcache_num; loop++) {
+        SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop);
+        shmcb_subcache_expire(s, header, subcache);
+        total += subcache->idx_used;
+        cache_total += subcache->data_used;
+        if (subcache->idx_used) {
+            SHMCBIndex *idx = SHMCB_INDEX(subcache, subcache->idx_pos);
+            non_empty_subcaches++;
+            idx_expiry = idx->expires;
+            expiry_total += (double)idx_expiry;
+            max_expiry = ((idx_expiry > max_expiry) ? idx_expiry : max_expiry);
+            if (!min_expiry)
+                min_expiry = idx_expiry;
+            else
+                min_expiry = ((idx_expiry < min_expiry) ? idx_expiry : min_expiry);
+        }
+    }
+    index_pct = (100 * total) / (header->index_num *
+                                 header->subcache_num);
+    cache_pct = (100 * cache_total) / (header->subcache_data_size *
+                                       header->subcache_num);
+    /* Generate HTML */
+    ap_rprintf(r, "cache type: <b>SHMCB</b>, shared memory: <b>%" APR_SIZE_T_FMT "</b> "
+               "bytes, current sessions: <b>%d</b><br>",
+               ctx->shm_size, total);
+    ap_rprintf(r, "subcaches: <b>%d</b>, indexes per subcache: <b>%d</b><br>",
+               header->subcache_num, header->index_num);
+    if (non_empty_subcaches) {
+        average_expiry = (time_t)(expiry_total / (double)non_empty_subcaches);
+        ap_rprintf(r, "time left on oldest entries' SSL sessions: ");
+        if (now < average_expiry)
+            ap_rprintf(r, "avg: <b>%d</b> seconds, (range: %d...%d)<br>",
+                       (int)(average_expiry - now),
+                       (int)(min_expiry - now),
+                       (int)(max_expiry - now));
+        else
+            ap_rprintf(r, "expiry_threshold: <b>Calculation error!</b><br>");
+    }
+
+    ap_rprintf(r, "index usage: <b>%d%%</b>, cache usage: <b>%d%%</b><br>",
+               index_pct, cache_pct);
+    ap_rprintf(r, "total sessions stored since starting: <b>%lu</b><br>",
+               header->stat_stores);
+    ap_rprintf(r, "total sessions expired since starting: <b>%lu</b><br>",
+               header->stat_expiries);
+    ap_rprintf(r, "total (pre-expiry) sessions scrolled out of the cache: "
+               "<b>%lu</b><br>", header->stat_scrolled);
+    ap_rprintf(r, "total retrieves since starting: <b>%lu</b> hit, "
+               "<b>%lu</b> miss<br>", header->stat_retrieves_hit,
+               header->stat_retrieves_miss);
+    ap_rprintf(r, "total removes since starting: <b>%lu</b> hit, "
+               "<b>%lu</b> miss<br>", header->stat_removes_hit,
+               header->stat_removes_miss);
+    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "leaving shmcb_status");
+}
+
+/*
+ * Subcache-level cache operations 
+ */
+
+static void shmcb_subcache_expire(server_rec *s, SHMCBHeader *header,
+                                  SHMCBSubcache *subcache)
+{
+    time_t now = time(NULL);
+    unsigned int loop = 0;
+    unsigned int new_idx_pos = subcache->idx_pos;
+    SHMCBIndex *idx = NULL;
+
+    while (loop < subcache->idx_used) {
+        idx = SHMCB_INDEX(subcache, new_idx_pos);
+        if (idx->expires > now)
+            /* it hasn't expired yet, we're done iterating */
+            break;
+        loop++;
+        new_idx_pos = SHMCB_CYCLIC_INCREMENT(new_idx_pos, 1, header->index_num);
+    }
+    if (!loop)
+        /* Nothing to do */
+        return;
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "will be expiring %u sessions", loop);
+    if (loop == subcache->idx_used) {
+        /* We're expiring everything, piece of cake */
+        subcache->idx_used = 0;
+        subcache->data_used = 0;
+    } else {
+        /* There remain other indexes, so we can use idx to adjust 'data' */
+        unsigned int diff = SHMCB_CYCLIC_SPACE(subcache->data_pos,
+                                               idx->data_pos,
+                                               header->subcache_data_size);
+        /* Adjust the indexes */
+        subcache->idx_used -= loop;
+        subcache->idx_pos = new_idx_pos;
+        /* Adjust the data area */
+        subcache->data_used -= diff;
+        subcache->data_pos = idx->data_pos;
+    }
+    header->stat_expiries += loop;
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "we now have %u sessions", subcache->idx_used);
+}
+
+static int shmcb_subcache_store(server_rec *s, SHMCBHeader *header,
+                                SHMCBSubcache *subcache, 
+                                unsigned char *data, unsigned int data_len,
+                                const unsigned char *id, unsigned int id_len,
+                                time_t expiry)
+{
+    unsigned int data_offset, new_idx, id_offset;
+    SHMCBIndex *idx;
+    unsigned int total_len = id_len + data_len;
+
+    /* Sanity check the input */
+    if (total_len > header->subcache_data_size) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+                     "inserting session larger (%d) than subcache data area (%d)",
+                     total_len, header->subcache_data_size);
+        return -1;
+    }
+
+    /* If there are entries to expire, ditch them first. */
+    shmcb_subcache_expire(s, header, subcache);
+
+    /* Loop until there is enough space to insert */
+    if (header->subcache_data_size - subcache->data_used < total_len
+        || subcache->idx_used == header->index_num) {
+        unsigned int loop = 0;
+
+        idx = SHMCB_INDEX(subcache, subcache->idx_pos);
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                     "about to force-expire, subcache: idx_used=%d, "
+                     "data_used=%d", subcache->idx_used, subcache->data_used);
+        do {
+            SHMCBIndex *idx2;
+
+            /* Adjust the indexes by one */
+            subcache->idx_pos = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, 1,
+                                                       header->index_num);
+            subcache->idx_used--;
+            if (!subcache->idx_used) {
+                /* There's nothing left */
+                subcache->data_used = 0;
+                break;
+            }
+            /* Adjust the data */
+            idx2 = SHMCB_INDEX(subcache, subcache->idx_pos);
+            subcache->data_used -= SHMCB_CYCLIC_SPACE(idx->data_pos, idx2->data_pos,
+                                                      header->subcache_data_size);
+            subcache->data_pos = idx2->data_pos;
+            /* Stats */
+            header->stat_scrolled++;
+            /* Loop admin */
+            idx = idx2;
+            loop++;
+        } while (header->subcache_data_size - subcache->data_used < total_len);
+
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                     "finished force-expire, subcache: idx_used=%d, "
+                     "data_used=%d", subcache->idx_used, subcache->data_used);
+    }
+
+    /* HERE WE ASSUME THAT THE NEW SESSION SHOULD GO ON THE END! I'M NOT
+     * CHECKING WHETHER IT SHOULD BE GENUINELY "INSERTED" SOMEWHERE.
+     *
+     * We either fix that, or find out at a "higher" (read "mod_ssl")
+     * level whether it is possible to have distinct session caches for
+     * any attempted tomfoolery to do with different session timeouts.
+     * Knowing in advance that we can have a cache-wide constant timeout
+     * would make this stuff *MUCH* more efficient. Mind you, it's very
+     * efficient right now because I'm ignoring this problem!!!
+     */
+    /* Insert the id */
+    id_offset = SHMCB_CYCLIC_INCREMENT(subcache->data_pos, subcache->data_used,
+                                       header->subcache_data_size);
+    shmcb_cyclic_ntoc_memcpy(header->subcache_data_size,
+                             SHMCB_DATA(header, subcache), id_offset,
+                             id, id_len);
+    subcache->data_used += id_len;
+    /* Insert the data */
+    data_offset = SHMCB_CYCLIC_INCREMENT(subcache->data_pos, subcache->data_used,
+                                         header->subcache_data_size);
+    shmcb_cyclic_ntoc_memcpy(header->subcache_data_size,
+                             SHMCB_DATA(header, subcache), data_offset,
+                             data, data_len);
+    subcache->data_used += data_len;
+    /* Insert the index */
+    new_idx = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, subcache->idx_used,
+                                     header->index_num);
+    idx = SHMCB_INDEX(subcache, new_idx);
+    idx->expires = expiry;
+    idx->data_pos = id_offset;
+    idx->data_used = total_len;
+    idx->id_len = id_len;
+    idx->removed = 0;
+    subcache->idx_used++;
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "insert happened at idx=%d, data=(%u:%u)", new_idx, 
+                 id_offset, data_offset);
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "finished insert, subcache: idx_pos/idx_used=%d/%d, "
+                 "data_pos/data_used=%d/%d",
+                 subcache->idx_pos, subcache->idx_used,
+                 subcache->data_pos, subcache->data_used);
+    return 0;
+}
+
+static int shmcb_subcache_retrieve(server_rec *s, SHMCBHeader *header,
+                                   SHMCBSubcache *subcache, 
+                                   const unsigned char *id, unsigned int idlen,
+                                   unsigned char *dest, unsigned int *destlen)
+{
+    unsigned int pos;
+    unsigned int loop = 0;
+
+    /* If there are entries to expire, ditch them first. */
+    shmcb_subcache_expire(s, header, subcache);
+    pos = subcache->idx_pos;
+
+    while (loop < subcache->idx_used) {
+        SHMCBIndex *idx = SHMCB_INDEX(subcache, pos);
+
+        /* Only consider 'idx' if the id matches, and the "removed"
+         * flag isn't set; check the data length too to avoid a buffer
+         * overflow in case of corruption, which should be impossible,
+         * but it's cheap to be safe. */
+        if (!idx->removed
+            && idx->id_len == idlen && (idx->data_used - idx->id_len) < *destlen
+            && shmcb_cyclic_memcmp(header->subcache_data_size,
+                                   SHMCB_DATA(header, subcache),
+                                   idx->data_pos, id, idx->id_len) == 0) {
+            unsigned int data_offset;
+            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                         "match at idx=%d, data=%d", pos, idx->data_pos);
+
+            /* Find the offset of the data segment, after the id */
+            data_offset = SHMCB_CYCLIC_INCREMENT(idx->data_pos, 
+                                                 idx->id_len,
+                                                 header->subcache_data_size);
+
+            *destlen = idx->data_used - idx->id_len;
+
+            /* Copy out the data */
+            shmcb_cyclic_cton_memcpy(header->subcache_data_size,
+                                     dest, SHMCB_DATA(header, subcache),
+                                     data_offset, *destlen);
+
+            return 0;
+        }
+        /* Increment */
+        loop++;
+        pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num);
+    }
+
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "shmcb_subcache_retrieve found no match");
+    return -1;
+
+}
+
+static int shmcb_subcache_remove(server_rec *s, SHMCBHeader *header,
+                                 SHMCBSubcache *subcache,
+                                 const unsigned char *id, unsigned int idlen)
+{
+    unsigned int pos;
+    unsigned int loop = 0;
+
+    /* Unlike the others, we don't do an expire-run first. This is to keep
+     * consistent statistics where a "remove" operation may actually be the
+     * higher layer spotting an expiry issue prior to us. Our caller is
+     * handling stats, so a failure return would be inconsistent if the
+     * intended session was in fact removed by an expiry run. */
+
+    pos = subcache->idx_pos;
+    while (loop < subcache->idx_used) {
+        SHMCBIndex *idx = SHMCB_INDEX(subcache, pos);
+
+        /* Only consider 'idx' if the id matches, and the "removed"
+         * flag isn't set. */
+        if (!idx->removed && idx->id_len == idlen
+            && shmcb_cyclic_memcmp(header->subcache_data_size,
+                                   SHMCB_DATA(header, subcache),
+                                   idx->data_pos, id, idx->id_len) == 0) {
+            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                         "possible match at idx=%d, data=%d", pos, idx->data_pos);
+            /* Found the matching session, remove it quietly. */
+            idx->removed = 1;
+            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                             "shmcb_subcache_remove removing matching session");
+            return 0;
+        }
+        /* Increment */
+        loop++;
+        pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num);
+    }
+
+    return -1; /* failure */
+}
+
+static const ap_socache_provider_t socache_shmcb = {
+    "shmcb",
+    AP_SOCACHE_FLAG_NOTMPSAFE,
+    socache_shmcb_create,
+    socache_shmcb_init,
+    socache_shmcb_kill,
+    socache_shmcb_store,
+    socache_shmcb_retrieve,
+    socache_shmcb_remove,
+    socache_shmcb_status
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+    ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "shmcb", 
+                         AP_SOCACHE_PROVIDER_VERSION,
+                         &socache_shmcb);
+}
+
+const module AP_MODULE_DECLARE_DATA socache_shmcb_module = {
+    STANDARD20_MODULE_STUFF,
+    NULL, NULL, NULL, NULL, NULL,
+    register_hooks
+};