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

#define SSL_MOD_CONFIG_KEY "ssl_module"

void
ssl_config_global_fix (SSLModConfigRec *mc)
{
  mc->bFixed = TRUE;
}

static void 
modssl_ctx_init (modssl_ctx_t *mctx)
{
  mctx->sc                  = NULL; /* set during module init */
  mctx->ssl_ctx             = NULL; /* set during module init */
  
  mctx->pks                 = NULL;
  mctx->pkp                 = NULL;
  
  mctx->protocol            = SSL_PROTOCOL_ALL;
  
  mctx->pphrase_dialog_type = SSL_PPTYPE_UNSET;
  mctx->pphrase_dialog_path = NULL;
  
  mctx->cert_chain          = NULL;
  
  mctx->crl_path            = NULL;
  mctx->crl_file            = NULL;
  mctx->crl                 = NULL; /* set during module init */
  
  mctx->auth.ca_cert_path   = NULL;
  mctx->auth.ca_cert_file   = NULL;
  mctx->auth.cipher_suite   = NULL;
  mctx->auth.verify_depth   = UNSET;
  mctx->auth.verify_mode    = SSL_CVERIFY_UNSET;
}

static void 
modssl_ctx_init_proxy (SSLSrvConfigRec *sc, rc_pool_t *p)
{
  modssl_ctx_t *mctx;
  
  mctx = sc->proxy = mp_alloc (p, sizeof(*sc->proxy));
  
  modssl_ctx_init (mctx);

  mctx->pkp = mp_alloc (p, sizeof(*mctx->pkp));
  
  mctx->pkp->cert_file = NULL;
  mctx->pkp->cert_path = NULL;
  mctx->pkp->certs     = NULL;
}

static void 
modssl_ctx_init_server (SSLSrvConfigRec *sc, rc_pool_t *p)
{
  modssl_ctx_t *mctx;
  
  mctx = sc->server = mp_alloc (p, sizeof(*sc->server));
  
  modssl_ctx_init (mctx);
  
  mctx->pks = mp_alloc (p, sizeof(*mctx->pks));
  
  /* mctx->pks->... certs/keys are set during module init */
}

static SSLSrvConfigRec *
ssl_config_server_new (rc_pool_t *p)
{
  SSLSrvConfigRec *sc = mp_alloc (p, sizeof (*sc));
  
  sc->mc                     = NULL;
  sc->enabled                = SSL_ENABLED_FALSE;
  sc->proxy_enabled          = UNSET;
  sc->vhost_id               = NULL;  /* set during module init */
  sc->vhost_id_len           = 0;     /* set during module init */
  sc->session_cache_timeout  = UNSET;
  sc->cipher_server_pref     = UNSET;
  
  modssl_ctx_init_proxy (sc, p);
  
  modssl_ctx_init_server (sc, p);
  
  return sc;
}

SSLModConfigRec *
ssl_config_global_create (struct rovm *r)
{
  rc_pool_t *pool = ROVM_POOL (r);
  SSLModConfigRec *mc;
  void *vmc;
  
  apr_pool_userdata_get (&vmc, SSL_MOD_CONFIG_KEY, pool);
  if (vmc)
    return vmc; /* reused for lifetime of the server */

  /*
   * allocate an own subpool which survives server restarts
   */
  mc = (SSLModConfigRec *) mp_alloc (pool, sizeof(*mc));
  mc->pPool = pool;
  mc->bFixed = FALSE;
  
  /*
   * initialize per-module configuration
   */
  mc->nSessionCacheMode      = SSL_SCMODE_UNSET;
  mc->szSessionCacheDataFile = NULL;
  mc->nSessionCacheDataSize  = 0;
  mc->pSessionCacheDataMM    = NULL;
  mc->pSessionCacheDataRMM   = NULL;
  mc->tSessionCacheDataTable = NULL;
  mc->nMutexMode             = SSL_MUTEXMODE_UNSET;
  mc->nMutexMech             = APR_LOCK_DEFAULT;
  mc->szMutexFile            = NULL;
  mc->pMutex                 = NULL;
  mc->aRandSeed              = apr_array_make(pool, 4, sizeof(ssl_randseed_t));
  mc->tVHostKeys             = apr_hash_make(pool);
  mc->tPrivateKey            = apr_hash_make(pool);
  mc->tPublicCert            = apr_hash_make(pool);
#if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT)
  mc->szCryptoDevice         = NULL;
#endif
  
  memset (mc->pTmpKeys, 0, sizeof(mc->pTmpKeys));
  
  apr_pool_userdata_set (mc, SSL_MOD_CONFIG_KEY, apr_pool_cleanup_null, pool);
  
  return mc;
}

