text: /* 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.
   */
  
  /*                      _             _
   *  _ __ ___   ___   __| |    ___ ___| |  mod_ssl
   * | '_ ` _ \ / _ \ / _` |   / __/ __| |  Apache Interface to OpenSSL
   * | | | | | | (_) | (_| |   \__ \__ \ |
   * |_| |_| |_|\___/ \__,_|___|___/___/_|
   *                      |_____|
   *  ssl_scache_dbm.c
   *  Session Cache via DBM
   */
  
  #include "ssl_private.h"
  
  static void ssl_scache_dbm_expire(server_rec *s);
  
  void ssl_scache_dbm_init(server_rec *s, apr_pool_t *p)
  {
      SSLModConfigRec *mc = myModConfig(s);
      apr_dbm_t *dbm;
      apr_status_t rv;
  
      /* for the DBM we need the data file */
      if (mc->szSessionCacheDataFile == NULL) {
          ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
                       "SSLSessionCache required");
          ssl_die();
      }
  
      /* open it once to create it and to make sure it _can_ be created */
      ssl_mutex_on(s);
      if ((rv = apr_dbm_open(&dbm, mc->szSessionCacheDataFile,
              APR_DBM_RWCREATE, SSL_DBM_FILE_MODE, mc->pPool)) != APR_SUCCESS) {
          ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                       "Cannot create SSLSessionCache DBM file `%s'",
                       mc->szSessionCacheDataFile);
          ssl_mutex_off(s);
          return;
      }
      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(mc->szSessionCacheDataFile, unixd_config.user_id, -1 /* no gid change */);
          if (chown(apr_pstrcat(p, mc->szSessionCacheDataFile, SSL_DBM_FILE_SUFFIX_DIR, NULL),
                    unixd_config.user_id, -1) == -1) {
              if (chown(apr_pstrcat(p, mc->szSessionCacheDataFile, ".db", NULL),
                        unixd_config.user_id, -1) == -1)
                  chown(apr_pstrcat(p, mc->szSessionCacheDataFile, ".dir", NULL),
                        unixd_config.user_id, -1);
          }
          if (chown(apr_pstrcat(p, mc->szSessionCacheDataFile, SSL_DBM_FILE_SUFFIX_PAG, NULL),
                    unixd_config.user_id, -1) == -1) {
              if (chown(apr_pstrcat(p, mc->szSessionCacheDataFile, ".db", NULL),
                        unixd_config.user_id, -1) == -1)
                  chown(apr_pstrcat(p, mc->szSessionCacheDataFile, ".pag", NULL),
                        unixd_config.user_id, -1);
          }
      }
  #endif
      ssl_mutex_off(s);
      ssl_scache_dbm_expire(s);
      return;
  }
  
  void ssl_scache_dbm_kill(server_rec *s)
  {
      SSLModConfigRec *mc = myModConfig(s);
      apr_pool_t *p;
  
      apr_pool_create_ex(&p, mc->pPool, NULL, NULL);
      if (p != NULL) {
          /* the correct way */
          unlink(apr_pstrcat(p, mc->szSessionCacheDataFile, SSL_DBM_FILE_SUFFIX_DIR, NULL));
          unlink(apr_pstrcat(p, mc->szSessionCacheDataFile, SSL_DBM_FILE_SUFFIX_PAG, NULL));
          /* the additional ways to be sure */
          unlink(apr_pstrcat(p, mc->szSessionCacheDataFile, ".dir", NULL));
          unlink(apr_pstrcat(p, mc->szSessionCacheDataFile, ".pag", NULL));
          unlink(apr_pstrcat(p, mc->szSessionCacheDataFile, ".db", NULL));
          unlink(mc->szSessionCacheDataFile);
          apr_pool_destroy(p);
      }
      return;
  }
  
  BOOL ssl_scache_dbm_store(server_rec *s, UCHAR *id, int idlen, time_t expiry, SSL_SESSION *sess)
  {
      SSLModConfigRec *mc = myModConfig(s);
      apr_dbm_t *dbm;
      apr_datum_t dbmkey;
      apr_datum_t dbmval;
      UCHAR ucaData[SSL_SESSION_MAX_DER];
      int nData;
      UCHAR *ucp;
      apr_status_t rv;
  
      /* streamline session data */
      if ((nData = i2d_SSL_SESSION(sess, NULL)) > sizeof(ucaData)) {
          ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
                       "streamline session data size too large: %d > "
                       "%" APR_SIZE_T_FMT,
                       nData, sizeof(ucaData));
          return FALSE;
      }
      ucp = ucaData;
      i2d_SSL_SESSION(sess, &ucp);
  
      /* 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 FALSE;
      }
  #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 FALSE;
      }
  #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 FALSE;
      }
      memcpy((char *)dbmval.dptr, &expiry, sizeof(time_t));
      memcpy((char *)dbmval.dptr+sizeof(time_t), ucaData, nData);
  
      /* and store it to the DBM file */
      ssl_mutex_on(s);
      if ((rv = apr_dbm_open(&dbm, mc->szSessionCacheDataFile,
              APR_DBM_RWCREATE, SSL_DBM_FILE_MODE, mc->pPool)) != APR_SUCCESS) {
          ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                       "Cannot open SSLSessionCache DBM file `%s' for writing "
                       "(store)",
                       mc->szSessionCacheDataFile);
          ssl_mutex_off(s);
          free(dbmval.dptr);
          return FALSE;
      }
      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'",
                       mc->szSessionCacheDataFile);
          apr_dbm_close(dbm);
          ssl_mutex_off(s);
          free(dbmval.dptr);
          return FALSE;
      }
      apr_dbm_close(dbm);
      ssl_mutex_off(s);
  
      /* free temporary buffers */
      free(dbmval.dptr);
  
      /* allow the regular expiring to occur */
      ssl_scache_dbm_expire(s);
  
      return TRUE;
  }
  
  SSL_SESSION *ssl_scache_dbm_retrieve(server_rec *s, UCHAR *id, int idlen)
  {
      SSLModConfigRec *mc = myModConfig(s);
      apr_dbm_t *dbm;
      apr_datum_t dbmkey;
      apr_datum_t dbmval;
      SSL_SESSION *sess = NULL;
      MODSSL_D2I_SSL_SESSION_CONST unsigned char *ucpData;
      int nData;
      time_t expiry;
      time_t now;
      apr_status_t rc;
  
      /* allow the regular expiring to occur */
      ssl_scache_dbm_expire(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.
       */
      ssl_mutex_on(s);
      if ((rc = apr_dbm_open(&dbm, mc->szSessionCacheDataFile,
              APR_DBM_RWCREATE, SSL_DBM_FILE_MODE, mc->pPool)) != APR_SUCCESS) {
          ap_log_error(APLOG_MARK, APLOG_ERR, rc, s,
                       "Cannot open SSLSessionCache DBM file `%s' for reading "
                       "(fetch)",
                       mc->szSessionCacheDataFile);
          ssl_mutex_off(s);
          return NULL;
      }
      rc = apr_dbm_fetch(dbm, dbmkey, &dbmval);
      if (rc != APR_SUCCESS) {
          apr_dbm_close(dbm);
          ssl_mutex_off(s);
          return NULL;
      }
      if (dbmval.dptr == NULL || dbmval.dsize <= sizeof(time_t)) {
          apr_dbm_close(dbm);
          ssl_mutex_off(s);
          return NULL;
      }
  
      /* parse resulting data */
      nData = dbmval.dsize-sizeof(time_t);
      ucpData = malloc(nData);
      if (ucpData == NULL) {
          apr_dbm_close(dbm);
          ssl_mutex_off(s);
          return NULL;
      }
      /* Cast needed, ucpData may be const */
      memcpy((unsigned char *)ucpData,
             (char *)dbmval.dptr + sizeof(time_t), nData);
      memcpy(&expiry, dbmval.dptr, sizeof(time_t));
  
      apr_dbm_close(dbm);
      ssl_mutex_off(s);
  
      /* make sure the stuff is still not expired */
      now = time(NULL);
      if (expiry <= now) {
          ssl_scache_dbm_remove(s, id, idlen);
          return NULL;
      }
  
      /* unstreamed SSL_SESSION */
      sess = d2i_SSL_SESSION(NULL, &ucpData, nData);
  
      return sess;
  }
  
  void ssl_scache_dbm_remove(server_rec *s, UCHAR *id, int idlen)
  {
      SSLModConfigRec *mc = myModConfig(s);
      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 */
      ssl_mutex_on(s);
      if ((rv = apr_dbm_open(&dbm, mc->szSessionCacheDataFile,
              APR_DBM_RWCREATE, SSL_DBM_FILE_MODE, mc->pPool)) != APR_SUCCESS) {
          ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                       "Cannot open SSLSessionCache DBM file `%s' for writing "
                       "(delete)",
                       mc->szSessionCacheDataFile);
          ssl_mutex_off(s);
          return;
      }
      apr_dbm_delete(dbm, dbmkey);
      apr_dbm_close(dbm);
      ssl_mutex_off(s);
  
      return;
  }
  
  static void ssl_scache_dbm_expire(server_rec *s)
  {
      SSLModConfigRec *mc = myModConfig(s);
      SSLSrvConfigRec *sc = mySrvConfig(s);
      static time_t tLast = 0;
      apr_dbm_t *dbm;
      apr_datum_t dbmkey;
      apr_datum_t dbmval;
      apr_pool_t *p;
      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 < tLast+sc->session_cache_timeout)
          return;
      tLast = 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
  
      ssl_mutex_on(s);
      for (;;) {
          /* allocate the key array in a memory sub pool */
          apr_pool_create_ex(&p, mc->pPool, NULL, NULL);
          if (p == NULL)
              break;
          if ((keylist = apr_palloc(p, sizeof(dbmkey)*KEYMAX)) == NULL) {
              apr_pool_destroy(p);
              break;
          }
  
          /* pass 1: scan DBM database */
          keyidx = 0;
          if ((rv = apr_dbm_open(&dbm, mc->szSessionCacheDataFile,
                                 APR_DBM_RWCREATE,SSL_DBM_FILE_MODE,
                                 p)) != APR_SUCCESS) {
              ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                           "Cannot open SSLSessionCache DBM file `%s' for "
                           "scanning",
                           mc->szSessionCacheDataFile);
              apr_pool_destroy(p);
              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(p, 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, mc->szSessionCacheDataFile,
                  APR_DBM_RWCREATE,SSL_DBM_FILE_MODE, p) != APR_SUCCESS) {
              ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
                           "Cannot re-open SSLSessionCache DBM file `%s' for "
                           "expiring",
                           mc->szSessionCacheDataFile);
              apr_pool_destroy(p);
              break;
          }
          for (i = 0; i < keyidx; i++) {
              apr_dbm_delete(dbm, keylist[i]);
              nDeleted++;
          }
          apr_dbm_close(dbm);
  
          /* destroy temporary pool */
          apr_pool_destroy(p);
  
          if (keyidx < KEYMAX)
              break;
      }
      ssl_mutex_off(s);
  
      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);
      return;
  }
  
  void ssl_scache_dbm_status(request_rec *r, int flags, apr_pool_t *p)
  {
      SSLModConfigRec *mc = myModConfig(r->server);
      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;
      ssl_mutex_on(r->server);
      /*
       * XXX - Check what pool is to be used - TBD
       */
      if ((rv = apr_dbm_open(&dbm, mc->szSessionCacheDataFile,
                                 APR_DBM_RWCREATE, SSL_DBM_FILE_MODE,
                             mc->pPool)) != APR_SUCCESS) {
          ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                       "Cannot open SSLSessionCache DBM file `%s' for status "
                       "retrival",
                       mc->szSessionCacheDataFile);
          ssl_mutex_off(r->server);
          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);
      ssl_mutex_off(r->server);
      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;
  }
  
  