/*
 *  OpenVPN -- An application to securely tunnel IP networks
 *             over a single TCP/UDP port, with support for SSL/TLS-based
 *             session authentication and key exchange,
 *             packet encryption, packet authentication, and
 *             packet compression.
 *
 *  Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
 *  Copyright (C) 2010 Fox Crypto B.V. <openvpn@fox-it.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2
 *  as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program (see the file COPYING included with this
 *  distribution); if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/**
 * @file Control Channel OpenSSL Backend
 */

#include "syshead.h"

#if defined(USE_SSL) && defined(USE_OPENSSL)

#include "errlevel.h"
#include "buffer.h"
#include "misc.h"
#include "manage.h"
#include "memdbg.h"
#include "ssl_backend.h"
#include "ssl_common.h"
#include "base64.h"

#ifdef ENABLE_CRYPTOAPI
#include "cryptoapi.h"
#endif

#include "ssl_verify_openssl.h"

#include <openssl/err.h>
#include <openssl/pkcs12.h>
#include <openssl/x509.h>
#include <openssl/crypto.h>

/*
 * Allocate space in SSL objects in which to store a struct tls_session
 * pointer back to parent.
 *
 */

int mydata_index; /* GLOBAL */

void
tls_init_lib()
{
  SSL_library_init();
  SSL_load_error_strings();
  OpenSSL_add_all_algorithms ();

  mydata_index = SSL_get_ex_new_index(0, "struct session *", NULL, NULL, NULL);
  ASSERT (mydata_index >= 0);
}

void
tls_free_lib()
{
  EVP_cleanup();
  ERR_free_strings();
}

void
tls_clear_error()
{
  ERR_clear_error ();
}

/*
 * OpenSSL callback to get a temporary RSA key, mostly
 * used for export ciphers.
 */
static RSA *
tmp_rsa_cb (SSL * s, int is_export, int keylength)
{
  static RSA *rsa_tmp = NULL;
  if (rsa_tmp == NULL)
    {
      msg (D_HANDSHAKE, "Generating temp (%d bit) RSA key", keylength);
      rsa_tmp = RSA_generate_key (keylength, RSA_F4, NULL, NULL);
    }
  return (rsa_tmp);
}

void
tls_ctx_server_new(struct tls_root_ctx *ctx)
{
  ASSERT(NULL != ctx);

  ctx->ctx = SSL_CTX_new (TLSv1_server_method ());

  if (ctx->ctx == NULL)
    msg (M_SSLERR, "SSL_CTX_new TLSv1_server_method");

  SSL_CTX_set_tmp_rsa_callback (ctx->ctx, tmp_rsa_cb);
}

void
tls_ctx_client_new(struct tls_root_ctx *ctx)
{
  ASSERT(NULL != ctx);

  ctx->ctx = SSL_CTX_new (TLSv1_client_method ());

  if (ctx->ctx == NULL)
    msg (M_SSLERR, "SSL_CTX_new TLSv1_client_method");
}

void
tls_ctx_free(struct tls_root_ctx *ctx)
{
  ASSERT(NULL != ctx);
  if (NULL != ctx->ctx)
    SSL_CTX_free (ctx->ctx);
  ctx->ctx = NULL;
}

bool tls_ctx_initialised(struct tls_root_ctx *ctx)
{
  ASSERT(NULL != ctx);
  return NULL != ctx->ctx;
}

/*
 * Print debugging information on SSL/TLS session negotiation.
 */

#ifndef INFO_CALLBACK_SSL_CONST
#define INFO_CALLBACK_SSL_CONST const
#endif
static void
info_callback (INFO_CALLBACK_SSL_CONST SSL * s, int where, int ret)
{
  if (where & SSL_CB_LOOP)
    {
      dmsg (D_HANDSHAKE_VERBOSE, "SSL state (%s): %s",
	   where & SSL_ST_CONNECT ? "connect" :
	   where & SSL_ST_ACCEPT ? "accept" :
	   "undefined", SSL_state_string_long (s));
    }
  else if (where & SSL_CB_ALERT)
    {
      dmsg (D_HANDSHAKE_VERBOSE, "SSL alert (%s): %s: %s",
	   where & SSL_CB_READ ? "read" : "write",
	   SSL_alert_type_string_long (ret),
	   SSL_alert_desc_string_long (ret));
    }
}