/*
 *  Create per-server SSL configuration
 */
SSLSrvConfigRec *
ssl_config_server_create (struct rovm *r, rc_pool_t *p)
{
  SSLSrvConfigRec *sc = ssl_config_server_new (p);
  
  sc->mc = ssl_config_global_create (r);

  return sc;
}

/*
 *  Create per-directory SSL configuration
 */
SSLDirConfigRec *
ssl_config_perdir_create (rc_pool_t *p, char *dir)
{
  SSLDirConfigRec *dc = mp_alloc (p, sizeof(*dc));

  dc->bSSLRequired  = FALSE;
  dc->aRequirement  = apr_array_make(p, 4, sizeof(ssl_require_t));
  dc->nOptions      = SSL_OPT_NONE|SSL_OPT_RELSET;
  dc->nOptionsAdd   = SSL_OPT_NONE;
  dc->nOptionsDel   = SSL_OPT_NONE;
  
  dc->szCipherSuite          = NULL;
  dc->nVerifyClient          = SSL_CVERIFY_UNSET;
  dc->nVerifyDepth           = UNSET;
  
  dc->szCACertificatePath    = NULL;
  dc->szCACertificateFile    = NULL;
  dc->szUserName             = NULL;
  
  return dc;
}

const char *
ssl_cmd_SSLPassPhraseDialog (cmd_parms *cmd, void *dummy, const char *arg)
{
  struct rovm *r = CMD_ROVM (cmd);
  SSLSrvConfigRec *sc = mySrvConfig (r);
  int arglen = strlen(arg);
  
  if (strcEQ(arg, "builtin")) 
    {
      sc->server->pphrase_dialog_type = SSL_PPTYPE_BUILTIN;
      sc->server->pphrase_dialog_path = NULL;
    }
  else if ((arglen > 5) && strEQn (arg, "exec:", 5)) 
    {
      sc->server->pphrase_dialog_type = SSL_PPTYPE_FILTER;
      sc->server->pphrase_dialog_path = apr_pstrdup (ROVM_POOL (r), arg+5);
      if (!sc->server->pphrase_dialog_path) 
        {
          return apr_pstrcat(ROVM_POOL (r),
                             "Invalid SSLPassPhraseDialog exec: path ",
                             arg+5, NULL);
        }
      if (!ssl_util_path_check(SSL_PCM_EXISTS,
                               sc->server->pphrase_dialog_path,
                               ROVM_POOL (r)))
        {
          return apr_pstrcat(ROVM_POOL (r),
                             "SSLPassPhraseDialog: file '",
                             sc->server->pphrase_dialog_path,
                             "' does not exist", NULL);
        }
    }
  else if ((arglen > 1) && (arg[0] == '|')) 
    {
      sc->server->pphrase_dialog_type = SSL_PPTYPE_PIPE;
      sc->server->pphrase_dialog_path = arg + 1;
    }
  else 
    return "SSLPassPhraseDialog: Invalid argument";
  
  return NULL;
}

BOOL
ssl_config_global_isfixed (SSLModConfigRec *mc)
{
  return mc->bFixed;
}

#define APR_SHM_MAXSIZE (64 * 1024 * 1024)

#define MODSSL_NO_SHARED_MEMORY_ERROR \
    "SSLSessionCache: shared memory cache not useable on this platform"

