NOEXPORT void ssl_start(CLI *c) {
    int i, err;
    SSL_SESSION *sess;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
    int unsafe_openssl;
#endif /* OpenSSL version < 1.1.0 */

    c->ssl=SSL_new(c->opt->ctx);
    if(!c->ssl) {
        sslerror("SSL_new");
        throw_exception(c, 1);
    }
    /* for callbacks */
    if(!SSL_set_ex_data(c->ssl, index_ssl_cli, c)) {
        sslerror("SSL_set_ex_data");
        throw_exception(c, 1);
    }
    if(c->opt->option.client) {
#ifndef OPENSSL_NO_TLSEXT
        /* c->opt->sni should always be initialized at this point,
         * either explicitly with "sni"
         * or implicitly with "protocolHost" or "connect" */
        if(c->opt->sni && *c->opt->sni) {
            s_log(LOG_INFO, "SNI: sending servername: %s", c->opt->sni);
            if(!SSL_set_tlsext_host_name(c->ssl, c->opt->sni)) {
                sslerror("SSL_set_tlsext_host_name");
                throw_exception(c, 1);
            }
        } else { /* c->opt->sni was set to an empty value */
            s_log(LOG_INFO, "SNI: extension disabled");
        }
#endif
        session_cache_retrieve(c);
        SSL_set_fd(c->ssl, (int)c->remote_fd.fd);
        SSL_set_connect_state(c->ssl);
    } else { /* TLS server */
        if(c->local_rfd.fd==c->local_wfd.fd)
            SSL_set_fd(c->ssl, (int)c->local_rfd.fd);
        else {
           /* does it make sense to have TLS on STDIN/STDOUT? */
            SSL_set_rfd(c->ssl, (int)c->local_rfd.fd);
            SSL_set_wfd(c->ssl, (int)c->local_wfd.fd);
        }
        SSL_set_accept_state(c->ssl);
    }

    if(c->opt->option.require_cert)
        s_log(LOG_INFO, "Peer certificate required");
    else
        s_log(LOG_INFO, "Peer certificate not required");

#if OPENSSL_VERSION_NUMBER < 0x10100000L
    unsafe_openssl=OpenSSL_version_num()<0x0090810fL ||
        (OpenSSL_version_num()>=0x10000000L &&
        OpenSSL_version_num()<0x1000002fL);
#endif /* OpenSSL version < 1.1.0 */
    while(1) {
        /* critical section for OpenSSL version < 0.9.8p or 1.x.x < 1.0.0b *
         * this critical section is a crude workaround for CVE-2010-3864   *
         * see http://www.securityfocus.com/bid/44884 for details          *
         * alternative solution is to disable internal session caching     *
         * NOTE: this critical section also covers callbacks (e.g. OCSP)   */
#if OPENSSL_VERSION_NUMBER < 0x10100000L
        if(unsafe_openssl)
            CRYPTO_THREAD_write_lock(stunnel_locks[LOCK_SSL]);
#endif /* OpenSSL version < 1.1.0 */

        if(c->opt->option.client)
            i=SSL_connect(c->ssl);
        else
            i=SSL_accept(c->ssl);

#if OPENSSL_VERSION_NUMBER < 0x10100000L
        if(unsafe_openssl)
            CRYPTO_THREAD_unlock(stunnel_locks[LOCK_SSL]);
#endif /* OpenSSL version < 1.1.0 */

        err=SSL_get_error(c->ssl, i);
        if(err==SSL_ERROR_NONE)
            break; /* ok -> done */
        if(err==SSL_ERROR_WANT_READ || err==SSL_ERROR_WANT_WRITE) {
            s_poll_init(c->fds, 0);
            s_poll_add(c->fds, c->ssl_rfd->fd,
                err==SSL_ERROR_WANT_READ,
                err==SSL_ERROR_WANT_WRITE);
            switch(s_poll_wait(c->fds, c->opt->timeout_busy, 0)) {
            case -1:
                sockerror("ssl_start: s_poll_wait");
                throw_exception(c, 1);
            case 0:
                s_log(LOG_INFO, "ssl_start: s_poll_wait:"
                    " TIMEOUTbusy exceeded: sending reset");
                s_poll_dump(c->fds, LOG_DEBUG);
                throw_exception(c, 1);
            case 1:
                break; /* OK */
            default:
                s_log(LOG_ERR, "ssl_start: s_poll_wait: unknown result");
                throw_exception(c, 1);
            }
            continue; /* ok -> retry */
        }
        if(err==SSL_ERROR_SYSCALL) {
            switch(get_last_socket_error()) {
            case S_EINTR:
            case S_EWOULDBLOCK:
#if S_EAGAIN!=S_EWOULDBLOCK
            case S_EAGAIN:
#endif
                continue;
            }
        }
        if(c->opt->option.client)
            sslerror("SSL_connect");
        else
            sslerror("SSL_accept");
        throw_exception(c, 1);
    }
    print_cipher(c);
    sess=SSL_get1_session(c->ssl);
    if(sess) {
        if(SSL_session_reused(c->ssl)) {
            print_session_id(sess);
        } else { /* a new session was negotiated */
            /* SSL_SESS_CACHE_NO_INTERNAL_STORE prevented automatic caching */
            if(!c->opt->option.client)
                SSL_CTX_add_session(c->opt->ctx, sess);
        }
        SSL_SESSION_free(sess);
    }
}