#include <sys/types.h>
#include <unistd.h>

#include "types.h"
#include "mpool.h"
#include "tktree.h"
#include "clstree.h"
#include "sha1.h"
#include "listen.h"
#include "ssl_conf.h"
#include "rovm.h"

#include "thread.h"
#include "thread_mutex.h"
#include "thread_cond.h"

#include "mpm_worker_fdqueue.h"
#include "mpm_worker_pod.h"
#include "mpm_worker.h"

#include "connection.h"
#include "request.h"
#include "proc_rc.h"

#include "log.h"
#include "common.h"
#include "ticket.h"

/*
 * Handle the Temporary RSA Keys and DH Params
 */

#define MODSSL_TMP_KEY_FREE(mc, type, idx) \
    if (mc->pTmpKeys[idx]) { \
        type##_free((type *)mc->pTmpKeys[idx]); \
        mc->pTmpKeys[idx] = NULL; \
    }

#define MODSSL_TMP_KEYS_FREE(mc, type) \
    MODSSL_TMP_KEY_FREE(mc, type, SSL_TMP_KEY_##type##_512); \
    MODSSL_TMP_KEY_FREE(mc, type, SSL_TMP_KEY_##type##_1024)

static void 
ssl_tmp_keys_free (struct rovm *r)
{
  SSLModConfigRec *mc = myModConfig (r);
  
  MODSSL_TMP_KEYS_FREE(mc, RSA);
  MODSSL_TMP_KEYS_FREE(mc, DH);
}

#define MODSSL_CFG_ITEM_FREE(func, item) \
    if (item) { \
        func(item); \
        item = NULL; \
    }

static void 
ssl_init_ctx_cleanup (modssl_ctx_t *mctx)
{
  MODSSL_CFG_ITEM_FREE(X509_STORE_free, mctx->crl);
  
  MODSSL_CFG_ITEM_FREE(SSL_CTX_free, mctx->ssl_ctx);
}

static void
ssl_init_ctx_cleanup_proxy (modssl_ctx_t *mctx)
{
  ssl_init_ctx_cleanup (mctx);
  
  if (mctx->pkp->certs) 
    {
      sk_X509_INFO_pop_free(mctx->pkp->certs, X509_INFO_free);
    }
}

static void
ssl_init_ctx_cleanup_server (modssl_ctx_t *mctx)
{
  int i;
  
  ssl_init_ctx_cleanup (mctx);
  
  for (i=0; i < SSL_AIDX_MAX; i++) 
    {
      MODSSL_CFG_ITEM_FREE(X509_free, mctx->pks->certs[i]);
      
      MODSSL_CFG_ITEM_FREE(EVP_PKEY_free, mctx->pks->keys[i]);
    }
}

rc_status_t 
ssl_init_ModuleKill (void *data)
{
  SSLSrvConfigRec *sc;
  struct rovm *r = (struct rovm *) data;

  /* Drop the session cache and mutex.  */
  ssl_scache_kill (r);

  /* Destroy the temporary keys and params.  */
  ssl_tmp_keys_free (r);

  /*
   * Free the non-pool allocated structures
   * in the per-server configurations
   */
  sc = mySrvConfig (r);
  
  ssl_init_ctx_cleanup_proxy (sc->proxy);
  
  ssl_init_ctx_cleanup_server (sc->server);

  return RC_SUCCESS;
}

/*
 * Support for external a Crypto Device ("engine"), usually
 * a hardware accellerator card for crypto operations.
 */
#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT)
void 
ssl_init_Engine (struct rovm *r, rc_pool_t *p)
{
  SSLModConfigRec *mc = myModConfig (r);
  ENGINE *e;
  
  if (mc->szCryptoDevice) 
    {
      if (!(e = ENGINE_by_id (mc->szCryptoDevice))) 
        {
          rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                    "Init: Failed to load Crypto Device API `%s'",
                    mc->szCryptoDevice);
          ssl_log_ssl_error (ROVMLOG_MARK, ROVMLOG_ERR, r);
          ssl_die ();
        }
      
      if (strEQ(mc->szCryptoDevice, "chil")) {
        ENGINE_ctrl(e, ENGINE_CTRL_CHIL_SET_FORKCHECK, 1, 0, 0);
      }

      if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) 
        {
          rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                    "Init: Failed to enable Crypto Device API `%s'",
                    mc->szCryptoDevice);
          ssl_log_ssl_error (ROVMLOG_MARK, ROVMLOG_ERR, r);
          ssl_die ();
        }
      
      ENGINE_free (e);
    }
}
#endif

