/*
 +----------------------------------------------------------------------+
 | Swoole                                                               |
 +----------------------------------------------------------------------+
 | This source file is subject to version 2.0 of the Apache license,    |
 | that is bundled with this package in the file LICENSE, and is        |
 | available through the world-wide-web at the following url:           |
 | http://www.apache.org/licenses/LICENSE-2.0.html                      |
 | If you did not receive a copy of the Apache2.0 license and are unable|
 | to obtain it through the world-wide-web, please send a note to       |
 | license@swoole.com so we can mail you a copy immediately.            |
 +----------------------------------------------------------------------+
 | Author: Tianfeng Han  <mikan.tenny@gmail.com>                        |
 +----------------------------------------------------------------------+
 */

#include "swoole.h"
#include "swoole_string.h"
#include "swoole_socket.h"
#include "swoole_ssl.h"

#ifdef SW_USE_OPENSSL

using swoole::network::Address;
using swoole::network::Socket;
using swoole::SSLContext;

#if OPENSSL_VERSION_NUMBER < 0x10000000L
#error "require openssl version 1.0 or later"
#endif

static bool openssl_init = false;
static bool openssl_thread_safety_init = false;
static int ssl_connection_index = 0;
static int ssl_port_index = 0;
static pthread_mutex_t *lock_array;

static int swSSL_verify_callback(int ok, X509_STORE_CTX *x509_store);
#ifndef OPENSSL_NO_RSA
static RSA *swSSL_rsa_key_callback(SSL *ssl, int is_export, int key_length);
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L
static int swSSL_set_default_dhparam(SSL_CTX *ssl_context);
#endif

#ifdef SW_SUPPORT_DTLS
static int swSSL_generate_cookie(SSL *ssl, uchar *cookie, uint *cookie_len);
static int swSSL_verify_cookie(SSL *ssl, const uchar *cookie, uint cookie_len);
#endif

#ifdef __GNUC__
#define MAYBE_UNUSED __attribute__((used))
#else
#define MAYBE_UNUSED
#endif

static void MAYBE_UNUSED swSSL_lock_callback(int mode, int type, const char *file, int line);

void swSSL_init(void) {
    if (openssl_init) {
        return;
    }
#if OPENSSL_VERSION_NUMBER >= 0x10100003L && !defined(LIBRESSL_VERSION_NUMBER)
    OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG | OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS,
                     nullptr);
#else
    OPENSSL_config(nullptr);
    SSL_library_init();
    SSL_load_error_strings();
    OpenSSL_add_all_algorithms();
#endif

    ssl_connection_index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
    if (ssl_connection_index < 0) {
        swError("SSL_get_ex_new_index() failed");
        return;
    }

    ssl_port_index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
    if (ssl_port_index < 0) {
        swError("SSL_get_ex_new_index() failed");
        return;
    }

    openssl_init = true;
}

int swSSL_get_ex_connection_index() {
    return ssl_connection_index;
}

int swSSL_get_ex_port_index() {
    return ssl_port_index;
}

void swSSL_destroy() {
    if (!openssl_init) {
        return;
    }

    SW_LOOP_N(CRYPTO_num_locks()) {
        pthread_mutex_destroy(&(lock_array[i]));
    }

    OPENSSL_free(lock_array);

#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_0
    (void) CRYPTO_THREADID_set_callback(nullptr);
#else
    CRYPTO_set_id_callback(nullptr);
#endif
    CRYPTO_set_locking_callback(nullptr);
    openssl_init = false;
    openssl_thread_safety_init = false;
}

static void MAYBE_UNUSED swSSL_lock_callback(int mode, int type, const char *file, int line) {
    if (mode & CRYPTO_LOCK) {
        pthread_mutex_lock(&(lock_array[type]));
    } else {
        pthread_mutex_unlock(&(lock_array[type]));
    }
}

static int ssl_error_cb(const char *str, size_t len, void *buf) {
    memcpy(buf, str, len);

    return 0;
}

const char *swSSL_get_error() {
    ERR_print_errors_cb(ssl_error_cb, sw_tg_buffer()->str);

    return sw_tg_buffer()->str;
}

#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_0
static void MAYBE_UNUSED swSSL_id_callback(CRYPTO_THREADID *id) {
    CRYPTO_THREADID_set_numeric(id, (ulong_t) pthread_self());
}
#else
static ulong_t swSSL_id_callback(void) {
    return (ulong_t) pthread_self();
}
#endif

