]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
mqtt: initial support for MQTTS
authorDaniel Stenberg <daniel@haxx.se>
Sat, 17 Jan 2026 16:23:44 +0000 (17:23 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Sat, 17 Jan 2026 21:43:36 +0000 (22:43 +0100)
Closes #19418

22 files changed:
CMakeLists.txt
configure.ac
docs/FAQ.md
docs/cmdline-opts/_DESCRIPTION.md
docs/internals/MQTT.md
docs/libcurl/opts/CURLOPT_PROTOCOLS.md
docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md
docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md
docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md
docs/libcurl/symbols-in-versions
docs/tests/FILEFORMAT.md
include/curl/curl.h
lib/mqtt.c
lib/mqtt.h
lib/url.c
lib/urldata.h
lib/version.c
scripts/schemetable.c
tests/data/Makefile.am
tests/data/test1640 [new file with mode: 0644]
tests/serverhelp.pm
tests/servers.pm

index 32bc5c326fa3f72054496ba07da65454eab13868..3b2427ee62bfa69b9726d88bea9bd84125994a61 100644 (file)
@@ -1953,6 +1953,7 @@ curl_add_if("IPNS"          NOT CURL_DISABLE_IPFS)
 curl_add_if("RTSP"          NOT CURL_DISABLE_RTSP)
 curl_add_if("RTMP"          USE_LIBRTMP)
 curl_add_if("MQTT"          NOT CURL_DISABLE_MQTT)
+curl_add_if("MQTTS"         NOT CURL_DISABLE_MQTT AND _ssl_enabled)
 curl_add_if("WS"            NOT CURL_DISABLE_WEBSOCKETS)
 curl_add_if("WSS"           NOT CURL_DISABLE_WEBSOCKETS AND _ssl_enabled)
 if(_items)
index 5135cdda546c42aa65a8893943bc9cc2057d6966..aa4a37ef09a1f21245c6935496b36f5fdff950bc 100644 (file)
@@ -5424,6 +5424,9 @@ if test "$CURL_DISABLE_GOPHER" != "1"; then
 fi
 if test "$CURL_DISABLE_MQTT" != "1"; then
   SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS MQTT"
+  if test "$SSL_ENABLED" = "1"; then
+    SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS MQTTS"
+  fi
 fi
 if test "$CURL_DISABLE_POP3" != "1"; then
   SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS POP3"
index 0febe1b73ca67ca6b49d2503032b44876bdea0c8..728d340fdf3bba2601e75ac45804190b1b5e7276 100644 (file)
@@ -21,7 +21,7 @@ The curl project produces two products:
 ### libcurl
 
 A client-side URL transfer library, supporting DICT, FILE, FTP, FTPS, GOPHER,
-GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, POP3, POP3S, RTMP,
+GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTMP,
 RTMPS, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS.
 
 libcurl supports HTTPS certificates, HTTP POST, HTTP PUT, FTP uploading,
index 3e06c1b38ffe402e9be9f5aad0598e601b3bb3cc..65ef69eec29ee5ebaf3ac517a404cfeb1f2011f7 100644 (file)
@@ -4,8 +4,8 @@
 
 **curl** is a tool for transferring data from or to a server using URLs. It
 supports these protocols: DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS,
-IMAP, IMAPS, LDAP, LDAPS, MQTT, POP3, POP3S, RTMP, RTMPS, RTSP, SCP, SFTP,
-SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS.
+IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTMP, RTMPS, RTSP, SCP,
+SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS.
 
 curl is powered by libcurl for all transfer-related features. See
 *libcurl(3)* for details.
index 7b2fa5668db5acfc789889f2f68904416cbf93fc..dadd2290dc04e151b9a846ca9bb93af8d68f1e44 100644 (file)
@@ -12,6 +12,10 @@ A plain "GET" subscribes to the topic and prints all published messages.
 
 Doing a "POST" publishes the post data to the topic and exits.
 
+## TLS protection
+
+Use `mqtts://` to do MQTT over TLS: MQTTS.
+
 ### Subscribing
 
 Command usage:
index 37d1e5fe3e3de3f1ddc95efb2e06d96ca2e42d99..f64faa26841723e406894734e375b3319b1db379 100644 (file)
@@ -51,6 +51,8 @@ CURLPROTO_IMAP
 CURLPROTO_IMAPS
 CURLPROTO_LDAP
 CURLPROTO_LDAPS
+CURLPROTO_MQTT
+CURLPROTO_MQTTS
 CURLPROTO_POP3
 CURLPROTO_POP3S
 CURLPROTO_RTMP
index 854520851099d81d2505259fbb60a4848607dba6..8de66099e19c374e41d2af438c0e8924f719b737 100644 (file)
@@ -42,8 +42,8 @@ set, it returns error.
 These are the available protocols:
 
 DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS,
-MQTT, POP3, POP3S, RTMP, RTMPE, RTMPS, RTMPT, RTMPTE, RTMPTS, RTSP, SCP, SFTP,
-SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS, WSS
+MQTT, MQTTS, POP3, POP3S, RTMP, RTMPE, RTMPS, RTMPT, RTMPTE, RTMPTS, RTSP,
+SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS, WSS
 
 You can set "ALL" as a short-cut to enable all protocols. Note that by setting
 all, you may enable protocols that were not supported the day you write this
index d76bd0cf0a809aac6d77efd6ee9d4872f368bb49..fcafb30588316a89b227c5c7fb61e6c36c51a3c8 100644 (file)
@@ -58,6 +58,8 @@ CURLPROTO_IMAP
 CURLPROTO_IMAPS
 CURLPROTO_LDAP
 CURLPROTO_LDAPS
+CURLPROTO_MQTT
+CURLPROTO_MQTTS
 CURLPROTO_POP3
 CURLPROTO_POP3S
 CURLPROTO_RTMP
index 2f14b04d00c6f3c32435fd3fb1ce5b5e0d6139c6..c2d8db8bb2c911c9e0b2c296ec329e839e33ceaf 100644 (file)
@@ -46,8 +46,8 @@ By default libcurl allows HTTP, HTTPS, FTP and FTPS on redirects (since
 These are the available protocols:
 
 DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS,
-MQTT, POP3, POP3S, RTMP, RTMPE, RTMPS, RTMPT, RTMPTE, RTMPTS, RTSP, SCP, SFTP,
-SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS, WSS
+MQTT, MQTTS, POP3, POP3S, RTMP, RTMPE, RTMPS, RTMPT, RTMPTE, RTMPTS, RTSP,
+SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS, WSS
 
 You can set "ALL" as a short-cut to enable all protocols. Note that by setting
 all, you may enable protocols that were not supported the day you write this
index e0e0a8e63a4c591db45283a08bffe82ef1b4e843..1859fe184a8c4aa4b7df5abc582f0fde9d5127ef 100644 (file)
@@ -968,6 +968,7 @@ CURLPROTO_IMAPS                 7.20.0
 CURLPROTO_LDAP                  7.19.4
 CURLPROTO_LDAPS                 7.19.4
 CURLPROTO_MQTT                  7.71.0
+CURLPROTO_MQTTS                 8.19.0
 CURLPROTO_POP3                  7.20.0
 CURLPROTO_POP3S                 7.20.0
 CURLPROTO_RTMP                  7.21.0
index fe42432ab0a363b28046269f6195157733854a36..26dae3e23fd4e20a797e732c1eba6c197e6dc8f3 100644 (file)
@@ -184,6 +184,7 @@ Available substitute variables include:
 - `%IMAPPORT` - Port number of the IMAP server
 - `%LOGDIR` - Log directory relative to %PWD
 - `%MQTTPORT` - Port number of the MQTT server
+- `%MQTTSPORT` - Port number of the MQTTS server
 - `%NOLISTENPORT` - Port number where no service is listening
 - `%POP36PORT` - IPv6 port number of the POP3 server
 - `%POP3PORT` - Port number of the POP3 server
index 717bf1e7ff2531a5381cf4b6569caaa8f8d9e489..007db544238450a0e78f617cc17c595c024540aa 100644 (file)
@@ -1100,6 +1100,7 @@ typedef CURLSTScode (*curl_hstswrite_callback)(CURL *easy,
 #define CURLPROTO_SMBS    (1L << 27)
 #define CURLPROTO_MQTT    (1L << 28)
 #define CURLPROTO_GOPHERS (1L << 29)
+#define CURLPROTO_MQTTS   (1L << 30)
 #define CURLPROTO_ALL     (~0L) /* enable everything */
 
 /* long may be 32 or 64 bits, but we should never depend on anything else
index fc3389e7f5fd67f11e95c4300ab306999d8d80aa..8ad59b8658e8d4047896d51e2772d352e68c8fc1 100644 (file)
@@ -36,6 +36,8 @@
 #include "url.h"
 #include "escape.h"
 #include "rand.h"
+#include "cfilters.h"
+#include "connect.h"
 
 /* first byte is command.
    second byte is for flags. */
@@ -934,6 +936,50 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done)
   return result;
 }
 
+#ifdef USE_SSL
+
+static CURLcode mqtts_connecting(struct Curl_easy *data, bool *done)
+{
+  struct connectdata *conn = data->conn;
+  CURLcode result;
+
+  result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, done);
+  if(result)
+    connclose(conn, "Failed TLS connection");
+  return result;
+}
+
+/*
+ * MQTTS protocol handler.
+ */
+
+const struct Curl_handler Curl_handler_mqtts = {
+  "mqtts",                            /* scheme */
+  mqtt_setup_conn,                    /* setup_connection */
+  mqtt_do,                            /* do_it */
+  mqtt_done,                          /* done */
+  ZERO_NULL,                          /* do_more */
+  ZERO_NULL,                          /* connect_it */
+  mqtts_connecting,                   /* connecting */
+  mqtt_doing,                         /* doing */
+  ZERO_NULL,                          /* proto_pollset */
+  mqtt_pollset,                       /* doing_pollset */
+  ZERO_NULL,                          /* domore_pollset */
+  ZERO_NULL,                          /* perform_pollset */
+  ZERO_NULL,                          /* disconnect */
+  ZERO_NULL,                          /* write_resp */
+  ZERO_NULL,                          /* write_resp_hd */
+  ZERO_NULL,                          /* connection_check */
+  ZERO_NULL,                          /* attach connection */
+  ZERO_NULL,                          /* follow */
+  PORT_MQTTS,                         /* defport */
+  CURLPROTO_MQTTS,                    /* protocol */
+  CURLPROTO_MQTT,                     /* family */
+  PROTOPT_SSL                         /* flags */
+};
+
+#endif
+
 /*
  * MQTT protocol handler.
  */