static int
ssl_tmp_key_init_rsa(struct rovm *r,
                     int bits, int idx)
{
  SSLModConfigRec *mc = myModConfig (r);
  
  if (!(mc->pTmpKeys[idx] =
        RSA_generate_key(bits, RSA_F4, NULL, NULL)))
    {
      rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                "Init: Failed to generate temporary "
                "%d bit RSA private key", bits);
      return -1;
    }
  
  return 0;
}

static int 
ssl_tmp_key_init_dh (struct rovm *r,
                     int bits, int idx)
{
  SSLModConfigRec *mc = myModConfig(r);
  
  if (!(mc->pTmpKeys[idx] = ssl_dh_GetTmpParam(bits)))
    {
      rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                "Init: Failed to generate temporary "
                "%d bit DH parameters", bits);
      return -1;
    }
  
  return 0;
}

#define MODSSL_TMP_KEY_INIT_RSA(s, bits) \
    ssl_tmp_key_init_rsa (s, bits, SSL_TMP_KEY_RSA_##bits)

#define MODSSL_TMP_KEY_INIT_DH(s, bits) \
    ssl_tmp_key_init_dh (s, bits, SSL_TMP_KEY_DH_##bits)

static int 
ssl_tmp_keys_init (struct rovm *r)
{
  rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
            "Init: Generating temporary RSA private keys (512/1024 bits)");

  if (MODSSL_TMP_KEY_INIT_RSA(r, 512) || MODSSL_TMP_KEY_INIT_RSA(r, 1024))
    return -1;

  rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
            "Init: Generating temporary DH parameters (512/1024 bits)");
  
  if (MODSSL_TMP_KEY_INIT_DH(r, 512) || MODSSL_TMP_KEY_INIT_DH(r, 1024))
    return -1;

  return 0;
}

static void 
ssl_init_ctx_protocol(struct rovm *r,
                      rc_pool_t *p,
                      rc_pool_t *ptemp,
                      modssl_ctx_t *mctx)
{
  SSL_CTX *ctx = NULL;
  SSL_METHOD *method = NULL;
  char *cp;
  int protocol = mctx->protocol;
  
  /*
   *  Create the new per-server SSL context
   */
  if (protocol == SSL_PROTOCOL_NONE) 
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "No SSL protocols available [hint: SSLProtocol]");
      ssl_die();
    }
  
  cp = apr_pstrcat(p,
                   (protocol & SSL_PROTOCOL_SSLV2 ? "SSLv2, " : ""),
                   (protocol & SSL_PROTOCOL_SSLV3 ? "SSLv3, " : ""),
                   (protocol & SSL_PROTOCOL_TLSV1 ? "TLSv1, " : ""),
                   NULL);
  cp[strlen(cp)-2] = NUL;
  
  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
            "Creating new SSL context (protocols: %s)", cp);
  
  if (protocol == SSL_PROTOCOL_SSLV2) 
    {
      method = mctx->pkp ?
        SSLv2_client_method() : /* proxy */
        SSLv2_server_method();  /* server */
      ctx = SSL_CTX_new(method);  /* only SSLv2 is left */
    }
  else 
    {
      method = mctx->pkp ?
        SSLv23_client_method() : /* proxy */
        SSLv23_server_method();  /* server */
      ctx = SSL_CTX_new(method); /* be more flexible */
    }
  mctx->ssl_ctx = ctx;
  
  SSL_CTX_set_options (ctx, SSL_OP_ALL);
  
  if (!(protocol & SSL_PROTOCOL_SSLV2)) {
    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
  }
  
  if (!(protocol & SSL_PROTOCOL_SSLV3)) {
    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3);
  }
  
  if (!(protocol & SSL_PROTOCOL_TLSV1)) {
    SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1);
  }
  
#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
  {
    SSLSrvConfigRec *sc = mySrvConfig(r);
    if (sc->cipher_server_pref == TRUE) {
      SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
    }
  }
#endif
  
  SSL_CTX_set_app_data(ctx, r);
  
  /*
   * Configure additional context ingredients
   */
  SSL_CTX_set_options (ctx, SSL_OP_SINGLE_DH_USE);
  
