3proxy/src/ssl.c
Vladimir Dubrovin 7b873b80b1 Event loops: sockets registered once, readiness kept until not found
A fiber waiting on a socket registered it with its loop and took it
off again on every wait: two epoll_ctl() calls a wait, some 39 for an
exchange of the mix benchmark, about an eighth of the proxy's time. A
socket is now registered once, edge-triggered for reading and writing,
until it is closed, and the loop keeps what it was reported ready for
until an operation finds it not to be: read, write, accept, connect,
splice() from a pipe and TLS tell it so through fiberblockfunc, the
close through fiberclosefunc. A wait for what a socket is known to be
ready for is answered at once, with no system call and no switch; only
with none of them ready does the fiber wait in its loop. Readiness is
trusted once: answered again with nothing reported or found since, what
was there may have been taken without its end being found, and the
system is asked with poll(). epoll and kqueue only; with poll() each
wait still registers its sockets.

What takes readability for an end rather than reading, the control
connection of a UDP association, the client waiting for a SOCKS BIND,
the server between requests of a kept-alive HTTP connection, asks
first with sockreadable(), a peek below any TLS layer.

kqueue: the end of either side, the peer's or our own shutdown, is
readable or writable, not a hang up, which poll() reports only with
both: kept, it made a wait for the answer after our shutdown return
at once, and sockmap give up with 93.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:41:47 +03:00

1363 lines
36 KiB
C

