From 6e35eb4879ec2ee97f43da295f52c6841beeafae Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 10 Oct 2025 14:33:36 +0200 Subject: [PATCH] lib: SSL connection reuse Protocol handlers not flagging PROTOPT_SSL that allow reuse of existing SSL connections now need to carry the flag PROTOPT_SSL_REUSE. Add PROTOPT_SSL_REUSE to imap, ldap, pop3, smtp and ftp. Add tests the http: urls do not reuse https: connections and vice versa. Reported-by: Sakthi SK Fixes #19006 Closes #19007 --- lib/ftp.c | 2 +- lib/imap.c | 3 ++- lib/ldap.c | 2 +- lib/openldap.c | 2 +- lib/pop3.c | 2 +- lib/smtp.c | 2 +- lib/url.c | 9 +++++---- lib/urldata.h | 3 +++ tests/http/test_01_basic.py | 20 ++++++++++++++++++++ 9 files changed, 35 insertions(+), 10 deletions(-) diff --git a/lib/ftp.c b/lib/ftp.c index adcad3dd75..f633d21305 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -271,7 +271,7 @@ const struct Curl_handler Curl_handler_ftp = { CURLPROTO_FTP, /* family */ PROTOPT_DUAL | PROTOPT_CLOSEACTION | PROTOPT_NEEDSPWD | PROTOPT_NOURLQUERY | PROTOPT_PROXY_AS_HTTP | - PROTOPT_WILDCARD /* flags */ + PROTOPT_WILDCARD | PROTOPT_SSL_REUSE /* flags */ }; diff --git a/lib/imap.c b/lib/imap.c index 69e4e0c2c6..47757d3f29 100644 --- a/lib/imap.c +++ b/lib/imap.c @@ -209,7 +209,8 @@ const struct Curl_handler Curl_handler_imap = { CURLPROTO_IMAP, /* protocol */ CURLPROTO_IMAP, /* family */ PROTOPT_CLOSEACTION| /* flags */ - PROTOPT_URLOPTIONS + PROTOPT_URLOPTIONS| + PROTOPT_SSL_REUSE }; #ifdef USE_SSL diff --git a/lib/ldap.c b/lib/ldap.c index 2314bbf585..0b475d07bb 100644 --- a/lib/ldap.c +++ b/lib/ldap.c @@ -199,7 +199,7 @@ const struct Curl_handler Curl_handler_ldap = { PORT_LDAP, /* defport */ CURLPROTO_LDAP, /* protocol */ CURLPROTO_LDAP, /* family */ - PROTOPT_NONE /* flags */ + PROTOPT_SSL_REUSE /* flags */ }; #ifdef HAVE_LDAP_SSL diff --git a/lib/openldap.c b/lib/openldap.c index b84268dae7..fb771161d6 100644 --- a/lib/openldap.c +++ b/lib/openldap.c @@ -139,7 +139,7 @@ const struct Curl_handler Curl_handler_ldap = { PORT_LDAP, /* defport */ CURLPROTO_LDAP, /* protocol */ CURLPROTO_LDAP, /* family */ - PROTOPT_NONE /* flags */ + PROTOPT_SSL_REUSE /* flags */ }; #ifdef USE_SSL diff --git a/lib/pop3.c b/lib/pop3.c index ce9f81e3d5..dbcc2d198d 100644 --- a/lib/pop3.c +++ b/lib/pop3.c @@ -200,7 +200,7 @@ const struct Curl_handler Curl_handler_pop3 = { CURLPROTO_POP3, /* protocol */ CURLPROTO_POP3, /* family */ PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */ - PROTOPT_URLOPTIONS + PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE }; #ifdef USE_SSL diff --git a/lib/smtp.c b/lib/smtp.c index 8daf0ad89d..76ed4f280a 100644 --- a/lib/smtp.c +++ b/lib/smtp.c @@ -205,7 +205,7 @@ const struct Curl_handler Curl_handler_smtp = { CURLPROTO_SMTP, /* protocol */ CURLPROTO_SMTP, /* family */ PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */ - PROTOPT_URLOPTIONS + PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE }; #ifdef USE_SSL diff --git a/lib/url.c b/lib/url.c index 6d69fc11bf..f0fe7d3d41 100644 --- a/lib/url.c +++ b/lib/url.c @@ -932,15 +932,16 @@ static bool url_match_multiplex_limits(struct connectdata *conn, static bool url_match_ssl_use(struct connectdata *conn, struct url_conn_match *m) { - if(m->needle->handler->flags&PROTOPT_SSL) { + if(m->needle->handler->flags & PROTOPT_SSL) { /* We are looking for SSL, if `conn` does not do it, not a match. */ if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) return FALSE; } else if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) { - /* We are not *requiring* SSL, however `conn` has it. If the - * protocol *family* is not the same, not a match. */ - if(get_protocol_family(conn->handler) != m->needle->handler->protocol) + /* If the protocol does not allow reuse of SSL connections OR + is of another protocol family, not a match. */ + if(!(m->needle->handler->flags & PROTOPT_SSL_REUSE) || + (get_protocol_family(conn->handler) != m->needle->handler->protocol)) return FALSE; } return TRUE; diff --git a/lib/urldata.h b/lib/urldata.h index b6b8f6f8fe..30bbbae416 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -578,6 +578,9 @@ struct Curl_handler { #define PROTOPT_USERPWDCTRL (1<<13) /* Allow "control bytes" (< 32 ASCII) in username and password */ #define PROTOPT_NOTCPPROXY (1<<14) /* this protocol cannot proxy over TCP */ +#define PROTOPT_SSL_REUSE (1<<15) /* this protocol may reuse an existing + SSL connection in the same family + without having PROTOPT_SSL. */ #define CONNCHECK_NONE 0 /* No checks */ #define CONNCHECK_ISDEAD (1<<0) /* Check if the connection is dead. */ diff --git a/tests/http/test_01_basic.py b/tests/http/test_01_basic.py index 4ac4e8e6e1..692bb3d7b2 100644 --- a/tests/http/test_01_basic.py +++ b/tests/http/test_01_basic.py @@ -273,3 +273,23 @@ class TestBasic: assert r.responses[0]['header']['request-te'] == te_out, f'{r.responses[0]}' else: assert 'request-te' not in r.responses[0]['header'], f'{r.responses[0]}' + + # check that an existing https: connection is not reused for http: + def test_01_18_tls_reuse(self, env: Env, httpd): + proto = 'h2' + curl = CurlClient(env=env) + url1 = f'https://{env.authority_for(env.domain1, proto)}/data.json' + url2 = f'http://{env.authority_for(env.domain1, proto)}/data.json' + r = curl.http_download(urls=[url1, url2], alpn_proto=proto, with_stats=True) + assert len(r.stats) == 2 + assert r.total_connects == 2, f'{r.dump_logs()}' + + # check that an existing http: connection is not reused for https: + def test_01_19_plain_reuse(self, env: Env, httpd): + proto = 'h2' + curl = CurlClient(env=env) + url1 = f'http://{env.domain1}:{env.http_port}/data.json' + url2 = f'https://{env.domain1}:{env.http_port}/data.json' + r = curl.http_download(urls=[url1, url2], alpn_proto=proto, with_stats=True) + assert len(r.stats) == 2 + assert r.total_connects == 2, f'{r.dump_logs()}' -- 2.47.3