index 3e45815ba90b9834ff8f4b5a18aebedb459d92bc..43040d001bade72e8c1792039db4fa143cd4cf9c 100644 (file)
@@ -25,6 +25,9 @@
  ***************************************************************************/
 #ifndef CURL_DISABLE_MQTT
 extern const struct Curl_handler Curl_handler_mqtt;
+#ifdef USE_SSL
+extern const struct Curl_handler Curl_handler_mqtts;
+#endif
 #endif
 
 #endif /* HEADER_CURL_MQTT_H */
index c8f36450a65fd995ddcea82de6a0b536834bab24..dd1bc2aed88c49b68cb5342e92fdc12c33b5acc8 100644 (file)
--- a/lib/url.c
+++ b/lib/url.c
@@ -1423,7 +1423,12 @@ const struct Curl_handler *Curl_getn_scheme_handler(const char *scheme,
 #else
     NULL,
 #endif
-    NULL, NULL,
+#if defined(USE_SSL) && !defined(CURL_DISABLE_MQTT)
+    &Curl_handler_mqtts,
+#else
+    NULL,
+#endif
+    NULL,
 #if defined(USE_SSL) && !defined(CURL_DISABLE_GOPHER)
     &Curl_handler_gophers,
 #else
index 04a7385e036b171f0c0499efcb5bbd352d27497d..a3a37a8e039e1626e37efe6472d83e71944b71cd 100644 (file)
@@ -50,6 +50,7 @@
 #define PORT_RTMPS  PORT_HTTPS
 #define PORT_GOPHER 70
 #define PORT_MQTT   1883
+#define PORT_MQTTS  8883
 
 struct curl_trc_featt;
 
index 83e519810b81223305763557b5223f7c14dad2e5..1fb94bb9fc072ed4768d9022ea41b108c4efc8b8 100644 (file)
@@ -341,6 +341,9 @@ static const char * const supported_protocols[] = {
 #ifndef CURL_DISABLE_MQTT
   "mqtt",
 #endif
+#if defined(USE_SSL) && !defined(CURL_DISABLE_MQTT)
+  "mqtts",
+#endif
 #ifndef CURL_DISABLE_POP3
   "pop3",
 #endif
index 2c1c3c0548fe9847a4660057862c8337a908410a..dded8dc753faf3a2bf0b60532fa1d4f60fcc2c5d 100644 (file)
@@ -51,6 +51,7 @@ static const struct detail scheme[] = {
              "  ((defined(USE_OPENLDAP) && defined(USE_SSL)) || \\\n"
              "   (!defined(USE_OPENLDAP) && defined(HAVE_LDAP_SSL)))" },
   { "mqtt", "#ifndef CURL_DISABLE_MQTT" },
+  { "mqtts", "#if defined(USE_SSL) && !defined(CURL_DISABLE_MQTT)" },
   { "pop3", "#ifndef CURL_DISABLE_POP3" },
   { "pop3s", "#if defined(USE_SSL) && !defined(CURL_DISABLE_POP3)" },
   { "rtmp", "#ifdef USE_LIBRTMP" },
index 6475055e1eb74c68fa792bbaf32685aa54611ddf..99184130afcbd3f6d4e0da976f47bf40cf224b1b 100644 (file)
@@ -218,6 +218,8 @@ test1620 test1621 test1622 \
 \
 test1630 test1631 test1632 test1633 test1634 test1635 test1636 \
 \
+test1640 \
+\
 test1650 test1651 test1652 test1653 test1654 test1655 test1656 test1657 \
 test1658 \
 test1660 test1661 test1662 test1663 test1664 test1665 \
diff --git a/tests/data/test1640 b/tests/data/test1640
new file mode 100644 (file)
index 0000000..e6f1f59
--- /dev/null
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="US-ASCII"?>
+<testcase>
+<info>
+<keywords>
+MQTT
+MQTT SUBSCRIBE
+MQTTS
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data nocheck="yes">
+hello
+</data>
+<datacheck hex="yes">
+00 04 31 31 39 30   68 65 6c 6c 6f 5b 4c 46 5d 0a
+</datacheck>
+</reply>
+
+# Client-side
+<client>
+<features>
+mqtts
+</features>
+<server>
+mqtts
+</server>
+<name>
+MQTTS SUBSCRIBE
+</name>
+<command option="binary-trace">
+mqtts://%HOSTIP:%MQTTSPORT/topic --insecure
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+# These are hexadecimal protocol dumps from the client
+# Strip out the random part of the client id from the CONNECT message
+# before comparison
+<strippart>
+s/^(.* 00044d5154540402003c000c6375726c).*/$1/
+</strippart>
+<protocol>
+client CONNECT 18 00044d5154540402003c000c6375726c
+server CONNACK 2 20020000
+client SUBSCRIBE a 00010005746f70696300
+server SUBACK 3 9003000100
+server PUBLISH d 300d0005746f70696368656c6c6f0a
+server DISCONNECT 0 e000
+</protocol>
+</verify>
+</testcase>
index bfef919a96c3394c0adc24c1453687de0b42ce83..eaf5b428048a75c3e11a62850bc1f9f07e651625 100644 (file)
@@ -125,7 +125,7 @@ sub servername_str {
 
     $proto = uc($proto) if($proto);
     die "unsupported protocol: '$proto'" unless($proto &&
-        ($proto =~ /^(((DNS|FTP|HTTP|HTTP\/2|HTTP\/3|IMAP|POP3|GOPHER|SMTP|HTTPS-MTLS)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|HTTPTLS|DICT|SMB|SMBS|TELNET|MQTT))$/));
+        ($proto =~ /^(((DNS|FTP|HTTP|HTTP\/2|HTTP\/3|IMAP|POP3|GOPHER|SMTP|HTTPS-MTLS)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|HTTPTLS|DICT|SMB|SMBS|TELNET|MQTT|MQTTS))$/));
 
     $ipver = (not $ipver) ? 'ipv4' : lc($ipver);
     die "unsupported IP version: '$ipver'" unless($ipver &&
index 5d5d98be6a7c00a9e92d3c1da8f4a362db7bb4cf..35cfb1572546e8839dad11dd7c89e84a5a176b8f 100644 (file)
@@ -237,8 +237,8 @@ sub init_serverpidfile_hash {
         }
     }
     for my $proto (('tftp', 'sftp', 'socks', 'ssh', 'rtsp', 'httptls',
-                    'dict', 'smb', 'smbs', 'telnet', 'mqtt', 'https-mtls',
-                    'dns')) {
+                    'dict', 'smb', 'smbs', 'telnet', 'mqtt', 'mqtts',
+                    'https-mtls', 'dns')) {
         for my $ipvnum ((4, 6)) {
             for my $idnum ((1, 2)) {
                 my $serv = servername_id($proto, $ipvnum, $idnum);
@@ -1030,6 +1030,7 @@ my %protofunc = ('http' => \&verifyhttp,
                  'pop3s' => \&verifyftp,
                  'imaps' => \&verifyftp,
                  'mqtt' => \&verifypid,
+                 'mqtts' => \&verifypid,
                  'smtps' => \&verifyftp,
                  'tftp' => \&verifyftp,
                  'ssh' => \&verifyssh,
@@ -1329,6 +1330,9 @@ sub runhttpsserver {
     if($proto eq "gophers") {
         $flags .= "--connect " . protoport("gopher");
     }
+    elsif($proto eq "mqtts") {
+        $flags .= "--connect " . protoport("mqtt");
+    }
     elsif(!$proxy) {
         $flags .= "--connect " . protoport("http");
     }
@@ -2955,6 +2959,36 @@ sub startservers {
                 $run{'mqtt'}="$pid $pid2";
             }
         }
+        elsif($what eq "mqtts" ) {
+            if(!$stunnel) {
+                # we cannot run mqtts tests without stunnel
+                return ("no stunnel", 4);
+            }
+            if($run{'mqtt'} &&
+               !responsive_mqtt_server("mqtt", "", $verbose)) {
+                if(stopserver('mqtt')) {
+                    return ("failed stopping unresponsive MQTT server", 3);
+                }
+            }
+            if(!$run{'mqtt'}) {
+                ($serr, $pid, $pid2, $PORT{"mqtt"}) = runmqttserver("", $verbose);
+                if($pid <= 0) {
+                    return ("failed starting mqtt server", $serr);
+                }
+                logmsg sprintf("* pid mqtt => %d %d\n", $pid, $pid2) if($verbose);
+                $run{'mqtt'}="$pid $pid2";
+            }
+            if(!$run{$what}) {
+                ($serr, $pid, $pid2, $PORT{$what}) =
+                    runhttpsserver($verbose, $what, "", $certfile);
+                if($pid <= 0) {
+                    return ("failed starting MQTTS server (stunnel)", $serr);
+                }
+                logmsg sprintf("* pid $what => %d %d\n", $pid, $pid2)
+                    if($verbose);
+                $run{$what}="$pid $pid2";
+            }
+        }
         elsif($what eq "http-unix") {
             if($run{'http-unix'} &&
                !responsive_http_server("http", $verbose, "unix", $HTTPUNIXPATH)) {
@@ -3094,7 +3128,7 @@ sub subvariables {
                        'HTTP2', 'HTTP2TLS',
                        'HTTP3',
                        'IMAP', 'IMAP6', 'IMAPS',
-                       'MQTT',
+                       'MQTT', 'MQTTS',
                        'NOLISTEN',
                        'POP3', 'POP36', 'POP3S',
                        'RTSP', 'RTSP6',