void
tls_ctx_set_options (struct tls_root_ctx *ctx, unsigned int ssl_flags)
{
  ASSERT(NULL != ctx);

  SSL_CTX_set_session_cache_mode (ctx->ctx, SSL_SESS_CACHE_OFF);
  SSL_CTX_set_options (ctx->ctx, SSL_OP_SINGLE_DH_USE);
  SSL_CTX_set_default_passwd_cb (ctx->ctx, pem_password_callback);

  /* Require peer certificate verification */
#if P2MP_SERVER
  if (ssl_flags & SSLF_CLIENT_CERT_NOT_REQUIRED)
    {
      msg (M_WARN, "WARNING: POTENTIALLY DANGEROUS OPTION "
	  "--client-cert-not-required may accept clients which do not present "
	  "a certificate");
    }
  else
#endif
  SSL_CTX_set_verify (ctx->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
		      verify_callback);

  SSL_CTX_set_info_callback (ctx->ctx, info_callback);
}

void
tls_ctx_restrict_ciphers(struct tls_root_ctx *ctx, const char *ciphers)
{
  ASSERT(NULL != ctx);

  if(!SSL_CTX_set_cipher_list(ctx->ctx, ciphers))
    msg(M_SSLERR, "Failed to set restricted TLS cipher list: %s", ciphers);
}

void
tls_ctx_load_dh_params (struct tls_root_ctx *ctx, const char *dh_file
#if ENABLE_INLINE_FILES
    , const char *dh_file_inline
#endif /* ENABLE_INLINE_FILES */
    )
{
  DH *dh;
  BIO *bio;

  ASSERT(NULL != ctx);

#if ENABLE_INLINE_FILES
  if (!strcmp (dh_file, INLINE_FILE_TAG) && dh_file_inline)
    {
      if (!(bio = BIO_new_mem_buf ((char *)dh_file_inline, -1)))
	msg (M_SSLERR, "Cannot open memory BIO for inline DH parameters");
    }
  else
#endif /* ENABLE_INLINE_FILES */
    {
      /* Get Diffie Hellman Parameters */
      if (!(bio = BIO_new_file (dh_file, "r")))
	msg (M_SSLERR, "Cannot open %s for DH parameters", dh_file);
    }

  dh = PEM_read_bio_DHparams (bio, NULL, NULL, NULL);
  BIO_free (bio);

  if (!dh)
    msg (M_SSLERR, "Cannot load DH parameters from %s", dh_file);
  if (!SSL_CTX_set_tmp_dh (ctx->ctx, dh))
    msg (M_SSLERR, "SSL_CTX_set_tmp_dh");

  msg (D_TLS_DEBUG_LOW, "Diffie-Hellman initialized with %d bit key",
       8 * DH_size (dh));

  DH_free (dh);
}

int
tls_ctx_load_pkcs12(struct tls_root_ctx *ctx, const char *pkcs12_file,
#if ENABLE_INLINE_FILES
    const char *pkcs12_file_inline,
#endif /* ENABLE_INLINE_FILES */
    bool load_ca_file
    )
{
  FILE *fp;
  EVP_PKEY *pkey;
  X509 *cert;
  STACK_OF(X509) *ca = NULL;
  PKCS12 *p12;
  int i;
  char password[256];

  ASSERT(NULL != ctx);

#if ENABLE_INLINE_FILES
  if (!strcmp (pkcs12_file, INLINE_FILE_TAG) && pkcs12_file_inline)
    {
      BIO *b64 = BIO_new(BIO_f_base64());
      BIO *bio = BIO_new_mem_buf((void *) pkcs12_file_inline,
	  (int) strlen(pkcs12_file_inline));
      ASSERT(b64 && bio);
      BIO_push(b64, bio);
      p12 = d2i_PKCS12_bio(b64, NULL);
      if (!p12)
	msg(M_SSLERR, "Error reading inline PKCS#12 file");
      BIO_free(b64);
      BIO_free(bio);
    }
  else
#endif
    {
      /* Load the PKCS #12 file */
      if (!(fp = openvpn_fopen(pkcs12_file, "rb")))
	msg(M_SSLERR, "Error opening file %s", pkcs12_file);
      p12 = d2i_PKCS12_fp(fp, NULL);
      fclose(fp);
      if (!p12)
	msg(M_SSLERR, "Error reading PKCS#12 file %s", pkcs12_file);
    }

  /* Parse the PKCS #12 file */
  if (!PKCS12_parse(p12, "", &pkey, &cert, &ca))
   {
     pem_password_callback (password, sizeof(password) - 1, 0, NULL);
     /* Reparse the PKCS #12 file with password */
     ca = NULL;
     if (!PKCS12_parse(p12, password, &pkey, &cert, &ca))
      {
#ifdef ENABLE_MANAGEMENT
	      if (management && (ERR_GET_REASON (ERR_peek_error()) == PKCS12_R_MAC_VERIFY_FAILURE))
		management_auth_failure (management, UP_TYPE_PRIVATE_KEY, NULL);
#endif
	PKCS12_free(p12);
	return 1;
      }
   }
  PKCS12_free(p12);

  /* Load Certificate */
  if (!SSL_CTX_use_certificate (ctx->ctx, cert))
   msg (M_SSLERR, "Cannot use certificate");

  /* Load Private Key */
  if (!SSL_CTX_use_PrivateKey (ctx->ctx, pkey))
   msg (M_SSLERR, "Cannot use private key");
  warn_if_group_others_accessible (pkcs12_file);

  /* Check Private Key */
  if (!SSL_CTX_check_private_key (ctx->ctx))
   msg (M_SSLERR, "Private key does not match the certificate");

  /* Set Certificate Verification chain */
  if (load_ca_file)
   {
     if (ca && sk_X509_num(ca))
      {
	for (i = 0; i < sk_X509_num(ca); i++)
	  {
	      if (!X509_STORE_add_cert(ctx->ctx->cert_store,sk_X509_value(ca, i)))
	      msg (M_SSLERR, "Cannot add certificate to certificate chain (X509_STORE_add_cert)");
	    if (!SSL_CTX_add_client_CA(ctx->ctx, sk_X509_value(ca, i)))
	      msg (M_SSLERR, "Cannot add certificate to client CA list (SSL_CTX_add_client_CA)");
	  }
      }
   }
  return 0;
}