void swSSL_init_thread_safety() {
    if (!openssl_init) {
        return;
    }

    if (openssl_thread_safety_init) {
        return;
    }

    lock_array = (pthread_mutex_t *) OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
    SW_LOOP_N(CRYPTO_num_locks()) {
        pthread_mutex_init(&(lock_array[i]), nullptr);
    }

#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_0
    (void) CRYPTO_THREADID_set_callback(swSSL_id_callback);
#else
    CRYPTO_set_id_callback(swSSL_id_callback);
#endif

    CRYPTO_set_locking_callback(swSSL_lock_callback);
    openssl_thread_safety_init = true;
}

bool swSSL_is_thread_safety() {
    return openssl_thread_safety_init;
}

static void swSSL_info_callback(const SSL *ssl, int where, int ret) {
    BIO *rbio, *wbio;
    swSocket *sock;

    if (where & SSL_CB_HANDSHAKE_START) {
        sock = (swSocket *) SSL_get_ex_data(ssl, ssl_connection_index);

        if (sock->ssl_state == SW_SSL_STATE_READY) {
            sock->ssl_renegotiation = 1;
            swDebug("SSL renegotiation");
        }
    }

    if ((where & SSL_CB_ACCEPT_LOOP) == SSL_CB_ACCEPT_LOOP) {
        sock = (swSocket *) SSL_get_ex_data(ssl, ssl_connection_index);

        if (!sock->ssl_handshake_buffer_set) {
            /*
             * By default OpenSSL uses 4k buffer during a handshake,
             * which is too low for long certificate chains and might
             * result in extra round-trips.
             *
             * To adjust a buffer size we detect that buffering was added
             * to write side of the connection by comparing rbio and wbio.
             * If they are different, we assume that it's due to buffering
             * added to wbio, and set buffer size.
             */

            rbio = SSL_get_rbio(ssl);
            wbio = SSL_get_wbio(ssl);

            if (rbio != wbio) {
                (void) BIO_set_write_buffer_size(wbio, SW_SSL_BUFFER_SIZE);
                sock->ssl_handshake_buffer_set = 1;
            }
        }
    }
}