const char *
ssl_cmd_SSLSessionCache (cmd_parms *cmd, void *dummy, const char *arg)
{
  struct rovm *r = CMD_ROVM (cmd);
  SSLModConfigRec *mc = myModConfig(r);
  const char *colon;
  char *cp, *cp2;
  int arglen = strlen(arg);
  
  if (ssl_config_global_isfixed(mc))
    return NULL;

  if (strcEQ(arg, "none")) 
    {
      mc->nSessionCacheMode      = SSL_SCMODE_NONE;
      mc->szSessionCacheDataFile = NULL;
    }
  else if (strcEQ(arg, "nonenotnull")) 
    {
      mc->nSessionCacheMode      = SSL_SCMODE_NONE_NOT_NULL;
      mc->szSessionCacheDataFile = NULL;
    }
  else if ((arglen > 4) && strcEQn(arg, "dbm:", 4)) 
    {
      mc->nSessionCacheMode      = SSL_SCMODE_DBM;
      mc->szSessionCacheDataFile = apr_pstrdup (mc->pPool, arg+4);
      if (!mc->szSessionCacheDataFile) 
        {
          return apr_psprintf(ROVM_POOL (r),
                              "SSLSessionCache: Invalid cache file path %s",
                              arg+4);
        }
    }
  else if (((arglen > 4) && strcEQn(arg, "shm:", 4)) ||
           ((arglen > 6) && strcEQn(arg, "shmht:", 6)) ||
           ((arglen > 6) && strcEQn(arg, "shmcb:", 6))) 
    {
#if !APR_HAS_SHARED_MEMORY
      return MODSSL_NO_SHARED_MEMORY_ERROR;
#endif
      mc->nSessionCacheMode      = SSL_SCMODE_SHMCB;
      colon = strchr (arg, ':');
      mc->szSessionCacheDataFile = apr_pstrdup (mc->pPool, colon+1);
      if (!mc->szSessionCacheDataFile) 
        {
          return apr_psprintf(ROVM_POOL (r),
                              "SSLSessionCache: Invalid cache file path %s",
                              colon+1);
        }
      mc->tSessionCacheDataTable = NULL;
      mc->nSessionCacheDataSize  = 1024*512; /* 512KB */
      
      if ((cp = strchr(mc->szSessionCacheDataFile, '('))) 
        {
          *cp++ = NUL;
          
          if (!(cp2 = strchr(cp, ')'))) 
            {
              return "SSLSessionCache: Invalid argument: "
                "no closing parenthesis";
            }
          
          *cp2 = NUL;
          
          mc->nSessionCacheDataSize = atoi(cp);
          
          if (mc->nSessionCacheDataSize < 8192) 
            {
              return "SSLSessionCache: Invalid argument: "
                "size has to be >= 8192 bytes";
            }
          
          if (mc->nSessionCacheDataSize >= APR_SHM_MAXSIZE) 
            {
              return apr_psprintf(ROVM_POOL (r),
                                  "SSLSessionCache: Invalid argument: "
                                  "size has to be < %d bytes on this "
                                  "platform", APR_SHM_MAXSIZE);
              
            }
        }
    }
  else if ((arglen > 3) && strcEQn(arg, "dc:", 3)) 
    {
#ifdef HAVE_DISTCACHE
      mc->nSessionCacheMode      = SSL_SCMODE_DC;
      mc->szSessionCacheDataFile = apr_pstrdup(mc->pPool, arg+3);
      if (!mc->szSessionCacheDataFile) 
        {
          return apr_pstrcat(ROVM_POOL (r),
                             "SSLSessionCache: Invalid cache file path: ",
                             arg+3, NULL);
        }
#else
      return "SSLSessionCache: distcache support disabled";
#endif
    }
  else
    return "SSLSessionCache: Invalid argument";
  
  return NULL;
}

const char *
ssl_cmd_SSLSessionCacheTimeout (cmd_parms *cmd, void *dummy, const char *arg)
{
  struct rovm *r = CMD_ROVM (cmd);
  SSLSrvConfigRec *sc = mySrvConfig(r);
  
  sc->session_cache_timeout = atoi(arg);
  
  if (sc->session_cache_timeout < 0) 
    {
      return "SSLSessionCacheTimeout: Invalid argument";
    }
  
  return NULL;
}

const char ssl_valid_ssl_mutex_string[] =
  "Valid SSLMutex mechanisms are: `none', `default'"