#ifdef ENABLE_CRYPTOAPI
void
tls_ctx_load_cryptoapi(struct tls_root_ctx *ctx, const char *cryptoapi_cert)
{
  ASSERT(NULL != ctx);

  /* Load Certificate and Private Key */
  if (!SSL_CTX_use_CryptoAPI_certificate (ctx->ctx, cryptoapi_cert))
    msg (M_SSLERR, "Cannot load certificate \"%s\" from Microsoft Certificate Store",
	   cryptoapi_cert);
}
#endif /* WIN32 */

static void
tls_ctx_add_extra_certs (struct tls_root_ctx *ctx, BIO *bio)
{
  X509 *cert;
  for (;;)
    {
      cert = NULL;
      if (!PEM_read_bio_X509 (bio, &cert, 0, NULL)) /* takes ownership of cert */
        break;
      if (!cert)
        msg (M_SSLERR, "Error reading extra certificate");
      if (SSL_CTX_add_extra_chain_cert(ctx->ctx, cert) != 1)
        msg (M_SSLERR, "Error adding extra certificate");
    }
}

void
tls_ctx_load_cert_file (struct tls_root_ctx *ctx, const char *cert_file,
#if ENABLE_INLINE_FILES
    const char *cert_file_inline,
#endif
    X509 **x509
    )
{
  BIO *in = NULL;
  X509 *x = NULL;
  int ret = 0;
  bool inline_file = false;

  ASSERT (NULL != ctx);
  if (NULL != x509)
    ASSERT (NULL == *x509);

#if ENABLE_INLINE_FILES
  inline_file = (strcmp (cert_file, INLINE_FILE_TAG) == 0);

  if (inline_file && cert_file_inline)
    in = BIO_new_mem_buf ((char *)cert_file_inline, -1);
  else
#endif /* ENABLE_INLINE_FILES */
    in = BIO_new_file (cert_file, "r");

  if (in == NULL)
    {
      SSLerr (SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_SYS_LIB);
      goto end;
    }

  x = PEM_read_bio_X509 (in, NULL, ctx->ctx->default_passwd_callback,
                         ctx->ctx->default_passwd_callback_userdata);
  if (x == NULL)
    {
      SSLerr (SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_PEM_LIB);
      goto end;
    }

  ret = SSL_CTX_use_certificate (ctx->ctx, x);
  if (ret)
    tls_ctx_add_extra_certs (ctx, in);

end:
  if (!ret)
    {
      if (inline_file)
        msg (M_SSLERR, "Cannot load inline certificate file");
      else
        msg (M_SSLERR, "Cannot load certificate file %s", cert_file);
    }

  if (in != NULL)
    BIO_free(in);
  if (x509)
    *x509 = x;
  else if (x)
    X509_free (x);
}

void
tls_ctx_free_cert_file (X509 *x509)
{
  X509_free(x509);
}