#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
  /*
   * Disallow a session from being resumed during a renegotiation,
   * so that an acceptable cipher suite can be negotiated.
   */
  SSL_CTX_set_options(ctx, SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
#endif
}

static void 
ssl_init_ctx_session_cache(struct rovm *r,
                           rc_pool_t *p,
                           rc_pool_t *ptemp,
                           modssl_ctx_t *mctx)
{
  SSL_CTX *ctx = mctx->ssl_ctx;
  SSLModConfigRec *mc = myModConfig(r);
  long cache_mode = SSL_SESS_CACHE_OFF;
  if (mc->nSessionCacheMode != SSL_SCMODE_NONE) 
    {
      /* SSL_SESS_CACHE_NO_INTERNAL will force OpenSSL
       * to ignore process local-caching and
       * to always get/set/delete sessions using mod_ssl's callbacks.
       */
      cache_mode = SSL_SESS_CACHE_SERVER|SSL_SESS_CACHE_NO_INTERNAL;
    }
  
  SSL_CTX_set_session_cache_mode (ctx, cache_mode);
  
  SSL_CTX_sess_set_new_cb(ctx,    ssl_callback_NewSessionCacheEntry);
  SSL_CTX_sess_set_get_cb(ctx,    ssl_callback_GetSessionCacheEntry);
  SSL_CTX_sess_set_remove_cb(ctx, ssl_callback_DelSessionCacheEntry);
}

#ifdef SSLC_VERSION_NUMBER
static int 
ssl_init_FindCAList_X509NameCmp(char **a, char **b)
{
  return(X509_NAME_cmp((void*)*a, (void*)*b));
}
#else
static int
ssl_init_FindCAList_X509NameCmp(X509_NAME **a, X509_NAME **b)
{
  return(X509_NAME_cmp(*a, *b));
}
#endif

static void 
ssl_init_PushCAList(STACK_OF(X509_NAME) *ca_list,
                    struct rovm *r, const char *file)
{
  int n;
  STACK_OF(X509_NAME) *sk;
  
  sk = (STACK_OF(X509_NAME) *)
    SSL_load_client_CA_file(MODSSL_PCHAR_CAST file);
  
  if (!sk)
    return;

  for (n = 0; n < sk_X509_NAME_num(sk); n++) 
    {
      char name_buf[256];
      X509_NAME *name = sk_X509_NAME_value(sk, n);
      
      rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
                "CA certificate: %s",
                X509_NAME_oneline(name, name_buf, sizeof(name_buf)));
      
      /*
       * note that SSL_load_client_CA_file() checks for duplicates,
       * but since we call it multiple times when reading a directory
       * we must also check for duplicates ourselves.
       */
      
      if (sk_X509_NAME_find(ca_list, name) < 0) 
        {
          /* this will be freed when ca_list is */
          sk_X509_NAME_push(ca_list, name);
        }
      else 
        {
          /* need to free this ourselves, else it will leak */
          X509_NAME_free(name);
        }
    }
  
  sk_X509_NAME_free(sk);
}

STACK_OF(X509_NAME) *
ssl_init_FindCAList(struct rovm *r,
                    rc_pool_t *ptemp,
                    const char *ca_file,
                    const char *ca_path)
{
  STACK_OF(X509_NAME) *ca_list;
  
  /*
   * Start with a empty stack/list where new
   * entries get added in sorted order.
   */
  ca_list = sk_X509_NAME_new(ssl_init_FindCAList_X509NameCmp);
  
  /*
   * Process CA certificate bundle file
   */
  if (ca_file) {
    ssl_init_PushCAList(ca_list, r, ca_file);
  }
  
  /*
   * Process CA certificate path files
   */
  if (ca_path) 
    {
      apr_dir_t *dir;
      apr_finfo_t direntry;
      apr_int32_t finfo_flags = APR_FINFO_TYPE|APR_FINFO_NAME;
      apr_status_t rv;
      
      if ((rv = apr_dir_open(&dir, ca_path, ptemp)) != APR_SUCCESS) 
        {
          rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                    "Failed to open Certificate Path `%s'",
                    ca_path);
          ssl_die();
        }
      
      while ((apr_dir_read(&direntry, finfo_flags, dir)) == APR_SUCCESS) 
        {
          const char *file;
          if (direntry.filetype == APR_DIR)
            continue; /* don't try to load directories */
          
          file = apr_pstrcat(ptemp, ca_path, "/", direntry.name, NULL);
          ssl_init_PushCAList(ca_list, r, file);
        }
      
      apr_dir_close(dir);
    }
  
  /*
   * Cleanup
   */
  sk_X509_NAME_set_cmp_func(ca_list, NULL);
  
  return ca_list;
}