#if APR_HAS_FLOCK_SERIALIZE
  ", `flock:/path/to/file'"
#endif
#if APR_HAS_FCNTL_SERIALIZE
  ", `fcntl:/path/to/file'"
#endif
#if APR_HAS_SYSVSEM_SERIALIZE && !defined(PERCHILD_MPM)
  ", `sysvsem'"
#endif
#if APR_HAS_POSIXSEM_SERIALIZE
  ", `posixsem'"
#endif
#if APR_HAS_PROC_PTHREAD_SERIALIZE
  ", `pthread'"
#endif
#if APR_HAS_FLOCK_SERIALIZE || APR_HAS_FCNTL_SERIALIZE
  ", `file:/path/to/file'"
#endif
#if (APR_HAS_SYSVSEM_SERIALIZE && !defined(PERCHILD_MPM)) || APR_HAS_POSIXSEM_SERIALIZE
  ", `sem'"
#endif
  " ";

/*
 *  Configuration functions for particular directives
 */

const char *
ssl_cmd_SSLMutex (cmd_parms *cmd, void *dummy, const char *arg_)
{
  struct rovm *r = CMD_ROVM (cmd);
  SSLModConfigRec *mc = myModConfig (r);
  /* Split arg_ into meth and file */
  char *meth = apr_pstrdup (ROVM_POOL (r), arg_);
  char *file = strchr(meth, ':');
  if (file) 
    {
      *(file++) = '\0';
      if (!*file) 
        file = NULL;
    }
  
  if (ssl_config_global_isfixed(mc))
    return NULL;

  if (!strcasecmp(meth, "none") || !strcasecmp(meth, "no")) 
    {
      mc->nMutexMode  = SSL_MUTEXMODE_NONE;
      return NULL;
    }

  /* APR determines temporary filename unless overridden below,
   * we presume file indicates an szMutexFile is a file path
   * unless the method sets szMutexFile=file and NULLs file
   */
  mc->nMutexMode  = SSL_MUTEXMODE_USED;
  mc->szMutexFile = NULL;
  
  /* NOTE: previously, 'yes' implied 'sem' */
  if (!strcasecmp(meth, "default") || !strcasecmp(meth, "yes"))
    mc->nMutexMech = APR_LOCK_DEFAULT;
#if APR_HAS_FCNTL_SERIALIZE
  else if ((!strcasecmp(meth, "fcntl") || !strcasecmp(meth, "file")) && file)
    mc->nMutexMech = APR_LOCK_FCNTL;
#endif
#if APR_HAS_FLOCK_SERIALIZE
  else if ((!strcasecmp(meth, "flock") || !strcasecmp(meth, "file")) && file)
    mc->nMutexMech = APR_LOCK_FLOCK;
#endif
#if APR_HAS_POSIXSEM_SERIALIZE
  else if (!strcasecmp(meth, "posixsem") || !strcasecmp(meth, "sem")) 
    {
      mc->nMutexMech = APR_LOCK_POSIXSEM;
      /* Posix/SysV semaphores aren't file based, use the literal name
       * if provided and fall back on APR's default if not.  Today, APR
       * will ignore it, but once supported it has an absurdly short limit.
       */
      if (file) 
        {
          mc->szMutexFile = apr_pstrdup(cmd->server->process->pool, file);
          
          file = NULL;
        }
    }
#endif
#if APR_HAS_SYSVSEM_SERIALIZE && !defined(PERCHILD_MPM)
  else if (!strcasecmp(meth, "sysvsem") || !strcasecmp(meth, "sem"))
    mc->nMutexMech = APR_LOCK_SYSVSEM;
#endif
#if APR_HAS_PROC_PTHREAD_SERIALIZE
  else if (!strcasecmp(meth, "pthread"))
    mc->nMutexMech = APR_LOCK_PROC_PTHREAD;
#endif
  else 
    return apr_pstrcat(ROVM_POOL (r), "Invalid SSLMutex argument ", arg_,
                       " (", ssl_valid_ssl_mutex_string, ")", NULL);

  /* Unless the method above assumed responsibility for setting up
   * mc->szMutexFile and NULLing out file, presume it is a file we
   * are looking to use
   */
  if (file) 
    {
      mc->szMutexFile = apr_pstrdup (ROVM_POOL (r), file);
      if (!mc->szMutexFile)
        return apr_pstrcat(ROVM_POOL (r), "Invalid SSLMutex ", meth,
                           ": filepath ", file, NULL);
    }
  
  return NULL;
}