int
tls_ctx_load_priv_file (struct tls_root_ctx *ctx, const char *priv_key_file
#if ENABLE_INLINE_FILES
    , const char *priv_key_file_inline
#endif
    )
{
  int status;
  SSL_CTX *ssl_ctx = NULL;
  BIO *in = NULL;
  EVP_PKEY *pkey = NULL;
  int ret = 1;

  ASSERT(NULL != ctx);

  ssl_ctx = ctx->ctx;

#if ENABLE_INLINE_FILES
  if (!strcmp (priv_key_file, INLINE_FILE_TAG) && priv_key_file_inline)
    in = BIO_new_mem_buf ((char *)priv_key_file_inline, -1);
  else
#endif /* ENABLE_INLINE_FILES */
    in = BIO_new_file (priv_key_file, "r");

  if (!in)
    goto end;

  pkey = PEM_read_bio_PrivateKey (in, NULL,
                                  ssl_ctx->default_passwd_callback,
                                  ssl_ctx->default_passwd_callback_userdata);
  if (!pkey)
    goto end;

  if (!SSL_CTX_use_PrivateKey (ssl_ctx, pkey))
    {
#ifdef ENABLE_MANAGEMENT
      if (management && (ERR_GET_REASON (ERR_peek_error()) == EVP_R_BAD_DECRYPT))
          management_auth_failure (management, UP_TYPE_PRIVATE_KEY, NULL);
#endif
      msg (M_WARN|M_SSL, "Cannot load private key file %s", priv_key_file);
      goto end;
    }
  warn_if_group_others_accessible (priv_key_file);

  /* Check Private Key */
  if (!SSL_CTX_check_private_key (ssl_ctx))
    msg (M_SSLERR, "Private key does not match the certificate");
  ret = 0;

end:
  if (pkey)
    EVP_PKEY_free (pkey);
  if (in)
    BIO_free (in);
  return ret;
}

#ifdef MANAGMENT_EXTERNAL_KEY

/* encrypt */
static int
rsa_pub_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
{
  ASSERT(0);
  return -1;
}

/* verify arbitrary data */
static int
rsa_pub_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
{
  ASSERT(0);
  return -1;
}

/* decrypt */
static int
rsa_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
{
  ASSERT(0);
  return -1;
}

/* called at RSA_free */
static int
rsa_finish(RSA *rsa)
{
  free ((void*)rsa->meth);
  rsa->meth = NULL;
  return 1;
}

