#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"

static void 
ssl_session_log(struct rovm *r,
                const char *request,
                unsigned char *id,
                unsigned int idlen,
                const char *status,
                const char *result,
                long timeout)
{
  char buf[SSL_SESSION_ID_STRING_LEN];
  char timeout_str[56] = {'\0'};
  
  if (CONF_LOGLEVEL (ROVM_CONF (r)) < ROVMLOG_DEBUG)
    return;
  
  if (timeout) 
    apr_snprintf(timeout_str, sizeof(timeout_str),
                 "timeout=%lds ", (timeout - time(NULL)));
  
  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
            "Inter-Process Session Cache: "
            "request=%s status=%s id=%s %s(session %s)",
            request, status,
            SSL_SESSION_id2sz(id, idlen, buf, sizeof(buf)),
            timeout_str, result);
}

/*  _________________________________________________________________
**
**  OpenSSL Callback Functions
**  _________________________________________________________________
*/

/*
 * Handle out temporary RSA private keys on demand
 *
 * The background of this as the TLSv1 standard explains it:
 *
 * | D.1. Temporary RSA keys
 * |
 * |    US Export restrictions limit RSA keys used for encryption to 512
 * |    bits, but do not place any limit on lengths of RSA keys used for
 * |    signing operations. Certificates often need to be larger than 512
 * |    bits, since 512-bit RSA keys are not secure enough for high-value
 * |    transactions or for applications requiring long-term security. Some
 * |    certificates are also designated signing-only, in which case they
 * |    cannot be used for key exchange.
 * |
 * |    When the public key in the certificate cannot be used for encryption,
 * |    the server signs a temporary RSA key, which is then exchanged. In
 * |    exportable applications, the temporary RSA key should be the maximum
 * |    allowable length (i.e., 512 bits). Because 512-bit RSA keys are
 * |    relatively insecure, they should be changed often. For typical
 * |    electronic commerce applications, it is suggested that keys be
 * |    changed daily or every 500 transactions, and more often if possible.
 * |    Note that while it is acceptable to use the same temporary key for
 * |    multiple transactions, it must be signed each time it is used.
 * |
 * |    RSA key generation is a time-consuming process. In many cases, a
 * |    low-priority process can be assigned the task of key generation.
 * |    Whenever a new key is completed, the existing temporary key can be
 * |    replaced with the new one.
 *
 * XXX: base on comment above, if thread support is enabled,
 * we should spawn a low-priority thread to generate new keys
 * on the fly.
 *
 * So we generated 512 and 1024 bit temporary keys on startup
 * which we now just hand out on demand....
 */

RSA *
ssl_callback_TmpRSA (SSL *ssl, int export, int keylen)
{
  conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
  SSLModConfigRec *mc = myModConfig (c->ts->arg);
  int idx;
  
  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
            "handing out temporary %d bit RSA key", keylen);
  
  /* doesn't matter if export flag is on,
   * we won't be asked for keylen > 512 in that case.
   * if we are asked for a keylen > 1024, it is too expensive
   * to generate on the fly.
   * XXX: any reason not to generate 2048 bit keys at startup?
   */
  
  switch (keylen) 
    {
    case 512:
      idx = SSL_TMP_KEY_RSA_512;
      break;
      
    case 1024:
    default:
      idx = SSL_TMP_KEY_RSA_1024;
    }
  
  return (RSA *)mc->pTmpKeys[idx];
}

/*
 * Hand out the already generated DH parameters...
 */
DH *
ssl_callback_TmpDH(SSL *ssl, int export, int keylen)
{
  conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
  SSLModConfigRec *mc = myModConfig(c->ts->arg);
  int idx;
  
  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
            "handing out temporary %d bit DH key", keylen);
  
  switch (keylen) 
    {
    case 512:
      idx = SSL_TMP_KEY_DH_512;
      break;
      
    case 1024:
    default:
      idx = SSL_TMP_KEY_DH_1024;
    }
  
  return (DH *)mc->pTmpKeys[idx];
}

/*
 *  This callback function is executed by OpenSSL whenever a new SSL_SESSION is
 *  added to the internal OpenSSL session cache. We use this hook to spread the
 *  SSL_SESSION also to the inter-process disk-cache to make share it with our
 *  other Apache pre-forked server processes.
 */
int 
ssl_callback_NewSessionCacheEntry(SSL *ssl, SSL_SESSION *session)
{
  /* Get Apache context back through OpenSSL context */
  conn_rec *conn      = (conn_rec *)SSL_get_app_data(ssl);
  struct rovm *r      = conn->ts->arg;
  SSLSrvConfigRec *sc = mySrvConfig(r);
  long timeout        = sc->session_cache_timeout;
  BOOL rc;
  unsigned char *id;
  unsigned int idlen;
  
  /*
   * Set the timeout also for the internal OpenSSL cache, because this way
   * our inter-process cache is consulted only when it's really necessary.
   */
  SSL_set_timeout(session, timeout);
  
  /*
   * Store the SSL_SESSION in the inter-process cache with the
   * same expire time, so it expires automatically there, too.
   */
  id = SSL_SESSION_get_session_id(session);
  idlen = SSL_SESSION_get_session_id_length(session);
  
  timeout += modssl_session_get_time(session);
  
  rc = ssl_scache_store(r, id, idlen, timeout, session);
  
  ssl_session_log(r, "SET", id, idlen,
                  rc == TRUE ? "OK" : "BAD",
                  "caching", timeout);
  
  /*
   * return 0 which means to OpenSSL that the session is still
   * valid and was not freed by us with SSL_SESSION_free().
   */
  return 0;
}

