/**
    TLS and RSA handshake through openssl

    Copyright (c) 2020-2021 The Creators of Simphone

    See the file COPYING.LESSER.txt for copying permission.
**/

#include "config.h"
#include "spth.h"

#include "logger.h"
#include "table.h"
#include "error.h"
#include "utils.h"
#include "crypto.h"
#include "sssl.h"
#include "socket.h"
#include "contact.h"
#include "param.h"

#include <openssl/opensslconf.h>

#if OPENSSL_VERSION_NUMBER < 0x10101000
#define OPENSSL_INIT_NO_ATEXIT 0
#include <ssl/ssl_locl.h>
#include <crypto/ec/ec_lcl.h>
#else
#include <ssl/ssl_local.h>
#include <crypto/ec/ec_local.h>
#endif

#include <include/openssl/crypto.h>
#include <include/openssl/rand.h>
#include <include/openssl/conf.h>

#if OPENSSL_VERSION_NUMBER < 0x10100000
#define SSL_OP_NO_RENEGOTIATION 0
#define SSL_OP_NO_TLSv1_3 0
#define SSL_OP_NO_ENCRYPT_THEN_MAC 0
#define RSA_bits(rsa) BN_num_bits ((rsa)->n)
#define EVP_PKEY_up_ref(key) (((EVP_PKEY *) (key))->references++)
#define X509_STORE_CTX_get0_untrusted(store) ((store)->untrusted)
#define BIO_get_data(bio) ((bio)->ptr)
#define BIO_set_data(bio, buf) ((bio)->ptr = (buf))
#define BIO_get_init(bio) ((bio)->init)
#define BIO_set_init(bio, flag) ((bio)->init = (flag))
#define BIO_get_num(bio) ((bio)->num)
#define BIO_set_num(bio, shutdown) ((bio)->num = (shutdown))
#else
#define BIO_get_num(bio) BIO_get_shutdown (bio)
#define BIO_set_num(bio, shutdown) BIO_set_shutdown (bio, shutdown)
#define CRYPTO_malloc_debug_init() CRYPTO_set_mem_debug (CRYPTO_MEM_CHECK_ON)
#endif

#include <string.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>

#define SIM_MODULE "ssl"

#define SSL_CIPHER "ECDHE-ECDSA-AES256-GCM-SHA384:"

static SSL_CTX *ssl_contexts[] = { NULL, NULL, NULL, NULL };
static X509 *ssl_certificates[] = { NULL, NULL, NULL };
static EVP_PKEY *ssl_keys[] = { NULL, NULL, NULL };

static int ssl_generating_flag = 0;
static int ssl_generating_flags[SIM_ARRAY_SIZE (ssl_contexts)];

#define SSL_CHECK_NOKEY(handshake) ((handshake) == SSL_HANDSHAKE_NOKEY || (handshake) == SSL_HANDSHAKE_ANONYMOUS)
#define SSL_CASE_CONTEXT(handshake) ((SSL_CHECK_NOKEY (handshake) || (handshake) == SSL_HANDSHAKE_SERVER) ? \
                                       (handshake) :                                                        \
                                       SSL_HANDSHAKE_CLIENT)
#define SSL_CASE_CURVE(nonanonymous) ((nonanonymous) ? NID_brainpoolP512r1 : NID_X9_62_prime256v1)

const char *sim_ssl_get_version (void) {
  return SSLeay_version (SSLEAY_VERSION);
}

simtype sim_ssl_convert_key__ (void *key, int keyidx) {
  int len;
  simbool ok = true;
  simtype str = nil ();
  if (keyidx == SSL_KEY_RSA) {
    RSA *rsa = EVP_PKEY_get1_RSA (key);
    if (rsa && (len = i2d_RSAPublicKey (rsa, NULL)) != 0) {
      simbyte *tmp = (str = string_new (len)).str;
      ok = i2d_RSAPublicKey (rsa, &tmp);
    }
    RSA_free (rsa);
  } else {
    EC_KEY *ec = EVP_PKEY_get1_EC_KEY (key);
    if (ec && (len = i2o_ECPublicKey (ec, NULL)) != 0) {
      simbyte *tmp = (str = string_new (len)).str;
      ok = i2o_ECPublicKey (ec, &tmp);
    }
    EC_KEY_free (ec);
  }
  if (! ok)
    string_free (str), str = nil ();
  return str;
}

static simbool sim_ssl_test_ec__ (const EC_KEY *ec, int keyidx, simbool warn) {
  int nid = ec ? EC_GROUP_get_curve_name (EC_KEY_get0_group (ec)) : NID_undef;
  if (keyidx != SSL_KEY_RSA && (SSL_CASE_CURVE (keyidx == SSL_KEY_EC)) != nid) {
    if (warn)
      LOG_WARN_ ("bad elliptic curve %d\n", nid);
  } else if (EC_KEY_check_key (ec)) {
    return true;
  } else
    LOG_WARN_ ("%s EC public key\n", ec ? "bad" : "no");
  return false; /* prevent peer from attacking me by sending a bad public key */
}

static simbool sim_ssl_test_ec_key__ (EVP_PKEY *key, int keyidx, simbool warn) {
  simbool ok = false;
  EC_KEY *ec = EVP_PKEY_get1_EC_KEY (key);
  if (ec) {
    ok = sim_ssl_test_ec__ (ec, keyidx, warn);
  } else
    LOG_ERROR_ ("EC key missing\n");
  EC_KEY_free (ec);
  return ok;
}

static EVP_PKEY *sim_ssl_test_key__ (X509 *cert, int keyidx, simbool warn) {
  EVP_PKEY *key = NULL;
  if (cert) {
    simbool ok = false;
    int nid = X509_get_signature_nid (cert);
    if (nid != NID_ecdsa_with_SHA512 && (keyidx == SSL_KEY_EC && nid != NID_sha512WithRSAEncryption)) {
      LOG_WARN_ ("bad certificate type %d\n", nid);
    } else if ((key = X509_get_pubkey (cert)) == NULL) {
      LOG_WARN_ ("get peer key failed\n");
    } else if (keyidx == SSL_KEY_RSA) {
      RSA *rsa = EVP_PKEY_get1_RSA (key);
      if (rsa) {
        int bits = RSA_bits (rsa);
        if (bits < SSL_RSA_BITS_MIN || bits > SSL_RSA_BITS_MAX) {
          LOG_WARN_ ("bad RSA key %d bits\n", bits);
        } else /* can/should i check the RSA public key somehow? */
          ok = true;
      } else
        LOG_WARN_ ("RSA key missing\n");
      RSA_free (rsa);
    } else
      ok = sim_ssl_test_ec_key__ (key, keyidx, warn);
    if (! ok)
      EVP_PKEY_free (key), key = NULL;
  } else
    LOG_WARN_ ("get certificate failed\n");
  return key;
}

void sim_ssl_free_key__ (void *key) {
  EVP_PKEY_free (key);
}

void *sim_ssl_new_key__ (const simtype key, int keyidx, simbool validate) {
  simbool ok = false;
  EVP_PKEY *pkey = EVP_PKEY_new ();
  if (pkey) {
    if (keyidx == SSL_KEY_RSA) {
      RSA *rsa = d2i_RSAPrivateKey (NULL, (const simbyte **) &key.str, key.len);
      if (rsa)
        ok = /*validate ? RSA_check_key (rsa) :*/ EVP_PKEY_set1_RSA (pkey, rsa);
      RSA_free (rsa);
    } else {
      EC_KEY *ec = EC_KEY_new_by_curve_name (SSL_CASE_CURVE (keyidx == SSL_KEY_EC));
      if (ec && d2i_ECPrivateKey (&ec, (const simbyte **) &key.str, key.len)) {
        if (! ec->pub_key && (ec->pub_key = EC_POINT_new (ec->group)) != NULL) {
          BN_CTX *bn = BN_CTX_new (); /* calculate public key from private key since crypto++ won't store it */
          if (bn && ! EC_POINT_mul (ec->group, ec->pub_key, ec->priv_key, NULL, NULL, bn))
            EC_POINT_free (ec->pub_key), ec->pub_key = NULL;
          BN_CTX_free (bn);
        }
        if (ec->pub_key) {
          EC_KEY_set_asn1_flag (ec, OPENSSL_EC_NAMED_CURVE); /* allow use of key in handshake */
          ok = validate ? sim_ssl_test_ec__ (ec, keyidx, true) : EVP_PKEY_set1_EC_KEY (pkey, ec);
        }
      }
      EC_KEY_free (ec);
    }
  }
  if (! ok)
    EVP_PKEY_free (pkey), pkey = NULL;
  return pkey;
}