static void
ssl_init_ctx_callbacks (struct rovm *r,
                        rc_pool_t *p,
                        rc_pool_t *ptemp,
                        modssl_ctx_t *mctx)
{
  SSL_CTX *ctx = mctx->ssl_ctx;
  
  SSL_CTX_set_tmp_rsa_callback(ctx, ssl_callback_TmpRSA);
  SSL_CTX_set_tmp_dh_callback(ctx,  ssl_callback_TmpDH);
  
  if (CONF_LOGLEVEL (ROVM_CONF (r)) >= ROVMLOG_DEBUG) 
    {
      /* this callback only logs if LogLevel >= info */
      SSL_CTX_set_info_callback(ctx, ssl_callback_LogTracingState);
    }
}

static void 
ssl_init_ctx_verify(struct rovm *r,
                    rc_pool_t *p,
                    rc_pool_t *ptemp,
                    modssl_ctx_t *mctx)
{
  SSL_CTX *ctx = mctx->ssl_ctx;
  
  int verify = SSL_VERIFY_NONE;
  STACK_OF(X509_NAME) *ca_list;
  
  if (mctx->auth.verify_mode == SSL_CVERIFY_UNSET)
    mctx->auth.verify_mode = SSL_CVERIFY_NONE;
  
  if (mctx->auth.verify_depth == UNSET)
    mctx->auth.verify_depth = 1;
  
  /*
   *  Configure callbacks for SSL context
   */
  if (mctx->auth.verify_mode == SSL_CVERIFY_REQUIRE)
    verify |= SSL_VERIFY_PEER_STRICT;
  
  if ((mctx->auth.verify_mode == SSL_CVERIFY_OPTIONAL) ||
      (mctx->auth.verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA))
    verify |= SSL_VERIFY_PEER;
  
  SSL_CTX_set_verify(ctx, verify, ssl_callback_SSLVerify);
  
  
  /*
   * Configure Client Authentication details
   */
  if (mctx->auth.ca_cert_file || mctx->auth.ca_cert_path) 
    {
      rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
                "Configuring client authentication");
      
      if (!SSL_CTX_load_verify_locations(ctx,
                                         MODSSL_PCHAR_CAST mctx->auth.ca_cert_file,
                                         MODSSL_PCHAR_CAST mctx->auth.ca_cert_path))
        {
          rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                    "Unable to configure verify locations "
                    "for client authentication");
          ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_ERR, r);
          ssl_die();
        }
      
      if (mctx->pks && (mctx->pks->ca_name_file || mctx->pks->ca_name_path)) 
        {
          ca_list = ssl_init_FindCAList(r, ptemp,
                                        mctx->pks->ca_name_file,
                                        mctx->pks->ca_name_path);
        } 
      else
        ca_list = ssl_init_FindCAList(r, ptemp,
                                      mctx->auth.ca_cert_file,
                                      mctx->auth.ca_cert_path);
      if (!ca_list) 
        {
          rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                    "Unable to determine list of acceptable "
                    "CA certificates for client authentication");
          ssl_die();
        }
      
      SSL_CTX_set_client_CA_list(ctx, (STACK *)ca_list);
    }
  
  /*
   * Give a warning when no CAs were configured but client authentication
   * should take place. This cannot work.
   */
  if (mctx->auth.verify_mode == SSL_CVERIFY_REQUIRE) 
    {
      ca_list = (STACK_OF(X509_NAME) *)SSL_CTX_get_client_CA_list(ctx);
      
      if (sk_X509_NAME_num(ca_list) == 0) 
        {
          rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, 
                    "Init: Oops, you want to request client "
                    "authentication, but no CAs are known for "
                    "verification!?  [Hint: SSLCACertificate*]");
        }
    }
}

static void 
ssl_init_ctx_cipher_suite(struct rovm *r,
                          rc_pool_t *p,
                          rc_pool_t *ptemp,
                          modssl_ctx_t *mctx)
{
  SSL_CTX *ctx = mctx->ssl_ctx;
  const char *suite = mctx->auth.cipher_suite;
  
  /*
   *  Configure SSL Cipher Suite
   */
  if (!suite)
    return;

  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
            "Configuring permitted SSL ciphers [%s]",
            suite);
  
  if (!SSL_CTX_set_cipher_list(ctx, MODSSL_PCHAR_CAST suite)) 
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "Unable to configure permitted SSL ciphers");
      ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_ERR, r);
      ssl_die();
    }
}