/*
 *  This callback function is executed by OpenSSL whenever a
 *  SSL_SESSION is looked up in the internal OpenSSL cache and it
 *  was not found. We use this to lookup the SSL_SESSION in the
 *  inter-process disk-cache where it was perhaps stored by one
 *  of our other Apache pre-forked server processes.
 */
SSL_SESSION *
ssl_callback_GetSessionCacheEntry(SSL *ssl,
                                  unsigned char *id,
                                  int idlen, int *do_copy)
{
  /* Get Apache context back through OpenSSL context */
  conn_rec *conn = (conn_rec *)SSL_get_app_data(ssl);
  struct rovm *r      = conn->ts->arg;
  SSL_SESSION *session;
  
  /*
   * Try to retrieve the SSL_SESSION from the inter-process cache
   */
  session = ssl_scache_retrieve(r, id, idlen);
  
  ssl_session_log(r, "GET", id, idlen,
                  session ? "FOUND" : "MISSED",
                  session ? "reuse" : "renewal", 0);
  
  /*
   * Return NULL or the retrieved SSL_SESSION. But indicate (by
   * setting do_copy to 0) that the reference count on the
   * SSL_SESSION should not be incremented by the SSL library,
   * because we will no longer hold a reference to it ourself.
   */
  *do_copy = 0;
  
  return session;
}

/*
 *  This callback function is executed by OpenSSL whenever a
 *  SSL_SESSION is removed from the the internal OpenSSL cache.
 *  We use this to remove the SSL_SESSION in the inter-process
 *  disk-cache, too.
 */
void 
ssl_callback_DelSessionCacheEntry(SSL_CTX *ctx,
                                  SSL_SESSION *session)
{
  struct rovm *r;
  SSLSrvConfigRec *sc;
  unsigned char *id;
  unsigned int idlen;
  
  /*
   * Get Apache context back through OpenSSL context
   */
  if (!(r = (struct rovm *)SSL_CTX_get_app_data(ctx)))
    return; /* on server shutdown Apache is already gone */
  
  sc = mySrvConfig(r);
  
  /*
   * Remove the SSL_SESSION from the inter-process cache
   */
  id = SSL_SESSION_get_session_id(session);
  idlen = SSL_SESSION_get_session_id_length(session);
  
  ssl_scache_remove (r, id, idlen);
  
  ssl_session_log(r, "REM", id, idlen, "OK", "dead", 0);
  
  return;
}

/*
 * This callback function is executed while OpenSSL processes the
 * SSL handshake and does SSL record layer stuff. We use it to
 * trace OpenSSL's processing in out SSL logfile.
 */
void 
ssl_callback_LogTracingState(MODSSL_INFO_CB_ARG_TYPE ssl, int where, int rc)
{
  conn_rec *c;
  struct rovm *r;
  SSLSrvConfigRec *sc;
  
  /*
   * find corresponding server
   */
  if (!(c = (conn_rec *)SSL_get_app_data((SSL *)ssl)))
    return;

  r = c->ts->arg;
  if (!(sc = mySrvConfig(r)))
    return;
  
  /*
   * create the various trace messages
   */
  if (CONF_LOGLEVEL (ROVM_CONF (r)) >= ROVMLOG_DEBUG) 
    {
      if (where & SSL_CB_HANDSHAKE_START)
        rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK,
                  "%s: Handshake: start", SSL_LIBRARY_NAME);
      else if (where & SSL_CB_HANDSHAKE_DONE)
        rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK,
                  "%s: Handshake: done", SSL_LIBRARY_NAME);
      else if (where & SSL_CB_LOOP)
        rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK,
                  "%s: Loop: %s",
                  SSL_LIBRARY_NAME, SSL_state_string_long(ssl));
      else if (where & SSL_CB_READ)
        rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK,
                  "%s: Read: %s",
                  SSL_LIBRARY_NAME, SSL_state_string_long(ssl));
      else if (where & SSL_CB_WRITE)
        rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK,
                  "%s: Write: %s",
                  SSL_LIBRARY_NAME, SSL_state_string_long(ssl));
      else if (where & SSL_CB_ALERT)
        {
          char *str = (where & SSL_CB_READ) ? "read" : "write";
          rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK,
                    "%s: Alert: %s:%s:%s",
                    SSL_LIBRARY_NAME, str,
                    SSL_alert_type_string_long(rc),
                    SSL_alert_desc_string_long(rc));
        }
      else if (where & SSL_CB_EXIT) 
        {
          if (rc == 0) 
            rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK,
                      "%s: Exit: failed in %s",
                      SSL_LIBRARY_NAME, SSL_state_string_long(ssl));
          else if (rc < 0) 
            rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK,
                      "%s: Exit: error in %s",
                      SSL_LIBRARY_NAME, SSL_state_string_long(ssl));
        }
    }
  
  /*
   * Because SSL renegotations can happen at any time (not only after
   * SSL_accept()), the best way to log the current connection details is
   * right after a finished handshake.
   */
  if (where & SSL_CB_HANDSHAKE_DONE) 
    {
      rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK,
                "Connection: Client IP: %s, Protocol: %s, "
                "Cipher: %s (%s/%s bits)",
                ssl_var_lookup(NULL, r, c, NULL, "REMOTE_ADDR"),
                ssl_var_lookup(NULL, r, c, NULL, "SSL_PROTOCOL"),
                ssl_var_lookup(NULL, r, c, NULL, "SSL_CIPHER"),
                ssl_var_lookup(NULL, r, c, NULL, "SSL_CIPHER_USEKEYSIZE"),
                ssl_var_lookup(NULL, r, c, NULL, "SSL_CIPHER_ALGKEYSIZE"));
    }
}