/* create and return a signed forever-valid X509 certificate from private key. return needs to be freed */
static X509 *sim_ssl_new_x509__ (EVP_PKEY *key, EVP_PKEY *signer) {
  X509 *cert = X509_new ();
  if (! cert) {
    LOG_ERROR_ ("X509 creation failed\n");
  } else if (X509_set_version (cert, 3) == true && X509_set_pubkey (cert, key) == true) {
    ASN1_UTCTIME *forever = ASN1_UTCTIME_new ();
    if (forever) {
      ASN1_UTCTIME *sincenever = ASN1_UTCTIME_new ();
      if (sincenever) { /* prevent invalid system time from rejecting the certificate */
        if (ASN1_GENERALIZEDTIME_set_string (forever, "99991231235959Z") != true ||
            X509_set_notAfter (cert, forever) != true) {
          LOG_ERROR_ ("X509 set forever failed\n");
        } else if (ASN1_GENERALIZEDTIME_set_string (sincenever, "00000101000000Z") == true &&
                   X509_set_notBefore (cert, sincenever) == true) {
          X509_NAME *name = X509_get_subject_name (cert);
          if (X509_set_issuer_name (cert, name) != true || ASN1_INTEGER_set (X509_get_serialNumber (cert), 0) != true) {
            LOG_ERROR_ ("X509 set issuer or serial number failed\n");
          } else if (! signer || X509_sign (cert, signer, EVP_sha512 ())) { /* sign with the signer private key */
            ASN1_UTCTIME_free (sincenever);
            ASN1_UTCTIME_free (forever);
            /*X509_print_fp (stderr, cert);*/
            return cert;
          } else
            LOG_ERROR_ ("X509 signing failed\n");
        } else
          LOG_ERROR_ ("X509 set sincenever failed\n");
        ASN1_UTCTIME_free (sincenever);
      } else
        LOG_ERROR_ ("X509 time creation failed too\n");
      ASN1_UTCTIME_free (forever);
    } else
      LOG_ERROR_ ("X509 time creation failed\n");
  } else
    LOG_ERROR_ ("X509 set version or public key failed\n");
  X509_free (cert);
  return NULL;
}

static simbool sim_ssl_set_key__ (SSL_CTX *sslctx, X509 *cert, EVP_PKEY *key) {
  if (SSL_CTX_use_certificate (sslctx, cert) != true) {
    LOG_ERROR_ ("failed to set EC public key\n");
  } else if (SSL_CTX_use_PrivateKey (sslctx, key) != true) {
    LOG_ERROR_ ("failed to set EC private key\n");
  } else if (SSL_CTX_check_private_key (sslctx) != true) {
    LOG_ERROR_ ("failed to check EC private key\n");
  } else
    return true;
  return false;
}

int sim_ssl_set_keys__ (void *ec, void *rsa) {
  int err = SIM_SSL_ERROR;
  X509 *eckey = ec ? sim_ssl_new_x509__ (ec, ec) : NULL;
  X509 *rsakey = rsa ? sim_ssl_new_x509__ (rsa, ec) : NULL;
  if ((! eckey && ec) || ! rsakey) {
    LOG_ERROR_ ("failed to set %s key\n", eckey ? "RSA" : "EC");
  } else if (! ec || sim_ssl_set_key__ (ssl_contexts[SSL_HANDSHAKE_CLIENT], eckey, ec)) {
    if (! ec || SSL_CTX_add_extra_chain_cert (ssl_contexts[SSL_HANDSHAKE_CLIENT], rsakey) == true) {
      ssl_certificates[SSL_KEY_EC] = eckey;
      ssl_certificates[SSL_KEY_RSA] = rsakey;
      ssl_keys[SSL_KEY_EC] = ec;
      ssl_keys[SSL_KEY_RSA] = rsa;
      EVP_PKEY_up_ref (rsa); /* referenced RSA key will be freed by ssl_uninit_ */
      return SIM_OK;
    }
    LOG_ERROR_ ("failed to set RSA public key\n");
  }
  X509_free (rsakey);
  X509_free (eckey);
  return err;
}

static int sim_ssl_generate_key__ (SSL_CTX *sslctx) {
  int err = SIM_SSL_ERROR;
  EVP_PKEY *key = EVP_PKEY_new ();
  if (key) {
    X509 *cert;
    EC_KEY *ec = EC_KEY_new_by_curve_name (SSL_CASE_CURVE (sslctx && ssl_contexts[SSL_HANDSHAKE_ANONYMOUS] != sslctx));
    if (ec && EC_KEY_generate_key (ec) == true && EVP_PKEY_set1_EC_KEY (key, ec) == true) {
      EC_KEY_set_asn1_flag (ec, OPENSSL_EC_NAMED_CURVE);
      if ((cert = sim_ssl_new_x509__ (key, key)) != NULL) {
        if (! sslctx) {
          ssl_certificates[SSL_KEY_ANONYMOUS] = cert;
          ssl_keys[SSL_KEY_ANONYMOUS] = key;
          cert = NULL;
          key = NULL;
          err = SIM_OK;
        } else if (sim_ssl_set_key__ (sslctx, cert, key))
          err = SIM_OK;
      }
      X509_free (cert);
    } else
      LOG_ERROR_ ("failed to generate EC anonymous key\n");
    EC_KEY_free (ec);
  } else
    LOG_ERROR_ ("failed to create EC anonymous key\n");
  EVP_PKEY_free (key);
  return err;
}

static int sim_ssl_callback_rand_status (void) {
  return 0;
}

static int sim_ssl_callback_rand_bytes_ (simbyte *output, int length) {
  random_get (random_session, PTH_PROTECT_ (pointer_new_len (output, length)));
  /*LOG_XTRA_SIMTYPE_ (pointer_new_len (output, length), LOG_BIT_BIN, "private ");*/
  return PTH_UNPROTECT (1);
}

static int sim_ssl_callback_rand_pseudo_ (simbyte *output, int length) {
  random_get (random_public, PTH_PROTECT_ (pointer_new_len (output, length)));
  /*LOG_XTRA_SIMTYPE_ (pointer_new_len (output, length), LOG_BIT_BIN, "public ");*/
  return PTH_UNPROTECT (1);
}

static const RAND_METHOD sim_ssl_rand = {
  NULL, sim_ssl_callback_rand_bytes_, NULL, NULL, sim_ssl_callback_rand_pseudo_, sim_ssl_callback_rand_status
};

/* this callback is required to enable the use of self-signed certificates and also
   allows aborting the handshake early enough if i don't like the peer's public key */