static void 
ssl_init_ctx_crl(struct rovm *r,
                 rc_pool_t *p,
                 rc_pool_t *ptemp,
                 modssl_ctx_t *mctx)
{
  /*
   * Configure Certificate Revocation List (CRL) Details
   */
  
  if (!(mctx->crl_file || mctx->crl_path))
    return;

  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
            "Configuring certificate revocation facility");
  
  mctx->crl =
    SSL_X509_STORE_create((char *)mctx->crl_file,
                          (char *)mctx->crl_path);
  
  if (!mctx->crl) 
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "Unable to configure X.509 CRL storage "
                "for certificate revocation");
      ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_ERR, r);
      ssl_die();
    }
}

static void 
ssl_init_ctx_cert_chain(struct rovm *r,
                        rc_pool_t *p,
                        rc_pool_t *ptemp,
                        modssl_ctx_t *mctx)
{
  BOOL skip_first = FALSE;
  int i, n;
  const char *chain = mctx->cert_chain;
  
  /*
   * Optionally configure extra server certificate chain certificates.
   * This is usually done by OpenSSL automatically when one of the
   * server cert issuers are found under SSLCACertificatePath or in
   * SSLCACertificateFile. But because these are intended for client
   * authentication it can conflict. For instance when you use a
   * Global ID server certificate you've to send out the intermediate
   * CA certificate, too. When you would just configure this with
   * SSLCACertificateFile and also use client authentication mod_ssl
   * would accept all clients also issued by this CA. Obviously this
   * isn't what we want in this situation. So this feature here exists
   * to allow one to explicity configure CA certificates which are
   * used only for the server certificate chain.
   */
  if (!chain)
    return;

  for (i = 0; (i < SSL_AIDX_MAX) && mctx->pks->cert_files[i]; i++) 
    {
      if (strEQ(mctx->pks->cert_files[i], chain)) 
        {
          skip_first = TRUE;
          break;
        }
    }
  
  n = SSL_CTX_use_certificate_chain(mctx->ssl_ctx,
                                    (char *)chain,
                                    skip_first, NULL);
  if (n < 0) 
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "Failed to configure CA certificate chain!");
      ssl_die();
    }
  
  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
            "Configuring server certificate chain "
            "(%d CA certificate%s)",
            n, n == 1 ? "" : "s");
}

static void 
ssl_init_ctx(struct rovm *r,
             rc_pool_t *p,
             rc_pool_t *ptemp,
             modssl_ctx_t *mctx)
{
  ssl_init_ctx_protocol(r, p, ptemp, mctx);
  ssl_init_ctx_session_cache(r, p, ptemp, mctx);
  ssl_init_ctx_callbacks(r, p, ptemp, mctx);
  ssl_init_ctx_verify(r, p, ptemp, mctx);
  ssl_init_ctx_cipher_suite(r, p, ptemp, mctx);
  ssl_init_ctx_crl(r, p, ptemp, mctx);
  
  if (mctx->pks) 
    {
      /* XXX: proxy support? */
      ssl_init_ctx_cert_chain(r, p, ptemp, mctx);
    }
}

static void 
ssl_init_proxy_certs(struct rovm *r,
                     rc_pool_t *p,
                     rc_pool_t *ptemp,
                     modssl_ctx_t *mctx)
{
  int n, ncerts = 0;
  STACK_OF(X509_INFO) *sk;
  modssl_pk_proxy_t *pkp = mctx->pkp;
  
  SSL_CTX_set_client_cert_cb(mctx->ssl_ctx,
                             ssl_callback_proxy_cert);
  
  if (!(pkp->cert_file || pkp->cert_path))
    return;

  sk = sk_X509_INFO_new_null();
  
  if (pkp->cert_file) {
    SSL_X509_INFO_load_file(ptemp, sk, pkp->cert_file);
  }
  
  if (pkp->cert_path) {
    SSL_X509_INFO_load_path(ptemp, sk, pkp->cert_path);
  }
  
  if ((ncerts = sk_X509_INFO_num(sk)) <= 0) 
    {
      sk_X509_INFO_free(sk);
      rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, 
                "no client certs found for SSL proxy");
      return;
    }
  
  /* Check that all client certs have got certificates and private
   * keys. */
  for (n = 0; n < ncerts; n++) 
    {
      X509_INFO *inf = sk_X509_INFO_value(sk, n);
      
      if (!inf->x509 || !inf->x_pkey) 
        {
          sk_X509_INFO_free(sk);
          rovm_log (NULL, ROVMLOG_STARTUP, ROVMLOG_MARK, 
                    "incomplete client cert configured for SSL proxy "
                    "(missing or encrypted private key?)");
          ssl_die();
          return;
        }
    }
  
  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
            "loaded %d client certs for SSL proxy",
            ncerts);
  pkp->certs = sk;
}

