#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"
#include "utils.h"

/*  _________________________________________________________________
**
**  Utility Functions
**  _________________________________________________________________
*/

BOOL 
ssl_util_path_check(ssl_pathcheck_t pcm, const char *path, apr_pool_t *p)
{
  apr_finfo_t finfo;
  
  if (path == NULL)
    return FALSE;
  if (pcm & SSL_PCM_EXISTS && apr_stat(&finfo, path,
                                       APR_FINFO_TYPE|APR_FINFO_SIZE, p) != 0)
    return FALSE;
  if (pcm & SSL_PCM_ISREG && finfo.filetype != APR_REG)
    return FALSE;
  if (pcm & SSL_PCM_ISDIR && finfo.filetype != APR_DIR)
    return FALSE;
  if (pcm & SSL_PCM_ISNONZERO && finfo.size <= 0)
    return FALSE;
  return TRUE;
}

/*
 * Run a filter program and read the first line of its stdout output
 */
char *
ssl_util_readfilter (struct rovm *r, apr_pool_t *p, const char *cmd,
                     const char * const *argv)
{
  static char buf[MAX_STRING_LEN];
  apr_file_t *fp;
  apr_size_t nbytes = 1;
  char c;
  int k;
  
  if ((fp = ssl_util_ppopen (r, p, cmd, argv)) == NULL)
    return NULL;
  /* XXX: we are reading 1 byte at a time here */
  for (k = 0; apr_file_read(fp, &c, &nbytes) == APR_SUCCESS
         && nbytes == 1 && (k < MAX_STRING_LEN-1); ) 
    {
      if (c == '\n' || c == '\r')
        break;
      buf[k++] = c;
    }

  buf[k] = NUL;
  ssl_util_ppclose(r, p, fp);
  
  return buf;
}

static const char *ssl_asn1_key_types[] = {"RSA", "DSA"};

const char *
ssl_asn1_keystr(int keytype)
{
  if (keytype >= SSL_AIDX_MAX)
    return NULL;

  return ssl_asn1_key_types[keytype];
}

const char *
ssl_asn1_table_keyfmt(apr_pool_t *p,
                      const char *id,
                      int keytype)
{
  const char *keystr = ssl_asn1_keystr(keytype);
  
  return apr_pstrcat (p, id, ":", keystr, NULL);
}

/*
 * certain key and cert data needs to survive restarts,
 * which are stored in the user data table of s->process->pool.
 * to prevent "leaking" of this data, we use malloc/free
 * rather than apr_palloc and these wrappers to help make sure
 * we do not leak the malloc-ed data.
 */
unsigned char *
ssl_asn1_table_set(apr_hash_t *table,
                   const char *key,
                   long int length)
{
  apr_ssize_t klen = strlen(key);
  ssl_asn1_t *asn1 = apr_hash_get(table, key, klen);
  
  /*
   * if a value for this key already exists,
   * reuse as much of the already malloc-ed data
   * as possible.
   */
  if (asn1) 
    {
      if (asn1->nData != length) 
        {
          free(asn1->cpData); /* XXX: realloc? */
          asn1->cpData = NULL;
        }
    }
  else 
    {
      asn1 = malloc(sizeof(*asn1));
      asn1->source_mtime = 0; /* used as a note for encrypted private keys */
      asn1->cpData = NULL;
    }
  
  asn1->nData = length;
  if (!asn1->cpData)
    asn1->cpData = malloc(length);
  
  apr_hash_set (table, key, klen, asn1);
  
  return asn1->cpData; /* caller will assign a value to this */
}

ssl_asn1_t *
ssl_asn1_table_get (apr_hash_t *table,
                    const char *key)
{
  return (ssl_asn1_t *)apr_hash_get(table, key, APR_HASH_KEY_STRING);
}

void ssl_asn1_table_unset (apr_hash_t *table,
                           const char *key)
{
  apr_ssize_t klen = strlen(key);
  ssl_asn1_t *asn1 = apr_hash_get(table, key, klen);
  
  if (!asn1)
    return;

  if (asn1->cpData)
    free(asn1->cpData);
  free(asn1);

  apr_hash_set (table, key, klen, NULL);
}

char *
ssl_util_algotypestr (ssl_algo_t t)
{
  char *cp;
  
  cp = "UNKNOWN";
  switch (t) 
    {
    case SSL_ALGO_RSA:
      cp = "RSA";
      break;
    case SSL_ALGO_DSA:
      cp = "DSA";
      break;
    default:
      break;
    }

  return cp;
}

ssl_algo_t
ssl_util_algotypeof (X509 *pCert, EVP_PKEY *pKey)
{
  ssl_algo_t t;
  EVP_PKEY *pFreeKey = NULL;
  
  t = SSL_ALGO_UNKNOWN;
  if (pCert != NULL)
    pFreeKey = pKey = X509_get_pubkey(pCert);
  if (pKey != NULL) 
    {
      switch (EVP_PKEY_key_type (pKey)) 
        {
        case EVP_PKEY_RSA:
          t = SSL_ALGO_RSA;
          break;
        case EVP_PKEY_DSA:
          t = SSL_ALGO_DSA;
          break;
        default:
          break;
        }
    }
#ifdef OPENSSL_VERSION_NUMBER
  /* Only refcounted in OpenSSL */
  if (pFreeKey != NULL)
    EVP_PKEY_free (pFreeKey);
#endif
  return t;
}