int
ssl_callback_SSLVerify_CRL(int ok, X509_STORE_CTX *ctx, conn_rec *c)
{
  struct rovm *s       = c->ts->arg;
  SSLSrvConfigRec *sc = mySrvConfig(s);
  SSLConnRec *sslconn = myConnConfig(c);
  modssl_ctx_t *mctx  = myCtxConfig(sslconn, sc);
  X509_OBJECT obj;
  X509_NAME *subject, *issuer;
  X509 *cert;
  X509_CRL *crl;
  EVP_PKEY *pubkey;
  int i, n, rc;
  
  /*
   * Unless a revocation store for CRLs was created we
   * cannot do any CRL-based verification, of course.
   */
  if (!mctx->crl) 
    return ok;

  /*
   * Determine certificate ingredients in advance
   */
  cert    = X509_STORE_CTX_get_current_cert(ctx);
  subject = X509_get_subject_name(cert);
  issuer  = X509_get_issuer_name(cert);

  /*
   * OpenSSL provides the general mechanism to deal with CRLs but does not
   * use them automatically when verifying certificates, so we do it
   * explicitly here. We will check the CRL for the currently checked
   * certificate, if there is such a CRL in the store.
   *
   * We come through this procedure for each certificate in the certificate
   * chain, starting with the root-CA's certificate. At each step we've to
   * both verify the signature on the CRL (to make sure it's a valid CRL)
   * and it's revocation list (to make sure the current certificate isn't
   * revoked).  But because to check the signature on the CRL we need the
   * public key of the issuing CA certificate (which was already processed
   * one round before), we've a little problem. But we can both solve it and
   * at the same time optimize the processing by using the following
   * verification scheme (idea and code snippets borrowed from the GLOBUS
   * project):
   *
   * 1. We'll check the signature of a CRL in each step when we find a CRL
   *    through the _subject_ name of the current certificate. This CRL
   *    itself will be needed the first time in the next round, of course.
   *    But we do the signature processing one round before this where the
   *    public key of the CA is available.
   *
   * 2. We'll check the revocation list of a CRL in each step when
   *    we find a CRL through the _issuer_ name of the current certificate.
   *    This CRLs signature was then already verified one round before.
   *
   * This verification scheme allows a CA to revoke its own certificate as
   * well, of course.
   */
  
  /*
   * Try to retrieve a CRL corresponding to the _subject_ of
   * the current certificate in order to verify it's integrity.
   */
  memset((char *)&obj, 0, sizeof(obj));
  rc = SSL_X509_STORE_lookup(mctx->crl,
                             X509_LU_CRL, subject, &obj);
  crl = obj.data.crl;
  if ((rc > 0) && crl) 
    {
      /*
       * Log information about CRL
       * (A little bit complicated because of ASN.1 and BIOs...)
       */
      if (CONF_LOGLEVEL (ROVM_CONF (s)) >= ROVMLOG_DEBUG) 
        {
          char buff[512]; /* should be plenty */
          BIO *bio = BIO_new(BIO_s_mem());
          
          BIO_printf(bio, "CA CRL: Issuer: ");
          X509_NAME_print(bio, issuer, 0);
          
          BIO_printf(bio, ", lastUpdate: ");
          ASN1_UTCTIME_print(bio, X509_CRL_get_lastUpdate(crl));
          
          BIO_printf(bio, ", nextUpdate: ");
          ASN1_UTCTIME_print(bio, X509_CRL_get_nextUpdate(crl));
          
          n = BIO_read(bio, buff, sizeof(buff) - 1);
          buff[n] = '\0';
          
          BIO_free(bio);
          
          rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, "%s", buff);
        }
      
      /*
       * Verify the signature on this CRL
       */
      pubkey = X509_get_pubkey(cert);
      rc = X509_CRL_verify(crl, pubkey);
#ifdef OPENSSL_VERSION_NUMBER
      /* Only refcounted in OpenSSL */
      if (pubkey)
        EVP_PKEY_free(pubkey);
#endif
      if (rc <= 0) 
        {
          rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, 
                    "Invalid signature on CRL");
          
          X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE);
          X509_OBJECT_free_contents(&obj);
          return FALSE;
        }
      
      /*
       * Check date of CRL to make sure it's not expired
       */
      i = X509_cmp_current_time(X509_CRL_get_nextUpdate(crl));
      
      if (i == 0) 
        {
          rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, 
                    "Found CRL has invalid nextUpdate field");
          
          X509_STORE_CTX_set_error(ctx,
                                   X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD);
          X509_OBJECT_free_contents(&obj);
          
          return FALSE;
        }
      
      if (i < 0) 
        {
          rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, 
                    "Found CRL is expired - "
                    "revoking all certificates until you get updated CRL");
          
          X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_HAS_EXPIRED);
          X509_OBJECT_free_contents(&obj);
          
          return FALSE;
        }
      
      X509_OBJECT_free_contents(&obj);
    }
  
  /*
   * Try to retrieve a CRL corresponding to the _issuer_ of
   * the current certificate in order to check for revocation.
   */
  memset((char *)&obj, 0, sizeof(obj));
  rc = SSL_X509_STORE_lookup(mctx->crl,
                             X509_LU_CRL, issuer, &obj);
  
  crl = obj.data.crl;
  if ((rc > 0) && crl) 
    {
      /*
       * Check if the current certificate is revoked by this CRL
       */
      n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl));
      
      for (i = 0; i < n; i++) 
        {
          X509_REVOKED *revoked =
            sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i);
          
          ASN1_INTEGER *sn = X509_REVOKED_get_serialNumber(revoked);
          
          if (!ASN1_INTEGER_cmp(sn, X509_get_serialNumber(cert))) 
            {
              if (CONF_LOGLEVEL (ROVM_CONF (s)) >= ROVMLOG_DEBUG) 
                {
                  char *cp = X509_NAME_oneline(issuer, NULL, 0);
                  long serial = ASN1_INTEGER_get(sn);
                  
                  rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                            "Certificate with serial %ld (0x%lX) "
                            "revoked per CRL from issuer %s",
                            serial, serial, cp);
                  modssl_free(cp);
                }
              
              X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED);
              X509_OBJECT_free_contents(&obj);
              
              return FALSE;
            }
        }
      
      X509_OBJECT_free_contents(&obj);
    }
  
  return ok;
}