/* sign arbitrary data */
static int
rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
{
  /* optional app data in rsa->meth->app_data; */
  char *in_b64 = NULL;
  char *out_b64 = NULL;
  int ret = -1;
  int len;

  if (padding != RSA_PKCS1_PADDING)
    {
      RSAerr (RSA_F_RSA_EAY_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
      goto done;
    }

  /* convert 'from' to base64 */
  if (openvpn_base64_encode (from, flen, &in_b64) <= 0)
    goto done;

  /* call MI for signature */
  if (management)
    out_b64 = management_query_rsa_sig (management, in_b64);
  if (!out_b64)
    goto done;

  /* decode base64 signature to binary */
  len = RSA_size(rsa);
  ret = openvpn_base64_decode (out_b64, to, len);

  /* verify length */
  if (ret != len)
    ret = -1;

 done:
  if (in_b64)
    free (in_b64);
  if (out_b64)
    free (out_b64);
  return ret;
}

int
tls_ctx_use_external_private_key (struct tls_root_ctx *ctx, X509 *cert)
{
  RSA *rsa = NULL;
  RSA *pub_rsa;
  RSA_METHOD *rsa_meth;

  ASSERT (NULL != ctx);
  ASSERT (NULL != cert);

  /* allocate custom RSA method object */
  ALLOC_OBJ_CLEAR (rsa_meth, RSA_METHOD);
  rsa_meth->name = "OpenVPN external private key RSA Method";
  rsa_meth->rsa_pub_enc = rsa_pub_enc;
  rsa_meth->rsa_pub_dec = rsa_pub_dec;
  rsa_meth->rsa_priv_enc = rsa_priv_enc;
  rsa_meth->rsa_priv_dec = rsa_priv_dec;
  rsa_meth->init = NULL;
  rsa_meth->finish = rsa_finish;
  rsa_meth->flags = RSA_METHOD_FLAG_NO_CHECK;
  rsa_meth->app_data = NULL;

  /* allocate RSA object */
  rsa = RSA_new();
  if (rsa == NULL)
    {
      SSLerr(SSL_F_SSL_USE_PRIVATEKEY, ERR_R_MALLOC_FAILURE);
      goto err;
    }

  /* get the public key */
  ASSERT(cert->cert_info->key->pkey); /* NULL before SSL_CTX_use_certificate() is called */
  pub_rsa = cert->cert_info->key->pkey->pkey.rsa;

  /* initialize RSA object */
  rsa->n = BN_dup(pub_rsa->n);
  rsa->flags |= RSA_FLAG_EXT_PKEY;
  if (!RSA_set_method(rsa, rsa_meth))
    goto err;

  /* bind our custom RSA object to ssl_ctx */
  if (!SSL_CTX_use_RSAPrivateKey(ctx->ctx, rsa))
    goto err;

  RSA_free(rsa); /* doesn't necessarily free, just decrements refcount */
  return 1;

 err:
  if (rsa)
    RSA_free(rsa);
  else
    {
      if (rsa_meth)
	free(rsa_meth);
    }
  msg (M_SSLERR, "Cannot enable SSL external private key capability");
  return 0;
}

#endif

static int
sk_x509_name_cmp(const X509_NAME * const *a, const X509_NAME * const *b)
{
  return X509_NAME_cmp (*a, *b);
}

void
tls_ctx_load_ca (struct tls_root_ctx *ctx, const char *ca_file,
#if ENABLE_INLINE_FILES
    const char *ca_file_inline,
#endif
    const char *ca_path, bool tls_server
    )
{
  STACK_OF(X509_INFO) *info_stack = NULL;
  STACK_OF(X509_NAME) *cert_names = NULL;
  X509_LOOKUP *lookup = NULL;
  X509_STORE *store = NULL;
  X509_NAME *xn = NULL;
  BIO *in = NULL;
  int i, added = 0;

  ASSERT(NULL != ctx);

  store = SSL_CTX_get_cert_store(ctx->ctx);
  if (!store)
    msg(M_SSLERR, "Cannot get certificate store (SSL_CTX_get_cert_store)");

  /* Try to add certificates and CRLs from ca_file */
  if (ca_file)
    {
#if ENABLE_INLINE_FILES
      if (!strcmp (ca_file, INLINE_FILE_TAG) && ca_file_inline)
        in = BIO_new_mem_buf ((char *)ca_file_inline, -1);
      else
#endif
        in = BIO_new_file (ca_file, "r");

      if (in)
        info_stack = PEM_X509_INFO_read_bio (in, NULL, NULL, NULL);

      if (info_stack)
        {
          for (i = 0; i < sk_X509_INFO_num (info_stack); i++)
            {
              X509_INFO *info = sk_X509_INFO_value (info_stack, i);
              if (info->crl)
                  X509_STORE_add_crl (store, info->crl);

              if (info->x509)
                {
                  X509_STORE_add_cert (store, info->x509);
                  added++;

                  if (!tls_server)
                    continue;

                  /* Use names of CAs as a client CA list */
                  if (cert_names == NULL)
                    {
                      cert_names = sk_X509_NAME_new (sk_x509_name_cmp);
                      if (!cert_names)
                        continue;
                    }

                  xn = X509_get_subject_name (info->x509);
                  if (!xn)
                    continue;

                  /* Don't add duplicate CA names */
                  if (sk_X509_NAME_find (cert_names, xn) == -1)
                    {
                      xn = X509_NAME_dup (xn);
                      if (!xn)
                        continue;
                      sk_X509_NAME_push (cert_names, xn);
                    }
                }
            }
          sk_X509_INFO_pop_free (info_stack, X509_INFO_free);
        }

      if (tls_server)
        SSL_CTX_set_client_CA_list (ctx->ctx, cert_names);

      if (!added || (tls_server && sk_X509_NAME_num (cert_names) != added))
        msg (M_SSLERR, "Cannot load CA certificate file %s", np(ca_file));
      if (in)
        BIO_free (in);
    }

  /* Set a store for certs (CA & CRL) with a lookup on the "capath" hash directory */
  if (ca_path)
    {
      lookup = X509_STORE_add_lookup (store, X509_LOOKUP_hash_dir ());
      if (lookup && X509_LOOKUP_add_dir (lookup, ca_path, X509_FILETYPE_PEM))
        msg(M_WARN, "WARNING: experimental option --capath %s", ca_path);
      else
        msg(M_SSLERR, "Cannot add lookup at --capath %s", ca_path);
#if OPENSSL_VERSION_NUMBER >= 0x00907000L
      X509_STORE_set_flags (store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
#else
      msg(M_WARN, "WARNING: this version of OpenSSL cannot handle CRL files in capath");
#endif
    }
}

void
tls_ctx_load_extra_certs (struct tls_root_ctx *ctx, const char *extra_certs_file
#if ENABLE_INLINE_FILES
    , const char *extra_certs_file_inline
#endif
    )
{
  BIO *in;
#if ENABLE_INLINE_FILES
  if (!strcmp (extra_certs_file, INLINE_FILE_TAG) && extra_certs_file_inline)
    in = BIO_new_mem_buf ((char *)extra_certs_file_inline, -1);
  else
#endif
    in = BIO_new_file (extra_certs_file, "r");

  if (in == NULL)
    msg (M_SSLERR, "Cannot load extra-certs file: %s", extra_certs_file);
  else
    tls_ctx_add_extra_certs (ctx, in);

  BIO_free (in);
}

/* **************************************
 *
 * Key-state specific functions
 *
 ***************************************/
/*
 *
 * BIO functions
 *
 */

#ifdef BIO_DEBUG

#warning BIO_DEBUG defined

static FILE *biofp;                            /* GLOBAL */
static bool biofp_toggle;                      /* GLOBAL */
static time_t biofp_last_open;                 /* GLOBAL */
static const int biofp_reopen_interval = 600;  /* GLOBAL */

static void
close_biofp()
{
  if (biofp)
    {
      ASSERT (!fclose (biofp));
      biofp = NULL;
    }
}

static void
open_biofp()
{
  const time_t current = time (NULL);
  const pid_t pid = getpid ();

  if (biofp_last_open + biofp_reopen_interval < current)
    close_biofp();
  if (!biofp)
    {
      char fn[256];
      openvpn_snprintf(fn, sizeof(fn), "bio/%d-%d.log", pid, biofp_toggle);
      biofp = fopen (fn, "w");
      ASSERT (biofp);
      biofp_last_open = time (NULL);
      biofp_toggle ^= 1;
    }
}

static void
bio_debug_data (const char *mode, BIO *bio, const uint8_t *buf, int len, const char *desc)
{
  struct gc_arena gc = gc_new ();
  if (len > 0)
    {
      open_biofp();
      fprintf(biofp, "BIO_%s %s time=" time_format " bio=" ptr_format " len=%d data=%s\n",
	      mode, desc, time (NULL), (ptr_type)bio, len, format_hex (buf, len, 0, &gc));
      fflush (biofp);
    }
  gc_free (&gc);
}

static void
bio_debug_oc (const char *mode, BIO *bio)
{
  open_biofp();
  fprintf(biofp, "BIO %s time=" time_format " bio=" ptr_format "\n",
	  mode, time (NULL), (ptr_type)bio);
  fflush (biofp);
}

#endif

/*
 * OpenVPN's interface to SSL/TLS authentication,
 * encryption, and decryption is exclusively
 * through "memory BIOs".
 */
static BIO *
getbio (BIO_METHOD * type, const char *desc)
{
  BIO *ret;
  ret = BIO_new (type);
  if (!ret)
    msg (M_SSLERR, "Error creating %s BIO", desc);
  return ret;
}

/*
 * Write to an OpenSSL BIO in non-blocking mode.
 */
static int
bio_write (BIO *bio, const uint8_t *data, int size, const char *desc)
{
  int i;
  int ret = 0;
  ASSERT (size >= 0);
  if (size)
    {
      /*
       * Free the L_TLS lock prior to calling BIO routines
       * so that foreground thread can still call
       * tls_pre_decrypt or tls_pre_encrypt,
       * allowing tunnel packet forwarding to continue.
       */
#ifdef BIO_DEBUG
      bio_debug_data ("write", bio, data, size, desc);
#endif
      i = BIO_write (bio, data, size);

      if (i < 0)
	{
	  if (BIO_should_retry (bio))
	    {
	      ;
	    }
	  else
	    {
	      msg (D_TLS_ERRORS | M_SSL, "TLS ERROR: BIO write %s error",
		   desc);
	      ret = -1;
	      ERR_clear_error ();
	    }
	}
      else if (i != size)
	{
	  msg (D_TLS_ERRORS | M_SSL,
	       "TLS ERROR: BIO write %s incomplete %d/%d", desc, i, size);
	  ret = -1;
	  ERR_clear_error ();
	}
      else
	{			/* successful write */
	  dmsg (D_HANDSHAKE_VERBOSE, "BIO write %s %d bytes", desc, i);
	  ret = 1;
	}
    }
  return ret;
}

/*
 * Inline functions for reading from and writing
 * to BIOs.
 */

static void
bio_write_post (const int status, struct buffer *buf)
{
  if (status == 1) /* success status return from bio_write? */
    {
      memset (BPTR (buf), 0, BLEN (buf)); /* erase data just written */
      buf->len = 0;
    }
}

/*
 * Read from an OpenSSL BIO in non-blocking mode.
 */
static int
bio_read (BIO *bio, struct buffer *buf, int maxlen, const char *desc)
{
  int i;
  int ret = 0;
  ASSERT (buf->len >= 0);
  if (buf->len)
    {
      ;
    }
  else
    {
      int len = buf_forward_capacity (buf);
      if (maxlen < len)
	len = maxlen;

      /*
       * BIO_read brackets most of the serious RSA
       * key negotiation number crunching.
       */
      i = BIO_read (bio, BPTR (buf), len);

      VALGRIND_MAKE_READABLE ((void *) &i, sizeof (i));

#ifdef BIO_DEBUG
      bio_debug_data ("read", bio, BPTR (buf), i, desc);
#endif
      if (i < 0)
	{
	  if (BIO_should_retry (bio))
	    {
	      ;
	    }
	  else
	    {
	      msg (D_TLS_ERRORS | M_SSL, "TLS_ERROR: BIO read %s error",
		   desc);
	      buf->len = 0;
	      ret = -1;
	      ERR_clear_error ();
	    }
	}
      else if (!i)
	{
	  buf->len = 0;
	}
      else
	{			/* successful read */
	  dmsg (D_HANDSHAKE_VERBOSE, "BIO read %s %d bytes", desc, i);
	  buf->len = i;
	  ret = 1;
	  VALGRIND_MAKE_READABLE ((void *) BPTR (buf), BLEN (buf));
	}
    }
  return ret;
}

void
key_state_ssl_init(struct key_state_ssl *ks_ssl, const struct tls_root_ctx *ssl_ctx, bool is_server, void *session)
{
  ASSERT(NULL != ssl_ctx);
  ASSERT(ks_ssl);
  CLEAR (*ks_ssl);

  ks_ssl->ssl = SSL_new (ssl_ctx->ctx);
  if (!ks_ssl->ssl)
    msg (M_SSLERR, "SSL_new failed");

  /* put session * in ssl object so we can access it
     from verify callback*/
  SSL_set_ex_data (ks_ssl->ssl, mydata_index, session);

  ks_ssl->ssl_bio = getbio (BIO_f_ssl (), "ssl_bio");
  ks_ssl->ct_in = getbio (BIO_s_mem (), "ct_in");
  ks_ssl->ct_out = getbio (BIO_s_mem (), "ct_out");

#ifdef BIO_DEBUG
  bio_debug_oc ("open ssl_bio", ks_ssl->ssl_bio);
  bio_debug_oc ("open ct_in", ks_ssl->ct_in);
  bio_debug_oc ("open ct_out", ks_ssl->ct_out);
#endif

  if (is_server)
    SSL_set_accept_state (ks_ssl->ssl);
  else
    SSL_set_connect_state (ks_ssl->ssl);

  SSL_set_bio (ks_ssl->ssl, ks_ssl->ct_in, ks_ssl->ct_out);
  BIO_set_ssl (ks_ssl->ssl_bio, ks_ssl->ssl, BIO_NOCLOSE);
}

void key_state_ssl_free(struct key_state_ssl *ks_ssl)
{
  if (ks_ssl->ssl) {
#ifdef BIO_DEBUG
    bio_debug_oc ("close ssl_bio", ks_ssl->ssl_bio);
    bio_debug_oc ("close ct_in", ks_ssl->ct_in);
    bio_debug_oc ("close ct_out", ks_ssl->ct_out);
#endif
    BIO_free_all(ks_ssl->ssl_bio);
    SSL_free (ks_ssl->ssl);
  }
}

int
key_state_write_plaintext (struct key_state_ssl *ks_ssl, struct buffer *buf)
{
  int ret = 0;
  perf_push (PERF_BIO_WRITE_PLAINTEXT);

#ifdef USE_OPENSSL
  ASSERT (NULL != ks_ssl);

  ret = bio_write (ks_ssl->ssl_bio, BPTR(buf), BLEN(buf),
      "tls_write_plaintext");
  bio_write_post (ret, buf);
#endif /* USE_OPENSSL */

  perf_pop ();
  return ret;
}

int
key_state_write_plaintext_const (struct key_state_ssl *ks_ssl, const uint8_t *data, int len)
{
  int ret = 0;
  perf_push (PERF_BIO_WRITE_PLAINTEXT);

  ASSERT (NULL != ks_ssl);

  ret = bio_write (ks_ssl->ssl_bio, data, len, "tls_write_plaintext_const");

  perf_pop ();
  return ret;
}

int
key_state_read_ciphertext (struct key_state_ssl *ks_ssl, struct buffer *buf,
    int maxlen)
{
  int ret = 0;
  perf_push (PERF_BIO_READ_CIPHERTEXT);

  ASSERT (NULL != ks_ssl);

  ret = bio_read (ks_ssl->ct_out, buf, maxlen, "tls_read_ciphertext");

  perf_pop ();
  return ret;
}

int
key_state_write_ciphertext (struct key_state_ssl *ks_ssl, struct buffer *buf)
{
  int ret = 0;
  perf_push (PERF_BIO_WRITE_CIPHERTEXT);

  ASSERT (NULL != ks_ssl);

  ret = bio_write (ks_ssl->ct_in, BPTR(buf), BLEN(buf), "tls_write_ciphertext");
  bio_write_post (ret, buf);

  perf_pop ();
  return ret;
}

int
key_state_read_plaintext (struct key_state_ssl *ks_ssl, struct buffer *buf,
    int maxlen)
{
  int ret = 0;
  perf_push (PERF_BIO_READ_PLAINTEXT);

  ASSERT (NULL != ks_ssl);

  ret = bio_read (ks_ssl->ssl_bio, buf, maxlen, "tls_read_plaintext");

  perf_pop ();
  return ret;
}

/* **************************************
 *
 * Information functions
 *
 * Print information for the end user.
 *
 ***************************************/
void
print_details (struct key_state_ssl * ks_ssl, const char *prefix)
{
  const SSL_CIPHER *ciph;
  X509 *cert;
  char s1[256];
  char s2[256];

  s1[0] = s2[0] = 0;
  ciph = SSL_get_current_cipher (ks_ssl->ssl);
  openvpn_snprintf (s1, sizeof (s1), "%s %s, cipher %s %s",
		    prefix,
		    SSL_get_version (ks_ssl->ssl),
		    SSL_CIPHER_get_version (ciph),
		    SSL_CIPHER_get_name (ciph));
  cert = SSL_get_peer_certificate (ks_ssl->ssl);
  if (cert != NULL)
    {
      EVP_PKEY *pkey = X509_get_pubkey (cert);
      if (pkey != NULL)
	{
	  if (pkey->type == EVP_PKEY_RSA && pkey->pkey.rsa != NULL
	      && pkey->pkey.rsa->n != NULL)
	    {
	      openvpn_snprintf (s2, sizeof (s2), ", %d bit RSA",
				BN_num_bits (pkey->pkey.rsa->n));
	    }
	  else if (pkey->type == EVP_PKEY_DSA && pkey->pkey.dsa != NULL
		   && pkey->pkey.dsa->p != NULL)
	    {
	      openvpn_snprintf (s2, sizeof (s2), ", %d bit DSA",
				BN_num_bits (pkey->pkey.dsa->p));
	    }
	  EVP_PKEY_free (pkey);
	}
      X509_free (cert);
    }
  /* The SSL API does not allow us to look at temporary RSA/DH keys,
   * otherwise we should print their lengths too */
  msg (D_HANDSHAKE, "%s%s", s1, s2);
}

void
show_available_tls_ciphers ()
{
  SSL_CTX *ctx;
  SSL *ssl;
  const char *cipher_name;
  int priority = 0;

  ctx = SSL_CTX_new (TLSv1_method ());
  if (!ctx)
    msg (M_SSLERR, "Cannot create SSL_CTX object");

  ssl = SSL_new (ctx);
  if (!ssl)
    msg (M_SSLERR, "Cannot create SSL object");

  printf ("Available TLS Ciphers,\n");
  printf ("listed in order of preference:\n\n");
  while ((cipher_name = SSL_get_cipher_list (ssl, priority++)))
    printf ("%s\n", cipher_name);
  printf ("\n");

  SSL_free (ssl);
  SSL_CTX_free (ctx);
}

void
get_highest_preference_tls_cipher (char *buf, int size)
{
  SSL_CTX *ctx;
  SSL *ssl;
  const char *cipher_name;

  ctx = SSL_CTX_new (TLSv1_method ());
  if (!ctx)
    msg (M_SSLERR, "Cannot create SSL_CTX object");
  ssl = SSL_new (ctx);
  if (!ssl)
    msg (M_SSLERR, "Cannot create SSL object");

  cipher_name = SSL_get_cipher_list (ssl, 0);
  strncpynt (buf, cipher_name, size);

  SSL_free (ssl);
  SSL_CTX_free (ctx);
}

#endif /* defined(USE_SSL) && defined(USE_OPENSSL) */