const char *
ssl_cmd_SSLEngine (cmd_parms *cmd, void *dummy, const char *arg)
{
  struct rovm *r = CMD_ROVM (cmd);
  SSLSrvConfigRec *sc = mySrvConfig (r);
  
  if (!strcasecmp(arg, "On")) 
    {
      sc->enabled = SSL_ENABLED_TRUE;
      return NULL;
    }
  else if (!strcasecmp(arg, "Off")) 
    {
      sc->enabled = SSL_ENABLED_FALSE;
      return NULL;
    }
  else if (!strcasecmp(arg, "Optional")) 
    {
      sc->enabled = SSL_ENABLED_OPTIONAL;
      return NULL;
    }
  
  return "Argument must be On, Off, or Optional";
}

const char *
ssl_cmd_SSLCipherSuite (cmd_parms *cmd, void *dummy, const char *arg)
{
  struct rovm *r = CMD_ROVM (cmd);
  SSLSrvConfigRec *sc = mySrvConfig(r);
  
  sc->server->auth.cipher_suite = arg;

  return NULL;
}

#define SSL_FLAGS_CHECK_FILE                            \
  (SSL_PCM_EXISTS|SSL_PCM_ISREG|SSL_PCM_ISNONZERO)

#define SSL_FLAGS_CHECK_DIR                     \
  (SSL_PCM_EXISTS|SSL_PCM_ISDIR)

static const char *
ssl_cmd_check_file(struct rovm *r,
                   const char **file)
{
  const char *filepath = apr_pstrdup (ROVM_POOL (r), *file);
  
  if (!filepath) 
    return apr_pstrcat(ROVM_POOL (r), "SSL Option", 
                       ": Invalid file path ", *file, NULL);
  *file = filepath;
  
  if (ssl_util_path_check(SSL_FLAGS_CHECK_FILE, *file, ROVM_POOL (r)))
    return NULL;

  return apr_pstrcat(ROVM_POOL (r), "SSL Option", 
                     ": file '", *file,
                     "' does not exist or is empty", NULL);
}

#define SSL_AIDX_CERTS 1
#define SSL_AIDX_KEYS  2

static const char *
ssl_cmd_check_aidx_max(struct rovm *r,
                       const char *arg,
                       int idx)
{
  SSLSrvConfigRec *sc = mySrvConfig(r);
  const char *err, *desc=NULL, **files=NULL;
  int i;
  
  if ((err = ssl_cmd_check_file(r, &arg)))
    return err;

  switch (idx) 
    {
    case SSL_AIDX_CERTS:
      desc = "certificates";
      files = sc->server->pks->cert_files;
      break;
    case SSL_AIDX_KEYS:
      desc = "private keys";
      files = sc->server->pks->key_files;
      break;
    }
  
  for (i = 0; i < SSL_AIDX_MAX; i++) 
    {
      if (!files[i]) 
        {
          files[i] = arg;
          return NULL;
        }
    }
  
  return apr_psprintf(ROVM_POOL (r),
                      "%s: only up to %d "
                      "different %s per virtual host allowed",
                      "SSL Option", SSL_AIDX_MAX, desc);
}

const char *
ssl_cmd_SSLCertificateFile (cmd_parms *cmd, void *dummy, const char *arg)
{
  struct rovm *r = CMD_ROVM (cmd);
  const char *err;
  
  if ((err = ssl_cmd_check_aidx_max (r, arg, SSL_AIDX_CERTS)))
    return err;
  
  return NULL;
}

const char *
ssl_cmd_SSLCertificateKeyFile (cmd_parms *cmd, void *dummy, const char *arg)
{
  struct rovm *r = CMD_ROVM (cmd);
  const char *err;
  
  if ((err = ssl_cmd_check_aidx_max (r, arg, SSL_AIDX_KEYS)))
    return err;

  return NULL;
}