/*
 * This OpenSSL callback function is called when OpenSSL
 * does client authentication and verifies the certificate chain.
 */
int
ssl_callback_SSLVerify (int ok, X509_STORE_CTX *ctx)
{
  /* Get Apache context back through OpenSSL context */
  SSL *ssl = X509_STORE_CTX_get_ex_data(ctx,
                                        SSL_get_ex_data_X509_STORE_CTX_idx());
  conn_rec *conn      = (conn_rec *)SSL_get_app_data(ssl);
  struct rovm *s      = conn->ts->arg;
  request_rec *r      = (request_rec *)SSL_get_app_data2(ssl);
  
  SSLSrvConfigRec *sc = mySrvConfig(s);
  SSLDirConfigRec *dc = r ? myDirConfig(r) : NULL;
  SSLConnRec *sslconn = myConnConfig(conn);
  modssl_ctx_t *mctx  = myCtxConfig(sslconn, sc);
  
  /* Get verify ingredients */
  int errnum   = X509_STORE_CTX_get_error(ctx);
  int errdepth = X509_STORE_CTX_get_error_depth(ctx);
  int depth, verify;
  
  /*
   * Log verification information
   */
  if (CONF_LOGLEVEL (ROVM_CONF (s)) >= ROVMLOG_DEBUG) 
    {
      X509 *cert  = X509_STORE_CTX_get_current_cert(ctx);
      char *sname = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
      char *iname = X509_NAME_oneline(X509_get_issuer_name(cert),  NULL, 0);
                                                        
      rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK,
                "Certificate Verification: "
                "depth: %d, subject: %s, issuer: %s",
                errdepth,
                sname ? sname : "-unknown-",
                iname ? iname : "-unknown-");
      
      if (sname) 
        modssl_free(sname);
      
      if (iname) 
        modssl_free(iname);
    }

  /*
   * Check for optionally acceptable non-verifiable issuer situation
   */
  if (dc && (dc->nVerifyClient != SSL_CVERIFY_UNSET))
    verify = dc->nVerifyClient;
  else
    verify = mctx->auth.verify_mode;

  if (verify == SSL_CVERIFY_NONE) 
    {
      /*
       * SSLProxyVerify is either not configured or set to "none".
       * (this callback doesn't happen in the server context if SSLVerify
       *  is not configured or set to "none")
       */
      return TRUE;
    }
  
  if (ssl_verify_error_is_optional(errnum) &&
      (verify == SSL_CVERIFY_OPTIONAL_NO_CA))
    {
      rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK,
                "Certificate Verification: Verifiable Issuer is "
                "configured as optional, therefore we're accepting "
                "the certificate");
      
      sslconn->verify_info = "GENEROUS";
      ok = TRUE;
    }
  
  /*
   * Additionally perform CRL-based revocation checks
   */
  if (ok) 
    {
      if (!(ok = ssl_callback_SSLVerify_CRL(ok, ctx, conn))) 
        errnum = X509_STORE_CTX_get_error(ctx);
    }
  
  /*
   * If we already know it's not ok, log the real reason
   */
  if (!ok) 
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK,
                "Certificate Verification: Error (%d): %s",
                errnum, X509_verify_cert_error_string(errnum));
      
      if (sslconn->client_cert) 
        {
          X509_free(sslconn->client_cert);
          sslconn->client_cert = NULL;
        }
      sslconn->client_dn = NULL;
      sslconn->verify_error = X509_verify_cert_error_string(errnum);
    }
  /*
   * Finally check the depth of the certificate verification
   */
  if (dc && (dc->nVerifyDepth != UNSET))
    depth = dc->nVerifyDepth;
  else
    depth = mctx->auth.verify_depth;
  
  if (errdepth > depth) 
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK,
                "Certificate Verification: Certificate Chain too long "
                "(chain has %d certificates, but maximum allowed are "
                "only %d)",
                errdepth, depth);
      
      errnum = X509_V_ERR_CERT_CHAIN_TOO_LONG;
      sslconn->verify_error = X509_verify_cert_error_string(errnum);
      
      ok = FALSE;
    }
  
  /*
   * And finally signal OpenSSL the (perhaps changed) state
   */
  return ok;
}

/*
 * caller will decrement the cert and key reference
 * so we need to increment here to prevent them from
 * being freed.
 */
#define modssl_set_cert_info(info, cert, pkey) \
    *cert = info->x509; \
    X509_reference_inc(*cert); \
    *pkey = info->x_pkey->dec_pkey; \
    EVP_PKEY_reference_inc(*pkey)

