/*- * SSLsplit - transparent SSL/TLS interception * https://www.roe.ch/SSLsplit * * Copyright (c) 2009-2018, Daniel Roethlisberger . * Copyright (c) 2018, Soner Tari . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "protossl.h" #include "prototcp.h" #include "protopassthrough.h" #include "pxysslshut.h" #include "cachemgr.h" #include #include #include /* * Context used for all server sessions. */ #ifdef USE_SSL_SESSION_ID_CONTEXT static unsigned long ssl_session_context = 0x31415926; #endif /* USE_SSL_SESSION_ID_CONTEXT */ void protossl_log_ssl_error(struct bufferevent *bev, UNUSED pxy_conn_ctx_t *ctx) { unsigned long sslerr; /* Can happen for socket errs, ssl errs; * may happen for unclean ssl socket shutdowns. */ sslerr = bufferevent_get_openssl_error(bev); if (!errno && !sslerr) { #if LIBEVENT_VERSION_NUMBER >= 0x02010000 /* We have disabled notification for unclean shutdowns * so this should not happen; log a warning. */ log_err_level_printf(LOG_WARNING, "Spurious error from bufferevent (errno=0,sslerr=0)\n"); #else /* LIBEVENT_VERSION_NUMBER < 0x02010000 */ /* Older versions of libevent will report these. */ if (OPTS_DEBUG(ctx->opts)) { log_dbg_printf("Unclean SSL shutdown, fd=%d\n", ctx->fd); } #endif /* LIBEVENT_VERSION_NUMBER < 0x02010000 */ } else if (ERR_GET_REASON(sslerr) == SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE) { /* these can happen due to client cert auth, * only log error if debugging is activated */ log_dbg_printf("Error from bufferevent: %i:%s %lu:%i:%s:%i:%s:%i:%s\n", errno, errno ? strerror(errno) : "-", sslerr, ERR_GET_REASON(sslerr), sslerr ? ERR_reason_error_string(sslerr) : "-", ERR_GET_LIB(sslerr), sslerr ? ERR_lib_error_string(sslerr) : "-", ERR_GET_FUNC(sslerr), sslerr ? ERR_func_error_string(sslerr) : "-"); while ((sslerr = bufferevent_get_openssl_error(bev))) { log_dbg_printf("Additional SSL error: %lu:%i:%s:%i:%s:%i:%s\n", sslerr, ERR_GET_REASON(sslerr), ERR_reason_error_string(sslerr), ERR_GET_LIB(sslerr), ERR_lib_error_string(sslerr), ERR_GET_FUNC(sslerr), ERR_func_error_string(sslerr)); } } else { /* real errors */ log_err_printf("Error from bufferevent: %i:%s %lu:%i:%s:%i:%s:%i:%s\n", errno, errno ? strerror(errno) : "-", sslerr, ERR_GET_REASON(sslerr), sslerr ? ERR_reason_error_string(sslerr) : "-", ERR_GET_LIB(sslerr), sslerr ? ERR_lib_error_string(sslerr) : "-", ERR_GET_FUNC(sslerr), sslerr ? ERR_func_error_string(sslerr) : "-"); while ((sslerr = bufferevent_get_openssl_error(bev))) { log_err_printf("Additional SSL error: %lu:%i:%s:%i:%s:%i:%s\n", sslerr, ERR_GET_REASON(sslerr), ERR_reason_error_string(sslerr), ERR_GET_LIB(sslerr), ERR_lib_error_string(sslerr), ERR_GET_FUNC(sslerr), ERR_func_error_string(sslerr)); } } } int protossl_log_masterkey(pxy_conn_ctx_t *ctx, pxy_conn_desc_t *this) { // XXX: Remove ssl check? But the caller function is called by non-ssl protos. if (this->ssl) { /* log master key */ if (ctx->opts->masterkeylog) { char *keystr; keystr = ssl_ssl_masterkey_to_str(this->ssl); if ((keystr == NULL) || (log_masterkey_print_free(keystr) == -1)) { if (errno == ENOMEM) ctx->enomem = 1; pxy_conn_term(ctx, 1); return -1; } } } return 0; } /* forward declaration of OpenSSL callbacks */ #ifndef OPENSSL_NO_TLSEXT static int protossl_ossl_servername_cb(SSL *ssl, int *al, void *arg); #endif /* !OPENSSL_NO_TLSEXT */ static int protossl_ossl_sessnew_cb(SSL *, SSL_SESSION *); static void protossl_ossl_sessremove_cb(SSL_CTX *, SSL_SESSION *); #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20800000L) static SSL_SESSION * protossl_ossl_sessget_cb(SSL *, unsigned char *, int, int *); #else /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ static SSL_SESSION * protossl_ossl_sessget_cb(SSL *, const unsigned char *, int, int *); #endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ /* * Dump information on a certificate to the debug log. */ static void protossl_debug_crt(X509 *crt) { char *sj = ssl_x509_subject(crt); if (sj) { log_dbg_printf("Subject DN: %s\n", sj); free(sj); } char *names = ssl_x509_names_to_str(crt); if (names) { log_dbg_printf("Common Names: %s\n", names); free(names); } char *fpr; if (!(fpr = ssl_x509_fingerprint(crt, 1))) { log_err_level_printf(LOG_WARNING, "Error generating X509 fingerprint\n"); } else { log_dbg_printf("Fingerprint: %s\n", fpr); free(fpr); } #ifdef DEBUG_CERTIFICATE /* dump certificate */ log_dbg_print_free(ssl_x509_to_str(crt)); log_dbg_print_free(ssl_x509_to_pem(crt)); #endif /* DEBUG_CERTIFICATE */ } /* * Called by OpenSSL when a new src SSL session is created. * OpenSSL increments the refcount before calling the callback and will * decrement it again if we return 0. Returning 1 will make OpenSSL skip * the refcount decrementing. In other words, return 0 if we did not * keep a pointer to the object (which we never do here). */ #ifdef HAVE_SSLV2 #define MAYBE_UNUSED #else /* !HAVE_SSLV2 */ #define MAYBE_UNUSED UNUSED #endif /* !HAVE_SSLV2 */ static int protossl_ossl_sessnew_cb(MAYBE_UNUSED SSL *ssl, SSL_SESSION *sess) #undef MAYBE_UNUSED { #ifdef DEBUG_SESSION_CACHE log_dbg_printf("===> OpenSSL new session callback:\n"); if (sess) { log_dbg_print_free(ssl_session_to_str(sess)); } else { log_dbg_printf("(null)\n"); } #endif /* DEBUG_SESSION_CACHE */ #ifdef HAVE_SSLV2 /* Session resumption seems to fail for SSLv2 with protocol * parsing errors, so we disable caching for SSLv2. */ if (SSL_version(ssl) == SSL2_VERSION) { log_err_level_printf(LOG_WARNING, "Session resumption denied to SSLv2" "client.\n"); return 0; } #endif /* HAVE_SSLV2 */ if (sess) { cachemgr_ssess_set(sess); } return 0; } /* * Called by OpenSSL when a src SSL session should be removed. * OpenSSL calls SSL_SESSION_free() after calling the callback; * we do not need to free the reference here. */ static void protossl_ossl_sessremove_cb(UNUSED SSL_CTX *sslctx, SSL_SESSION *sess) { #ifdef DEBUG_SESSION_CACHE log_dbg_printf("===> OpenSSL remove session callback:\n"); if (sess) { log_dbg_print_free(ssl_session_to_str(sess)); } else { log_dbg_printf("(null)\n"); } #endif /* DEBUG_SESSION_CACHE */ if (sess) { cachemgr_ssess_del(sess); } } /* * Called by OpenSSL when a src SSL session is requested by the client. */ static SSL_SESSION * #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20800000L) protossl_ossl_sessget_cb(UNUSED SSL *ssl, unsigned char *id, int idlen, int *copy) #else /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ protossl_ossl_sessget_cb(UNUSED SSL *ssl, const unsigned char *id, int idlen, int *copy) #endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ { SSL_SESSION *sess; #ifdef DEBUG_SESSION_CACHE log_dbg_printf("===> OpenSSL get session callback:\n"); #endif /* DEBUG_SESSION_CACHE */ *copy = 0; /* SSL should not increment reference count of session */ sess = cachemgr_ssess_get(id, idlen); #ifdef DEBUG_SESSION_CACHE if (sess) { log_dbg_print_free(ssl_session_to_str(sess)); } #endif /* DEBUG_SESSION_CACHE */ log_dbg_printf("SSL session cache: %s\n", sess ? "HIT" : "MISS"); return sess; } /* * Set SSL_CTX options that are the same for incoming and outgoing SSL_CTX. */ static void protossl_sslctx_setoptions(SSL_CTX *sslctx, pxy_conn_ctx_t *ctx) { SSL_CTX_set_options(sslctx, SSL_OP_ALL); #ifdef SSL_OP_TLS_ROLLBACK_BUG SSL_CTX_set_options(sslctx, SSL_OP_TLS_ROLLBACK_BUG); #endif /* SSL_OP_TLS_ROLLBACK_BUG */ #ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION SSL_CTX_set_options(sslctx, SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION); #endif /* SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION */ #ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS SSL_CTX_set_options(sslctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS); #endif /* SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS */ #ifdef SSL_OP_NO_TICKET SSL_CTX_set_options(sslctx, SSL_OP_NO_TICKET); #endif /* SSL_OP_NO_TICKET */ #ifdef SSL_OP_NO_SSLv2 #ifdef HAVE_SSLV2 if (ctx->opts->no_ssl2) { #endif /* HAVE_SSLV2 */ SSL_CTX_set_options(sslctx, SSL_OP_NO_SSLv2); #ifdef HAVE_SSLV2 } #endif /* HAVE_SSLV2 */ #endif /* !SSL_OP_NO_SSLv2 */ #ifdef HAVE_SSLV3 if (ctx->opts->no_ssl3) { SSL_CTX_set_options(sslctx, SSL_OP_NO_SSLv3); } #endif /* HAVE_SSLV3 */ #ifdef HAVE_TLSV10 if (ctx->opts->no_tls10) { SSL_CTX_set_options(sslctx, SSL_OP_NO_TLSv1); } #endif /* HAVE_TLSV10 */ #ifdef HAVE_TLSV11 if (ctx->opts->no_tls11) { SSL_CTX_set_options(sslctx, SSL_OP_NO_TLSv1_1); } #endif /* HAVE_TLSV11 */ #ifdef HAVE_TLSV12 if (ctx->opts->no_tls12) { SSL_CTX_set_options(sslctx, SSL_OP_NO_TLSv1_2); } #endif /* HAVE_TLSV12 */ #ifdef SSL_OP_NO_COMPRESSION if (!ctx->opts->sslcomp) { SSL_CTX_set_options(sslctx, SSL_OP_NO_COMPRESSION); } #endif /* SSL_OP_NO_COMPRESSION */ SSL_CTX_set_cipher_list(sslctx, ctx->opts->ciphers); } /* * Create and set up a new SSL_CTX instance for terminating SSL. * Set up all the necessary callbacks, the certificate, the cert chain and key. */ static SSL_CTX * protossl_srcsslctx_create(pxy_conn_ctx_t *ctx, X509 *crt, STACK_OF(X509) *chain, EVP_PKEY *key) { SSL_CTX *sslctx = SSL_CTX_new(ctx->opts->sslmethod()); if (!sslctx) return NULL; protossl_sslctx_setoptions(sslctx, ctx); #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(LIBRESSL_VERSION_NUMBER) if (ctx->opts->sslversion) { if (SSL_CTX_set_min_proto_version(sslctx, ctx->opts->sslversion) == 0 || SSL_CTX_set_max_proto_version(sslctx, ctx->opts->sslversion) == 0) { SSL_CTX_free(sslctx); return NULL; } } #endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ SSL_CTX_sess_set_new_cb(sslctx, protossl_ossl_sessnew_cb); SSL_CTX_sess_set_remove_cb(sslctx, protossl_ossl_sessremove_cb); SSL_CTX_sess_set_get_cb(sslctx, protossl_ossl_sessget_cb); SSL_CTX_set_session_cache_mode(sslctx, SSL_SESS_CACHE_SERVER | SSL_SESS_CACHE_NO_INTERNAL); #ifdef USE_SSL_SESSION_ID_CONTEXT SSL_CTX_set_session_id_context(sslctx, (void *)(&ssl_session_context), sizeof(ssl_session_context)); #endif /* USE_SSL_SESSION_ID_CONTEXT */ #ifndef OPENSSL_NO_TLSEXT SSL_CTX_set_tlsext_servername_callback(sslctx, protossl_ossl_servername_cb); SSL_CTX_set_tlsext_servername_arg(sslctx, ctx); #endif /* !OPENSSL_NO_TLSEXT */ #ifndef OPENSSL_NO_DH if (ctx->opts->dh) { SSL_CTX_set_tmp_dh(sslctx, ctx->opts->dh); } else { SSL_CTX_set_tmp_dh_callback(sslctx, ssl_tmp_dh_callback); } #endif /* !OPENSSL_NO_DH */ #ifndef OPENSSL_NO_ECDH if (ctx->opts->ecdhcurve) { EC_KEY *ecdh = ssl_ec_by_name(ctx->opts->ecdhcurve); SSL_CTX_set_tmp_ecdh(sslctx, ecdh); EC_KEY_free(ecdh); } else { EC_KEY *ecdh = ssl_ec_by_name(NULL); SSL_CTX_set_tmp_ecdh(sslctx, ecdh); EC_KEY_free(ecdh); } #endif /* !OPENSSL_NO_ECDH */ SSL_CTX_use_certificate(sslctx, crt); SSL_CTX_use_PrivateKey(sslctx, key); for (int i = 0; i < sk_X509_num(chain); i++) { X509 *c = sk_X509_value(chain, i); ssl_x509_refcount_inc(c); /* next call consumes a reference */ SSL_CTX_add_extra_chain_cert(sslctx, c); } #ifdef DEBUG_SESSION_CACHE if (OPTS_DEBUG(ctx->opts)) { int mode = SSL_CTX_get_session_cache_mode(sslctx); log_dbg_printf("SSL session cache mode: %08x\n", mode); if (mode == SSL_SESS_CACHE_OFF) log_dbg_printf("SSL_SESS_CACHE_OFF\n"); if (mode & SSL_SESS_CACHE_CLIENT) log_dbg_printf("SSL_SESS_CACHE_CLIENT\n"); if (mode & SSL_SESS_CACHE_SERVER) log_dbg_printf("SSL_SESS_CACHE_SERVER\n"); if (mode & SSL_SESS_CACHE_NO_AUTO_CLEAR) log_dbg_printf("SSL_SESS_CACHE_NO_AUTO_CLEAR\n"); if (mode & SSL_SESS_CACHE_NO_INTERNAL_LOOKUP) log_dbg_printf("SSL_SESS_CACHE_NO_INTERNAL_LOOKUP\n"); if (mode & SSL_SESS_CACHE_NO_INTERNAL_STORE) log_dbg_printf("SSL_SESS_CACHE_NO_INTERNAL_STORE\n"); } #endif /* DEBUG_SESSION_CACHE */ return sslctx; } static int protossl_srccert_write_to_gendir(pxy_conn_ctx_t *ctx, X509 *crt, int is_orig) { char *fn; int rv; if (!ctx->sslctx->origcrtfpr) return -1; if (is_orig) { rv = asprintf(&fn, "%s/%s.crt", ctx->opts->certgendir, ctx->sslctx->origcrtfpr); } else { if (!ctx->sslctx->usedcrtfpr) return -1; rv = asprintf(&fn, "%s/%s-%s.crt", ctx->opts->certgendir, ctx->sslctx->origcrtfpr, ctx->sslctx->usedcrtfpr); } if (rv == -1) { ctx->enomem = 1; return -1; } rv = log_cert_submit(fn, crt); free(fn); return rv; } void protossl_srccert_write(pxy_conn_ctx_t *ctx) { if (ctx->opts->certgen_writeall || ctx->sslctx->generated_cert) { if (protossl_srccert_write_to_gendir(ctx, SSL_get_certificate(ctx->src.ssl), 0) == -1) { log_err_level_printf(LOG_CRIT, "Failed to write used certificate\n"); } } if (ctx->opts->certgen_writeall) { if (protossl_srccert_write_to_gendir(ctx, ctx->sslctx->origcrt, 1) == -1) { log_err_level_printf(LOG_CRIT, "Failed to write orig certificate\n"); } } } static cert_t * protossl_srccert_create(pxy_conn_ctx_t *ctx) { cert_t *cert = NULL; if (ctx->opts->tgcrtdir) { if (ctx->sslctx->sni) { cert = cachemgr_tgcrt_get(ctx->sslctx->sni); if (!cert) { char *wildcarded; wildcarded = ssl_wildcardify(ctx->sslctx->sni); if (!wildcarded) { ctx->enomem = 1; return NULL; } cert = cachemgr_tgcrt_get(wildcarded); free(wildcarded); } if (cert && OPTS_DEBUG(ctx->opts)) { log_dbg_printf("Target cert by SNI\n"); } } else if (ctx->sslctx->origcrt) { char **names = ssl_x509_names(ctx->sslctx->origcrt); for (char **p = names; *p; p++) { if (!cert) { cert = cachemgr_tgcrt_get(*p); } if (!cert) { char *wildcarded; wildcarded = ssl_wildcardify(*p); if (!wildcarded) { ctx->enomem = 1; } else { cert = cachemgr_tgcrt_get( wildcarded); free(wildcarded); } } free(*p); } free(names); if (ctx->enomem) { return NULL; } if (cert && OPTS_DEBUG(ctx->opts)) { log_dbg_printf("Target cert by origcrt\n"); } } if (cert) { ctx->sslctx->immutable_cert = 1; } } if (!cert && ctx->sslctx->origcrt && ctx->opts->key) { cert = cert_new(); cert->crt = cachemgr_fkcrt_get(ctx->sslctx->origcrt); if (cert->crt) { if (OPTS_DEBUG(ctx->opts)) log_dbg_printf("Certificate cache: HIT\n"); } else { if (OPTS_DEBUG(ctx->opts)) log_dbg_printf("Certificate cache: MISS\n"); cert->crt = ssl_x509_forge(ctx->opts->cacrt, ctx->opts->cakey, ctx->sslctx->origcrt, ctx->opts->key, NULL, ctx->opts->crlurl); cachemgr_fkcrt_set(ctx->sslctx->origcrt, cert->crt); } cert_set_key(cert, ctx->opts->key); cert_set_chain(cert, ctx->opts->chain); ctx->sslctx->generated_cert = 1; } if ((WANT_CONNECT_LOG(ctx) || ctx->opts->certgendir) && ctx->sslctx->origcrt) { ctx->sslctx->origcrtfpr = ssl_x509_fingerprint(ctx->sslctx->origcrt, 0); if (!ctx->sslctx->origcrtfpr) ctx->enomem = 1; } if ((WANT_CONNECT_LOG(ctx) || ctx->opts->certgen_writeall) && cert && cert->crt) { ctx->sslctx->usedcrtfpr = ssl_x509_fingerprint(cert->crt, 0); if (!ctx->sslctx->usedcrtfpr) ctx->enomem = 1; } return cert; } /* * Create new SSL context for the incoming connection, based on the original * destination SSL certificate. * Returns NULL if no suitable certificate could be found. */ static SSL * protossl_srcssl_create(pxy_conn_ctx_t *ctx, SSL *origssl) { cert_t *cert; cachemgr_dsess_set((struct sockaddr*)&ctx->dstaddr, ctx->dstaddrlen, ctx->sslctx->sni, SSL_get0_session(origssl)); ctx->sslctx->origcrt = SSL_get_peer_certificate(origssl); if (OPTS_DEBUG(ctx->opts)) { if (ctx->sslctx->origcrt) { log_dbg_printf("===> Original server certificate:\n"); protossl_debug_crt(ctx->sslctx->origcrt); } else { log_dbg_printf("===> Original server has no cert!\n"); } } cert = protossl_srccert_create(ctx); if (!cert) return NULL; if (OPTS_DEBUG(ctx->opts)) { log_dbg_printf("===> Forged server certificate:\n"); protossl_debug_crt(cert->crt); } if (WANT_CONNECT_LOG(ctx)) { ctx->sslctx->ssl_names = ssl_x509_names_to_str(ctx->sslctx->origcrt ? ctx->sslctx->origcrt : cert->crt); if (!ctx->sslctx->ssl_names) ctx->enomem = 1; } SSL_CTX *sslctx = protossl_srcsslctx_create(ctx, cert->crt, cert->chain, cert->key); cert_free(cert); if (!sslctx) { ctx->enomem = 1; return NULL; } SSL *ssl = SSL_new(sslctx); SSL_CTX_free(sslctx); /* SSL_new() increments refcount */ if (!ssl) { ctx->enomem = 1; return NULL; } #ifdef SSL_MODE_RELEASE_BUFFERS /* lower memory footprint for idle connections */ SSL_set_mode(ssl, SSL_get_mode(ssl) | SSL_MODE_RELEASE_BUFFERS); #endif /* SSL_MODE_RELEASE_BUFFERS */ return ssl; } #ifndef OPENSSL_NO_TLSEXT /* * OpenSSL servername callback, called when OpenSSL receives a servername * TLS extension in the clientHello. Must switch to a new SSL_CTX with * a different certificate if we want to replace the server cert here. * We generate a new certificate if the current one does not match the * supplied servername. This should only happen if the original destination * server supplies a certificate which does not match the server name we * indicate to it. */ static int protossl_ossl_servername_cb(SSL *ssl, UNUSED int *al, void *arg) { pxy_conn_ctx_t *ctx = arg; const char *sn; X509 *sslcrt; if (!(sn = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))) return SSL_TLSEXT_ERR_NOACK; if (!ctx->sslctx->sni) { if (OPTS_DEBUG(ctx->opts)) { log_dbg_printf("Warning: SNI parser yielded no " "hostname, copying OpenSSL one: " "[NULL] != [%s]\n", sn); } ctx->sslctx->sni = strdup(sn); if (!ctx->sslctx->sni) { ctx->enomem = 1; return SSL_TLSEXT_ERR_NOACK; } } if (OPTS_DEBUG(ctx->opts)) { if (!!strcmp(sn, ctx->sslctx->sni)) { /* * This may happen if the client resumes a session, but * uses a different SNI hostname when resuming than it * used when the session was created. OpenSSL * correctly ignores the SNI in the ClientHello in this * case, but since we have already sent the SNI onwards * to the original destination, there is no way back. * We log an error and hope this never happens. */ log_dbg_printf("Warning: SNI parser yielded different " "hostname than OpenSSL callback for " "the same ClientHello message: " "[%s] != [%s]\n", ctx->sslctx->sni, sn); } } /* generate a new certificate with sn as additional altSubjectName * and replace it both in the current SSL ctx and in the cert cache */ if (ctx->opts->allow_wrong_host && !ctx->sslctx->immutable_cert && !ssl_x509_names_match((sslcrt = SSL_get_certificate(ssl)), sn)) { X509 *newcrt; SSL_CTX *newsslctx; if (OPTS_DEBUG(ctx->opts)) { log_dbg_printf("Certificate cache: UPDATE " "(SNI mismatch)\n"); } newcrt = ssl_x509_forge(ctx->opts->cacrt, ctx->opts->cakey, sslcrt, ctx->opts->key, sn, ctx->opts->crlurl); if (!newcrt) { ctx->enomem = 1; return SSL_TLSEXT_ERR_NOACK; } cachemgr_fkcrt_set(ctx->sslctx->origcrt, newcrt); ctx->sslctx->generated_cert = 1; if (OPTS_DEBUG(ctx->opts)) { log_dbg_printf("===> Updated forged server " "certificate:\n"); protossl_debug_crt(newcrt); } if (WANT_CONNECT_LOG(ctx)) { if (ctx->sslctx->ssl_names) { free(ctx->sslctx->ssl_names); } ctx->sslctx->ssl_names = ssl_x509_names_to_str(newcrt); if (!ctx->sslctx->ssl_names) { ctx->enomem = 1; } } if (WANT_CONNECT_LOG(ctx) || ctx->opts->certgendir) { if (ctx->sslctx->usedcrtfpr) { free(ctx->sslctx->usedcrtfpr); } ctx->sslctx->usedcrtfpr = ssl_x509_fingerprint(newcrt, 0); if (!ctx->sslctx->usedcrtfpr) { ctx->enomem = 1; } } newsslctx = protossl_srcsslctx_create(ctx, newcrt, ctx->opts->chain, ctx->opts->key); if (!newsslctx) { X509_free(newcrt); ctx->enomem = 1; return SSL_TLSEXT_ERR_NOACK; } SSL_set_SSL_CTX(ssl, newsslctx); /* decr's old incr new refc */ SSL_CTX_free(newsslctx); X509_free(newcrt); } else if (OPTS_DEBUG(ctx->opts)) { log_dbg_printf("Certificate cache: KEEP (SNI match or " "target mode)\n"); } return SSL_TLSEXT_ERR_OK; } #endif /* !OPENSSL_NO_TLSEXT */ /* * Create new SSL context for outgoing connections to the original destination. * If hostname sni is provided, use it for Server Name Indication. */ SSL * protossl_dstssl_create(pxy_conn_ctx_t *ctx) { SSL_CTX *sslctx; SSL *ssl; SSL_SESSION *sess; sslctx = SSL_CTX_new(ctx->opts->sslmethod()); if (!sslctx) { ctx->enomem = 1; return NULL; } protossl_sslctx_setoptions(sslctx, ctx); #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(LIBRESSL_VERSION_NUMBER) if (ctx->opts->sslversion) { if (SSL_CTX_set_min_proto_version(sslctx, ctx->opts->sslversion) == 0 || SSL_CTX_set_max_proto_version(sslctx, ctx->opts->sslversion) == 0) { SSL_CTX_free(sslctx); ctx->enomem = 1; return NULL; } } #endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ if (ctx->opts->verify_peer) { SSL_CTX_set_verify(sslctx, SSL_VERIFY_PEER, NULL); SSL_CTX_set_default_verify_paths(sslctx); } else { SSL_CTX_set_verify(sslctx, SSL_VERIFY_NONE, NULL); } if (ctx->opts->clientcrt) { if (!SSL_CTX_use_certificate(sslctx, ctx->opts->clientcrt)) log_dbg_printf("loading client certificate failed"); } if (ctx->opts->clientkey) { if (!SSL_CTX_use_PrivateKey(sslctx, ctx->opts->clientkey)) log_dbg_printf("loading client key failed"); } ssl = SSL_new(sslctx); SSL_CTX_free(sslctx); /* SSL_new() increments refcount */ if (!ssl) { ctx->enomem = 1; return NULL; } #ifndef OPENSSL_NO_TLSEXT if (ctx->sslctx->sni) { SSL_set_tlsext_host_name(ssl, ctx->sslctx->sni); } #endif /* !OPENSSL_NO_TLSEXT */ #ifdef SSL_MODE_RELEASE_BUFFERS /* lower memory footprint for idle connections */ SSL_set_mode(ssl, SSL_get_mode(ssl) | SSL_MODE_RELEASE_BUFFERS); #endif /* SSL_MODE_RELEASE_BUFFERS */ /* session resuming based on remote endpoint address and port */ sess = cachemgr_dsess_get((struct sockaddr *)&ctx->dstaddr, ctx->dstaddrlen, ctx->sslctx->sni); /* new sess inst */ if (sess) { if (OPTS_DEBUG(ctx->opts)) { log_dbg_printf("Attempt reuse dst SSL session\n"); } SSL_set_session(ssl, sess); /* increments sess refcount */ SSL_SESSION_free(sess); } return ssl; } /* * Set up a bufferevent structure for either a dst or src connection, * optionally with or without SSL. Sets all callbacks, enables read * and write events, but does not call bufferevent_socket_connect(). * * For dst connections, pass -1 as fd. Pass a pointer to an initialized * SSL struct as ssl if the connection should use SSL. * * Returns pointer to initialized bufferevent structure, as returned * by bufferevent_socket_new() or bufferevent_openssl_socket_new(). */ static struct bufferevent * NONNULL(1,3) protossl_bufferevent_setup(pxy_conn_ctx_t *ctx, evutil_socket_t fd, SSL *ssl) { #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINEST, "protossl_bufferevent_setup: ENTER, fd=%d\n", fd); #endif /* DEBUG_PROXY */ struct bufferevent *bev = bufferevent_openssl_socket_new(ctx->evbase, fd, ssl, ((fd == -1) ? BUFFEREVENT_SSL_CONNECTING : BUFFEREVENT_SSL_ACCEPTING), BEV_OPT_DEFER_CALLBACKS); if (!bev) { log_err_level_printf(LOG_CRIT, "Error creating bufferevent socket\n"); return NULL; } #if LIBEVENT_VERSION_NUMBER >= 0x02010000 #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINEST, "protossl_bufferevent_setup: bufferevent_openssl_set_allow_dirty_shutdown\n"); #endif /* DEBUG_PROXY */ /* Prevent unclean (dirty) shutdowns to cause error * events on the SSL socket bufferevent. */ bufferevent_openssl_set_allow_dirty_shutdown(bev, 1); #endif /* LIBEVENT_VERSION_NUMBER >= 0x02010000 */ // @attention Do not set callbacks here, srvdst does not set r cb //bufferevent_setcb(bev, pxy_bev_readcb, pxy_bev_writecb, pxy_bev_eventcb, ctx); // @todo Should we enable events here? //bufferevent_enable(bev, EV_READ|EV_WRITE); return bev; } static struct bufferevent * NONNULL(1,3) protossl_bufferevent_setup_child(pxy_conn_child_ctx_t *ctx, evutil_socket_t fd, SSL *ssl) { #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINEST, "protossl_bufferevent_setup_child: ENTER, fd=%d\n", fd); #endif /* DEBUG_PROXY */ struct bufferevent *bev = bufferevent_openssl_socket_new(ctx->conn->evbase, fd, ssl, ((fd == -1) ? BUFFEREVENT_SSL_CONNECTING : BUFFEREVENT_SSL_ACCEPTING), BEV_OPT_DEFER_CALLBACKS); if (!bev) { log_err_level_printf(LOG_CRIT, "Error creating bufferevent socket\n"); return NULL; } #if LIBEVENT_VERSION_NUMBER >= 0x02010000 #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINEST, "protossl_bufferevent_setup_child: bufferevent_openssl_set_allow_dirty_shutdown\n"); #endif /* DEBUG_PROXY */ /* Prevent unclean (dirty) shutdowns to cause error * events on the SSL socket bufferevent. */ bufferevent_openssl_set_allow_dirty_shutdown(bev, 1); #endif /* LIBEVENT_VERSION_NUMBER >= 0x02010000 */ bufferevent_setcb(bev, pxy_bev_readcb_child, pxy_bev_writecb_child, pxy_bev_eventcb_child, ctx); // @attention We cannot enable events here, because src events will be deferred until after dst is connected //bufferevent_enable(bev, EV_READ|EV_WRITE); return bev; } /* * Free bufferenvent and close underlying socket properly. * For OpenSSL bufferevents, this will shutdown the SSL connection. */ static void protossl_bufferevent_free_and_close_fd(struct bufferevent *bev, pxy_conn_ctx_t *ctx) { evutil_socket_t fd = bufferevent_getfd(bev); #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINER, "protossl_bufferevent_free_and_close_fd: in=%zu, out=%zu, fd=%d\n", evbuffer_get_length(bufferevent_get_input(bev)), evbuffer_get_length(bufferevent_get_output(bev)), fd); #endif /* DEBUG_PROXY */ SSL *ssl = bufferevent_openssl_get_ssl(bev); /* does not inc refc */ // @todo Do we need to NULL all cbs? // @see https://stackoverflow.com/questions/31688709/knowing-all-callbacks-have-run-with-libevent-and-bufferevent-free //bufferevent_setcb(bev, NULL, NULL, NULL, NULL); bufferevent_free(bev); /* does not free SSL unless the option BEV_OPT_CLOSE_ON_FREE was set */ pxy_ssl_shutdown(ctx->opts, ctx->evbase, ssl, fd); } void protossl_free(pxy_conn_ctx_t *ctx) { if (ctx->sslctx->ssl_names) { free(ctx->sslctx->ssl_names); } if (ctx->sslctx->origcrtfpr) { free(ctx->sslctx->origcrtfpr); } if (ctx->sslctx->usedcrtfpr) { free(ctx->sslctx->usedcrtfpr); } if (ctx->sslctx->origcrt) { X509_free(ctx->sslctx->origcrt); } if (ctx->sslctx->sni) { free(ctx->sslctx->sni); } if (ctx->sslctx->srvdst_ssl_version) { free(ctx->sslctx->srvdst_ssl_version); } if (ctx->sslctx->srvdst_ssl_cipher) { free(ctx->sslctx->srvdst_ssl_cipher); } free(ctx->sslctx); } #ifndef OPENSSL_NO_TLSEXT /* * The SNI hostname has been resolved. Fill the first resolved address into * the context and continue connecting. */ static void protossl_sni_resolve_cb(int errcode, struct evutil_addrinfo *ai, void *arg) { pxy_conn_ctx_t *ctx = arg; #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINEST, "protossl_sni_resolve_cb: ENTER, fd=%d\n", ctx->fd); #endif /* DEBUG_PROXY */ if (errcode) { log_err_printf("Cannot resolve SNI hostname '%s': %s\n", ctx->sslctx->sni, evutil_gai_strerror(errcode)); evutil_closesocket(ctx->fd); pxy_conn_ctx_free(ctx, 1); return; } memcpy(&ctx->dstaddr, ai->ai_addr, ai->ai_addrlen); ctx->dstaddrlen = ai->ai_addrlen; evutil_freeaddrinfo(ai); pxy_conn_connect(ctx); } #endif /* !OPENSSL_NO_TLSEXT */ #ifndef OPENSSL_NO_TLSEXT #define MAYBE_UNUSED #else /* OPENSSL_NO_TLSEXT */ #define MAYBE_UNUSED UNUSED #endif /* OPENSSL_NO_TLSEXT */ void protossl_fd_readcb(MAYBE_UNUSED evutil_socket_t fd, UNUSED short what, void *arg) { pxy_conn_ctx_t *ctx = arg; #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINEST, "protossl_fd_readcb: ENTER, fd=%d\n", ctx->fd); #endif /* DEBUG_PROXY */ #ifndef OPENSSL_NO_TLSEXT // Child connections will use the sni info obtained by the parent conn /* for SSL, peek ClientHello and parse SNI from it */ unsigned char buf[1024]; ssize_t n; const unsigned char *chello; int rv; n = recv(fd, buf, sizeof(buf), MSG_PEEK); if (n == -1) { log_err_printf("Error peeking on fd, aborting connection\n"); #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINE, "ERROR: Error peeking on fd, aborting connection, fd=%d\n", ctx->fd); #endif /* DEBUG_PROXY */ evutil_closesocket(fd); pxy_conn_ctx_free(ctx, 1); return; } if (n == 0) { /* socket got closed while we were waiting */ log_err_printf("Socket got closed while waiting\n"); #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINE, "ERROR: Socket got closed while waiting, fd=%d\n", ctx->fd); #endif /* DEBUG_PROXY */ evutil_closesocket(fd); pxy_conn_ctx_free(ctx, 1); return; } rv = ssl_tls_clienthello_parse(buf, n, 0, &chello, &ctx->sslctx->sni); if ((rv == 1) && !chello) { log_err_printf("Peeking did not yield a (truncated) ClientHello message, aborting connection\n"); #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINE, "ERROR: Peeking did not yield a (truncated) ClientHello message, aborting connection, fd=%d\n", ctx->fd); #endif /* DEBUG_PROXY */ evutil_closesocket(fd); pxy_conn_ctx_free(ctx, 1); return; } if (OPTS_DEBUG(ctx->opts)) { log_dbg_printf("SNI peek: [%s] [%s], fd=%d\n", ctx->sslctx->sni ? ctx->sslctx->sni : "n/a", ((rv == 1) && chello) ? "incomplete" : "complete", ctx->fd); } if ((rv == 1) && chello && (ctx->sslctx->sni_peek_retries++ < 50)) { /* ssl_tls_clienthello_parse indicates that we * should retry later when we have more data, and we * haven't reached the maximum retry count yet. * Reschedule this event as timeout-only event in * order to prevent busy looping over the read event. * Because we only peeked at the pending bytes and * never actually read them, fd is still ready for * reading now. We use 25 * 0.2 s = 5 s timeout. */ struct timeval retry_delay = {0, 100}; event_free(ctx->ev); ctx->ev = event_new(ctx->evbase, fd, 0, ctx->protoctx->fd_readcb, ctx); if (!ctx->ev) { log_err_level_printf(LOG_CRIT, "Error creating retry event, aborting connection\n"); #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINE, "ERROR: Error creating retry event, aborting connection, fd=%d\n", ctx->fd); #endif /* DEBUG_PROXY */ evutil_closesocket(fd); pxy_conn_ctx_free(ctx, 1); return; } event_add(ctx->ev, &retry_delay); return; } event_free(ctx->ev); ctx->ev = NULL; if (ctx->sslctx->sni && !ctx->dstaddrlen && ctx->spec->sni_port) { char sniport[6]; struct evutil_addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = ctx->af; hints.ai_flags = EVUTIL_AI_ADDRCONFIG; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; snprintf(sniport, sizeof(sniport), "%i", ctx->spec->sni_port); evdns_getaddrinfo(ctx->dnsbase, ctx->sslctx->sni, sniport, &hints, protossl_sni_resolve_cb, ctx); return; } #endif /* !OPENSSL_NO_TLSEXT */ pxy_conn_connect(ctx); } int protossl_setup_srvdst_ssl(pxy_conn_ctx_t *ctx) { ctx->srvdst.ssl = protossl_dstssl_create(ctx); if (!ctx->srvdst.ssl) { log_err_level_printf(LOG_CRIT, "Error creating SSL for srvdst\n"); pxy_conn_term(ctx, 1); return -1; } return 0; } static int NONNULL(1) protossl_setup_srvdst(pxy_conn_ctx_t *ctx) { if (protossl_setup_srvdst_ssl(ctx) == -1) { return -1; } ctx->srvdst.bev = protossl_bufferevent_setup(ctx, -1, ctx->srvdst.ssl); if (!ctx->srvdst.bev) { log_err_level_printf(LOG_CRIT, "Error creating srvdst\n"); SSL_free(ctx->srvdst.ssl); ctx->srvdst.ssl = NULL; pxy_conn_term(ctx, 1); return -1; } ctx->srvdst.free = protossl_bufferevent_free_and_close_fd; return 0; } int protossl_setup_srvdst_new_bev_ssl_connecting(pxy_conn_ctx_t *ctx) { ctx->srvdst.bev = bufferevent_openssl_filter_new(ctx->evbase, ctx->srvdst.bev, ctx->srvdst.ssl, BUFFEREVENT_SSL_CONNECTING, BEV_OPT_DEFER_CALLBACKS); if (!ctx->srvdst.bev) { log_err_level_printf(LOG_CRIT, "Error creating srvdst bufferevent\n"); SSL_free(ctx->srvdst.ssl); ctx->srvdst.ssl = NULL; pxy_conn_term(ctx, 1); return -1; } ctx->srvdst.free = protossl_bufferevent_free_and_close_fd; return 0; } void protossl_conn_connect(pxy_conn_ctx_t *ctx) { #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINEST, "protossl_conn_connect: ENTER, fd=%d\n", ctx->fd); #endif /* DEBUG_PROXY */ if (prototcp_setup_dst(ctx) == -1) { return; } bufferevent_setcb(ctx->dst.bev, pxy_bev_readcb, pxy_bev_writecb, pxy_bev_eventcb, ctx); bufferevent_enable(ctx->dst.bev, EV_READ|EV_WRITE); /* create server-side socket and eventbuffer */ if (protossl_setup_srvdst(ctx) == -1) { return; } // @attention Sometimes dst write cb fires but not event cb, especially if this listener cb is not finished yet, so the conn stalls. // @todo Why does event cb not fire sometimes? // @attention BEV_OPT_DEFER_CALLBACKS seems responsible for the issue with srvdst, libevent acts as if we call event connect() ourselves. // @see Launching connections on socket-based bufferevents at http://www.wangafu.net/~nickm/libevent-book/Ref6_bufferevent.html // Disable and NULL r cb, we do nothing for srvdst in r cb bufferevent_setcb(ctx->srvdst.bev, NULL, pxy_bev_writecb, pxy_bev_eventcb, ctx); bufferevent_enable(ctx->srvdst.bev, EV_WRITE); /* initiate connection */ if (bufferevent_socket_connect(ctx->srvdst.bev, (struct sockaddr *)&ctx->dstaddr, ctx->dstaddrlen) == -1) { log_err_level_printf(LOG_CRIT, "protossl_conn_connect: bufferevent_socket_connect for srvdst failed\n"); #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINE, "protossl_conn_connect: bufferevent_socket_connect for srvdst failed, fd=%d\n", ctx->fd); #endif /* DEBUG_PROXY */ // @attention Do not try to close the conn here, otherwise both pxy_conn_connect() and eventcb try to free the conn using pxy_conn_free(), // they are running on different threads, causing multithreading issues, e.g. signal 10. // @todo Should we use thrmgr->mutex? Can we? } } int protossl_setup_dst_ssl_child(pxy_conn_child_ctx_t *ctx) { // Children rely on the findings of parent ctx->dst.ssl = protossl_dstssl_create(ctx->conn); if (!ctx->dst.ssl) { log_err_level_printf(LOG_CRIT, "Error creating SSL\n"); // pxy_conn_free()>pxy_conn_free_child() will close the fd, since we have a non-NULL src.bev now pxy_conn_term(ctx->conn, 1); return -1; } return 0; } int protossl_setup_dst_child(pxy_conn_child_ctx_t *ctx) { if (protossl_setup_dst_ssl_child(ctx) == -1) { return -1; } ctx->dst.bev = protossl_bufferevent_setup_child(ctx, -1, ctx->dst.ssl); if (!ctx->dst.bev) { log_err_level_printf(LOG_CRIT, "Error creating dst bufferevent\n"); SSL_free(ctx->dst.ssl); ctx->dst.ssl = NULL; pxy_conn_term(ctx->conn, 1); return -1; } ctx->dst.free = protossl_bufferevent_free_and_close_fd; return 0; } void protossl_connect_child(pxy_conn_child_ctx_t *ctx) { #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINEST, "protossl_connect_child: ENTER, child fd=%d, fd=%d\n", ctx->fd, ctx->conn->fd); #endif /* DEBUG_PROXY */ /* create server-side socket and eventbuffer */ protossl_setup_dst_child(ctx); } int protossl_setup_src_ssl(pxy_conn_ctx_t *ctx) { // @todo Make srvdst.ssl the origssl param ctx->src.ssl = protossl_srcssl_create(ctx, ctx->srvdst.ssl); if (!ctx->src.ssl) { if (ctx->opts->passthrough && !ctx->enomem) { log_err_level_printf(LOG_WARNING, "No cert found; falling back to passthrough\n"); protopassthrough_engage(ctx); // report protocol change by returning 1 return 1; } pxy_conn_term(ctx, 1); return -1; } return 0; } static int NONNULL(1) protossl_setup_src(pxy_conn_ctx_t *ctx) { int rv; if ((rv = protossl_setup_src_ssl(ctx)) != 0) { return rv; } ctx->src.bev = protossl_bufferevent_setup(ctx, ctx->fd, ctx->src.ssl); if (!ctx->src.bev) { log_err_level_printf(LOG_CRIT, "Error creating src bufferevent\n"); SSL_free(ctx->src.ssl); ctx->src.ssl = NULL; pxy_conn_term(ctx, 1); return -1; } ctx->src.free = protossl_bufferevent_free_and_close_fd; return 0; } int protossl_setup_src_new_bev_ssl_accepting(pxy_conn_ctx_t *ctx) { ctx->src.bev = bufferevent_openssl_filter_new(ctx->evbase, ctx->src.bev, ctx->src.ssl, BUFFEREVENT_SSL_ACCEPTING, BEV_OPT_DEFER_CALLBACKS); if (!ctx->src.bev) { log_err_level_printf(LOG_CRIT, "Error creating src bufferevent\n"); SSL_free(ctx->src.ssl); ctx->src.ssl = NULL; pxy_conn_term(ctx, 1); return -1; } ctx->src.free = protossl_bufferevent_free_and_close_fd; return 0; } int protossl_setup_dst_new_bev_ssl_connecting_child(pxy_conn_child_ctx_t *ctx) { ctx->dst.bev = bufferevent_openssl_filter_new(ctx->conn->evbase, ctx->dst.bev, ctx->dst.ssl, BUFFEREVENT_SSL_CONNECTING, BEV_OPT_DEFER_CALLBACKS); if (!ctx->dst.bev) { log_err_level_printf(LOG_CRIT, "Error creating dst bufferevent\n"); SSL_free(ctx->dst.ssl); ctx->dst.ssl = NULL; pxy_conn_term(ctx->conn, 1); return -1; } ctx->dst.free = protossl_bufferevent_free_and_close_fd; return 0; } static void NONNULL(1) protossl_close_srvdst(pxy_conn_ctx_t *ctx) { #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINER, "protossl_close_srvdst: Closing srvdst, fd=%d, srvdst fd=%d\n", ctx->fd, bufferevent_getfd(ctx->srvdst.bev)); #endif /* DEBUG_PROXY */ // @attention Free the srvdst of the conn asap, we don't need it anymore, but we need its fd // So save its ssl info for logging ctx->sslctx->srvdst_ssl_version = strdup(SSL_get_version(ctx->srvdst.ssl)); ctx->sslctx->srvdst_ssl_cipher = strdup(SSL_get_cipher(ctx->srvdst.ssl)); // @attention When both eventcb and writecb for srvdst are enabled, either eventcb or writecb may get a NULL srvdst bev, causing a crash with signal 10. // So, from this point on, we should check if srvdst is NULL or not. ctx->srvdst.free(ctx->srvdst.bev, ctx); ctx->srvdst.bev = NULL; ctx->srvdst.closed = 1; } static int NONNULL(1) protossl_enable_src(pxy_conn_ctx_t *ctx) { int rv; if ((rv = protossl_setup_src(ctx)) != 0) { // Might have switched to passthrough mode return rv; } bufferevent_setcb(ctx->src.bev, pxy_bev_readcb, pxy_bev_writecb, pxy_bev_eventcb, ctx); protossl_close_srvdst(ctx); if (pxy_setup_child_listener(ctx) == -1) { return -1; } #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINER, "protossl_enable_src: Enabling src, %s, fd=%d, child_fd=%d\n", ctx->sslproxy_header, ctx->fd, ctx->child_fd); #endif /* DEBUG_PROXY */ // Now open the gates bufferevent_enable(ctx->src.bev, EV_READ|EV_WRITE); return 0; } static void NONNULL(1,2) protossl_bev_eventcb_connected_dst(UNUSED struct bufferevent *bev, pxy_conn_ctx_t *ctx) { #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINEST, "protossl_bev_eventcb_connected_dst: ENTER, fd=%d\n", ctx->fd); #endif /* DEBUG_PROXY */ ctx->dst_connected = 1; if (ctx->srvdst_connected && ctx->dst_connected && !ctx->connected) { ctx->connected = 1; if (protossl_enable_src(ctx) == -1) { return; } } } static void NONNULL(1,2) protossl_bev_eventcb_connected_srvdst(UNUSED struct bufferevent *bev, pxy_conn_ctx_t *ctx) { #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINEST, "protossl_bev_eventcb_connected_srvdst: ENTER, fd=%d\n", ctx->fd); #endif /* DEBUG_PROXY */ ctx->srvdst_connected = 1; // @attention Create and enable dst.bev before, but connect here, because we check if dst.bev is NULL elsewhere if (bufferevent_socket_connect(ctx->dst.bev, (struct sockaddr *)&ctx->spec->conn_dst_addr, ctx->spec->conn_dst_addrlen) == -1) { #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINE, "protossl_bev_eventcb_connected_srvdst: FAILED bufferevent_socket_connect for dst, fd=%d\n", ctx->fd); #endif /* DEBUG_PROXY */ pxy_conn_term(ctx, 1); return; } if (ctx->srvdst_connected && ctx->dst_connected && !ctx->connected) { ctx->connected = 1; if (protossl_enable_src(ctx) == -1) { return; } } } static void NONNULL(1,2) protossl_bev_eventcb_error_srvdst(struct bufferevent *bev, pxy_conn_ctx_t *ctx) { #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINE, "protossl_bev_eventcb_error_srvdst: BEV_EVENT_ERROR, fd=%d\n", ctx->fd); #endif /* DEBUG_PROXY */ if (!ctx->connected) { #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINE, "protossl_bev_eventcb_error_srvdst: ERROR !ctx->connected, fd=%d\n", ctx->fd); #endif /* DEBUG_PROXY */ /* the callout to the original destination failed, * e.g. because it asked for client cert auth, so * close the accepted socket and clean up */ if (ctx->opts->passthrough && bufferevent_get_openssl_error(bev)) { /* ssl callout failed, fall back to plain TCP passthrough of SSL connection */ log_err_level_printf(LOG_WARNING, "SSL srvdst connection failed; falling back to passthrough\n"); protopassthrough_engage(ctx); return; } pxy_conn_term(ctx, 0); } } static void NONNULL(1) protossl_bev_eventcb_dst(struct bufferevent *bev, short events, pxy_conn_ctx_t *ctx) { if (events & BEV_EVENT_CONNECTED) { protossl_bev_eventcb_connected_dst(bev, ctx); } else if (events & BEV_EVENT_EOF) { prototcp_bev_eventcb_eof_dst(bev, ctx); } else if (events & BEV_EVENT_ERROR) { prototcp_bev_eventcb_error_dst(bev, ctx); } } static void NONNULL(1) protossl_bev_eventcb_srvdst(struct bufferevent *bev, short events, pxy_conn_ctx_t *ctx) { if (events & BEV_EVENT_CONNECTED) { protossl_bev_eventcb_connected_srvdst(bev, ctx); } else if (events & BEV_EVENT_EOF) { prototcp_bev_eventcb_eof_srvdst(bev, ctx); } else if (events & BEV_EVENT_ERROR) { protossl_bev_eventcb_error_srvdst(bev, ctx); } } void protossl_bev_eventcb(struct bufferevent *bev, short events, void *arg) { pxy_conn_ctx_t *ctx = arg; if (events & BEV_EVENT_ERROR) { protossl_log_ssl_error(bev, ctx); } if (bev == ctx->src.bev) { prototcp_bev_eventcb_src(bev, events, ctx); } else if (bev == ctx->dst.bev) { protossl_bev_eventcb_dst(bev, events, ctx); } else if (bev == ctx->srvdst.bev) { protossl_bev_eventcb_srvdst(bev, events, ctx); } else { log_err_printf("protossl_bev_eventcb: UNKWN conn end\n"); } } void protossl_bev_eventcb_child(struct bufferevent *bev, short events, void *arg) { pxy_conn_child_ctx_t *ctx = arg; if (events & BEV_EVENT_ERROR) { protossl_log_ssl_error(bev, ctx->conn); } if (bev == ctx->src.bev) { prototcp_bev_eventcb_src_child(bev, events, ctx); } else if (bev == ctx->dst.bev) { prototcp_bev_eventcb_dst_child(bev, events, ctx); } else { log_err_printf("protossl_bev_eventcb_child: UNKWN conn end\n"); } } protocol_t protossl_setup(pxy_conn_ctx_t *ctx) { ctx->protoctx->proto = PROTO_SSL; ctx->protoctx->connectcb = protossl_conn_connect; ctx->protoctx->fd_readcb = protossl_fd_readcb; ctx->protoctx->bev_eventcb = protossl_bev_eventcb; ctx->protoctx->proto_free = protossl_free; ctx->sslctx = malloc(sizeof(ssl_ctx_t)); if (!ctx->sslctx) { free(ctx->protoctx->arg); free(ctx->protoctx); return PROTO_ERROR; } memset(ctx->sslctx, 0, sizeof(ssl_ctx_t)); return PROTO_SSL; } protocol_t protossl_setup_child(pxy_conn_child_ctx_t *ctx) { ctx->protoctx->proto = PROTO_SSL; ctx->protoctx->connectcb = protossl_connect_child; ctx->protoctx->bev_eventcb = protossl_bev_eventcb_child; return PROTO_SSL; } /* vim: set noet ft=c: */