static void 
ssl_init_proxy_ctx(struct rovm *r,
                   rc_pool_t *p,
                   rc_pool_t *ptemp,
                   SSLSrvConfigRec *sc)
{
  ssl_init_ctx(r, p, ptemp, sc->proxy);
  ssl_init_proxy_certs(r, p, ptemp, sc->proxy);
}

static void 
ssl_init_server_check(struct rovm *r,
                      rc_pool_t *p,
                      rc_pool_t *ptemp,
                      modssl_ctx_t *mctx)
{
  /*
   * check for important parameters and the
   * possibility that the user forgot to set them.
   */
  if (!mctx->pks->cert_files[0]) 
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "No SSL Certificate set [hint: SSLCertificateFile]");
      ssl_die();
    }
  
  /*
   *  Check for problematic re-initializations
   */
  if (mctx->pks->certs[SSL_AIDX_RSA] ||
      mctx->pks->certs[SSL_AIDX_DSA])
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "Illegal attempt to re-initialise SSL for server "
                "(theoretically shouldn't happen!)");
      ssl_die();
    }
}

static int 
ssl_server_import_cert(struct rovm *r,
                       modssl_ctx_t *mctx,
                       const char *id,
                       int idx)
{
  SSLModConfigRec *mc = myModConfig(r);
  ssl_asn1_t *asn1;
  MODSSL_D2I_X509_CONST unsigned char *ptr;
  const char *type = ssl_asn1_keystr(idx);
  X509 *cert;
  
  if (!(asn1 = ssl_asn1_table_get(mc->tPublicCert, id)))
    return FALSE;

  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
            "Configuring %s server certificate", type);
  
  ptr = asn1->cpData;
  if (!(cert = d2i_X509(NULL, &ptr, asn1->nData))) 
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "Unable to import %s server certificate", type);
      ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_ERR, r);
      ssl_die();
    }

  if (SSL_CTX_use_certificate(mctx->ssl_ctx, cert) <= 0) 
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK,
                "Unable to configure %s server certificate", type);
      ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_ERR, r);
      ssl_die();
    }
  
  mctx->pks->certs[idx] = cert;
  
  return TRUE;
}

static void 
ssl_check_public_cert (struct rovm *r, rc_pool_t *ptemp, X509 *cert, int type)
{
  int is_ca, pathlen;
  char *cn;
  
  if (!cert)
    return;

  /*
   * Some information about the certificate(s)
   */
  
  if (SSL_X509_isSGC(cert)) 
    {
      rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                "%s server certificate enables "
                "Server Gated Cryptography (SGC)",
                ssl_asn1_keystr(type));
    }
  
  if (SSL_X509_getBC(cert, &is_ca, &pathlen)) 
    {
      if (is_ca) 
        {
          rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, 
                    "%s server certificate is a CA certificate "
                    "(BasicConstraints: CA == TRUE !?)",
                    ssl_asn1_keystr(type));
        }
      
      if (pathlen > 0) 
        {
          rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, 
                    "%s server certificate is not a leaf certificate "
                    "(BasicConstraints: pathlen == %d > 0 !?)",
                    ssl_asn1_keystr(type), pathlen);
        }
    }
  
  if (SSL_X509_getCN(ptemp, cert, &cn)) 
    {
      int fnm_flags = APR_FNM_PERIOD|APR_FNM_CASE_BLIND;
      
      if (apr_fnmatch_test(cn) &&
          (apr_fnmatch(cn, CONF_HOSTNAME (ROVM_CONF (r)),
                       fnm_flags) == APR_FNM_NOMATCH))
        {
          rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, 
                    "%s server certificate wildcard CommonName (CN) `%s' "
                    "does NOT match server name!?",
                    ssl_asn1_keystr(type), cn);
        }
      else if (strNE(CONF_HOSTNAME (ROVM_CONF (r)), cn)) 
        {
          rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, 
                    "%s server certificate CommonName (CN) `%s' "
                    "does NOT match server name!?",
                    ssl_asn1_keystr(type), cn);
        }
    }
}