static int ssl_callback_verify_ (int ok, X509_STORE_CTX *store) {
  int ret = X509_STORE_CTX_get_error (store), idx;
  unsigned i;
  BIO *bio;
  SSL *ssl;
  simsocket sock;
  STACK_OF (X509) *certificates = X509_STORE_CTX_get0_untrusted (store);
  simcontact contact = NULL;
  simtype hash, key, addr = nil ();
  char errbuf[SIM_SIZE_ERROR];
  switch (ret) {
    default:
      LOG_DEBUG_ ("verify error: %s\n", X509_verify_cert_error_string (ret));
      return false;
    case X509_V_OK:
    case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:; /* if ok = false, will be called again with ok = true */
  }
  if ((idx = SSL_get_ex_data_X509_STORE_CTX_idx ()) < 0) {
    LOG_ERROR_ ("get X509 exdata index failed: %s\n", sim_error_get_message (SIM_SSL_ERROR, 0, true, errbuf));
    return false;
  }
  if ((ssl = X509_STORE_CTX_get_ex_data (store, idx)) == NULL) {
    LOG_ERROR_ ("get X509 exdata value failed: %s\n", sim_error_get_message (SIM_SSL_ERROR, 0, true, errbuf));
    return false;
  }
  if ((bio = SSL_get_rbio (ssl)) == NULL || (sock = (simsocket) BIO_get_callback_arg (bio)) == NULL) {
    LOG_ERROR_ ("get $%d bio failed: %s\n", SSL_get_fd (ssl), sim_error_get_message (SIM_SSL_ERROR, 0, true, errbuf));
    return false;
  }
  LOG_DEBUG_ ("verify $%d ok = %d\n", sock->fd, ok);
  for (idx = SSL_KEY_EC; idx <= SSL_KEY_RSA; idx++) {
    X509 *cert = sk_X509_value (certificates, idx);
    EVP_PKEY *peerkey = NULL;
    if (cert || sock->master->handshake != SSL_HANDSHAKE_PROXY || idx == SSL_KEY_EC)
      peerkey = sim_ssl_test_key__ (cert, idx, sock->master->handshake != SSL_HANDSHAKE_PROXY);
    if (! peerkey) {
      if (sock->master->handshake != SSL_HANDSHAKE_PROXY)
        return false;
      pth_protect_ ();
      break;
    }
    key = sim_ssl_convert_key__ (peerkey, idx);
    EVP_PKEY_free (peerkey);
    if (key.typ != SIMSTRING) {
      LOG_WARN_ ("get $%d peer %s key failed\n", sock->fd, idx == SSL_KEY_EC ? "EC" : "RSA");
      return false;
    }
    if (sock->master->pub[PTH_PROTECT_ (idx)].typ == SIMNIL) {
      sock->master->pub[idx] = key;
    } else if (string_check_diff_len (sock->master->pub[idx], key.str, key.len)) {
      LOG_ERROR_ ("get $%d peer %s key changed\n", sock->fd, idx == SSL_KEY_EC ? "EC" : "RSA");
      string_free (key);
      return PTH_UNPROTECT (false);
    } else
      string_free (key);
    if (idx == SSL_KEY_EC) {
      pth_unprotect ();
    }
  }
  if (ok)
    return PTH_UNPROTECT (true);
  if (idx > SSL_KEY_RSA) {
    hash = sim_crypt_md_hash_address (sim_crypt_md_new (CRYPT_MD_SHA), sim_crypt_md_new (CRYPT_MD_RIPEMD),
                                      sock->master->pub[SSL_KEY_EC], sock->master->pub[SSL_KEY_RSA]);
    for (i = 1; i <= contact_list.array.len; i++) {
      if (! string_check_diff_len ((contact = contact_list.array.arr[i].ptr)->key, hash.str, hash.len)) {
        static const char *ssl_auth_names[] = { "forgotten", "revoked", "deleted", "blocked" };
        if (contact->auth >= CONTACT_AUTH_NEW) {
          if (contact != contact_list.me)
            contact->seen[CONTACT_SEEN_RECEIVE] = time (NULL);
          break;
        }
        if (sock->master->handshake == SSL_HANDSHAKE_SERVER)
          break;
        LOG_DEBUG_ ("rejected $%d connection with %s contact '%s'\n", sock->fd,
                    ssl_auth_names[contact->auth - CONTACT_AUTH_FORGET], contact->nick.str);
        string_free (hash);
        return PTH_UNPROTECT (false);
      }
      contact = NULL;
    }
    addr = sim_contact_convert_to_address (hash, 'S');
    string_free (hash);
  }
  if (sock->master->handshake == SSL_HANDSHAKE_SERVER) {
    sock->master->from = addr;
    idx = SSL_KEY_EC;
    if (! contact || contact->auth < CONTACT_AUTH_ACCEPTED) {
      LOG_INFO_ ("accepted $%d proxy connection from @%020llu\n", sock->fd, CONTACT_CONVERT_TO_ID (addr.ptr));
#if OPENSSL_VERSION_NUMBER >= 0x10100000
      if (ssl->s3->flags & TLS1_FLAGS_RECEIVED_EXTMS)
        return PTH_UNPROTECT (true);
#endif
      idx = SSL_KEY_ANONYMOUS;
    } else
      LOG_INFO_ ("accepted $%d proxy connection from '%s'\n", sock->fd, contact->nick.str);
    pth_unprotect ();
    if (SSL_use_certificate (ssl, ssl_certificates[idx]) != true) {
      LOG_ERROR_ ("accepted proxy connection: missing EC public key\n");
    } else if (SSL_use_PrivateKey (ssl, ssl_keys[idx]) != true) {
      LOG_ERROR_ ("accepted proxy connection: missing EC private key\n");
    } else if (idx == SSL_KEY_EC && SSL_add1_chain_cert (ssl, ssl_certificates[SSL_KEY_RSA]) != true)
      LOG_ERROR_ ("accepted proxy connection: missing RSA public key\n");
    return true;
  }
  if (sock->master->handshake == SSL_HANDSHAKE_PROXY) {
    if (contact && contact->auth >= CONTACT_AUTH_ACCEPTED) {
      sock->master->from = pointer_new (contact->addr);
      LOG_INFO_ ("connected $%d to proxy '%s'\n", sock->fd, contact->nick.str);
      string_free (addr);
      return PTH_UNPROTECT (true);
    }
#if OPENSSL_VERSION_NUMBER >= 0x10100000
    if (! (ssl->s3->flags & TLS1_FLAGS_RECEIVED_EXTMS))
      ssl->ext.peer_supportedgroups_len = 0;
#endif
    ok = true;
  } else if (sock->master->from.typ != SIMNIL && string_check_diff_len (sock->master->from, addr.str, addr.len)) {
    LOG_NOTE_ ("rejected $%d connection to @%020llu instead of '%s'\n", sock->fd,
               CONTACT_CONVERT_TO_ID (contact ? contact->addr : addr.ptr), CONTACT_GET_NICK (sock->master->from.ptr));
  } else if (contact && (contact->auth >= CONTACT_AUTH_ACCEPTED ||
                         (sock->master->from.typ != SIMNIL && param_get_number ("contact.strangers") > 1))) {
    if (contact->auth < CONTACT_AUTH_ACCEPTED)
      LOG_NOTE_ ("connected $%d to non-accepted contact '%s'\n", sock->fd, contact->nick.str);
    sock->master->from = pointer_new (contact->addr);
    ok = true;
  } else if (param_get_number ("contact.strangers") && sock->master->from.typ == SIMNIL) {
    LOG_NOTE_ ("connected $%d with unknown contact '%s'\n", sock->fd, CONTACT_GET_NICK (addr.ptr));
    ok = param_get_number ("contact.strangers") > 1;
    if (! contact) {
      sock->master->from = addr;
      return PTH_UNPROTECT (ok);
    }
    sock->master->from = pointer_new (contact->addr);
  } else
    LOG_DEBUG_ ("rejected $%d connection %s '%s'\n", sock->fd,
                sock->master->from.typ == SIMNIL ? "from" : "to", CONTACT_GET_NICK (addr.ptr));
  string_free (addr);
  return PTH_UNPROTECT (ok);
}

static int sim_ssl_callback_noverify__ (int ok, X509_STORE_CTX *store) {
  int ret = X509_STORE_CTX_get_error (store);
  switch (ret) {
    default:
      LOG_DEBUG_ ("noverify error: %s\n", X509_verify_cert_error_string (ret));
      return false;
    case X509_V_OK:
    case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:;
  }
  return true;
}

