From: Joe Orton Date: Tue, 8 Apr 2008 10:47:04 +0000 (+0000) Subject: Session cache interface redesign, Part 8: X-Git-Tag: 2.3.0~773 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=327993602850373b0b93b05d80753ae15f179bf8;p=thirdparty%2Fapache%2Fhttpd.git Session cache interface redesign, Part 8: 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 --- diff --git a/modules/cache/ap_socache.h b/modules/cache/ap_socache.h new file mode 100644 index 00000000000..7d89201ee67 --- /dev/null +++ b/modules/cache/ap_socache.h @@ -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 */ +/** @} */ diff --git a/modules/cache/config.m4 b/modules/cache/config.m4 index 9eae493c3b5..918b79f3640 100644 --- a/modules/cache/config.m4 +++ b/modules/cache/config.m4 @@ -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 ], +[#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 ], + [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 index 00000000000..5400d10a9cd --- /dev/null +++ b/modules/cache/mod_socache_dbm.c @@ -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 +#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: DBM, maximum size: unlimited
"); + ap_rprintf(r, "current sessions: %d, current size: %d bytes
", nElem, nSize); + ap_rprintf(r, "average session size: %d bytes
", 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 index 00000000000..204dd38a110 --- /dev/null +++ b/modules/cache/mod_socache_dc.c @@ -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: DC (Distributed Cache), " + " target: %s
", 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 index 00000000000..6c954d193a9 --- /dev/null +++ b/modules/cache/mod_socache_memcache.c @@ -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 index 00000000000..5226b7fb363 --- /dev/null +++ b/modules/cache/mod_socache_shmcb.c @@ -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 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: SHMCB, shared memory: %" APR_SIZE_T_FMT " " + "bytes, current sessions: %d
", + ctx->shm_size, total); + ap_rprintf(r, "subcaches: %d, indexes per subcache: %d
", + 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: %d seconds, (range: %d...%d)
", + (int)(average_expiry - now), + (int)(min_expiry - now), + (int)(max_expiry - now)); + else + ap_rprintf(r, "expiry_threshold: Calculation error!
"); + } + + ap_rprintf(r, "index usage: %d%%, cache usage: %d%%
", + index_pct, cache_pct); + ap_rprintf(r, "total sessions stored since starting: %lu
", + header->stat_stores); + ap_rprintf(r, "total sessions expired since starting: %lu
", + header->stat_expiries); + ap_rprintf(r, "total (pre-expiry) sessions scrolled out of the cache: " + "%lu
", header->stat_scrolled); + ap_rprintf(r, "total retrieves since starting: %lu hit, " + "%lu miss
", header->stat_retrieves_hit, + header->stat_retrieves_miss); + ap_rprintf(r, "total removes since starting: %lu hit, " + "%lu miss
", 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 +};