static int 
ssl_server_import_key(struct rovm *r,
                      modssl_ctx_t *mctx,
                      const char *id,
                      int idx)
{
  SSLModConfigRec *mc = myModConfig(r);
  ssl_asn1_t *asn1;
  MODSSL_D2I_PrivateKey_CONST unsigned char *ptr;
  const char *type = ssl_asn1_keystr(idx);
  int pkey_type = (idx == SSL_AIDX_RSA) ? EVP_PKEY_RSA : EVP_PKEY_DSA;
  EVP_PKEY *pkey;
  
  if (!(asn1 = ssl_asn1_table_get(mc->tPrivateKey, id)))
    return FALSE;
  
  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
            "Configuring %s server private key", type);
  
  ptr = asn1->cpData;
  if (!(pkey = d2i_PrivateKey(pkey_type, NULL, &ptr, asn1->nData)))
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "Unable to import %s server private key", type);
      ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_ERR, r);
      ssl_die();
    }
  
  if (SSL_CTX_use_PrivateKey(mctx->ssl_ctx, pkey) <= 0) 
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "Unable to configure %s server private key", type);
      ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_ERR, r);
      ssl_die();
    }
  
  /*
   * XXX: wonder if this is still needed, this is old todo doc.
   * (see http://www.psy.uq.edu.au/~ftp/Crypto/ssleay/TODO.html)
   */
  if ((pkey_type == EVP_PKEY_DSA) && mctx->pks->certs[idx]) 
    {
      EVP_PKEY *pubkey = X509_get_pubkey(mctx->pks->certs[idx]);
      
      if (pubkey && EVP_PKEY_missing_parameters(pubkey)) 
        {
          EVP_PKEY_copy_parameters(pubkey, pkey);
          rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                    "Copying DSA parameters from private key to certificate");
          ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_ERR, r);
          EVP_PKEY_free(pubkey);
        }
    }
  
  mctx->pks->keys[idx] = pkey;
  
  return TRUE;
}

static void
ssl_init_server_certs(struct rovm *r,
                      rc_pool_t *p,
                      rc_pool_t *ptemp,
                      modssl_ctx_t *mctx)
{
  const char *rsa_id, *dsa_id;
  const char *vhost_id = mctx->sc->vhost_id;
  int i;
  int have_rsa, have_dsa;
  
  rsa_id = ssl_asn1_table_keyfmt(ptemp, vhost_id, SSL_AIDX_RSA);
  dsa_id = ssl_asn1_table_keyfmt(ptemp, vhost_id, SSL_AIDX_DSA);
  
  have_rsa = ssl_server_import_cert(r, mctx, rsa_id, SSL_AIDX_RSA);
  have_dsa = ssl_server_import_cert(r, mctx, dsa_id, SSL_AIDX_DSA);
  
  if (!(have_rsa || have_dsa)) 
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "Oops, no RSA or DSA server certificate found "
                "for '%s:%d'?!", CONF_HOSTNAME (ROVM_CONF (r)), CONF_PORT (ROVM_CONF (r)));
      ssl_die();
    }
  
  for (i = 0; i < SSL_AIDX_MAX; i++) {
    ssl_check_public_cert(r, ptemp, mctx->pks->certs[i], i);
  }
  
  have_rsa = ssl_server_import_key(r, mctx, rsa_id, SSL_AIDX_RSA);
  have_dsa = ssl_server_import_key(r, mctx, dsa_id, SSL_AIDX_DSA);
  
  if (!(have_rsa || have_dsa)) 
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "Oops, no RSA or DSA server private key found?!");
      ssl_die();
    }
}

static void
ssl_init_server_ctx(struct rovm *r,
                    rc_pool_t *p,
                    rc_pool_t *ptemp,
                    SSLSrvConfigRec *sc)
{
  ssl_init_server_check(r, p, ptemp, sc->server);
  ssl_init_ctx(r, p, ptemp, sc->server);
  ssl_init_server_certs(r, p, ptemp, sc->server);
}

/*
 * Configure a particular server
 */