#define SSLPROXY_CERT_CB_LOG_FMT \
   "Proxy client certificate callback: (%s) "

static void modssl_proxy_info_log(struct rovm *r,
                                  X509_INFO *info,
                                  const char *msg)
{
  SSLSrvConfigRec *sc = mySrvConfig(r);
  char name_buf[256];
  X509_NAME *name;
  char *dn;
  
  if (CONF_LOGLEVEL (ROVM_CONF (r)) < ROVMLOG_DEBUG)
    return;

  name = X509_get_subject_name(info->x509);
  dn = X509_NAME_oneline(name, name_buf, sizeof(name_buf));
  
  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
            SSLPROXY_CERT_CB_LOG_FMT "%s, sending %s",
            sc->vhost_id, msg, dn ? dn : "-uknown-");
}

int 
ssl_callback_proxy_cert(SSL *ssl, MODSSL_CLIENT_CERT_CB_ARG_TYPE **x509, EVP_PKEY **pkey)
{
  conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
  struct rovm *s = c->ts->arg;
  SSLSrvConfigRec *sc = mySrvConfig(s);
  X509_NAME *ca_name, *issuer;
  X509_INFO *info;
  STACK_OF(X509_NAME) *ca_list;
  STACK_OF(X509_INFO) *certs = sc->proxy->pkp->certs;
  int i, j;
  
  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
            SSLPROXY_CERT_CB_LOG_FMT "entered",
            sc->vhost_id);
  
  if (!certs || (sk_X509_INFO_num(certs) <= 0)) 
    {
      rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, 
                SSLPROXY_CERT_CB_LOG_FMT
                "downstream server wanted client certificate "
                "but none are configured", sc->vhost_id);
      return FALSE;
    }
  
  ca_list = SSL_get_client_CA_list(ssl);
  
  if (!ca_list || (sk_X509_NAME_num(ca_list) <= 0)) 
    {
      /*
       * downstream server didn't send us a list of acceptable CA certs,
       * so we send the first client cert in the list.
       */
      info = sk_X509_INFO_value(certs, 0);
      
      modssl_proxy_info_log(s, info, "no acceptable CA list");
      
      modssl_set_cert_info(info, x509, pkey);
      
      return TRUE;
    }
  
  for (i = 0; i < sk_X509_NAME_num(ca_list); i++) 
    {
      ca_name = sk_X509_NAME_value(ca_list, i);
      
      for (j = 0; j < sk_X509_INFO_num(certs); j++) 
        {
          info = sk_X509_INFO_value(certs, j);
          issuer = X509_get_issuer_name(info->x509);
          
          if (X509_NAME_cmp(issuer, ca_name) == 0) 
            {
              modssl_proxy_info_log(s, info, "found acceptable cert");
              
              modssl_set_cert_info(info, x509, pkey);
              
              return TRUE;
            }
        }
    }

  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
            SSLPROXY_CERT_CB_LOG_FMT
            "no client certificate found!?", sc->vhost_id);
  
  return FALSE;
}

/*
 *  Access Handler
 */