namespace swoole {

#ifndef OPENSSL_NO_NEXTPROTONEG

const std::string HTTP2_H2_ALPN("\x2h2");
const std::string HTTP2_H2_16_ALPN("\x5h2-16");
const std::string HTTP2_H2_14_ALPN("\x5h2-14");
const std::string HTTP1_NPN("\x08http/1.1");

static bool ssl_select_proto(const uchar **out, uchar *outlen, const uchar *in, uint inlen, const std::string &key) {
    for (auto p = in, end = in + inlen; p + key.size() <= end; p += *p + 1) {
        if (std::equal(std::begin(key), std::end(key), p)) {
            *out = p + 1;
            *outlen = *p;
            return true;
        }
    }
    return false;
}

static bool ssl_select_h2(const uchar **out, uchar *outlen, const uchar *in, uint inlen) {
    return ssl_select_proto(out, outlen, in, inlen, HTTP2_H2_ALPN) ||
           ssl_select_proto(out, outlen, in, inlen, HTTP2_H2_16_ALPN) ||
           ssl_select_proto(out, outlen, in, inlen, HTTP2_H2_14_ALPN);
}


#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
static int ssl_alpn_advertised(
    SSL *ssl, const uchar **out, uchar *outlen, const uchar *in, uint32_t inlen, void *arg) {
    unsigned int srvlen;
    unsigned char *srv;

    std::string value;
    SSLContext *cfg = (SSLContext*) arg;
    if (cfg->http_v2) {
        value = HTTP2_H2_ALPN + HTTP1_NPN;
    } else {
        value = HTTP1_NPN;
    }

    srv = (unsigned char *) value.c_str();
    srvlen = value.length();
    if (SSL_select_next_proto((unsigned char **) out, outlen, srv, srvlen, in, inlen) != OPENSSL_NPN_NEGOTIATED) {
        return SSL_TLSEXT_ERR_NOACK;
    }
    return SSL_TLSEXT_ERR_OK;
}
#endif

static int ssl_select_next_proto_cb(SSL *ssl, uchar **out, uchar *outlen, const uchar *in, uint inlen, void *arg) {
#ifdef SW_LOG_TRACE_OPEN
    std::string info("[NPN] server offers:\n");
    for (unsigned int i = 0; i < inlen; i += in[i] + 1) {
        info += "        * " + std::string(reinterpret_cast<const char *>(&in[i + 1]), in[i]);
    }
    swTraceLog(SW_TRACE_HTTP2, "[NPN] server offers: %s", info.c_str());
#endif
    SSLContext *ctx = (SSLContext *) arg;
    if (ctx->http_v2 && !ssl_select_h2(const_cast<const unsigned char**>(out), outlen, in, inlen)) {
        swWarn("HTTP/2 protocol was not selected, expects [h2]");
        return SSL_TLSEXT_ERR_NOACK;
    } else if (ctx->http) {
        *out = (uchar*) HTTP1_NPN.c_str();
        *outlen = HTTP1_NPN.length();
    }
    return SSL_TLSEXT_ERR_OK;
}
#endif

static int ssl_passwd_callback(char *buf, int num, int verify, void *data) {
    SSLContext *ctx = (SSLContext*) data;
    if (!ctx->passphrase.empty()) {
        int len = ctx->passphrase.length();
        if (len < num - 1) {
            memcpy(buf, ctx->passphrase.c_str(), len);
            buf[len] = '\0';
            return (int) len;
        }
    }
    return 0;
}

bool SSLContext::create() {
    if (!openssl_init) {
        swSSL_init();
    }

    const SSL_METHOD *method;
#ifdef SW_SUPPORT_DTLS
    if (protocols & SW_SSL_DTLS) {
        method = DTLS_method();
    } else
#endif
    {
        method = SSLv23_method();
    }
    if (protocols == 0) {
        protocols = SW_SSL_ALL;
    }
    context = SSL_CTX_new(method);
    if (context == nullptr) {
        int error = ERR_get_error();
        swWarn("SSL_CTX_new() failed, Error: %s[%d]", ERR_reason_error_string(error), error);
        return false;
    }

#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
    SSL_CTX_set_options(context, SSL_OP_MICROSOFT_SESS_ID_BUG);
#endif

#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
    SSL_CTX_set_options(context, SSL_OP_NETSCAPE_CHALLENGE_BUG);
#endif

    /* server side options */
#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
    SSL_CTX_set_options(context, SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG);
#endif

#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
    SSL_CTX_set_options(context, SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER);
#endif

#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING
    /* this option allow a potential SSL 2.0 rollback (CAN-2005-2969) */
    SSL_CTX_set_options(context, SSL_OP_MSIE_SSLV2_RSA_PADDING);
#endif

#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
    SSL_CTX_set_options(context, SSL_OP_SSLEAY_080_CLIENT_DH_BUG);
#endif

#ifdef SSL_OP_TLS_D5_BUG
    SSL_CTX_set_options(context, SSL_OP_TLS_D5_BUG);
#endif

#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
    SSL_CTX_set_options(context, SSL_OP_TLS_BLOCK_PADDING_BUG);
#endif

#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
    SSL_CTX_set_options(context, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
#endif

#if OPENSSL_VERSION_NUMBER >= 0x009080dfL
    /* only in 0.9.8m+ */
    SSL_CTX_clear_options(context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1);
#endif

#ifdef SSL_OP_NO_SSLv2
    if (!(protocols & SW_SSL_SSLv2)) {
        SSL_CTX_set_options(context, SSL_OP_NO_SSLv2);
    }
#endif
#ifdef SSL_OP_NO_SSLv3
    if (!(protocols & SW_SSL_SSLv3)) {
        SSL_CTX_set_options(context, SSL_OP_NO_SSLv3);
    }
#endif
#ifdef SSL_OP_NO_TLSv1
    if (!(protocols & SW_SSL_TLSv1)) {
        SSL_CTX_set_options(context, SSL_OP_NO_TLSv1);
    }
#endif
#ifdef SSL_OP_NO_TLSv1_1
    SSL_CTX_clear_options(context, SSL_OP_NO_TLSv1_1);
    if (!(protocols & SW_SSL_TLSv1_1)) {
        SSL_CTX_set_options(context, SSL_OP_NO_TLSv1_1);
    }
#endif
#ifdef SSL_OP_NO_TLSv1_2
    SSL_CTX_clear_options(context, SSL_OP_NO_TLSv1_2);
    if (!(protocols & SW_SSL_TLSv1_2) && !(protocols & SW_SSL_DTLS)) {
        SSL_CTX_set_options(context, SSL_OP_NO_TLSv1_2);
    }
#endif
#ifdef SSL_OP_NO_TLSv1_3
    SSL_CTX_clear_options(context, SSL_OP_NO_TLSv1_3);
    if (!(protocols & SW_SSL_TLSv1_3)) {
        SSL_CTX_set_options(context, SSL_OP_NO_TLSv1_3);
    }
#endif

#ifdef SSL_OP_NO_COMPRESSION
    if (disable_compress) {
        SSL_CTX_set_options(context, SSL_OP_NO_COMPRESSION);
    }
#endif

#ifdef SSL_MODE_RELEASE_BUFFERS
    SSL_CTX_set_mode(context, SSL_MODE_RELEASE_BUFFERS);
#endif

#ifdef SSL_MODE_NO_AUTO_CHAIN
    SSL_CTX_set_mode(context, SSL_MODE_NO_AUTO_CHAIN);
#endif

    SSL_CTX_set_read_ahead(context, 1);
    SSL_CTX_set_info_callback(context, swSSL_info_callback);

    if (!passphrase.empty()) {
        SSL_CTX_set_default_passwd_cb_userdata(context, this);
        SSL_CTX_set_default_passwd_cb(context, ssl_passwd_callback);
    }

    if (!cert_file.empty()) {
        /*
         * set the local certificate from CertFile
         */
        if (SSL_CTX_use_certificate_file(context, cert_file.c_str(), SSL_FILETYPE_PEM) <= 0) {
            int error = ERR_get_error();
            swWarn("SSL_CTX_use_certificate_file(%s) failed, Error: %s[%d]", cert_file.c_str(),
                   ERR_reason_error_string(error), error);
            return true;
        }
        /*
         * if the crt file have many certificate entry ,means certificate chain
         * we need call this function
         */
        if (SSL_CTX_use_certificate_chain_file(context, cert_file.c_str()) <= 0) {
            int error = ERR_get_error();
            swWarn("SSL_CTX_use_certificate_chain_file(%s) failed, Error: %s[%d]", cert_file.c_str(),
                   ERR_reason_error_string(error), error);
            return false;
        }
        /*
         * set the private key from KeyFile (may be the same as CertFile)
         */
        if (SSL_CTX_use_PrivateKey_file(context, key_file.c_str(), SSL_FILETYPE_PEM) <= 0) {
            int error = ERR_get_error();
            swWarn("SSL_CTX_use_PrivateKey_file(%s) failed, Error: %s[%d]", key_file.c_str(),
                   ERR_reason_error_string(error), error);
            return false;
        }
        /*
         * verify private key
         */
        if (!SSL_CTX_check_private_key(context)) {
            swWarn("Private key does not match the public certificate");
            return false;
        }
    }

#ifdef SW_SUPPORT_DTLS
    if (protocols & SW_SSL_DTLS) {
        SSL_CTX_set_cookie_generate_cb(context, swSSL_generate_cookie);
        SSL_CTX_set_cookie_verify_cb(context, swSSL_verify_cookie);
    }
#endif

    if (verify_peer && !set_capath()) {
        return false;
    } else {
        SSL_CTX_set_verify(context, SSL_VERIFY_NONE, nullptr);
    }

#if OPENSSL_VERSION_NUMBER >= 0x10002000L
    if (http || http_v2) {
        std::string value;
        if (http_v2) {
            value = HTTP2_H2_ALPN + HTTP1_NPN;
        } else {
            value = HTTP1_NPN;
        }
#ifndef OPENSSL_NO_NEXTPROTONEG
        SSL_CTX_set_next_proto_select_cb(context, ssl_select_next_proto_cb, nullptr);
#endif
        if (SSL_CTX_set_alpn_protos(context, (const unsigned char *) value.c_str(), value.length()) < 0) {
            return false;
        }

#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
        SSL_CTX_set_alpn_select_cb(context, ssl_alpn_advertised, (void *) this);
#endif

        SSL_CTX_set_session_id_context(context, (const unsigned char *) "HTTP", sizeof("HTTP") - 1);
        SSL_CTX_set_session_cache_mode(context, SSL_SESS_CACHE_SERVER);
        SSL_CTX_sess_set_cache_size(context, 1);
    }
#endif

    if (!client_cert_file.empty() && !set_client_certificate()) {
        swWarn("set_client_certificate() error");
        return false;
    }

    if (!set_ciphers()) {
        swWarn("set_cipher() error");
        return false;
    }

    return true;
}

bool SSLContext::set_capath() {
    if (!cafile.empty() || !capath.empty()) {
        const char *_cafile = cafile.empty() ? nullptr : cafile.c_str();
        const char *_capath = capath.empty() ? nullptr : capath.c_str();
        if (!SSL_CTX_load_verify_locations(context, _cafile, _capath)) {
            return false;
        }
    } else {
        if (!SSL_CTX_set_default_verify_paths(context)) {
            swWarn("Unable to set default verify locations and no CA settings specified");
            return false;
        }
    }

    if (verify_depth > 0) {
        SSL_CTX_set_verify_depth(context, verify_depth);
    }

    return true;
}

bool SSLContext::set_ciphers() {
#ifndef TLS1_2_VERSION
    return true;
#endif

    if (!ciphers.empty()) {
        if (SSL_CTX_set_cipher_list(context, ciphers.c_str()) == 0) {
            swWarn("SSL_CTX_set_cipher_list(\"%s\") failed", ciphers.c_str());
            return false;
        }
        if (prefer_server_ciphers) {
            SSL_CTX_set_options(context, SSL_OP_CIPHER_SERVER_PREFERENCE);
        }
    }

#ifndef OPENSSL_NO_RSA
    SSL_CTX_set_tmp_rsa_callback(context, swSSL_rsa_key_callback);
#endif

    if (!dhparam.empty() && !set_dhparam()) {
        return false;
    }
#if OPENSSL_VERSION_NUMBER < 0x10100000L
    else {
        swSSL_set_default_dhparam(context);
    }
#endif
    if (!ecdh_curve.empty() && !set_ecdh_curve()) {
        return false;
    }
    return true;
}

bool SSLContext::set_client_certificate() {
    STACK_OF(X509_NAME) * list;

    const char *cert_file = client_cert_file.c_str();
    int depth = verify_depth;

    SSL_CTX_set_verify(context, SSL_VERIFY_PEER, swSSL_verify_callback);
    SSL_CTX_set_verify_depth(context, depth);

    if (SSL_CTX_load_verify_locations(context, cert_file, nullptr) == 0) {
        swWarn("SSL_CTX_load_verify_locations(\"%s\") failed", cert_file);
        return false;
    }

    ERR_clear_error();
    list = SSL_load_client_CA_file(cert_file);
    if (list == nullptr) {
        swWarn("SSL_load_client_CA_file(\"%s\") failed", cert_file);
        return false;
    }

    ERR_clear_error();
    SSL_CTX_set_client_CA_list(context, list);

    return true;
}

bool SSLContext::set_ecdh_curve() {
#ifndef OPENSSL_NO_ECDH
    /*
     * Elliptic-Curve Diffie-Hellman parameters are either "named curves"
     * from RFC 4492 section 5.1.1, or explicitly described curves over
     * binary fields.  OpenSSL only supports the "named curves", which provide
     * maximum interoperability.
     */
#if (defined SSL_CTX_set1_curves_list || defined SSL_CTRL_SET_CURVES_LIST)
    /*
     * OpenSSL 1.0.2+ allows configuring a curve list instead of a single
     * curve previously supported.  By default an internal list is used,
     * with prime256v1 being preferred by server in OpenSSL 1.0.2b+
     * and X25519 in OpenSSL 1.1.0+.
     *
     * By default a curve preferred by the client will be used for
     * key exchange.  The SSL_OP_CIPHER_SERVER_PREFERENCE option can
     * be used to prefer server curves instead, similar to what it
     * does for ciphers.
     */
    SSL_CTX_set_options(context, SSL_OP_SINGLE_ECDH_USE);
#if SSL_CTRL_SET_ECDH_AUTO
    /* not needed in OpenSSL 1.1.0+ */
    SSL_CTX_set_ecdh_auto(context, 1);
#endif
    if (strcmp(ecdh_curve.c_str(), "auto") == 0) {
        return true;
    }
    if (SSL_CTX_set1_curves_list(context, ecdh_curve.c_str()) == 0) {
        swWarn("SSL_CTX_set1_curves_list(\"%s\") failed", ecdh_curve.c_str());
        return false;
    }
#else
    EC_KEY *ecdh;
    /*
     * Elliptic-Curve Diffie-Hellman parameters are either "named curves"
     * from RFC 4492 section 5.1.1, or explicitly described curves over
     * binary fields. OpenSSL only supports the "named curves", which provide
     * maximum interoperability.
     */
    int nid = OBJ_sn2nid(ecdh_curve.c_str());
    if (nid == 0) {
        swWarn("Unknown curve name \"%s\"", ecdh_curve.c_str());
        return false;
    }

    ecdh = EC_KEY_new_by_curve_name(nid);
    if (ecdh == nullptr) {
        swWarn("Unable to create curve \"%s\"", ecdh_curve.c_str());
        return false;
    }

    SSL_CTX_set_options(context, SSL_OP_SINGLE_ECDH_USE);
    SSL_CTX_set_tmp_ecdh(context, ecdh);

    EC_KEY_free(ecdh);
#endif
#endif

    return true;
}

bool SSLContext::set_dhparam() {
    DH *dh;
    BIO *bio;

    const char *file = dhparam.c_str();

    bio = BIO_new_file(file, "r");
    if (bio == nullptr) {
        swWarn("BIO_new_file(%s) failed", file);
        return false;
    }

    dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr);
    if (dh == nullptr) {
        swWarn("PEM_read_bio_DHparams(%s) failed", file);
        BIO_free(bio);
        return false;
    }

    SSL_CTX_set_tmp_dh(context, dh);

    DH_free(dh);
    BIO_free(bio);

    return true;
}

SSLContext::~SSLContext() {
    SSL_CTX_free(context);
}

}