void 
ssl_init_ConfigureServer(struct rovm *r,
                         rc_pool_t *p,
                         rc_pool_t *ptemp,
                         SSLSrvConfigRec *sc)
{
  /* Initialize the server if SSL is enabled or optional.
   */
  if ((sc->enabled == SSL_ENABLED_TRUE) || (sc->enabled == SSL_ENABLED_OPTIONAL)) 
    {
      rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                "Configuring server for SSL protocol");
      ssl_init_server_ctx (r, p, ptemp, sc);
    }
  
  if (sc->proxy_enabled)
    ssl_init_proxy_ctx (r, p, ptemp, sc);

}

int
ssl_init_Module (struct rovm *r)
{
  rc_pool_t *p = ROVM_PCONF (r);
  SSLModConfigRec *mc = myModConfig (r);
  SSLSrvConfigRec *sc;

  /* We initialize mc->pid per-process in the child init,
     but it should be initialized for startup before we
     call ssl_rand_seed() below.  */
  mc->pid = getpid ();

  /* Let us cleanup on restarts and exists.  */
  mp_cleanup_register (p, r, ssl_init_ModuleKill, apr_pool_cleanup_null);

  /* Any init round fixes the global config.  */
  ssl_config_global_create (r); /* just to avoid problems */
  ssl_config_global_fix (mc);

  {
    sc = mySrvConfig (r);

    if (sc->server)
      sc->server->sc = sc;
    if (sc->proxy)
      sc->proxy->sc = sc;
    
    /* Create the server host:port string because we need it a lot.  */
    sc->vhost_id = ssl_util_vhostid (p, r);
    sc->vhost_id_len = strlen (sc->vhost_id);

    sc->enabled = SSL_ENABLED_TRUE;

    /* If sc->enabled is UNSET, then SSL is optional on this vhost  */
    /* Fix up stuff that may not have been set */
    if (sc->enabled == SSL_ENABLED_UNSET)
      sc->enabled = SSL_ENABLED_FALSE;
    if (sc->proxy_enabled == UNSET)
      sc->proxy_enabled = FALSE;
    if (sc->session_cache_timeout == UNSET) 
      sc->session_cache_timeout = SSL_SESSION_CACHE_TIMEOUT;
    if (sc->server->pphrase_dialog_type == SSL_PPTYPE_UNSET)
      sc->server->pphrase_dialog_type = SSL_PPTYPE_BUILTIN;
  }

  /* SSL external crypto device ("engine") support.  */
#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT)
  ssl_init_Engine (r, p);
#endif
  
#if APR_HAS_THREADS
  ssl_util_thread_setup (p);
#endif
  
  rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
            "Init: Initialized %s library", SSL_LIBRARY_NAME);

  /* Seed the Pseudo Random Number Generator (PRNG)
     only need ptemp here; nothing inside allocated from the pool
     needs to live once we return from ssl_rand_seed().  */
  ssl_rand_seed (r, ROVM_POOL (r), SSL_RSCTX_STARTUP, "Init: ");

  /* read server private keys/public certs into memory.
     decrypting any encrypted keys via configured SSLPassPhraseDialogs
     anything that needs to live longer than ptemp needs to also survive
     restarts, in which case they'll live inside s->process->pool.  */
  ssl_pphrase_Handle (r, ROVM_POOL (r));

  if (ssl_tmp_keys_init (r))
    return -1;

  /* initialize the mutex handling.  */
  if (!ssl_mutex_init (r, p))
    return -1;

  /* initialize session caching.  */
  ssl_scache_init (r, p);

  /* initialize servers.  */
  rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
            "Init: Initializing (virtual) servers for SSL");

  {
    sc = mySrvConfig (r);
    /*
     * Either now skip this server when SSL is disabled for
     * it or give out some information about what we're
     * configuring.
     */
    
    /*
     * Read the server certificate and key
     */
    ssl_init_ConfigureServer (r, p, ROVM_POOL (r), sc);
  }

  SSL_init_app_data2_idx (); /* for SSL_get_app_data2() at request time */

  return 0;
}

void
ssl_init_Child (rc_pool_t *p, struct rovm *r)
{
  SSLModConfigRec *mc = myModConfig(r);
  mc->pid = getpid(); /* only call getpid() once per-process */
  
  /* XXX: there should be an ap_srand() function */
  srand((unsigned int)time(NULL));
  
  /* open the mutex lockfile */
  ssl_mutex_reinit (r, p);
}