/*
(c) 2007-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
#include "structures.h"
#ifdef WITH_WOLFSSL
#include <wolfssl/options.h>
#include <wolfssl/openssl/crypto.h>
#include <wolfssl/openssl/x509.h>
#include <wolfssl/openssl/pem.h>
#include <wolfssl/openssl/ssl.h>
#include <wolfssl/openssl/err.h>
#else
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif
#include "proxy.h"
#include "ssl.h"
#ifndef _WIN32
#define WINAPI
#endif
#if defined(WITH_WOLFSSL) && !defined(SSL_set_mode)
#define SSL_set_mode(ssl,op) ((void)0)
#endif
#ifndef isnumber
#define isnumber(i_n_arg) ((i_n_arg>='0')&&(i_n_arg<='9'))
#endif
PROXYFUNC tcppmfunc, proxyfunc, smtppfunc, ftpprfunc;
static struct pluginlink * pl;
static struct alpn client_alpn_protos;
static int ssl_loaded = 0;
static char *certcache = NULL;
static char *srvcert = NULL;
static char *srvkey = NULL;
static char *clicert = NULL;
static char *clikey = NULL;
static char *server_ca_file = NULL;
static char *server_ca_dir = NULL;
static char *server_ca_store = NULL;
static char *server_ca_key = NULL;
static char *client_ca_file = NULL;
static char *client_ca_dir = NULL;
static char *client_ca_store = NULL;
static int mitm = 0;
static int serv = 0;
static int cli = 0;
static int client_min_proto_version = 0;
static int client_max_proto_version = 0;
static int server_min_proto_version = 0;
static int server_max_proto_version = 0;
static int client_verify = 0;
static int server_verify = 0;
static char * client_ciphersuites = NULL;
static char * server_ciphersuites = NULL;
static char * client_cipher_list = NULL;
static char * server_cipher_list = NULL;
static char * client_sni = NULL;
static int client_mode = 0;
static int ktls = 0;
struct SSLsock {
SOCKET s;
SSL_CONN conn;
/* what a read and a write which could not be done wait for, POLLIN
or POLLOUT, 0 once one is: TLS may have to write to read */
short rwant, wwant;
};
struct SSLstate {
struct SSLsock cli, srv;
struct clientparam* param;
SSL_CONFIG *config;
};
#define STATE ((struct SSLstate *)(state))
static struct SSLsock *searchSSL(void* state, SOCKET s){
if(!state || s == INVALID_SOCKET) return NULL;
if(STATE->cli.s == s) return &STATE->cli;
if(STATE->srv.s == s) return &STATE->srv;
return NULL;
}
#define SOSTATE ((struct SSLstate *)(param->sostate))
static void addSSL(
SOCKET cli_s, SSL_CONN cli_conn,
SOCKET srv_s, SSL_CONN srv_conn,
struct clientparam* param){
if(!param->sostate) return;
if (cli_s != INVALID_SOCKET){
SOSTATE->cli.s = cli_s;
SOSTATE->cli.conn = cli_conn;
SOSTATE->cli.rwant = SOSTATE->cli.wwant = 0;
}
if (srv_s != INVALID_SOCKET){
SOSTATE->srv.s = srv_s;
SOSTATE->srv.conn = srv_conn;
SOSTATE->srv.rwant = SOSTATE->srv.wwant = 0;
}
}
void delSSL(void *state, SOCKET s){
if(!state || s == INVALID_SOCKET) return;
if(STATE->cli.s == s && STATE->cli.conn) {
ssl_conn_free(STATE->cli.conn);
STATE->cli.conn = NULL;
STATE->cli.s = INVALID_SOCKET;
}
else if(STATE->srv.s == s && STATE->srv.conn) {
ssl_conn_free(STATE->srv.conn);
STATE->srv.conn = NULL;
STATE->srv.s = INVALID_SOCKET;
}
}
struct sockfuncs sso;
#ifdef _WIN32
static int WINAPI ssl_send(void *state, SOCKET s, const char *msg, int len, int flags){
#else
static ssize_t ssl_send(void *state, SOCKET s, const void *msg, size_t len, int flags){
#endif
struct SSLsock *sslq;
if ((sslq = searchSSL(state, s))){
int res, err;
if((res = ssl_write(sslq->conn, (void *)msg, len)) <= 0){
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ){
sslq->wwant = (err == SSL_ERROR_WANT_READ)? POLLIN : POLLOUT;
/* TLS found the socket not ready, past the socket functions */
if(fiberblockfunc) fiberblockfunc(s, sslq->wwant);
seterrno3(EAGAIN);
return -1;
}
else seterrno3(err);
}
else sslq->wwant = 0;
return res;
}
return sso._send(sso.state, s, msg, len, flags);
}
#ifdef _WIN32
static int WINAPI ssl_sendto(void *state, SOCKET s, const char *msg, int len, int flags, const struct sockaddr *to, int tolen){
#else
static ssize_t ssl_sendto(void *state, SOCKET s, const void *msg, size_t len, int flags, const struct sockaddr *to, SASIZETYPE tolen){
#endif
struct SSLsock *sslq;
if ((sslq = searchSSL(state, s))){
int res, err;
if((res = ssl_write(sslq->conn, (void *)msg, len)) <= 0){
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ){
sslq->wwant = (err == SSL_ERROR_WANT_READ)? POLLIN : POLLOUT;
/* TLS found the socket not ready, past the socket functions */
if(fiberblockfunc) fiberblockfunc(s, sslq->wwant);
seterrno3(EAGAIN);
return -1;
}
else seterrno3(err);
}
else sslq->wwant = 0;
return res;
}
return sso._sendto(sso.state, s, msg, len, flags, to, tolen);
}
#ifdef _WIN32
static int WINAPI ssl_recvfrom(void *state, SOCKET s, char *msg, int len, int flags, struct sockaddr *from, int *fromlen){
#else
static ssize_t ssl_recvfrom(void *state, SOCKET s, void *msg, size_t len, int flags, struct sockaddr *from, SASIZETYPE *fromlen){
#endif
struct SSLsock *sslq;
if ((sslq = searchSSL(state, s))){
int res, err;
if((res = ssl_read(sslq->conn, (void *)msg, len)) <= 0) {
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
sslq->rwant = (err == SSL_ERROR_WANT_WRITE)? POLLOUT : POLLIN;
/* TLS found the socket not ready, past the socket functions */
if(fiberblockfunc) fiberblockfunc(s, sslq->rwant);
seterrno3(EAGAIN);
return -1;
}
else seterrno3(err);
}
else sslq->rwant = 0;
return res;
}
return sso._recvfrom(sso.state, s, msg, len, flags, from, fromlen);
}
#ifdef _WIN32
static int WINAPI ssl_recv(void *state, SOCKET s, char *msg, int len, int flags){
#else
static ssize_t ssl_recv(void *state, SOCKET s, void *msg, size_t len, int flags){
#endif
struct SSLsock *sslq;
if ((sslq = searchSSL(state,s))){
int res, err;
if((res = ssl_read(sslq->conn, (void *)msg, len)) <= 0) {
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
sslq->rwant = (err == SSL_ERROR_WANT_WRITE)? POLLOUT : POLLIN;
/* TLS found the socket not ready, past the socket functions */
if(fiberblockfunc) fiberblockfunc(s, sslq->rwant);
seterrno3(EAGAIN);
return -1;
}
else seterrno3(err);
}
else sslq->rwant = 0;
return res;
}
return sso._recv(sso.state, s, msg, len, flags);
}
static int WINAPI ssl_shutdown(void *state, SOCKET s, int how){
delSSL(state, s);
return sso._shutdown(sso.state, s, how);
}
static int WINAPI ssl_closesocket(void *state, SOCKET s){
delSSL(state, s);
return sso._closesocket(sso.state, s);
}
/* Data already decrypted makes its socket readable. A read or a write TLS
could not do has its socket waited for as TLS needs it. */
#ifdef _WIN32
static int WINAPI ssl_prepoll(void *state, struct pollfd *fds, unsigned int nfds){
#else
static int ssl_prepoll(void *state, struct pollfd *fds, nfds_t nfds){
#endif
struct SSLsock *sslq = NULL;
unsigned int i;
int ret = 0;
short events;
for(i = 0; i < nfds; i++){
if((fds[i].events & POLLIN) && (sslq = searchSSL(state, fds[i].fd)) && ssl_pending(sslq->conn)){
fds[i].revents = POLLIN;
ret++;
}
else fds[i].revents = 0;
}
if(ret) return ret;
for(i = 0; i < nfds; i++){
if(!(sslq = searchSSL(state, fds[i].fd))) continue;
events = fds[i].events & ~(POLLIN|POLLOUT);
if(fds[i].events & POLLIN) events |= sslq->rwant? sslq->rwant : POLLIN;
if(fds[i].events & POLLOUT) events |= sslq->wwant? sslq->wwant : POLLOUT;
fds[i].events = events;
}
return sso._prepoll(sso.state, fds, nfds);
}
SSL_CONN ssl_handshake_to_server(SOCKET s, char * hostname, SSL_CONFIG *config, SSL_CERT *server_cert, char **errSSL)
{
int err = 0;
ssl_conn *conn;
*errSSL = NULL;
conn = (ssl_conn *)malloc(sizeof(ssl_conn));
if ( conn == NULL ){
return NULL;
}
conn->ctx = NULL;
conn->ssl = SSL_new(config->srv_ctx);
if ( conn->ssl == NULL ) {
free(conn);
return NULL;
}
if(hostname && *hostname && config->client_verify){
X509_VERIFY_PARAM *param;
param = SSL_get0_param(conn->ssl);
X509_VERIFY_PARAM_set1_host(param, hostname, strlen(hostname));
}
if(!SSL_set_fd(conn->ssl, s)){
ssl_conn_free(conn);
*errSSL = getSSLErr();
return NULL;
}
#if OPENSSL_VERSION_NUMBER >= 0x0090806fL
if(hostname && *hostname)SSL_set_tlsext_host_name(conn->ssl, hostname);
#endif
do {
struct pollfd fds[1] = {{INVALID_SOCKET}};
int sslerr;
err = SSL_connect(conn->ssl);
if (err != -1) break;
sslerr = SSL_get_error(conn->ssl, err);
if(sslerr == SSL_ERROR_WANT_READ){
fds[0].fd = s;
fds[0].events = POLLIN;
if(fiberblockfunc) fiberblockfunc(s, POLLIN);
}
else if(sslerr == SSL_ERROR_WANT_WRITE){
fds[0].fd = s;
fds[0].events = POLLOUT;
if(fiberblockfunc) fiberblockfunc(s, POLLOUT);
}
else break;
if(sockpoll(&sso, sso.state, fds, 1, config->timeout*1000) <= 0 || !(fds[0].revents & (POLLOUT|POLLIN))) break;
} while (err == -1);
if ( err != 1 ) {
*errSSL = getSSLErr();
ssl_conn_free(conn);
return NULL;
}
if(server_cert){
X509 *cert;
cert = SSL_get_peer_certificate(conn->ssl);
if(!cert) {
ssl_conn_free(conn);
return NULL;
}
*server_cert = cert;
}
return conn;
}
SSL_CONN ssl_handshake_to_client(SOCKET s, SSL_CONFIG *config, X509 *server_cert, EVP_PKEY *server_key, char** errSSL){
int err = 0;
X509 *cert;
ssl_conn *conn;
*errSSL = NULL;
conn = (ssl_conn *)malloc(sizeof(ssl_conn));
if ( conn == NULL )
return NULL;
conn->ctx = NULL;
conn->ssl = NULL;
if(!config->cli_ctx){
conn->ctx = ssl_cli_ctx(config, server_cert, server_key, errSSL);
if(!conn->ctx){
ssl_conn_free(conn);
return NULL;
}
}
conn->ssl = SSL_new(config->cli_ctx?config->cli_ctx : conn->ctx);
if ( conn->ssl == NULL ) {
*errSSL = getSSLErr();
if(conn->ctx)SSL_CTX_free(conn->ctx);
free(conn);
return NULL;
}
if(!SSL_set_fd(conn->ssl, s)){
*errSSL = getSSLErr();
ssl_conn_free(conn);
return NULL;
}
do {
struct pollfd fds[1] = {{INVALID_SOCKET}};
int sslerr;
err = SSL_accept(conn->ssl);
if (err != -1) break;
sslerr = SSL_get_error(conn->ssl, err);
if(sslerr == SSL_ERROR_WANT_READ){
fds[0].fd = s;
fds[0].events = POLLIN;
if(fiberblockfunc) fiberblockfunc(s, POLLIN);
}
else if(sslerr == SSL_ERROR_WANT_WRITE){
fds[0].fd = s;
fds[0].events = POLLOUT;
if(fiberblockfunc) fiberblockfunc(s, POLLOUT);
}
else break;
if(sockpoll(&sso, sso.state, fds, 1, config->timeout*1000) <= 0 || !(fds[0].revents & (POLLOUT|POLLIN))) break;
} while (err == -1);
if ( err != 1 ) {
*errSSL = getSSLErr();
ssl_conn_free(conn);
return NULL;
}
cert = SSL_get_peer_certificate(conn->ssl);
if ( cert != NULL )
X509_free(cert);
return conn;
}
#define PCONF (((struct SSLstate *)param->sostate)->config)
/* After a handshake: read ahead, unless the kernel took receiving (kernel TLS
reads a record at a time), and say once for the service whether it took
either direction, which depends on the library, the kernel and the cipher. */
static void ssl_after_handshake(struct clientparam* param, SSL_CONN conn){
SSL *ssl = ((ssl_conn *)conn)->ssl;
int ksend = 0, krecv = 0;
SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_AUTO_RETRY);
#if defined(SSL_OP_ENABLE_KTLS) && defined(BIO_get_ktls_send) && defined(BIO_get_ktls_recv)
if(PCONF->ktls){
ksend = BIO_get_ktls_send(SSL_get_wbio(ssl));
krecv = BIO_get_ktls_recv(SSL_get_rbio(ssl));
if(!PCONF->ktls_reported){
char buf[96];
PCONF->ktls_reported = 1;
sprintf(buf, "kernel TLS: sending %s, receiving %s",
ksend? "in the kernel" : "in 3proxy", krecv? "in the kernel" : "in 3proxy");
param->srv->logfunc(param, (unsigned char *)buf);
}
}
#endif
if(!krecv) SSL_set_read_ahead(ssl, SSL_READAHEAD);
/* splice() passes the data of a TLS socket only where the kernel does TLS
for it both ways */
if(!ksend || !krecv) param->nosplice = 1;
}
#ifdef WITHSPLICE
/* Whether TLS handles the socket, and whether it has read data for it that
it has not handed out, which a splice() from the socket would not see. */
static int ssl_splicetls(struct clientparam *param, SOCKET s){
struct SSLsock *sslq = searchSSL(param->sostate, s);
if(!sslq) return 0;
return ssl_buffered(sslq->conn)? SPLICE_TLSPENDING : SPLICE_TLS;
}
#endif
SSL_CONN dosrvcon(struct clientparam* param, SSL_CERT* cert){
SSL_CONN ServerConn;
char *errSSL=NULL;
ServerConn = ssl_handshake_to_server(param->remsock, (char *)param->hostname, PCONF, cert, &errSSL);
if ( ServerConn == NULL) {
if(ServerConn) ssl_conn_free(ServerConn);
param->res = 8011;
param->srv->logfunc(param, (unsigned char *)"SSL handshake to server failed");
if(ServerConn == NULL) param->srv->logfunc(param, (unsigned char *)"ServerConn is NULL");
if(cert && *cert == NULL) param->srv->logfunc(param, (unsigned char *)"ServerCert is NULL");
if(errSSL)param->srv->logfunc(param, (unsigned char *)errSSL);
return NULL;
}
ssl_after_handshake(param, ServerConn);
return ServerConn;
}
int domitm(struct clientparam* param){
SSL_CERT ServerCert=NULL, FakeCert=NULL;
SSL_CONN ServerConn, ClientConn;
char *errSSL=NULL;
ServerConn = dosrvcon(param, &ServerCert);
if(!ServerConn) return 1;
FakeCert = ssl_copy_cert(ServerCert, PCONF);
_ssl_cert_free(ServerCert);
if ( FakeCert == NULL ) {
param->res = 8012;
param->srv->logfunc(param, (unsigned char *)"Failed to create certificate copy");
ssl_conn_free(ServerConn);
return 2;
}
ClientConn = ssl_handshake_to_client(param->clisock, PCONF, FakeCert, PCONF->server_key?PCONF->server_key:PCONF->CA_key, &errSSL);
_ssl_cert_free(FakeCert);
if ( ClientConn == NULL ) {
param->res = 8012;
param->srv->logfunc(param, (unsigned char *)"Handshake to client failed");
if(errSSL)param->srv->logfunc(param, (unsigned char *)errSSL);
ssl_conn_free(ServerConn);
return 3;
}
ssl_after_handshake(param, ClientConn);
addSSL(param->clisock, ClientConn, param->remsock, ServerConn, param);
return 0;
}
int docli(struct clientparam* param){
SSL_CONN ServerConn;
SSL_CERT ServerCert=NULL;
unsigned char hostname[HOSTNAMELEN];
/* the name given to the server is the configured one, if any */
memcpy(hostname, param->hostname, sizeof(hostname));
if(PARAMSTR(param, hostname, PCONF->client_sni)) *param->hostname = 0;
ServerConn = dosrvcon(param, &ServerCert);
memcpy(param->hostname, hostname, sizeof(hostname));
_ssl_cert_free(ServerCert);
if(!ServerConn) return 1;
addSSL(INVALID_SOCKET, NULL, param->remsock, ServerConn, param);
return 0;
}
X509 * getCert (const char *fname){
BIO *f;
X509 *CA_cert;
f = BIO_new_file(fname, "r");
if(!f) return NULL;
CA_cert=PEM_read_bio_X509(f, NULL, NULL, NULL);
BIO_free(f);
return CA_cert;
}
EVP_PKEY * getKey(const char *fname){
BIO *f;
EVP_PKEY *key;
f = BIO_new_file(fname, "r");
if(!f) return NULL;
key = PEM_read_bio_PrivateKey(f, NULL, NULL, NULL);
BIO_free(f);
return key;
}
#ifdef WITH_WOLFSSL
/* wolfSSL's SSL_CTX_use_PrivateKey(EVP_PKEY*) compat is unreliable: it
* silently fails (returns 0, no error queued) for keys loaded via
* PEM_read_bio_PrivateKey. Export the key to DER and load via the
* native buffer API instead. */
static int ssl_ctx_use_pkey(SSL_CTX *ctx, EVP_PKEY *key){
unsigned char *der = NULL;
int len, rc;
len = i2d_PrivateKey(key, &der);
if(len <= 0 || !der) return 0;
rc = wolfSSL_CTX_use_PrivateKey_buffer(ctx, der, (long)len, SSL_FILETYPE_ASN1);
OPENSSL_free(der);
return rc;
}
#else
#define ssl_ctx_use_pkey(ctx, key) SSL_CTX_use_PrivateKey((ctx), (key))
#endif
SSL_CTX * ssl_cli_ctx(SSL_CONFIG *config, X509 *server_cert, EVP_PKEY *server_key, char** errSSL){
SSL_CTX *ctx;
int err = 0;
#if !defined(WITH_WOLFSSL) && OPENSSL_VERSION_NUMBER < 0x10100000L
ctx = SSL_CTX_new(SSLv23_server_method());
#else
ctx = SSL_CTX_new(TLS_server_method());
#endif
if (!ctx) {
*errSSL = getSSLErr();
return NULL;
}
if(server_cert) {
err = SSL_CTX_use_certificate(ctx, (X509 *) server_cert);
if ( err <= 0 ) {
*errSSL = getSSLErr();
SSL_CTX_free(ctx);
return NULL;
}
}
/* Load the private key only when a cert is already loaded into ctx.
* wolfSSL validates the key against the cert and requires the cert
* to be present first; OpenSSL tolerates either order but is happy
* with cert-first too. When server_cert is NULL (serv path), the
* caller loads the cert chain and the key itself after this call. */
if(server_cert) {
err = ssl_ctx_use_pkey(ctx, server_key);
if ( err <= 0 ) {
*errSSL = getSSLErr();
SSL_CTX_free(ctx);
return NULL;
}
}
SSL_CTX_set_session_id_context(ctx, (const unsigned char *)"3proxy", 6);
#ifdef SSL_OP_ENABLE_KTLS
if(config->ktls) SSL_CTX_set_options(ctx, SSL_OP_ENABLE_KTLS);
#endif
if(config->server_min_proto_version)SSL_CTX_set_min_proto_version(ctx, config->server_min_proto_version);
if(config->server_max_proto_version)SSL_CTX_set_max_proto_version(ctx, config->server_max_proto_version);
if(config->server_cipher_list)SSL_CTX_set_cipher_list(ctx, config->server_cipher_list);
#if !defined(WITH_WOLFSSL) && OPENSSL_VERSION_NUMBER >= 0x10101000L
if(config->server_ciphersuites)SSL_CTX_set_ciphersuites(ctx, config->server_ciphersuites);
#elif defined(WITH_WOLFSSL)
/* wolfSSL has no separate TLS1.3 ciphersuites API; route to cipher list. */
if(config->server_ciphersuites)wolfSSL_CTX_set_cipher_list(ctx, config->server_ciphersuites);
#endif
if(config->server_verify){
if(config->server_ca_file || config->server_ca_dir){
SSL_CTX_load_verify_locations(ctx, config->server_ca_file, config->server_ca_dir);
}
#if !defined(WITH_WOLFSSL) && OPENSSL_VERSION_NUMBER >= 0x30000000L
else if(config->server_ca_store){
SSL_CTX_load_verify_store(ctx, config->server_ca_store);
}
#endif
else
SSL_CTX_set_default_verify_paths(ctx);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT|SSL_VERIFY_CLIENT_ONCE, NULL);
}
return ctx;
}
static void* ssl_filter_open(void * idata, struct srvparam * srv){
char fname[256];
char *errSSL;
struct ssl_config *sc;
sc = malloc(sizeof(struct ssl_config));
if(!sc) return NULL;
memset(sc, 0, sizeof(struct ssl_config));
if(certcache) sc->certcache = strdup(certcache);
sc->client_min_proto_version = client_min_proto_version;
sc->client_max_proto_version = client_max_proto_version;
sc->server_min_proto_version = server_min_proto_version;
sc->server_max_proto_version = server_max_proto_version;
sc->client_mode = client_mode;
sc->ktls = ktls;
sc->timeout = srv->timeouts[CONNECT_TO];
sc->client_verify = client_verify;
sc->server_verify = server_verify;
if(client_ciphersuites) sc->client_ciphersuites = strdup(client_ciphersuites);
if(server_ciphersuites) sc->server_ciphersuites = strdup(server_ciphersuites);
if(client_cipher_list) sc->client_cipher_list = strdup(client_cipher_list);
if(server_cipher_list) sc->server_cipher_list = strdup(server_cipher_list);
if(srvkey){
sc->server_key = getKey(srvkey);
if(!sc->server_key){
fprintf(stderr, "failed to read: %s\n", srvkey);
return sc;
}
}
if(clikey){
sc->client_key = getKey(clikey);
if(!sc->client_key){
fprintf(stderr, "failed to read: %s\n", clikey);
return sc;
}
}
if(client_ca_file)sc->client_ca_file =strdup(client_ca_file);
if(client_ca_dir)sc->client_ca_dir = strdup(client_ca_dir);
if(client_ca_store)sc->client_ca_store = strdup(client_ca_store);
if(server_ca_file)sc->server_ca_file = strdup(server_ca_file);
if(server_ca_dir)sc->server_ca_dir = strdup(server_ca_dir);
if(server_ca_store)sc->server_ca_store = strdup(server_ca_store);
if(client_sni)sc->client_sni=strdup(client_sni);
if(client_alpn_protos.protos_len){
sc->client_alpn_protos = client_alpn_protos;
sc->client_alpn_protos.protos = malloc(client_alpn_protos.protos_len);
if(!sc->client_alpn_protos.protos) sc->client_alpn_protos.protos_len = 0;
else memcpy(sc->client_alpn_protos.protos, client_alpn_protos.protos, client_alpn_protos.protos_len);
}
if(mitm){
if(!server_ca_file){
if(!certcache) {
return sc;
}
sprintf(fname, "%.240s3proxy.pem", certcache);
}
sc->CA_cert = getCert(server_ca_file?server_ca_file:fname);
if(!sc->CA_cert){
fprintf(stderr, "failed to read: %s\n", server_ca_file?server_ca_file:fname);
return sc;
}
if(!server_ca_key){
if(!certcache) {
return sc;
}
sprintf(fname, "%.240s3proxy.key", sc->certcache);
}
sc->CA_key = getKey(server_ca_key?server_ca_key:fname);
if(!sc->CA_key){
fprintf(stderr, "failed to read: %s\n", server_ca_key?server_ca_key:fname);
return sc;
}
if(!sc->server_key && sc->certcache){
sprintf(fname, "%.128sserver.key", sc->certcache);
sc->server_key = getKey(fname);
}
sc->mitm = 1;
}
if(cli){
if(clicert){
sc->client_cert = getCert(clicert);
if(!sc->client_cert){
fprintf(stderr, "failed to read client cert from: %s\n", clicert);
return sc;
}
if(!sc->client_key){
fprintf(stderr, "no client key\n");
return sc;
}
}
sc->cli = 1;
}
if(serv){
if(!srvcert || !srvkey) return sc;
if(!sc->server_key){
return sc;
}
if(!(sc->cli_ctx = ssl_cli_ctx(sc, NULL, sc->server_key, &errSSL))){
fprintf(stderr, "failed to create context: %s\n", errSSL);
return sc;
}
if(SSL_CTX_use_certificate_chain_file(sc->cli_ctx, srvcert) != 1){
fprintf(stderr, "failed to read server cert: %s\n", srvcert);
return sc;
}
if(ssl_ctx_use_pkey(sc->cli_ctx, sc->server_key) <= 0){
fprintf(stderr, "failed to use server key\n");
return sc;
}
sc->serv = 1;
}
if(mitm || cli || serv){
srv->so._send = ssl_send;
srv->so._recv = ssl_recv;
srv->so._sendto = ssl_sendto;
srv->so._recvfrom = ssl_recvfrom;
srv->so._shutdown = ssl_shutdown;
srv->so._closesocket = ssl_closesocket;
srv->so._prepoll = ssl_prepoll;
}
if(sc && (sc->mitm || sc->cli)){
#if !defined(WITH_WOLFSSL) && OPENSSL_VERSION_NUMBER < 0x10100000L
sc->srv_ctx = SSL_CTX_new(SSLv23_client_method());
#else
sc->srv_ctx = SSL_CTX_new(TLS_client_method());
#endif
if ( sc->srv_ctx == NULL ) {
fprintf(stderr, "failed to set client context\n");
sc->mitm = sc->cli = 0;
}
#ifdef SSL_OP_ENABLE_KTLS
if(sc->srv_ctx && sc->ktls) SSL_CTX_set_options(sc->srv_ctx, SSL_OP_ENABLE_KTLS);
#endif
if(sc->client_cert){
SSL_CTX_use_certificate(sc->srv_ctx, (X509 *) sc->client_cert);
ssl_ctx_use_pkey(sc->srv_ctx, sc->client_key);
}
if(sc->client_min_proto_version)SSL_CTX_set_min_proto_version(sc->srv_ctx, sc->client_min_proto_version);
if(sc->client_max_proto_version)SSL_CTX_set_max_proto_version(sc->srv_ctx, sc->client_max_proto_version);
if(sc->client_cipher_list)SSL_CTX_set_cipher_list(sc->srv_ctx, sc->client_cipher_list);
#if !defined(WITH_WOLFSSL) && OPENSSL_VERSION_NUMBER >= 0x10101000L
if(sc->client_ciphersuites)SSL_CTX_set_ciphersuites(sc->srv_ctx, sc->client_ciphersuites);
#elif defined(WITH_WOLFSSL)
/* wolfSSL has no separate TLS1.3 ciphersuites API; route to cipher list. */
if(sc->client_ciphersuites)wolfSSL_CTX_set_cipher_list(sc->srv_ctx, sc->client_ciphersuites);
#endif
#if (defined(WITH_WOLFSSL) && defined(HAVE_ALPN) && LIBWOLFSSL_VERSION_HEX >= 0x03007000) || (!defined(WITH_WOLFSSL) && OPENSSL_VERSION_NUMBER >= 0x10200000L)
if(sc->client_alpn_protos.protos_len)SSL_CTX_set_alpn_protos(sc->srv_ctx, sc->client_alpn_protos.protos, sc->client_alpn_protos.protos_len);
#endif
if(sc->client_verify){
if(sc->client_ca_file || sc->client_ca_dir){
SSL_CTX_load_verify_locations(sc->srv_ctx, sc->client_ca_file, sc->client_ca_dir);
}
#if !defined(WITH_WOLFSSL) && OPENSSL_VERSION_NUMBER >= 0x30000000L
else if(sc->client_ca_store){
SSL_CTX_load_verify_store(sc->srv_ctx, sc->client_ca_store);
}
#endif
else
SSL_CTX_set_default_verify_paths(sc->srv_ctx);
SSL_CTX_set_verify(sc->srv_ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
}
#ifdef WITH_WOLFSSL
else {
/* wolfSSL defaults to peer verification; OpenSSL defaults to
* SSL_VERIFY_NONE. Make the no-verify intent explicit so the
* upstream handshake (ssl_cli / ssl_mitm) succeeds without a
* trusted CA store. */
SSL_CTX_set_verify(sc->srv_ctx, SSL_VERIFY_NONE, NULL);
}
#endif
}
#ifdef WITHSPLICE
/* the data of a TLS connection passes through 3proxy, unless the kernel
does TLS for it: then each connection which got it both ways may
splice() */
#ifdef SSL_OP_ENABLE_KTLS
if(!sc || !sc->ktls)
#endif
srv->usesplice = 0;
#endif
return sc;
}
static FILTER_ACTION ssl_filter_client(void *fo, struct clientparam * param, void** fc){
struct SSLstate *ssls;
ssls = (struct SSLstate *) malloc(sizeof(struct SSLstate));
memset(ssls, 0, sizeof(struct SSLstate));
ssls->config = fo;
ssls->param = param;
param->sostate = ssls;
*fc = ssls;
if(ssls->config->serv){
SSL_CONN ClientConn;
char *err;
ClientConn = ssl_handshake_to_client(param->clisock, ssls->config, NULL, NULL, &err);
if ( ClientConn == NULL ) {
param->res = 8013;
param->srv->logfunc(param, (unsigned char *)"Handshake to client failed");
if(err)param->srv->logfunc(param, (unsigned char *)err);
return REJECT;
}
ssl_after_handshake(param, ClientConn);
addSSL(param->clisock, ClientConn, INVALID_SOCKET, NULL, param);
}
return CONTINUE;
}
static FILTER_ACTION ssl_filter_connect(void *fc, struct clientparam * param){
if(PCONF->cli && !PCONF->client_mode) {
if(docli(param)) {
return REJECT;
}
}
return PASS;
}
static FILTER_ACTION ssl_filter_afterauth(void *fc, struct clientparam * param){
if(PCONF->cli && PCONF->client_mode == 1) {
if(docli(param)) {
return REJECT;
}
}
return PASS;
}
static FILTER_ACTION ssl_filter_predata(void *fc, struct clientparam * param){
if(param->operation != HTTP_CONNECT && param->operation != CONNECT) return PASS;
if(PCONF->mitm){
if(domitm(param)) {
return REJECT;
}
if(!param->redirectfunc) param->redirectfunc = proxyfunc;
return CONTINUE;
}
else if(PCONF->cli && PCONF->client_mode == 2) {
if(docli(param)) {
return REJECT;
}
}
return PASS;
}
static FILTER_ACTION ssl_parent(struct clientparam * param){
if(PCONF->cli && PCONF->client_mode == 3) {
if(docli(param)) {
return REJECT;
}
}
return PASS;
}
static void ssl_filter_clear(void *state){
struct clientparam *param;
if(!state) return;
param = STATE->param;
free(state);
param->sostate = NULL;
}
#define CONFIG ((SSL_CONFIG *)fo)
static void ssl_filter_close(void *fo){
free(CONFIG->certcache);
if ( CONFIG->CA_cert != NULL ) {
X509_free(CONFIG->CA_cert);
}
if ( CONFIG->server_cert != NULL ) {
X509_free(CONFIG->server_cert);
}
if ( CONFIG->client_cert != NULL ) {
X509_free(CONFIG->client_cert);
}
if ( CONFIG->CA_key != NULL ) {
EVP_PKEY_free(CONFIG->CA_key);
}
if ( CONFIG->server_key != NULL ) {
EVP_PKEY_free(CONFIG->server_key);
}
if ( CONFIG->client_key != NULL ) {
EVP_PKEY_free(CONFIG->client_key);
}
if ( CONFIG->srv_ctx != NULL ) {
SSL_CTX_free(CONFIG->srv_ctx);
}
if ( CONFIG->cli_ctx != NULL ) {
SSL_CTX_free(CONFIG->cli_ctx);
}
free(CONFIG->client_ciphersuites);
free(CONFIG->server_ciphersuites);
free(CONFIG->client_cipher_list);
free(CONFIG->server_cipher_list);
free(CONFIG->client_ca_file);
free(CONFIG->client_ca_dir);
free(CONFIG->client_ca_store);
free(CONFIG->server_ca_file);
free(CONFIG->server_ca_dir);
free(CONFIG->server_ca_store);
free(CONFIG->client_sni);
if(CONFIG->client_alpn_protos.protos_len){
free(CONFIG->client_alpn_protos.protos);
CONFIG->client_alpn_protos.protos = NULL;
CONFIG->client_alpn_protos.protos_len = 0;
}
free(fo);
}
static struct filter ssl_filter = {
NULL,
"ssl filter",
"ssl_filter",
ssl_filter_open,
ssl_filter_client,
NULL,
ssl_filter_connect,
ssl_filter_afterauth,
NULL, NULL,
ssl_filter_predata,
NULL, NULL,
ssl_filter_clear,
ssl_filter_close
};
int filterset = 0;
static void setfilters(){
filterset++;
if(filterset > 1) return;
ssl_filter.next = pl->conf->filters;
pl->conf->filters = &ssl_filter;
sso = *pl->so;
}
static void unsetfilters(){
struct filter * sf;
if(!filterset) return;
filterset--;
if(filterset > 0) return;
if(pl->conf->filters == &ssl_filter) pl->conf->filters = ssl_filter.next;
else for(sf = pl->conf->filters; sf && sf->next; sf=sf->next){
if(sf->next == &ssl_filter) {
sf->next = ssl_filter.next;
break;
}
}
}
int h_mitm(int argc, unsigned char **argv){
if(mitm) return 0;
if(serv) return 2;
mitm = 1;
setfilters();
return 0;
}
int h_nomitm(int argc, unsigned char **argv){
if(!mitm) return 0;
mitm = 0;
unsetfilters();
return 0;
}
int h_serv(int argc, unsigned char **argv){
if(serv) return 0;
if(mitm) return 2;
serv = 1;
setfilters();
return 0;
}
int h_noserv(int argc, unsigned char **argv){
if(!serv) return 0;
serv = 0;
unsetfilters();
return 0;
}
int h_cli(int argc, unsigned char **argv){
if(cli) return 0;
if(mitm) return 2;
cli = 1;
setfilters();
return 0;
}
int h_nocli(int argc, unsigned char **argv){
if(!cli) return 0;
cli = 0;
unsetfilters();
return 0;
}
int h_certcache(int argc, unsigned char **argv){
size_t len;
len = strlen((char *)argv[1]);
if(!len || (argv[1][len - 1] != '/' && argv[1][len - 1] != '\\')) return 1;
if(certcache) free(certcache);
certcache = strdup((char *)argv[1]);
return 0;
}
int h_srvcert(int argc, unsigned char **argv){
free(srvcert);
srvcert = argc > 1? strdup((char *)argv[1]) : NULL;
return 0;
}
int h_srvkey(int argc, unsigned char **argv){
free(srvkey);
srvkey = argc > 1? strdup((char *)argv[1]) : NULL;
return 0;
}
int h_clicert(int argc, unsigned char **argv){
free(clicert);
clicert = argc > 1? strdup((char *)argv[1]) : NULL;
return 0;
}
int h_clikey(int argc, unsigned char **argv){
free(clikey);
clikey = argc > 1? strdup((char *)argv[1]) : NULL;
return 0;
}
int h_client_cipher_list(int argc, unsigned char **argv){
free(client_cipher_list);
client_cipher_list = argc > 1? strdup((char *)argv[1]) : NULL;
return 0;
}
int h_server_cipher_list(int argc, unsigned char **argv){
free(server_cipher_list);
server_cipher_list = argc > 1? strdup((char *)argv[1]) : NULL;
return 0;
}
int h_client_ciphersuites(int argc, unsigned char **argv){
free(client_ciphersuites);
client_ciphersuites = argc > 1? strdup((char *)argv[1]) : NULL;
return 0;
}
int h_server_ciphersuites(int argc, unsigned char **argv){
free(server_ciphersuites);
server_ciphersuites = argc > 1? strdup((char *)argv[1]) : NULL;
return 0;
}
int h_server_ca_file(int argc, unsigned char **argv){
free(server_ca_file);
server_ca_file = argc > 1? strdup((char *)argv[1]) : NULL;
return 0;
}
int h_server_ca_key(int argc, unsigned char **argv){
free(server_ca_key);
server_ca_key = argc > 1? strdup((char *)argv[1]) : NULL;
return 0;
}
int h_client_ca_file(int argc, unsigned char **argv){
free(client_ca_file);
client_ca_file = argc > 1? strdup((char *)argv[1]) : NULL;
return 0;
}
int h_client_ca_dir(int argc, unsigned char **argv){
free(client_ca_dir);
client_ca_dir = argc > 1? strdup((char *)argv[1]) : NULL;
return 0;
}
int h_client_ca_store(int argc, unsigned char **argv){
free(client_ca_store);
client_ca_store = argc > 1? strdup((char *)argv[1]) : NULL;
return 0;
}
int h_client_sni(int argc, unsigned char **argv){
free(client_sni);
client_sni = argc > 1? strdup((char *)argv[1]) : NULL;
return 0;
}
int h_client_alpn(int argc, unsigned char **argv){
int len, i;
if(client_alpn_protos.protos_len){
free(client_alpn_protos.protos);
client_alpn_protos.protos = NULL;
client_alpn_protos.protos_len = 0;
}
if(argc <= 1) return 0;
for(len = argc - 1, i = 1; i < argc; i++){
len += strlen((char *)argv[i]);
}
if(!(client_alpn_protos.protos = malloc(len))) return 10;
for(len = 0, i = 1; i < argc; i++){
int l;
l = strlen((char *)argv[i]);
if(l >= 255) return 2;
client_alpn_protos.protos[len++] = l;
memcpy(client_alpn_protos.protos + len, argv[i], l);
len += l;
}
client_alpn_protos.protos_len = len;
return 0;
}
int h_server_ca_dir(int argc, unsigned char **argv){
free(server_ca_dir);
server_ca_dir = argc > 1? strdup((char *)argv[1]) : NULL;
return 0;
}
int h_server_ca_store(int argc, unsigned char **argv){
free(server_ca_store);
server_ca_store = argc > 1? strdup((char *)argv[1]) : NULL;
return 0;
}
struct vermap{
char *sver;
int iver;
} versions[] = {
#ifdef SSL3_VERSION
{"SSLv3",SSL3_VERSION},
#endif
#ifdef TLS1_VERSION
{"TLSv1",TLS1_VERSION},
#endif
#ifdef TLS1_1_VERSION
{"TLSv1.1",TLS1_1_VERSION},
#endif
#ifdef TLS1_2_VERSION
{"TLSv1.2",TLS1_2_VERSION},
#endif
#ifdef TLS1_3_VERSION
{"TLSv1.3",TLS1_3_VERSION},
#endif
{NULL, 0}
};
int string_to_version(unsigned char *ver){
int i;
for (i=0; versions[i].sver; i++){
if(!strcasecmp(versions[i].sver, (char *)ver)) return versions[i].iver;
}
return 0;
}
int h_client_min_proto_version(int argc, unsigned char **argv){
client_min_proto_version = argc>1? string_to_version(argv[1]) : 0;
return 0;
}
int h_client_max_proto_version(int argc, unsigned char **argv){
client_max_proto_version = argc>1? string_to_version(argv[1]) : 0;
return 0;
}
int h_server_min_proto_version(int argc, unsigned char **argv){
server_min_proto_version = argc>1? string_to_version(argv[1]) : 0;
return 0;
}
int h_server_max_proto_version(int argc, unsigned char **argv){
server_max_proto_version = argc>1? string_to_version(argv[1]) : 0;
return 0;
}
int h_client_verify(int argc, unsigned char **argv){
client_verify = 1;
return 0;
}
int h_no_client_verify(int argc, unsigned char **argv){
client_verify = 0;
return 0;
}
int h_server_verify(int argc, unsigned char **argv){
server_verify = 1;
return 0;
}
int h_client_mode(int argc, unsigned char **argv){
client_mode = 0;
if(argc <= 1) return 0;
client_mode = atoi((char *)argv[1]);
return 0;
}
int h_no_server_verify(int argc, unsigned char **argv){
server_verify = 0;
return 0;
}
int h_ktls(int argc, unsigned char **argv){
#ifdef SSL_OP_ENABLE_KTLS
ktls = 1;
#else
fprintf(stderr, "ssl_ktls: kernel TLS is not supported by this TLS library, ignored\n");
#endif
return 0;
}
int h_noktls(int argc, unsigned char **argv){
ktls = 0;
return 0;
}
static struct symbol ssl_symbols[] = {
{NULL, "ssl_parent", (void *)&ssl_parent},
};
void ssl_install(void){
h_nomitm(0, NULL);
h_noserv(0, NULL);
h_nocli(0, NULL);
pl = &pluginlink;
free(certcache);
certcache = NULL;
free(srvcert);
srvcert = NULL;
free(srvkey);
srvkey = NULL;
free(clicert);
clicert = NULL;
free(clikey);
clikey = NULL;
client_min_proto_version = 0;
client_max_proto_version = 0;
server_min_proto_version = 0;
server_max_proto_version = 0;
client_verify = 0;
server_verify = 0;
client_mode = 0;
ktls = 0;
free(client_ciphersuites);
client_ciphersuites = NULL;
free(server_ciphersuites);
server_ciphersuites = NULL;
free(client_cipher_list);
client_cipher_list = NULL;
free(server_cipher_list);
server_cipher_list = NULL;
free(server_ca_file);
server_ca_file = NULL;
free(server_ca_key);
server_ca_key = NULL;
free(client_ca_file);
client_ca_file = NULL;
free(client_ca_dir);
client_ca_dir = NULL;
free(client_ca_store);
client_ca_store = NULL;
free(server_ca_dir);
server_ca_dir = NULL;
free(server_ca_store);
server_ca_store = NULL;
if(!ssl_loaded){
ssl_loaded = 1;
ssl_init();
ssl_symbols[0].next = pl->symbols.next;
pl->symbols.next = ssl_symbols;
}
#ifdef WITHSPLICE
splicetls = ssl_splicetls;
#endif
tcppmfunc = (PROXYFUNC)pl->findbyname("tcppm");
if(!tcppmfunc) return;
proxyfunc = (PROXYFUNC)pl->findbyname("proxy");
if(!proxyfunc)proxyfunc = tcppmfunc;
smtppfunc = (PROXYFUNC)pl->findbyname("smtpp");
if(!smtppfunc)smtppfunc = tcppmfunc;
ftpprfunc = (PROXYFUNC)pl->findbyname("ftppr");
if(!ftpprfunc)ftpprfunc = tcppmfunc;
}