static int swSSL_verify_callback(int ok, X509_STORE_CTX *x509_store) {
#if 0
    char *subject, *issuer;
    int err, depth;
    X509 *cert;
    X509_NAME *sname, *iname;
    X509_STORE_CTX_get_ex_data(x509_store, SSL_get_ex_data_X509_STORE_CTX_idx());
    cert = X509_STORE_CTX_get_current_cert(x509_store);
    err = X509_STORE_CTX_get_error(x509_store);
    depth = X509_STORE_CTX_get_error_depth(x509_store);

    sname = X509_get_subject_name(cert);
    subject = sname ? X509_NAME_oneline(sname, nullptr, 0) : "(none)";

    iname = X509_get_issuer_name(cert);
    issuer = iname ? X509_NAME_oneline(iname, nullptr, 0) : "(none)";
    swWarn("verify:%d, error:%d, depth:%d, subject:\"%s\", issuer:\"%s\"", ok, err, depth, subject, issuer);

    if (sname)
    {
        OPENSSL_free(subject);
    }
    if (iname)
    {
        OPENSSL_free(issuer);
    }
#endif

    return 1;
}

#ifdef SW_SUPPORT_DTLS

#define COOKIE_SECRET_LENGTH (32)

static void calculate_cookie(SSL *ssl, uchar *cookie_secret, uint cookie_length) {
    long rv = (long) ssl;
    long inum = (cookie_length - (((long) cookie_secret) % sizeof(long))) / sizeof(long);
    long i = 0;
    long *ip = (long *) cookie_secret;
    for (i = 0; i < inum; ++i, ++ip) {
        *ip = rv;
    }
}

