diff --git a/CMakeLists.txt b/CMakeLists.txt index 32bc5c326f..3b2427ee62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/configure.ac b/configure.ac index 5135cdda54..aa4a37ef09 100644 --- a/configure.ac +++ b/configure.ac @@ -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" diff --git a/docs/FAQ.md b/docs/FAQ.md index 0febe1b73c..728d340fdf 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -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, diff --git a/docs/cmdline-opts/_DESCRIPTION.md b/docs/cmdline-opts/_DESCRIPTION.md index 3e06c1b38f..65ef69eec2 100644 --- a/docs/cmdline-opts/_DESCRIPTION.md +++ b/docs/cmdline-opts/_DESCRIPTION.md @@ -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. diff --git a/docs/internals/MQTT.md b/docs/internals/MQTT.md index 7b2fa5668d..dadd2290dc 100644 --- a/docs/internals/MQTT.md +++ b/docs/internals/MQTT.md @@ -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: diff --git a/docs/libcurl/opts/CURLOPT_PROTOCOLS.md b/docs/libcurl/opts/CURLOPT_PROTOCOLS.md index 37d1e5fe3e..f64faa2684 100644 --- a/docs/libcurl/opts/CURLOPT_PROTOCOLS.md +++ b/docs/libcurl/opts/CURLOPT_PROTOCOLS.md @@ -51,6 +51,8 @@ CURLPROTO_IMAP CURLPROTO_IMAPS CURLPROTO_LDAP CURLPROTO_LDAPS +CURLPROTO_MQTT +CURLPROTO_MQTTS CURLPROTO_POP3 CURLPROTO_POP3S CURLPROTO_RTMP diff --git a/docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md b/docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md index 8545208510..8de66099e1 100644 --- a/docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md +++ b/docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md @@ -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 diff --git a/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md b/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md index d76bd0cf0a..fcafb30588 100644 --- a/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md +++ b/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md @@ -58,6 +58,8 @@ CURLPROTO_IMAP CURLPROTO_IMAPS CURLPROTO_LDAP CURLPROTO_LDAPS +CURLPROTO_MQTT +CURLPROTO_MQTTS CURLPROTO_POP3 CURLPROTO_POP3S CURLPROTO_RTMP diff --git a/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md b/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md index 2f14b04d00..c2d8db8bb2 100644 --- a/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md +++ b/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md @@ -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 diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index e0e0a8e63a..1859fe184a 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -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 diff --git a/docs/tests/FILEFORMAT.md b/docs/tests/FILEFORMAT.md index fe42432ab0..26dae3e23f 100644 --- a/docs/tests/FILEFORMAT.md +++ b/docs/tests/FILEFORMAT.md @@ -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 diff --git a/include/curl/curl.h b/include/curl/curl.h index 717bf1e7ff..007db54423 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -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 diff --git a/lib/mqtt.c b/lib/mqtt.c index fc3389e7f5..8ad59b8658 100644 --- a/lib/mqtt.c +++ b/lib/mqtt.c @@ -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. */ diff --git a/lib/mqtt.h b/lib/mqtt.h index 3e45815ba9..43040d001b 100644 --- a/lib/mqtt.h +++ b/lib/mqtt.h @@ -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 */ diff --git a/lib/url.c b/lib/url.c index c8f36450a6..dd1bc2aed8 100644 --- 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 diff --git a/lib/urldata.h b/lib/urldata.h index 04a7385e03..a3a37a8e03 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -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; diff --git a/lib/version.c b/lib/version.c index 83e519810b..1fb94bb9fc 100644 --- a/lib/version.c +++ b/lib/version.c @@ -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 diff --git a/scripts/schemetable.c b/scripts/schemetable.c index 2c1c3c0548..dded8dc753 100644 --- a/scripts/schemetable.c +++ b/scripts/schemetable.c @@ -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" }, diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 6475055e1e..99184130af 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -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 index 0000000000..e6f1f59fb0 --- /dev/null +++ b/tests/data/test1640 @@ -0,0 +1,54 @@ + + + + +MQTT +MQTT SUBSCRIBE +MQTTS + + + +# Server-side + + +hello + + +00 04 31 31 39 30 68 65 6c 6c 6f 5b 4c 46 5d 0a + + + +# Client-side + + +mqtts + + +mqtts + + +MQTTS SUBSCRIBE + + +mqtts://%HOSTIP:%MQTTSPORT/topic --insecure + + + +# Verify data after the test has been "shot" + +# These are hexadecimal protocol dumps from the client +# Strip out the random part of the client id from the CONNECT message +# before comparison + +s/^(.* 00044d5154540402003c000c6375726c).*/$1/ + + +client CONNECT 18 00044d5154540402003c000c6375726c +server CONNACK 2 20020000 +client SUBSCRIBE a 00010005746f70696300 +server SUBACK 3 9003000100 +server PUBLISH d 300d0005746f70696368656c6c6f0a +server DISCONNECT 0 e000 + + + diff --git a/tests/serverhelp.pm b/tests/serverhelp.pm index bfef919a96..eaf5b42804 100644 --- a/tests/serverhelp.pm +++ b/tests/serverhelp.pm @@ -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 && diff --git a/tests/servers.pm b/tests/servers.pm index 5d5d98be6a..35cfb15725 100644 --- a/tests/servers.pm +++ b/tests/servers.pm @@ -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',