rc_file_t *
ssl_util_ppopen (struct rovm *r, apr_pool_t *p, const char *cmd,
                 const char * const *argv)
{
  apr_procattr_t *procattr;
  apr_proc_t *proc;
  
  if (apr_procattr_create(&procattr, p) != APR_SUCCESS)
    return NULL;
  if (apr_procattr_io_set (procattr, APR_FULL_BLOCK, APR_FULL_BLOCK,
                          APR_FULL_BLOCK) != APR_SUCCESS)
    return NULL;
  if (apr_procattr_dir_set (procattr,
                           rv_make_dirstr_parent (p, cmd)) != APR_SUCCESS)
    return NULL;
  if (apr_procattr_cmdtype_set (procattr, APR_PROGRAM) != APR_SUCCESS)
    return NULL;
  if ((proc = (apr_proc_t *)apr_pcalloc(p, sizeof(apr_proc_t))) == NULL)
    return NULL;
  if (apr_proc_create (proc, cmd, argv, NULL, procattr, p) != APR_SUCCESS)
    return NULL;
  return proc->out;
}

void 
ssl_util_ppclose (struct rovm *r, apr_pool_t *p, apr_file_t *fp)
{
  apr_file_close(fp);
  return;
}

char *
ssl_util_vhostid (rc_pool_t *p, struct rovm *r)
{
  char *id;
  SSLSrvConfigRec *sc;
  char *host;
  apr_port_t port;
  
  host = CONF_HOSTNAME (ROVM_CONF (r));
  if (CONF_PORT (ROVM_CONF (r)) != 0)
    port = CONF_PORT (ROVM_CONF (r));
  else 
    {
      sc = mySrvConfig (r);
      if (sc->enabled == TRUE)
        port = DEFAULT_ROVM_LISTENING_PORT;
      else
        {
          port = DEFAULT_ROVM_LISTENING_PORT;
          rovm_log (NULL, ROVMLOG_CRIT, ROVMLOG_MARK, "We don't use SSL?");
        }
    }
  id = apr_psprintf (p, "%s:%lu", host, (unsigned long) port);

  return id;
}

#if APR_HAS_THREADS
/*
 * To ensure thread-safetyness in OpenSSL - work in progress
 */

static apr_thread_mutex_t **lock_cs;
static int                  lock_num_locks;

static rc_status_t 
ssl_util_thread_cleanup (void *data)
{
  CRYPTO_set_locking_callback (NULL);
  CRYPTO_set_id_callback (NULL);

  /* Let the registered mutex cleanups do their own thing.  */
  return RC_SUCCESS;
}

#ifdef HAVE_SSLC
#if SSLC_VERSION_NUMBER >= 0x2000
static int ssl_util_thr_lock(int mode, int type,
                             char *file, int line)
#else
static void ssl_util_thr_lock(int mode, int type,
                              char *file, int line)
#endif
#else
static void ssl_util_thr_lock(int mode, int type,
                              const char *file, int line)
#endif
{
  if (type < lock_num_locks) 
    {
      if (mode & CRYPTO_LOCK) 
        {
          rc_thread_mutex_lock (lock_cs[type]);
        }
      else 
        {
          rc_thread_mutex_unlock (lock_cs[type]);
        }
#ifdef HAVE_SSLC
#if SSLC_VERSION_NUMBER >= 0x2000
      return 1;
    }
  else 
    {
      return -1;
#endif
#endif
    }
}

static unsigned long
ssl_util_thr_id (void)
{
  /* OpenSSL needs this to return an unsigned long.  On OS/390, the pthread
     id is a structure twice that big.  Use the TCB pointer instead as a
     unique unsigned long. */
#ifdef __MVS__
  struct PSA 
  {
    char unmapped[540];
    unsigned long PSATOLD;
  } *psaptr = 0;
  
  return psaptr->PSATOLD;
#else
  return (unsigned long) rc_os_thread_current ();
#endif
}

void 
ssl_util_thread_setup (rc_pool_t *p)
{
  int i;
  
  lock_num_locks = CRYPTO_num_locks ();
  lock_cs = mp_alloc (p, lock_num_locks * sizeof (*lock_cs));
  
  for (i = 0; i < lock_num_locks; i++)
    rc_thread_mutex_create (&(lock_cs[i]), RC_THREAD_MUTEX_DEFAULT, p);

  CRYPTO_set_id_callback (ssl_util_thr_id);
  CRYPTO_set_locking_callback (ssl_util_thr_lock);

  mp_cleanup_register (p, NULL, ssl_util_thread_cleanup,
                       apr_pool_cleanup_null);
}
#endif