int
ssl_hook_Access (request_rec *r)
{
  SSLDirConfigRec *dc = myDirConfig(r);
  SSLSrvConfigRec *sc = mySrvConfig(REQUEST_ROVM (r));
  SSLConnRec *sslconn = myConnConfig(REQUEST_CONN (r));
  SSL *ssl            = sslconn ? sslconn->ssl : NULL;
  SSL_CTX *ctx = NULL;
  apr_array_header_t *requires;
  ssl_require_t *ssl_requires;
  int i;
  BOOL renegotiate = FALSE, renegotiate_quick = FALSE;
  X509 *cert;
  X509 *peercert;
  X509_STORE *cert_store = NULL;
  X509_STORE_CTX cert_store_ctx;
  STACK_OF(SSL_CIPHER) *cipher_list_old = NULL, *cipher_list = NULL;
  SSL_CIPHER *cipher = NULL;
  int depth, verify_old, verify, n;
  
  if (ssl)
    ctx = SSL_get_SSL_CTX(ssl);

  /*
   * Support for SSLRequireSSL directive
   */
  if (dc->bSSLRequired && !ssl) 
    {
      if (sc->enabled == SSL_ENABLED_OPTIONAL) 
        {
          /* This vhost was configured for optional SSL, just tell the
           * client that we need to upgrade.
           */
          rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                    "We should send a upgrade messages to client.");
          
          return -1;
        }
      
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "access to file failed, reason: %s",
                "SSL connection required");
      
      return -1;
    }
  
  /*
   * Check to see if SSL protocol is on
   */
  if (!((sc->enabled == SSL_ENABLED_TRUE) || (sc->enabled == SSL_ENABLED_OPTIONAL) || ssl))
    return 0;

  /*
   * Support for per-directory reconfigured SSL connection parameters.
   *
   * This is implemented by forcing an SSL renegotiation with the
   * reconfigured parameter suite. But Apache's internal API processing
   * makes our life very hard here, because when internal sub-requests occur
   * we nevertheless should avoid multiple unnecessary SSL handshakes (they
   * require extra network I/O and especially time to perform).
   *
   * But the optimization for filtering out the unnecessary handshakes isn't
   * obvious and trivial.  Especially because while Apache is in its
   * sub-request processing the client could force additional handshakes,
   * too. And these take place perhaps without our notice. So the only
   * possibility is to explicitly _ask_ OpenSSL whether the renegotiation
   * has to be performed or not. It has to performed when some parameters
   * which were previously known (by us) are not those we've now
   * reconfigured (as known by OpenSSL) or (in optimized way) at least when
   * the reconfigured parameter suite is stronger (more restrictions) than
   * the currently active one.
   */
  
  /*
   * Override of SSLCipherSuite
   *
   * We provide two options here:
   *
   * o The paranoid and default approach where we force a renegotiation when
   *   the cipher suite changed in _any_ way (which is straight-forward but
   *   often forces renegotiations too often and is perhaps not what the
   *   user actually wanted).
   *
   * o The optimized and still secure way where we force a renegotiation
   *   only if the currently active cipher is no longer contained in the
   *   reconfigured/new cipher suite. Any other changes are not important
   *   because it's the servers choice to select a cipher from the ones the
   *   client supports. So as long as the current cipher is still in the new
   *   cipher suite we're happy. Because we can assume we would have
   *   selected it again even when other (better) ciphers exists now in the
   *   new cipher suite. This approach is fine because the user explicitly
   *   has to enable this via ``SSLOptions +OptRenegotiate''. So we do no
   *   implicit optimizations.
   */
  if (dc->szCipherSuite) 
    {
      /* remember old state */
      
      if (dc->nOptions & SSL_OPT_OPTRENEGOTIATE)
        cipher = SSL_get_current_cipher(ssl);
      else 
        {
          cipher_list_old = (STACK_OF(SSL_CIPHER) *)SSL_get_ciphers(ssl);
          
          if (cipher_list_old)
            cipher_list_old = sk_SSL_CIPHER_dup(cipher_list_old);
        }
      
      /* configure new state */
      if (!modssl_set_cipher_list(ssl, dc->szCipherSuite)) 
        {
          rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, 
                    "Unable to reconfigure (per-directory) "
                    "permitted SSL ciphers");
          ssl_log_ssl_error (ROVMLOG_MARK, ROVMLOG_ERR, REQUEST_ROVM (r));
          
          if (cipher_list_old)
            sk_SSL_CIPHER_free(cipher_list_old);

          return -1;
        }
      
      /* determine whether a renegotiation has to be forced */
      cipher_list = (STACK_OF(SSL_CIPHER) *)SSL_get_ciphers (ssl);
      
      if (dc->nOptions & SSL_OPT_OPTRENEGOTIATE) 
        {
          /* optimized way */
          if ((!cipher && cipher_list) || (cipher && !cipher_list))
            renegotiate = TRUE;
          else if (cipher && cipher_list &&
                   (sk_SSL_CIPHER_find(cipher_list, cipher) < 0))
            renegotiate = TRUE;
          
        }
      else 
        {
          /* paranoid way */
          if ((!cipher_list_old && cipher_list) || (cipher_list_old && !cipher_list))
            renegotiate = TRUE;
          else if (cipher_list_old && cipher_list) 
            {
              for (n = 0; !renegotiate && (n < sk_SSL_CIPHER_num(cipher_list)); n++)
                {
                  SSL_CIPHER *value = sk_SSL_CIPHER_value(cipher_list, n);
                  
                  if (sk_SSL_CIPHER_find(cipher_list_old, value) < 0)
                    renegotiate = TRUE;
                }
              
              for (n = 0; !renegotiate && (n < sk_SSL_CIPHER_num(cipher_list_old)); n++)
                {
                  SSL_CIPHER *value = sk_SSL_CIPHER_value(cipher_list_old, n);
                  
                  if (sk_SSL_CIPHER_find(cipher_list, value) < 0)
                    renegotiate = TRUE;
                }
            }
        }
      
      /* cleanup */
      if (cipher_list_old)
        sk_SSL_CIPHER_free(cipher_list_old);
      
      /* tracing */
      if (renegotiate)
        rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
                  "Reconfigured cipher suite will force renegotiation");
    }
  
  /*
   * override of SSLVerifyDepth
   *
   * The depth checks are handled by us manually inside the verify callback
   * function and not by OpenSSL internally (and our function is aware of
   * both the per-server and per-directory contexts). So we cannot ask
   * OpenSSL about the currently verify depth. Instead we remember it in our
   * ap_ctx attached to the SSL* of OpenSSL.  We've to force the
   * renegotiation if the reconfigured/new verify depth is less than the
   * currently active/remembered verify depth (because this means more
   * restriction on the certificate chain).
   */
  if (dc->nVerifyDepth != UNSET) 
    {
      /* XXX: doesnt look like sslconn->verify_depth is actually used */
      if (!(n = sslconn->verify_depth))
        sslconn->verify_depth = n = sc->server->auth.verify_depth;
      
      /* determine whether a renegotiation has to be forced */
      if (dc->nVerifyDepth < n) 
        {
          renegotiate = TRUE;
          rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
                    "Reduced client verification depth will force "
                    "renegotiation");
        }
    }
  /*
   * override of SSLVerifyClient
   *
   * We force a renegotiation if the reconfigured/new verify type is
   * stronger than the currently active verify type.
   *
   * The order is: none << optional_no_ca << optional << require
   *
   * Additionally the following optimization is possible here: When the
   * currently active verify type is "none" but a client certificate is
   * already known/present, it's enough to manually force a client
   * verification but at least skip the I/O-intensive renegotation
   * handshake.
   */
  if (dc->nVerifyClient != SSL_CVERIFY_UNSET) 
    {
      /* remember old state */
      verify_old = SSL_get_verify_mode (ssl);
      /* configure new state */
      verify = SSL_VERIFY_NONE;
      
      if (dc->nVerifyClient == SSL_CVERIFY_REQUIRE)
        verify |= SSL_VERIFY_PEER_STRICT;
      
      if ((dc->nVerifyClient == SSL_CVERIFY_OPTIONAL) ||
          (dc->nVerifyClient == SSL_CVERIFY_OPTIONAL_NO_CA))
        verify |= SSL_VERIFY_PEER;
      
      modssl_set_verify(ssl, verify, ssl_callback_SSLVerify);
      SSL_set_verify_result(ssl, X509_V_OK);
      
      /* determine whether we've to force a renegotiation */
      if (!renegotiate && verify != verify_old) 
        {
          if (((verify_old == SSL_VERIFY_NONE) &&
               (verify     != SSL_VERIFY_NONE)) ||
              
              (!(verify_old & SSL_VERIFY_PEER) &&
               (verify     & SSL_VERIFY_PEER)) ||
              
              (!(verify_old & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) &&
               (verify     & SSL_VERIFY_FAIL_IF_NO_PEER_CERT)))
            {
              renegotiate = TRUE;
              /* optimization */
              if ((dc->nOptions & SSL_OPT_OPTRENEGOTIATE) &&
                  (verify_old == SSL_VERIFY_NONE) &&
                  ((peercert = SSL_get_peer_certificate(ssl)) != NULL))
                {
                  renegotiate_quick = TRUE;
                  X509_free(peercert);
                }
              
              rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
                        "Changed client verification type will force "
                        "%srenegotiation",
                        renegotiate_quick ? "quick " : "");
            }
        }
    }
  
  /*
   * override SSLCACertificateFile & SSLCACertificatePath
   * This is only enabled if the SSL_set_cert_store() function
   * is available in the ssl library.  the 1.x based mod_ssl
   * used SSL_CTX_set_cert_store which is not thread safe.
   */
  