/* this hook is the only chance for getting a hold of the premaster secret */
#if OPENSSL_VERSION_NUMBER < 0x10101000
static int ssl_callback_generate_ (SSL *ssl, unsigned char *output, unsigned char *input, int length) {
  int len, copied = 0, ret = tls1_generate_master_secret (ssl, output, input, length); /* call the original handler */
  if (PTH_PROTECT_ (ret) == SSL_MAX_MASTER_KEY_LENGTH) {
#else
static int ssl_callback_generate_ (SSL *ssl, unsigned char *output, unsigned char *input, size_t length, size_t *size) {
  size_t len;
  int copied = 0, ret = tls1_generate_master_secret (ssl, output, input, length, size); /* call the original handler */
  if (PTH_PROTECT_ (ret) == true && *size == SSL_MAX_MASTER_KEY_LENGTH) {
#endif
    simsocket sock;
    BIO *bio = SSL_get_rbio (ssl);
    if (bio && (sock = (simsocket) BIO_get_callback_arg (bio)) != NULL) {
      struct _ssl_master *master = sock->master;
      if (master->length) {
        LOG_ERROR_ ("too many handshakes $%d\n", sock->fd);
        return PTH_UNPROTECT (0);
      }
      len = sizeof (master->premaster);
      if (length > len || (length != len / 2 && SSL_CASE_CONTEXT (master->handshake) == SSL_HANDSHAKE_CLIENT) ||
          sizeof (master->random) != 2 * SSL3_RANDOM_SIZE || sizeof (master->master) != SSL_MAX_MASTER_KEY_LENGTH) {
        LOG_ERROR_ ("premaster secret $%d bad length (%ld bytes)\n", sock->fd, (long) length);
        return PTH_UNPROTECT (0);
      }
      master->length = length;
      memcpy (master->master, ssl->session->master_key, SSL_MAX_MASTER_KEY_LENGTH);
      memcpy (master->random, ssl->s3->client_random, SSL3_RANDOM_SIZE);
      memcpy (master->random + SSL3_RANDOM_SIZE, ssl->s3->server_random, SSL3_RANDOM_SIZE);
      while (len) {
        memcpy (master->premaster + copied, input, length);
        copied += length;
        if ((len -= length) < length)
          length = len;
      }
    } else {
      LOG_ERROR_ ("failed $%d to get bio\n", SSL_get_fd (ssl));
      ret = 0;
    }
  } else
    LOG_ERROR_ ("failed $%d to generate master key\n", SSL_get_fd (ssl));
  return PTH_UNPROTECT (ret);
}

/* SSL_connect and SSL_accept clear this flag so it needs to be set from inside them (in this callback) */
static void sim_ssl_callback_info__ (const SSL *ssl, int where, int ret) {
  if (where == SSL_CB_HANDSHAKE_START)
    ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS; /* disable re-negotiation */
}

#if HAVE_LIBPTH || OPENSSL_VERSION_NUMBER >= 0x10100000
#define sim_ssl_init_lock__() SIM_OK
#define sim_ssl_uninit_lock__(locks)
#else
#ifdef _WIN32
static CRITICAL_SECTION *ssl_locks = NULL;

#define pthread_mutex_lock(lock) (EnterCriticalSection (lock), 0)
#define pthread_mutex_unlock(lock) (LeaveCriticalSection (lock), 0)
#define pthread_mutex_destroy(lock) (DeleteCriticalSection (lock), 0)
#else
#include <pthread.h>

static pthread_mutex_t *ssl_locks = NULL;
#endif

static void sim_ssl_callback_lock__ (int mode, int type, const char *file, int line) {
  if (! (mode & CRYPTO_LOCK)) {
    /*LOG_XTRA_ ("unlock %d %s:%d\n", type, file, line);*/
    if (pthread_mutex_unlock (&ssl_locks[type]))
      LOG_ERROR_ ("unlock %d %s:%d error %d\n", type, file, line, errno);
  } else if (pthread_mutex_lock (&ssl_locks[type]))
    LOG_ERROR_ ("lock %d %s:%d error %d\n", type, file, line, errno);
  /*else
    LOG_XTRA_ ("lock %d %s:%d\n", type, file, line);*/
}

static void sim_ssl_uninit_lock__ (int locks) {
  CRYPTO_set_locking_callback (NULL);
  while (locks-- && ssl_locks) {
    int err = pthread_mutex_destroy (&ssl_locks[locks]);
    if (err)
      LOG_ERROR_ ("lock destroy %d error %d\n", locks, err);
  }
  sim_free (ssl_locks, sizeof (*ssl_locks) * CRYPTO_num_locks ()), ssl_locks = NULL;
}

static int sim_ssl_init_lock__ (void) {
  int err, lock, locks = CRYPTO_num_locks ();
  ssl_locks = sim_new (sizeof (*ssl_locks) * locks);
  for (lock = 0; lock < locks; lock++) {
#ifdef _WIN32
    if (! InitializeCriticalSectionAndSpinCount (&ssl_locks[lock], 0)) {
      err = GetLastError ();
#else
    if ((err = pthread_mutex_init (&ssl_locks[lock], NULL)) != 0) {
#endif
      sim_ssl_uninit_lock__ (lock);
      return err;
    }
  }
  CRYPTO_set_locking_callback (sim_ssl_callback_lock__);
  return SIM_OK;
}
#endif /* HAVE_LIBPTH */

static simbool sim_ssl_init_params__ (SSL_CTX *sslctx, const simtype curves, const simtype sigalgs) {
  static const int ssl_curve_ids[] = { SSL_CASE_CURVE (true), SSL_CASE_CURVE (false) };
  static const int ssl_sigalg_ids[] = { NID_sha512, EVP_PKEY_EC, NID_sha512, EVP_PKEY_RSA };
  static const char ssl_certificate_types[] = { TLS_CT_ECDSA_SIGN, TLS_CT_RSA_SIGN };
  simbool ok = true;
  if (curves.len) {
    int *ids = curves.ptr, num = curves.len / sizeof (int) - (*ids == NID_undef);
    if (sigalgs.typ == SIMNIL || (num && SSL_CTX_set1_curves (sslctx, (ids + (*ids == NID_undef)), num) != true)) {
      if (sigalgs.typ != SIMNIL)
        while (ERR_get_error ()) {}
      ok = SSL_CTX_set1_curves (sslctx, &ssl_curve_ids[true], 1);
    }
  } else
    ok = SSL_CTX_set1_curves (sslctx, &ssl_curve_ids[false], curves.typ == SIMNIL ? 2 : 1);
  if (ok != true) {
    LOG_ERROR_ ("failed to set %s\n", curves.len ? "curves" : "curve");
    return false;
  }
  if (sigalgs.typ == SIMNIL) {
    ok = SSL_CTX_set1_sigalgs (sslctx, ssl_sigalg_ids, curves.len ? 2 : 4);
  } else if (! sigalgs.len) {
#if OPENSSL_VERSION_NUMBER >= 0x10100000
    static const int ssl_sigalg_default_ids[] = {
      NID_sha512, EVP_PKEY_RSA, NID_sha512, EVP_PKEY_EC, NID_sha384, EVP_PKEY_RSA, NID_sha384, EVP_PKEY_EC,
      NID_sha256, EVP_PKEY_RSA, NID_sha256, EVP_PKEY_EC, NID_sha224, EVP_PKEY_RSA, NID_sha224, EVP_PKEY_EC,
      NID_sha1, EVP_PKEY_RSA, NID_sha1, EVP_PKEY_EC
    };
    ok = SSL_CTX_set1_sigalgs (sslctx, ssl_sigalg_default_ids, SIM_ARRAY_SIZE (ssl_sigalg_default_ids));
#endif
  } else if ((ok = SSL_CTX_set1_sigalgs (sslctx, sigalgs.ptr, sigalgs.len / sizeof (int))) != true) {
    while (ERR_get_error ()) {}
    ok = SSL_CTX_set1_sigalgs (sslctx, ssl_sigalg_ids, 2);
  }
  if (ok != true || SSL_CTX_set1_client_sigalgs (sslctx, ssl_sigalg_ids, curves.len ? 2 : 4) != true) {
    LOG_ERROR_ ("failed to set signature %s\n", curves.len ? "algorithm" : "algorithms");
    return false;
  }
  if (SSL_CTX_set1_client_certificate_types (sslctx, ssl_certificate_types, 2) == true)
    return true;
  LOG_ERROR_ ("failed to set certificate type\n");
  return false;
}

static SSL_CTX *ssl_new_context_ (const char *ciphers, const simtype curves, const simtype sigalgs) {
  SSL_CTX *sslctx = SSL_CTX_new (TLSv1_2_method ());
  /* enable only THE "cipher" that will be used, disable all others */
  if (sslctx && ((ciphers && ! *ciphers) || SSL_CTX_set_cipher_list (sslctx, ciphers ? ciphers : SSL_CIPHER) == true ||
                 SSL_CTX_set_cipher_list (sslctx, SSL_CIPHER) == true)) {
    const int mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
    /* disable security holes */
    SSL_CTX_set_options (sslctx, SSL_OP_NO_RENEGOTIATION | SSL_OP_NO_TICKET | SSL_OP_NO_COMPRESSION);
    /* disable old protocols */
#if OPENSSL_VERSION_NUMBER >= 0x10100000
    SSL_CTX_set_min_proto_version (sslctx, TLS1_2_VERSION);
    SSL_CTX_set_max_proto_version (sslctx, TLS1_2_VERSION);
#endif
    SSL_CTX_set_options (sslctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
    SSL_CTX_set_options (sslctx, SSL_OP_NO_TLSv1_3 | SSL_OP_NO_ENCRYPT_THEN_MAC);
    /* disable weird stuff */
    SSL_CTX_set_options (sslctx, SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
    SSL_CTX_clear_options (sslctx, SSL_OP_LEGACY_SERVER_CONNECT | SSL_OP_SINGLE_ECDH_USE);
    SSL_CTX_clear_mode (sslctx, SSL_MODE_AUTO_RETRY);
    /* disable sessions */
    SSL_CTX_set_session_cache_mode (sslctx, SSL_SESS_CACHE_NO_INTERNAL); /* SSL_SESS_CACHE_OFF == 0 */
    /* disable re-negotiation */
    SSL_CTX_set_info_callback (sslctx, sim_ssl_callback_info__);
#ifdef DONOT_DEFINE
    SSL_CTX_set_verify_depth (sslctx, 1); /* doesn't work */
#endif
    /* set verify callback to the context and also set flags to require "client" authentication */
    SSL_CTX_set_verify (sslctx, mode, curves.len ? sim_ssl_callback_noverify__ : ssl_callback_verify_);
    /* set strict certificate checking (disable fallback to SHA1 etc) */
    SSL_CTX_set_cert_flags (sslctx, SSL_CERT_FLAG_TLS_STRICT);
    /* set allowed elliptic curves and signature algorithms */
    if (sim_ssl_init_params__ (sslctx, curves, sigalgs))
      return sslctx;
  } else
    LOG_ERROR_ ("%s\n", sslctx ? "failed to set cipher list" : "failed to initialize context");
  SSL_CTX_free (sslctx);
  return NULL;
}

static simtype sim_ssl_new_param__ (const simtype array, int nid) {
  unsigned i, j, l = 1;
  simtype buf;
  int *ids = (buf = string_buffer_new ((array.len + 1) * sizeof (*ids))).ptr;
  if (array.len) {
    *ids = nid;
    for (i = 1; i <= array.len; i++) {
      int id = OBJ_sn2nid (array.arr[i].ptr);
      for (j = 1; j < l; j++)
        if (ids[j] == id)
          id = NID_undef;
      if (id == NID_undef) {
        LOG_WARN_ ("failed to find %s %s\n", nid == NID_ecdsa_with_SHA512 ? "sigalg" : "curve", array.arr[i].str);
      } else if ((ids[l++] = id) == *ids)
        *ids = NID_undef;
    }
  } else
    *ids = NID_undef;
  if (nid == NID_ecdsa_with_SHA512) {
    simtype tmp;
    int n = l - (*ids == NID_undef);
    int *nids = (tmp = string_buffer_new (n * 2 * sizeof (*nids))).ptr;
    i = *ids == NID_undef ? 1 : 0;
    for (j = 0; n; n--) {
      if (OBJ_find_sigid_algs (ids[i], &nids[j], &nids[j + 1]) == true) {
        j += 2;
      } else
        LOG_WARN_ ("failed to find sigalgs %s\n", OBJ_nid2sn (ids[i]));
      i++;
    }
    string_buffer_free (buf);
    buf = tmp;
    l = j;
  }
  return string_buffer_truncate (buf, sizeof (*ids) * l);
}

int ssl_init_ (const char *ciphers, const simtype curves, const simtype sigalgs) {
  int err = SIM_SSL_ERROR;
#ifdef SIM_MEMORY_CHECK
  CRYPTO_malloc_debug_init ();
  CRYPTO_mem_ctrl (CRYPTO_MEM_CHECK_ON);
#endif
  ssl_generating_flag = 0;
  memset (ssl_generating_flags, 0, sizeof (ssl_generating_flags));
#if OPENSSL_VERSION_NUMBER < 0x10100000
  if (SSL_library_init () == true)
#else
  if (OPENSSL_init_ssl (OPENSSL_INIT_NO_LOAD_CONFIG | OPENSSL_INIT_NO_ATEXIT, NULL) == true)
#endif
    if ((err = sim_ssl_init_lock__ ()) == SIM_OK)
      SSL_load_error_strings ();
  RAND_set_rand_method (&sim_ssl_rand);
  if (err == SIM_OK && ciphers && curves.typ != SIMNIL && sigalgs.typ != SIMNIL) {
    simtype str = strstr (ciphers, SSL_CIPHER) || ! *ciphers ? pointer_new (ciphers) : string_cat (SSL_CIPHER, ciphers);
    simtype tmpcurves = sim_ssl_new_param__ (curves, SSL_CASE_CURVE (false));
    simtype tmpsigalgs = sim_ssl_new_param__ (sigalgs, NID_ecdsa_with_SHA512);
    ssl_contexts[SSL_HANDSHAKE_CLIENT] = ssl_new_context_ (NULL, nil (), nil ());
    ssl_contexts[SSL_HANDSHAKE_SERVER] = ssl_new_context_ (NULL, pointer_new (""), nil ());
    ssl_contexts[SSL_HANDSHAKE_ANONYMOUS] = ssl_new_context_ ("", tmpcurves, nil ());
    ssl_contexts[SSL_HANDSHAKE_NOKEY] = ssl_new_context_ (str.ptr, tmpcurves, tmpsigalgs);
    string_free (tmpsigalgs);
    string_free (tmpcurves);
    LOG_DEBUG_ ("init options 0x%lX %s\n", SSL_CTX_get_options (ssl_contexts[SSL_HANDSHAKE_CLIENT]), str.str);
    if (ssl_contexts[SSL_HANDSHAKE_CLIENT] && ssl_contexts[SSL_HANDSHAKE_SERVER])
      if (ssl_contexts[SSL_HANDSHAKE_ANONYMOUS] && ssl_contexts[SSL_HANDSHAKE_NOKEY]) {
        SSL_CTX_set_verify (ssl_contexts[SSL_HANDSHAKE_ANONYMOUS], SSL_VERIFY_NONE, NULL); /* do not expect ANY key from anonymous "client" */
#if OPENSSL_VERSION_NUMBER >= 0x10100000
        SSL_CTX_clear_cert_flags (ssl_contexts[SSL_HANDSHAKE_SERVER], SSL_CERT_FLAG_TLS_STRICT);
#endif
        ((SSL3_ENC_METHOD *) &TLSv1_2_enc_data)->generate_master_secret = ssl_callback_generate_; /* install hook for master key generation */
        return SIM_OK;
      }
    ssl_uninit_ (true);
    err = SIM_SSL_ERROR;
  }
  return err;
}

void ssl_uninit_ (simbool init) {
  ((SSL3_ENC_METHOD *) &TLSv1_2_enc_data)->generate_master_secret = tls1_generate_master_secret;
  SSL_CTX_free (ssl_contexts[SSL_HANDSHAKE_NOKEY]), ssl_contexts[SSL_HANDSHAKE_NOKEY] = NULL;
  SSL_CTX_free (ssl_contexts[SSL_HANDSHAKE_ANONYMOUS]), ssl_contexts[SSL_HANDSHAKE_ANONYMOUS] = NULL;
  SSL_CTX_free (ssl_contexts[SSL_HANDSHAKE_SERVER]), ssl_contexts[SSL_HANDSHAKE_SERVER] = NULL;
  SSL_CTX_free (ssl_contexts[SSL_HANDSHAKE_CLIENT]), ssl_contexts[SSL_HANDSHAKE_CLIENT] = NULL;
  X509_free (ssl_certificates[SSL_KEY_ANONYMOUS]);
  sim_ssl_free_key__ (ssl_keys[SSL_KEY_ANONYMOUS]);
  sim_ssl_free_key__ (ssl_keys[SSL_KEY_RSA]);
  ssl_certificates[SSL_KEY_ANONYMOUS] = ssl_certificates[SSL_KEY_RSA] = ssl_certificates[SSL_KEY_EC] = NULL;
  ssl_keys[SSL_KEY_ANONYMOUS] = ssl_keys[SSL_KEY_RSA] = ssl_keys[SSL_KEY_EC] = NULL;
  ERR_remove_thread_state (NULL);
  sim_ssl_uninit_lock__ (CRYPTO_num_locks ());
  RAND_cleanup ();
  ERR_free_strings ();
  EVP_cleanup ();
  CRYPTO_cleanup_all_ex_data ();
  OBJ_cleanup ();
  CONF_modules_free ();
#ifdef SIM_MEMORY_CHECK
  if (! init)
    CRYPTO_mem_leaks_fp (stderr);
#endif
}

void ssl_exit_thread_ (void) {
#if ! HAVE_LIBPTH
  PTH_UNPROTECT_PROTECT_ (ERR_remove_thread_state (NULL));
#endif
}

static int sim_ssl_callback_log_write_ (BIO *bio, const char *input, int length) {
  int len = length;
  const char *s, *e;
  if (LOG_PROTECT_ (SIM_MODULE, SIM_LOG_XTRA)) {
    for (s = input; len > 0; len--) {
      simtype *buf = BIO_get_data (bio);
      for (e = s; len && *e++ != '\n'; len--) {}
      if (len) {
        LOG_XTRA_ ("$%d ", ((simsocket) BIO_get_callback_arg (bio))->fd);
        if (BIO_get_num (bio))
          LOG_XTRA_ ("%.*s", BIO_get_num (bio), buf->str);
        LOG_XTRA_ ("%.*s", (int) (e - s), s);
        string_free (*buf), *buf = nil ();
        BIO_set_num (bio, 0);
      } else {
        simtype str = *buf;
        *buf = string_new (BIO_get_num (bio) + e - s);
        memcpy (buf->str, str.str, BIO_get_num (bio));
        string_free (str);
        memcpy (buf->str + BIO_get_num (bio), s, e - s);
        BIO_set_num (bio, e - s + BIO_get_num (bio));
      }
      s = e;
    }
    LOG_UNPROTECT_ ();
  }
  return length;
}

static int sim_ssl_callback_log_puts__ (BIO *bio, const char *input) {
  return sim_ssl_callback_log_write_ (bio, input, strlen (input));
}

static int sim_ssl_callback_bio_create (BIO *bio) {
  simtype *buf = sim_new (sizeof (*buf));
  BIO_set_init (bio, true);
  BIO_set_num (bio, 0);
  BIO_set_data (bio, buf);
  *buf = nil ();
  return true;
}

static int sim_ssl_callback_bio_destroy (BIO *bio) {
  if (BIO_get_init (bio)) {
    simtype *buf = BIO_get_data (bio);
    BIO_set_init (bio, false);
    BIO_set_num (bio, 0);
    string_free (*buf);
    sim_free (buf, sizeof (*buf));
  }
  return true;
}

static long sim_ssl_callback_bio_ctrl (BIO *bio, int cmd, long argnum, void *argptr) {
  if (cmd == BIO_CTRL_FLUSH)
    return true;
  if (cmd == BIO_C_GET_FD) {
    int *ptr = argptr;
    int fd = BIO_get_init (bio) && BIO_get_callback_arg (bio) ? ((simsocket) BIO_get_callback_arg (bio))->fd : -1;
    if (ptr && fd != -1)
      *ptr = fd;
    return fd;
  }
  if (cmd != BIO_CTRL_PUSH && cmd != BIO_CTRL_POP)
    LOG_ERROR_ ("bio ctrl cmd %d\n", cmd);
  return false;
}

static int sim_ssl_callback_bio_write_ (BIO *bio, const char *input, int length) {
  simsocket sock = (simsocket) BIO_get_callback_arg (bio);
  int err = PTH_PROTECT_ (SIM_SOCKET_BAD_LENGTH);
  if (! socket_check_client (sock)) {
    const simbyte *buf = (const simbyte *) input;
    unsigned len = length, n = (unsigned) -1, h;
    while (len >= SOCKET_OVERHEAD_SSL && buf[0] >= 0x14 && buf[0] <= 0x16) {
      if ((n = (buf[3] << 8) + buf[4] + SOCKET_OVERHEAD_SSL) > len)
        break;
      buf += n;
      len -= n;
      h = SOCKET_CALC_OVERHEAD_TCP (n);
      sock->rcvheaders += h, sock->sndheaders += h;
    }
    if (len)
      LOG_ERROR_SIMTYPE_ (pointer_new_len (input, length), LOG_BIT_BIN, "bio $%d write %d/%d ", sock->fd, n, len);
    err = socket_send_all_ (sock, (const simbyte *) input, length);
  } else if (length < 0x8000) {
    simbyte *buf = sim_new (length + 2);
    buf[0] = (simbyte) (length >> 8) | 0x80;
    buf[1] = (simbyte) length;
    memcpy (buf + 2, input, length);
    err = _socket_send_proxy_ (sock, pointer_new_len (buf, length + 2), SOCKET_SEND_PROXY | SOCKET_SEND_PAD, "BIO", 0);
    sim_free (buf, length + 2);
  } else
    LOG_ERROR_SIMTYPE_ (pointer_new_len (input, length), LOG_BIT_BIN, "bio $%d write %d ", sock->fd, length);
  if (err != SIM_OK)
    socket_set_errno (err);
  return PTH_UNPROTECT (err == SIM_OK ? length : err == SIM_SOCKET_END ? 0 : -1);
}

static int sim_ssl_callback_bio_read_ (BIO *bio, char *output, int length) {
  int err;
  unsigned size = PTH_PROTECT_ (0);
  simsocket sock = (simsocket) BIO_get_callback_arg (bio);
  if (! socket_check_client (sock)) {
    err = socket_recv_some_ (sock, SOCKET_RECV_TCP, pointer_new_len (output, length), &size);
    if (err == SIM_OK && size) {
      if (length != SOCKET_OVERHEAD_SSL || size != SOCKET_OVERHEAD_SSL ||
          output[1] != 3 || output[0] < 0x14 || output[0] > 0x16) {
        length = SOCKET_CALC_OVERHEAD_TCP (size + SOCKET_OVERHEAD_SSL);
        sock->rcvheaders += length, sock->sndheaders += length;
      }
    } else
      socket_set_errno (err == SIM_OK ? SIM_SOCKET_END : err);
    return PTH_UNPROTECT (err == SIM_OK ? size : (unsigned) -1);
  }
  for (;;) {
    int num = BIO_get_num (bio);
    simtype *buf = BIO_get_data (bio);
    if (length < num) {
      memcpy (output, buf->str, length);
      BIO_set_num (bio, num - length);
      memmove (buf->str, buf->str + length, num - length);
      return PTH_UNPROTECT (size + length);
    }
    BIO_set_num (bio, 0);
    memcpy (output, buf->str, num);
    string_free (*buf), *buf = nil ();
    output += num;
    size += num;
    if (! (length -= num))
      return PTH_UNPROTECT (size);
    if ((err = socket_recv_proxy_ (sock, SOCKET_RECV_PROXY, buf)) != SIM_OK) {
      socket_set_errno (err);
      return PTH_UNPROTECT (err == SIM_SOCKET_END ? 0 : -1);
    }
    BIO_set_num (bio, buf->len);
    if (BIO_get_num (bio) >= 2 && buf->str[0] >= 0x80) {
      int len = (buf->str[0] << 8) + buf->str[1] - 0x8000;
      if (len > BIO_get_num (bio) - 2) {
        socket_set_errno (SIM_SOCKET_BAD_PACKET);
        return PTH_UNPROTECT (-1);
      }
      BIO_set_num (bio, len);
      memmove (buf->str, buf->str + 2, len);
    }
  }
}

#if OPENSSL_VERSION_NUMBER >= 0x10100000
BIO_METHOD *sim_ssl_new_log__ (void) {
  static BIO_METHOD *sim_ssl_log = NULL;
  if (! sim_ssl_log && (sim_ssl_log = BIO_meth_new (BIO_TYPE_DESCRIPTOR, "log")) != NULL) {
    if (BIO_meth_set_write (sim_ssl_log, sim_ssl_callback_log_write_))
      if (BIO_meth_set_puts (sim_ssl_log, sim_ssl_callback_log_puts__))
        if (BIO_meth_set_create (sim_ssl_log, sim_ssl_callback_bio_create))
          if (BIO_meth_set_destroy (sim_ssl_log, sim_ssl_callback_bio_destroy))
            return sim_ssl_log;
    LOG_ERROR_ ("bio log failed\n");
    BIO_meth_free (sim_ssl_log);
    sim_ssl_log = NULL;
  }
  return sim_ssl_log;
}

BIO_METHOD *sim_ssl_new_bio__ (void) {
  static BIO_METHOD *sim_ssl_bio = NULL;
  if (! sim_ssl_bio && (sim_ssl_bio = BIO_meth_new (BIO_TYPE_DESCRIPTOR, "proxy")) != NULL) {
    if (BIO_meth_set_write (sim_ssl_bio, sim_ssl_callback_bio_write_))
      if (BIO_meth_set_read (sim_ssl_bio, sim_ssl_callback_bio_read_))
        if (BIO_meth_set_ctrl (sim_ssl_bio, sim_ssl_callback_bio_ctrl))
          if (BIO_meth_set_create (sim_ssl_bio, sim_ssl_callback_bio_create))
            if (BIO_meth_set_destroy (sim_ssl_bio, sim_ssl_callback_bio_destroy))
              return sim_ssl_bio;
    LOG_ERROR_ ("bio new failed\n");
    BIO_meth_free (sim_ssl_bio);
    sim_ssl_bio = NULL;
  }
  return sim_ssl_bio;
}
#else
static const BIO_METHOD sim_ssl_log = {
  BIO_TYPE_DESCRIPTOR, "log", sim_ssl_callback_log_write_, NULL, sim_ssl_callback_log_puts__,
  NULL, NULL, sim_ssl_callback_bio_create, sim_ssl_callback_bio_destroy, NULL
};

static const BIO_METHOD sim_ssl_bio = {
  BIO_TYPE_DESCRIPTOR, "proxy", sim_ssl_callback_bio_write_, sim_ssl_callback_bio_read_,
  NULL, NULL, sim_ssl_callback_bio_ctrl, sim_ssl_callback_bio_create, sim_ssl_callback_bio_destroy, NULL
};

#define sim_ssl_new_log__() ((BIO_METHOD *) &sim_ssl_log)
#define sim_ssl_new_bio__() ((BIO_METHOD *) &sim_ssl_bio)
#endif

/* create new ssl instance. use all the ssl context parameters, keys and certificates */
static SSL *sim_ssl_new__ (simsocket sock, SSL_CTX *sslctx) {
  SSL *ssl = SSL_new (sslctx);
  if (! ssl) {
    LOG_ERROR_ ("new $%d failed\n", sock->fd);
#ifndef OPENSSL_NO_SSL_TRACE
  } else if (LOG_CHECK_LEVEL_ (SIM_LOG_XTRA)) {
    BIO *bio;
    BIO_METHOD *methods = sim_ssl_new_log__ ();
    if ((sock->master->bio = bio = methods ? BIO_new (methods) : NULL) != NULL) {
      BIO_set_callback_arg (bio, (char *) sock);
      SSL_set_msg_callback (ssl, SSL_trace);
      SSL_set_msg_callback_arg (ssl, bio);
    }
#endif
  }
  return ssl;
}

void ssl_free (simsocket sock) {
  if (sock->master) {
    string_free (sock->master->from);
    string_free (sock->master->to);
    string_free (sock->master->pub[SSL_KEY_EC]);
    string_free (sock->master->pub[SSL_KEY_RSA]);
    string_free (sock->master->key);
    BIO_free (sock->master->bio);
    sim_free (sock->master, sizeof (*sock->master)), sock->master = NULL;
  }
}

static int sim_ssl_handshake_ (simsocket sock, const SSL *ssl) {
  if (PTH_PROTECT_ (sock->master ? sock->master->length : 0)) {
    simtype master = pointer_new_len (sock->master->master, SSL_MAX_MASTER_KEY_LENGTH);
    if (! string_check_diff_len (master, ssl->session->master_key, ssl->session->master_key_length))
      return PTH_UNPROTECT (SIM_OK);
    LOG_ERROR_ ("failed to generate premaster key $%d\n", sock->fd);
  } else
    LOG_ERROR_ ("failed to generate premaster keys $%d\n", sock->fd);
  return PTH_UNPROTECT (SIM_SSL_NO_PREMASTER);
}

static int sim_ssl_handshake__ (simsocket sock, SSL *ssl, int (action) (SSL *)) {
  int ret, err = SIM_OK;
  BIO *bio;
  BIO_METHOD *methods = sim_ssl_new_bio__ ();
  if (! methods || (bio = BIO_new (methods)) == NULL)
    return SIM_SSL_ERROR;
  BIO_set_callback_arg (bio, (char *) sock);
  SSL_set_bio (ssl, bio, bio);
  if (ERR_peek_error ()) { /* flush the error queue if not empty */
    char errbuf[SIM_SIZE_ERROR];
    const char *error = sim_error_get_buffer (SIM_SSL_ERROR, false, LOG_CHECK_LEVEL_ (SIM_LOG_WARN) ? errbuf : NULL);
    LOG_WARN_ ("unexpected $%d error %d: %s\n", sock->fd, err, error);
  }
  if ((ret = action (ssl)) == -1) {
    if ((err = SSL_get_error (ssl, ret)) != SSL_ERROR_SYSCALL) {
      LOG_DEBUG_ ("socket $%d error %d: %s\n", sock->fd, err, ERR_error_string (err, NULL));
      err = SIM_SSL_ERROR;
    } else if ((err = socket_get_errno ()) == 0)
      err = SIM_SSL_ERROR;
  }
#ifdef DONOT_DEFINE
  if (! (ssl->s3->flags & SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS)) {
    LOG_FATAL_ (SIM_OK, "openssl renegotiation $%d was re-enabled !! \n", sock->fd);
    err = SIM_SSL_NO_HANDSHAKE;
  }
#endif
  if (err == SIM_OK && ret != 1) {
    if ((err = SSL_get_error (ssl, ret)) == SSL_ERROR_SYSCALL) {
      LOG_DEBUG_ ("eof $%d\n", sock->fd);
      err = SIM_SOCKET_END;
    } else {
      LOG_DEBUG_ ("eof $%d error %d: %s\n", sock->fd, err, ERR_error_string (err, NULL));
      err = SIM_SSL_ERROR;
    }
  }
  if (err == SIM_OK)
    err = sim_ssl_handshake_ (sock, ssl);
  return sock->err == SIM_OK ? err : sock->err;
}

static int sim_ssl_handshake_socket__ (simsocket sock, SSL_CTX *sslctx, const char *address) {
  int err = SIM_SSL_ERROR, handshake = sock->master->handshake, idx;
  SSL *ssl = sim_ssl_new__ (sock, sslctx);
  if (ssl) {
    EC_KEY *ec = NULL;
    if (((idx = SSL_CHECK_NOKEY (handshake) ? SSL_KEY_RSA : SSL_KEY_EC) != SSL_KEY_EC) ^ ! address) {
      if ((err = sim_ssl_handshake__ (sock, ssl, SSL_connect)) != SIM_OK) {
        LOG_DEBUG_ ("connect $%d error %d\n", sock->fd, err);
#if OPENSSL_VERSION_NUMBER < 0x10100000
      } else if (! address && ! sim_ssl_test_ec__ (ssl->session->sess_cert->peer_ecdh_tmp, idx, true)) {
#else
      } else if (! address && ! sim_ssl_test_ec_key__ (ssl->s3->peer_tmp, idx, true)) {
#endif
        err = SIM_SSL_BAD_TMP_KEY;
        LOG_DEBUG_ ("connect $%d error %d\n", sock->fd, err);
      } else
        LOG_DEBUG_ ("free $%d\n", sock->fd);
#if OPENSSL_VERSION_NUMBER < 0x10100000
    } else if ((ec = EC_KEY_new_by_curve_name (SSL_CASE_CURVE (idx == SSL_KEY_EC))) == NULL) {
      LOG_ERROR_ ("accept $%d temporary key creation failed\n", sock->fd);
    } else {
      if (SSL_set_tmp_ecdh (ssl, ec) != true) { /* generate ephemeral (temporary) key for ECDH */
        LOG_ERROR_ ("accept $%d temporary key generation failed\n", sock->fd);
#endif
      } else if ((err = sim_ssl_handshake__ (sock, ssl, SSL_accept)) != SIM_OK) {
        LOG_DEBUG_ ("accept $%d error %d\n", sock->fd, err);
      } else
        LOG_DEBUG_ ("free $%d\n", sock->fd);
      EC_KEY_free (ec);
#if OPENSSL_VERSION_NUMBER < 0x10100000
    }
#endif
    SSL_free (ssl);
  }
  return err;
}

int ssl_handshake_client_ (simsocket sock, const char *address, int handshake) {
  static const char *ssl_handshake_names[] = { "local", "nokey", "anonymous", "server", "client", "proxy" };
  int err, idx = SSL_CASE_CONTEXT (handshake);
  simbool init = SSL_CHECK_NOKEY (handshake) || handshake == SSL_HANDSHAKE_LOCAL;
  simtype to;
  if (init ? sock->master || sock->crypt.decrypt :
             ! sock->master || ! sock->crypt.decrypt || (address && string_check_diff (sock->master->to, address))) {
    char nick[SIM_SIZE_NICK];
    sprintf (nick, " %s", CONTACT_GET_NICK (sock->master ? sock->master->to.ptr : NULL));
    LOG_ERROR_ ("new $%d %s master %p %p %p '%s' -> '%s'\n", sock->fd,
                ssl_handshake_names[handshake - SSL_HANDSHAKE_LOCAL],
                (void *) sock->master, sock->crypt.decrypt, sock->crypt.encrypt, nick, CONTACT_GET_NICK (address));
    return SIM_SSL_NO_HANDSHAKE;
  }
  if (! init) {
    to = sock->master->to;
    sock->master->to = nil ();
    ssl_free (sock);
    memcpy (&sock->proxy, &sock->crypt, sizeof (sock->crypt));
    memset (&sock->crypt, 0, sizeof (sock->crypt));
    sock->crypt.maxsize = sock->proxy.maxsize;
    sock->crypt.ivs = nil ();
  } else
    to = address ? string_copy (address) : nil ();
  sock->master = sim_new (sizeof (*sock->master));
  sock->master->handshake = handshake;
  sock->master->length = 0;
  sock->master->bio = NULL;
  sock->master->from = address ? pointer_new (address) : nil ();
  sock->master->to = to;
  sock->master->key = sock->master->pub[SSL_KEY_RSA] = sock->master->pub[SSL_KEY_EC] = nil ();
  LOG_DEBUG_ ("new $%d %s '%s'\n", sock->fd,
              ssl_handshake_names[handshake - SSL_HANDSHAKE_LOCAL], CONTACT_GET_NICK (address));
  while (ssl_generating_flag && (idx == SSL_HANDSHAKE_ANONYMOUS || idx == SSL_HANDSHAKE_SERVER))
    pth_usleep_ (10000);
  ssl_generating_flags[idx]++;
  PTH_UNPROTECT_PROTECT_ (err = sim_ssl_handshake_socket__ (sock, ssl_contexts[idx], address));
  ssl_generating_flags[idx]--;
  return err;
}

int ssl_reinit_ (void) {
  int err = SIM_OK;
  if (! ssl_generating_flag++) {
    LOG_DEBUG_ ("new keys\n");
    while (ssl_generating_flags[SSL_HANDSHAKE_ANONYMOUS] || ssl_generating_flags[SSL_HANDSHAKE_SERVER])
      pth_usleep_ (10000);
    PTH_UNPROTECT_PROTECT_ (err = sim_ssl_generate_key__ (NULL));
    if (err == SIM_OK)
      PTH_UNPROTECT_PROTECT_ (err = sim_ssl_generate_key__ (ssl_contexts[SSL_HANDSHAKE_SERVER]));
    if (err == SIM_OK)
      PTH_UNPROTECT_PROTECT_ (err = sim_ssl_generate_key__ (ssl_contexts[SSL_HANDSHAKE_ANONYMOUS]));
  }
  ssl_generating_flag--;
  return err;
}

int ssl_rsa_size_ (const simtype key) {
  int size = PTH_UNPROTECT (-42); /* compensate for OAEP overhead */
  RSA *rsa;
  if (key.typ == SIMNIL) {
    rsa = EVP_PKEY_get1_RSA (ssl_keys[SSL_KEY_RSA]);
    size = 0;
  } else
    rsa = d2i_RSAPublicKey (NULL, (const simbyte **) &key.str, key.len);
  if (rsa)
    size += RSA_size (rsa);
  RSA_free (rsa);
  return PTH_PROTECT_ (size);
}

simtype ssl_rsa_crypt_public_ (const simtype input, const simtype key, const simtype hash) {
  simtype ret = nil ();
  if (key.len) {
    unsigned size = PTH_UNPROTECT (0);
    RSA *rsa = d2i_RSAPublicKey (NULL, (const simbyte **) &key.str, key.len);
    if (rsa) {
      ret = string_new (RSA_size (rsa));
      if (hash.typ != SIMNIL) {
        if (EVP_MD_size (EVP_sha512 ()) == (int) hash.len)
          if ((int) (size = RSA_public_decrypt (input.len, input.str, ret.str, rsa, RSA_NO_PADDING)) > 0)
            if (RSA_verify_PKCS1_PSS (rsa, hash.str, EVP_sha512 (), ret.str, ret.len - input.len - 2) != 1)
              size = 0;
      } else
        size = RSA_public_encrypt (input.len, input.str, ret.str, rsa, RSA_PKCS1_OAEP_PADDING);
      if (size != ret.len)
        string_free (ret), ret = nil ();
    } else
      LOG_ERROR_ ("RSA public %s: no RSA key\n", hash.typ == SIMNIL ? "encrypt" : "decrypt");
    RSA_free (rsa);
    pth_protect_ ();
  } else
    LOG_ERROR_ ("RSA public %s: no key\n", hash.typ == SIMNIL ? "encrypt" : "decrypt");
  return ret;
}

simtype ssl_rsa_crypt_private_ (const simtype input, simbool decrypt) {
  simtype ret = nil ();
  EVP_PKEY *key = ssl_keys[SSL_KEY_RSA];
  if (key) {
    unsigned size = PTH_UNPROTECT (0);
    RSA *rsa = EVP_PKEY_get1_RSA (key);
    if (rsa) {
      ret = string_buffer_new (RSA_size (rsa));
      if (! decrypt) {
        simtype tmp = string_buffer_new (ret.len);
        if (EVP_MD_size (EVP_sha512 ()) == (int) input.len)
          if (RSA_padding_add_PKCS1_PSS (rsa, tmp.str, input.str, EVP_sha512 (), -2) == 1)
            size = RSA_private_encrypt (ret.len, tmp.str, ret.str, rsa, RSA_NO_PADDING);
        string_buffer_free (tmp);
      } else
        size = RSA_private_decrypt (input.len, input.str, ret.str, rsa, RSA_PKCS1_OAEP_PADDING);
      if ((int) size <= 0 || size > ret.len) {
        string_buffer_free (ret), ret = nil ();
      } else
        ret = string_buffer_truncate (ret, size);
    } else
      LOG_ERROR_ ("RSA private %s: no RSA key\n", decrypt ? "decrypt" : "encrypt");
    RSA_free (rsa);
    pth_protect_ ();
  } else
    LOG_ERROR_ ("RSA private %s: no key\n", decrypt ? "decrypt" : "encrypt");
  return ret;
}

int ssl_log_keys (const char *module, int level, simnumber id) {
  int err = ENOMEM;
  BIO *mem = BIO_new (BIO_s_mem ());
  if (mem) {
    char c;
    EVP_PKEY *key;
    simcontact contact;
    err = SIM_CONTACT_UNKNOWN;
    if (! id) {
#ifdef DONOT_DEFINE
      if (ssl_keys[SSL_KEY_EC]) {
        BIO_puts (mem, "EC key:\n");
        EVP_PKEY_print_private (mem, ssl_keys[SSL_KEY_EC], 4, NULL);
      }
      if (ssl_keys[SSL_KEY_RSA]) {
        BIO_puts (mem, "RSA key:\n");
        EVP_PKEY_print_private (mem, ssl_keys[SSL_KEY_RSA], 4, NULL);
      }
      err = SIM_OK;
#endif
    } else if ((contact = contact_list_find_id (id)) != NULL) {
      if (contact->ec.typ != SIMNIL && (key = EVP_PKEY_new ()) != NULL) {
        simtype tmp = contact->ec;
        EC_KEY *ec = EC_KEY_new_by_curve_name (SSL_CASE_CURVE (true));
        if (ec && o2i_ECPublicKey (&ec, (const simbyte **) &tmp.str, tmp.len) && EVP_PKEY_set1_EC_KEY (key, ec)) {
          EC_KEY_set_asn1_flag (ec, OPENSSL_EC_NAMED_CURVE); /* print curve name */
          BIO_puts (mem, "EC key:\n");
          EVP_PKEY_print_public (mem, key, 4, NULL);
        }
        EC_KEY_free (ec);
        EVP_PKEY_free (key);
      }
      if (contact->rsa.typ != SIMNIL && (key = EVP_PKEY_new ()) != NULL) {
        simtype tmp = contact->rsa;
        RSA *rsa = d2i_RSAPublicKey (NULL, (const simbyte **) &tmp.str, tmp.len);
        if (rsa && EVP_PKEY_set1_RSA (key, rsa)) {
          BIO_puts (mem, "RSA key:\n");
          EVP_PKEY_print_public (mem, key, 4, NULL);
        }
        RSA_free (rsa);
        EVP_PKEY_free (key);
      }
      err = SIM_OK;
    }
    if (log_protect_ (module, level)) {
      while (BIO_read (mem, &c, sizeof (c)) == sizeof (c))
        log_any_ (module, level, "%c", c);
      log_unprotect_ ();
    }
    BIO_free (mem);
  }
  return err;
}