static int swSSL_generate_cookie(SSL *ssl, uchar *cookie, uint *cookie_len) {
    uchar *buffer, result[EVP_MAX_MD_SIZE];
    uint length = 0, result_len;
    Address sa{};

    uchar cookie_secret[COOKIE_SECRET_LENGTH];
    calculate_cookie(ssl, cookie_secret, sizeof(cookie_secret));

    /* Read peer information */
    (void) BIO_dgram_get_peer(SSL_get_wbio(ssl), &sa);

    length = 0;
    switch (sa.addr.ss.sa_family) {
    case AF_INET:
        length += sizeof(struct in_addr);
        break;
    case AF_INET6:
        length += sizeof(struct in6_addr);
        break;
    default:
        OPENSSL_assert(0);
        break;
    }

    length += sizeof(in_port_t);
    buffer = (uchar *) OPENSSL_malloc(length);

    if (buffer == nullptr) {
        swSysWarn("out of memory");
        return 0;
    }

    switch (sa.addr.ss.sa_family) {
    case AF_INET:
        memcpy(buffer, &sa.addr.inet_v4.sin_port, sizeof(in_port_t));
        memcpy(buffer + sizeof(sa.addr.inet_v4.sin_port), &sa.addr.inet_v4.sin_addr, sizeof(struct in_addr));
        break;
    case AF_INET6:
        memcpy(buffer, &sa.addr.inet_v6.sin6_port, sizeof(in_port_t));
        memcpy(buffer + sizeof(in_port_t), &sa.addr.inet_v6.sin6_addr, sizeof(struct in6_addr));
        break;
    default:
        OPENSSL_assert(0);
        break;
    }

    HMAC(EVP_sha1(), (const void *) cookie_secret, COOKIE_SECRET_LENGTH, buffer, length, result, &result_len);
    OPENSSL_free(buffer);

    memcpy(cookie, result, result_len);
    *cookie_len = result_len;

    return 1;
}