#ifdef HAVE_SSL_SET_CERT_STORE
  /*
   * check if per-dir and per-server config field are not the same.
   * if f is defined in per-dir and not defined in per-server
   * or f is defined in both but not the equal ...
   */
#define MODSSL_CFG_NE(f)                                \
  (dc->f && (!sc->f || (sc->f && strNE(dc->f, sc->f))))
  
#define MODSSL_CFG_CA(f)                        \
  (dc->f ? dc->f : sc->f)
  
  if (MODSSL_CFG_NE(szCACertificateFile) ||
        MODSSL_CFG_NE(szCACertificatePath))
    {
      STACK_OF(X509_NAME) *ca_list;
      const char *ca_file = MODSSL_CFG_CA(szCACertificateFile);
      const char *ca_path = MODSSL_CFG_CA(szCACertificatePath);
      
      cert_store = X509_STORE_new();
      
      if (!X509_STORE_load_locations(cert_store, ca_file, ca_path)) 
        {
          rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                    "Unable to reconfigure verify locations "
                    "for client authentication");
          ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_ERR, REQUEST_ROVM (r));
          
          X509_STORE_free(cert_store);
          
          return -1;
        }
      
      /* SSL_free will free cert_store */
      SSL_set_cert_store(ssl, cert_store);
      
      if (!(ca_list = ssl_init_FindCAList(REQUEST_ROVM (r), r->pool,
                                          ca_file, ca_path)))
        {
          rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                    "Unable to determine list of available "
                    "CA certificates for client authentication");
          
          return -1;
        }
      
      SSL_set_client_CA_list(ssl, ca_list);
      renegotiate = TRUE;
      
      rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
                "Changed client verification locations will force "
                "renegotiation");
    }
