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

extern void ssl_io_filter_init (conn_rec *c, SSL *ssl);

/* the various processing hooks.  */
static rc_status_t
ssl_cleanup_pre_config (void *data)
{
  /*
   * Try to kill the internals of the SSL library.
   */
#ifdef HAVE_OPENSSL
#if OPENSSL_VERSION_NUMBER >= 0x00907001
  /* Corresponds to OPENSSL_load_builtin_modules():
   * XXX: borrowed from apps.h, but why not CONF_modules_free()
   * which also invokes CONF_modules_finish()?
   */
  CONF_modules_unload(1);
#endif
#endif
  /* Corresponds to SSL_library_init: */
  EVP_cleanup();
#if HAVE_ENGINE_LOAD_BUILTIN_ENGINES
  ENGINE_cleanup();
#endif
#ifdef HAVE_OPENSSL
#if OPENSSL_VERSION_NUMBER >= 0x00907001
  CRYPTO_cleanup_all_ex_data();
#endif
#endif
  ERR_remove_state(0);
  
  /* Don't call ERR_free_strings here; ERR_load_*_strings only
   * actually load the error strings once per process due to static
   * variable abuse in OpenSSL. */
  
  /*
   * TODO: determine somewhere we can safely shove out diagnostics
   *       (when enabled) at this late stage in the game:
   * CRYPTO_mem_leaks_fp(stderr);
   */
  return RC_SUCCESS;
}

static int
ssl_hook_pre_config (r)
     struct rovm *r;
{
  /* We must register the library in full, to ensure our configuration
     code can successfully test the SSL environment.  */
  CRYPTO_malloc_init ();
#ifdef HAVE_OPENSSL
  ERR_load_crypto_strings ();
#endif
  SSL_load_error_strings ();
  SSL_library_init ();
#if HAVE_ENGINE_LOAD_BUILTIN_ENGINES
  ENGINE_load_builtin_engines ();
#endif
#ifdef HAVE_OPENSSL
  OpenSSL_add_all_algorithms ();
#if OPENSSL_VERSION_NUMBER >= 0x00907001
  OPENSSL_load_builtin_modules ();
#endif
#endif
  
  /* Let us cleanup the ssl library when the module is unloaded.  */
  mp_cleanup_register (ROVM_PCONF (r), NULL, ssl_cleanup_pre_config,
                       apr_pool_cleanup_null);

  return 0;
}

static SSLConnRec *
ssl_init_connection_ctx (conn_rec *c)
{
  SSLConnRec *sslconn = myConnConfig (c);
  
  if (sslconn)
    return sslconn;
  
  sslconn = apr_pcalloc (c->pool, sizeof(*sslconn));
  
  myConnConfigSet (c, sslconn);
  
  return sslconn;
}

char * 
rv_md5_binary (apr_pool_t *p, const unsigned char *buf, int length)
{
  const char *hex = "0123456789abcdef";
  apr_md5_ctx_t my_md5;
  unsigned char hash[APR_MD5_DIGESTSIZE];
  char *r, result[33]; /* (MD5_DIGESTSIZE * 2) + 1 */
  int i;
  
  /*
   * Take the MD5 hash of the string argument.
   */
  
  apr_md5_init(&my_md5);
#if APR_CHARSET_EBCDIC
  apr_md5_set_xlate(&my_md5, ap_hdrs_to_ascii);
#endif
  apr_md5_update(&my_md5, buf, (unsigned int)length);
  apr_md5_final(hash, &my_md5);
  
  for (i = 0, r = result; i < APR_MD5_DIGESTSIZE; i++) 
    {
      *r++ = hex[hash[i] >> 4];
      *r++ = hex[hash[i] & 0xF];
    }
  *r = '\0';
  
  return apr_pstrndup(p, result, APR_MD5_DIGESTSIZE*2);
}

int 
ssl_init_ssl_connection (conn_rec *c)
{
  SSLSrvConfigRec *sc = mySrvConfig (c->ts->arg);
  SSL *ssl;
  SSLConnRec *sslconn = myConnConfig (c);
  char *vhost_md5;
  modssl_ctx_t *mctx;
  
  /*
   * Seed the Pseudo Random Number Generator (PRNG)
   */
  ssl_rand_seed (c->ts->arg, c->pool, SSL_RSCTX_CONNECT, "");
  
  if (!sslconn) 
    sslconn = ssl_init_connection_ctx(c);
  
  mctx = sslconn->is_proxy ? sc->proxy : sc->server;
  
  /*
   * Create a new SSL connection with the configured server SSL context and
   * attach this to the socket. Additionally we register this attachment
   * so we can detach later.
   */
  if (!(ssl = SSL_new(mctx->ssl_ctx))) 
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK,
                "Unable to create a new SSL connection from the SSL "
                "context");
      ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_ERR, c->ts->arg);
      c->aborted = 1;
      
      return -1; /* XXX */
    }
  
  vhost_md5 = rv_md5_binary (c->pool, (unsigned char *)sc->vhost_id, sc->vhost_id_len);
  
  if (!SSL_set_session_id_context (ssl, (unsigned char *)vhost_md5,
                                   APR_MD5_DIGESTSIZE*2))
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK,
                "Unable to set session id context to `%s'", vhost_md5);
      ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_ERR, c->ts->arg);
      c->aborted = 1;
      
      return -1; /* XXX */
    }
  
  SSL_set_app_data(ssl, c);
  SSL_set_app_data2(ssl, NULL); /* will be request_rec */
  
  sslconn->ssl = ssl;
  
  /*
   *  Configure callbacks for SSL connection
   */
  SSL_set_tmp_rsa_callback (ssl, ssl_callback_TmpRSA);
  SSL_set_tmp_dh_callback (ssl,  ssl_callback_TmpDH);
  
  SSL_set_verify_result (ssl, X509_V_OK);
  
  ssl_io_filter_init (c, ssl);
  
  return 0;
}

int 
ssl_hook_pre_connection(conn_rec *c)
{
  SSLSrvConfigRec *sc = mySrvConfig(c->ts->arg);
  SSLConnRec *sslconn = myConnConfig (c);
  
  /*
   * Immediately stop processing if SSL is disabled for this connection
   */
  if (!(sc && (sc->enabled == SSL_ENABLED_TRUE ||
               (sslconn && sslconn->is_proxy))))
    return -1;
  
  /*
   * Create SSL context
   */
  if (!sslconn)
    sslconn = ssl_init_connection_ctx (c);
  if (sslconn->disabled)
    return -1;

  /*
   * Remember the connection information for
   * later access inside callback functions
   */
  
  rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK,
            "Connection to child %ld established "
            "(server %s)", 123, sc->vhost_id);
  
  return ssl_init_ssl_connection (c);
}

/*
  SSL 기능을 활성화합니다.
*/
int
init_rovm_ssl (struct rovm *r)
{
  if (ssl_init_Module (r))
    return -1;

  ssl_init_Child (ROVM_POOL (r), r);

  return 0;
}

/*
  Config 과정이 일어나기 전에 실행되어야 할 부분.
 */
int
init_rovm_ssl_pre_config (r)
     struct rovm *r;
{
  ROVM_SSL_SERVER (r) = ssl_config_server_create (r, ROVM_PCONF (r));
  ROVM_SSL_DIR (r) = ssl_config_perdir_create (ROVM_PCONF (r), NULL);

  if (ssl_hook_pre_config (r))
    return -1;

  return 0;
}
