]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
lib: SSL connection reuse
authorStefan Eissing <stefan@eissing.org>
Fri, 10 Oct 2025 12:33:36 +0000 (14:33 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Sun, 12 Oct 2025 13:30:12 +0000 (15:30 +0200)
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
lib/imap.c
lib/ldap.c
lib/openldap.c
lib/pop3.c
lib/smtp.c
lib/url.c
lib/urldata.h
tests/http/test_01_basic.py

index adcad3dd7587942def8195e472bc602948321654..f633d213058a63b0f96f3a434ee795b55ef4c294 100644 (file)
--- 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 */
 };
 
 
index 69e4e0c2c60834a188398c1bb1e517fb2a06e05e..47757d3f29780524e08c1f43ad725f0c8b0f5eb3 100644 (file)
@@ -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
index 2314bbf58512b31c42a44b2260c3f526c2ca869b..0b475d07bbc438da0db94b6970cff851b254750d 100644 (file)
@@ -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
index b84268dae7c0d58245bb7eb15b478d7941865d55..fb771161d61d80eb0ccd7facac8b8571e696f7b9 100644 (file)
@@ -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
index ce9f81e3d5aaeb17116b2f13c2951663621c78c7..dbcc2d198df26bdc9c32f6599c5703e7e1c06356 100644 (file)
@@ -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
index 8daf0ad89de3f5995a84443479f680ab2242fe69..76ed4f280afdd2b762f3c812d92d343be98a316f 100644 (file)
@@ -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
index 6d69fc11bf88c8d9da1ef71421e13365930fde48..f0fe7d3d41350c5607c33fffced9ad338d464cc3 100644 (file)
--- 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;
index b6b8f6f8fe3de1d83851acfdf0114c2a663c9e89..30bbbae416e440256de83b38c121fc3a17c9d5c0 100644 (file)
@@ -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. */
index 4ac4e8e6e1e891b29fa0ccbf03579b03d4a34afc..692bb3d7b266c749e0ccb523a974b049b4ce68f7 100644 (file)
@@ -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()}'