static int swSSL_verify_cookie(SSL *ssl, const uchar *cookie, uint cookie_len) {
    uint result_len = 0;
    uchar result[COOKIE_SECRET_LENGTH];

    swSSL_generate_cookie(ssl, result, &result_len);

    return cookie_len == result_len && memcmp(result, cookie, result_len) == 0;
}
#endif

#ifndef OPENSSL_NO_RSA
static RSA *swSSL_rsa_key_callback(SSL *ssl, int is_export, int key_length) {
    static RSA *rsa_tmp = nullptr;
    if (rsa_tmp) {
        return rsa_tmp;
    }

    BIGNUM *bn = BN_new();
    if (bn == nullptr) {
        swWarn("allocation error generating RSA key");
        return nullptr;
    }

    if (!BN_set_word(bn, RSA_F4) || ((rsa_tmp = RSA_new()) == nullptr) ||
        !RSA_generate_key_ex(rsa_tmp, key_length, bn, nullptr)) {
        if (rsa_tmp) {
            RSA_free(rsa_tmp);
        }
        rsa_tmp = nullptr;
    }
    BN_free(bn);
    return rsa_tmp;
}
#endif

#if OPENSSL_VERSION_NUMBER < 0x10100000L
static int swSSL_set_default_dhparam(SSL_CTX *ssl_context) {
    DH *dh;
    static unsigned char dh1024_p[] = {
        0xBB, 0xBC, 0x2D, 0xCA, 0xD8, 0x46, 0x74, 0x90, 0x7C, 0x43, 0xFC, 0xF5, 0x80, 0xE9, 0xCF, 0xDB,
        0xD9, 0x58, 0xA3, 0xF5, 0x68, 0xB4, 0x2D, 0x4B, 0x08, 0xEE, 0xD4, 0xEB, 0x0F, 0xB3, 0x50, 0x4C,
        0x6C, 0x03, 0x02, 0x76, 0xE7, 0x10, 0x80, 0x0C, 0x5C, 0xCB, 0xBA, 0xA8, 0x92, 0x26, 0x14, 0xC5,
        0xBE, 0xEC, 0xA5, 0x65, 0xA5, 0xFD, 0xF1, 0xD2, 0x87, 0xA2, 0xBC, 0x04, 0x9B, 0xE6, 0x77, 0x80,
        0x60, 0xE9, 0x1A, 0x92, 0xA7, 0x57, 0xE3, 0x04, 0x8F, 0x68, 0xB0, 0x76, 0xF7, 0xD3, 0x6C, 0xC8,
        0xF2, 0x9B, 0xA5, 0xDF, 0x81, 0xDC, 0x2C, 0xA7, 0x25, 0xEC, 0xE6, 0x62, 0x70, 0xCC, 0x9A, 0x50,
        0x35, 0xD8, 0xCE, 0xCE, 0xEF, 0x9E, 0xA0, 0x27, 0x4A, 0x63, 0xAB, 0x1E, 0x58, 0xFA, 0xFD, 0x49,
        0x88, 0xD0, 0xF6, 0x5D, 0x14, 0x67, 0x57, 0xDA, 0x07, 0x1D, 0xF0, 0x45, 0xCF, 0xE1, 0x6B, 0x9B};

    static unsigned char dh1024_g[] = {0x02};
    dh = DH_new();
    if (dh == nullptr) {
        swWarn("DH_new() failed");
        return SW_ERR;
    }

    dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), nullptr);
    dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), nullptr);

    if (dh->p == nullptr || dh->g == nullptr) {
        DH_free(dh);
    }
    SSL_CTX_set_tmp_dh(ssl_context, dh);
    DH_free(dh);
    return SW_OK;
}
#endif

#endif