#endif /* HAVE_SSL_SET_CERT_STORE */
  
  /* If a renegotiation is now required for this location, and the
   * request includes a message body (and the client has not
   * requested a "100 Continue" response), then the client will be
   * streaming the request body over the wire already.  In that
   * case, it is not possible to stop and perform a new SSL
   * handshake immediately; once the SSL library moves to the
   * "accept" state, it will reject the SSL packets which the client
   * is sending for the request body.
   *
   * To allow authentication to complete in this auth hook, the
   * solution used here is to fill a (bounded) buffer with the
   * request body, and then to reinject that request body later.
   */
  if (renegotiate && !renegotiate_quick) 
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "TODO");
    }
  /*
   * now do the renegotiation if anything was actually reconfigured
   */
  if (renegotiate) 
    {
      /*
       * Now we force the SSL renegotation by sending the Hello Request
       * message to the client. Here we have to do a workaround: Actually
       * OpenSSL returns immediately after sending the Hello Request (the
       * intent AFAIK is because the SSL/TLS protocol says it's not a must
       * that the client replies to a Hello Request). But because we insist
       * on a reply (anything else is an error for us) we have to go to the
       * ACCEPT state manually. Using SSL_set_accept_state() doesn't work
       * here because it resets too much of the connection.  So we set the
       * state explicitly and continue the handshake manually.
       */
      rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                "Requesting connection re-negotiation");
      
      if (renegotiate_quick) 
        {
          STACK_OF(X509) *cert_stack;
          
          /* perform just a manual re-verification of the peer */
          rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
                    "Performing quick renegotiation: "
                    "just re-verifying the peer");
          
          cert_stack = (STACK_OF(X509) *)SSL_get_peer_cert_chain(ssl);
          
          cert = SSL_get_peer_certificate(ssl);
          
          if (!cert_stack && cert) 
            {
              /* client cert is in the session cache, but there is
               * no chain, since ssl3_get_client_certificate()
               * sk_X509_shift-ed the peer cert out of the chain.
               * we put it back here for the purpose of quick_renegotiation.
               */
              cert_stack = sk_new_null();
              sk_X509_push(cert_stack, MODSSL_PCHAR_CAST cert);
            }
          
          if (!cert_stack || (sk_X509_num(cert_stack) == 0)) 
            {
              rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                        "Cannot find peer certificate chain");
              
              return -1;
            }
          
          if (!(cert_store ||
                (cert_store = SSL_CTX_get_cert_store(ctx))))
            {
              rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                        "Cannot find certificate storage");
              
              return -1;
            }
          
          if (!cert) 
            cert = sk_X509_value(cert_stack, 0);
          
          X509_STORE_CTX_init(&cert_store_ctx, cert_store, cert, cert_stack);
          depth = SSL_get_verify_depth(ssl);
          
          if (depth >= 0)
            X509_STORE_CTX_set_depth(&cert_store_ctx, depth);

          X509_STORE_CTX_set_ex_data(&cert_store_ctx,
                                     SSL_get_ex_data_X509_STORE_CTX_idx(),
                                     (char *)ssl);
          
          if (!modssl_X509_verify_cert(&cert_store_ctx)) 
            {
              rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                        "Re-negotiation verification step failed");
              ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_ERR, REQUEST_ROVM (r));
            }
          
          SSL_set_verify_result(ssl, cert_store_ctx.error);
          X509_STORE_CTX_cleanup(&cert_store_ctx);
          
          if (cert_stack != SSL_get_peer_cert_chain(ssl)) 
            {
              /* we created this ourselves, so free it */
              sk_X509_pop_free(cert_stack, X509_free);
            }
        }
      else 
        {
          request_rec *id = r;
          
          /* do a full renegotiation */
          rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
                    "Performing full renegotiation: "
                    "complete handshake protocol");
          
          SSL_set_session_id_context(ssl,
                                     (unsigned char *)&id,
                                     sizeof(id));
          
          SSL_renegotiate(ssl);
          SSL_do_handshake(ssl);
          
          if (SSL_get_state(ssl) != SSL_ST_OK) 
            {
              rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                        "Re-negotiation request failed");
              
              r->conn->aborted = 1;
              return -1;
            }
          
          rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                    "Awaiting re-negotiation handshake");
          
          /* XXX: Should replace SSL_set_state with SSL_renegotiate(ssl);
           * However, this causes failures in perl-framework currently,
           * perhaps pre-test if we have already negotiated?
           */
          SSL_set_state(ssl, SSL_ST_ACCEPT);
          SSL_do_handshake(ssl);
          
          if (SSL_get_state(ssl) != SSL_ST_OK) 
            {
              rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                        "Re-negotiation handshake failed: "
                        "Not accepted by client!?");
              
              r->conn->aborted = 1;
              return -1;
            }
        }
      
      /*
       * Remember the peer certificate's DN
       */
      if ((cert = SSL_get_peer_certificate(ssl))) 
        {
          if (sslconn->client_cert)
            X509_free(sslconn->client_cert);
          
          sslconn->client_cert = cert;
          sslconn->client_dn = NULL;
        }
      
      /*
       * Finally check for acceptable renegotiation results
       */
      if (dc->nVerifyClient != SSL_CVERIFY_NONE) 
        {
          BOOL do_verify = (dc->nVerifyClient == SSL_CVERIFY_REQUIRE);
          
          if (do_verify && (SSL_get_verify_result(ssl) != X509_V_OK)) 
            {
              rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                        "Re-negotiation handshake failed: "
                        "Client verification failed");
              
              return -1;
            }
          
          if (do_verify) 
            {
              if ((peercert = SSL_get_peer_certificate(ssl)) == NULL) 
                {
                  rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                            "Re-negotiation handshake failed: "
                            "Client certificate missing");
                  
                  return -1;
                }
              
              X509_free(peercert);
            }
        }
      
      /*
       * Also check that SSLCipherSuite has been enforced as expected.
       */
      if (cipher_list) 
        {
          
          cipher = SSL_get_current_cipher(ssl);
          if (sk_SSL_CIPHER_find(cipher_list, cipher) < 0) 
            {
              rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                        "SSL cipher suite not renegotiated: "
                        "access to file denied using cipher %s",
                        SSL_CIPHER_get_name (cipher));
              return -1;
            }
        }
    }
  
  /* If we're trying to have the user name set from a client
   * certificate then we need to set it here. This should be safe as
   * the user name probably isn't important from an auth checking point
   * of view as the certificate supplied acts in that capacity.
   * However, if FakeAuth is being used then this isn't the case so
   * we need to postpone setting the username until later.
   */
  if ((dc->nOptions & SSL_OPT_FAKEBASICAUTH) == 0 && dc->szUserName) 
    {
      char *val = ssl_var_lookup(r->pool, REQUEST_ROVM (r), r->conn,
                                 r, (char *)dc->szUserName);
      if (val && val[0])
        rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, "User TODO");
    }
  
  /*
   * Check SSLRequire boolean expressions
   */
  requires = dc->aRequirement;
  ssl_requires = (ssl_require_t *)requires->elts;
  
  for (i = 0; i < requires->nelts; i++) 
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "TODO");
    }
  
  /*
   * Else access is granted from our point of view (except vendor
   * handlers override). But we have to return DECLINED here instead
   * of OK, because mod_auth and other modules still might want to
   * deny access.
   */
  
  return 0;
}

