From 791937b881177c07c6ab8f151723342b3b9167ce Mon Sep 17 00:00:00 2001 From: Gealber Morales Date: Fri, 4 Jun 2021 09:25:38 +0200 Subject: [PATCH] mqtt: add support for username and password Minor-edits-by: Daniel Stenberg Added test 2200 to 2205 Closes #7243 --- lib/mqtt.c | 210 ++++++++++++++++++++++++++++++++++------ tests/data/Makefile.inc | 3 + tests/data/test2200 | 62 ++++++++++++ tests/data/test2201 | 50 ++++++++++ tests/data/test2202 | 59 +++++++++++ tests/data/test2203 | 62 ++++++++++++ tests/data/test2204 | 56 +++++++++++ tests/data/test2205 | 51 ++++++++++ tests/runtests.pl | 2 +- tests/server/mqttd.c | 60 +++++++++++- 10 files changed, 579 insertions(+), 36 deletions(-) create mode 100644 tests/data/test2200 create mode 100644 tests/data/test2201 create mode 100644 tests/data/test2202 create mode 100644 tests/data/test2203 create mode 100644 tests/data/test2204 create mode 100644 tests/data/test2205 diff --git a/lib/mqtt.c b/lib/mqtt.c index d49a5f1cc..9a3ca222f 100644 --- a/lib/mqtt.c +++ b/lib/mqtt.c @@ -143,32 +143,197 @@ static int mqtt_getsock(struct Curl_easy *data, return GETSOCK_READSOCK(FIRSTSOCKET); } +static int mqtt_encode_len(char *buf, size_t len) +{ + unsigned char encoded; + int i; + + for(i = 0; (len > 0) && (i<4); i++) { + encoded = len % 0x80; + len /= 0x80; + if(len) + encoded |= 0x80; + buf[i] = encoded; + } + + return i; +} + +/* add the passwd to the CONNECT packet */ +static int add_passwd(const char *passwd, const size_t plen, + char *pkt, const size_t start, int remain_pos) +{ + /* magic number that need to be set properly */ + const size_t conn_flags_pos = remain_pos + 8; + if(plen > 0xffff) + return 1; + + /* set password flag */ + pkt[conn_flags_pos] |= 0x40; + + /* length of password provided */ + pkt[start] = (char)((plen >> 8) & 0xFF); + pkt[start + 1] = (char)(plen & 0xFF); + memcpy(&pkt[start + 2], passwd, plen); + return 0; +} + +/* add user to the CONN packet */ +static int add_user(const char *username, const size_t ulen, + unsigned char *pkt, const size_t start, int remain_pos) +{ + /* magic number that need to be set properly */ + const size_t conn_flags_pos = remain_pos + 8; + if(ulen > 0xffff) + return 1; + + /* set username flag */ + pkt[conn_flags_pos] |= 0x80; + /* length of username provided */ + pkt[start] = (unsigned char)((ulen >> 8) & 0xFF); + pkt[start + 1] = (unsigned char)(ulen & 0xFF); + memcpy(&pkt[start + 2], username, ulen); + return 0; +} + +/* add client ID to the CONN packet */ +static int add_client_id(const char *client_id, const size_t client_id_len, + char *pkt, const size_t start) +{ + if(client_id_len != MQTT_CLIENTID_LEN) + return 1; + pkt[start] = 0x00; + pkt[start + 1] = MQTT_CLIENTID_LEN; + memcpy(&pkt[start + 2], client_id, MQTT_CLIENTID_LEN); + return 0; +} + +/* Set initial values of CONN packet */ +static int init_connpack(char *packet, char *remain, int remain_pos) +{ + /* Fixed header starts */ + /* packet type */ + packet[0] = MQTT_MSG_CONNECT; + /* remaining length field */ + memcpy(&packet[1], remain, remain_pos); + /* Fixed header ends */ + + /* Variable header starts */ + /* protocol length */ + packet[remain_pos + 1] = 0x00; + packet[remain_pos + 2] = 0x04; + /* protocol name */ + packet[remain_pos + 3] = 'M'; + packet[remain_pos + 4] = 'Q'; + packet[remain_pos + 5] = 'T'; + packet[remain_pos + 6] = 'T'; + /* protocol level */ + packet[remain_pos + 7] = 0x04; + /* CONNECT flag: CleanSession */ + packet[remain_pos + 8] = 0x02; + /* keep-alive 0 = disabled */ + packet[remain_pos + 9] = 0x00; + packet[remain_pos + 10] = 0x3c; + /*end of variable header*/ + return remain_pos + 10; +} + static CURLcode mqtt_connect(struct Curl_easy *data) { CURLcode result = CURLE_OK; - const size_t client_id_offset = 14; - const size_t packetlen = client_id_offset + MQTT_CLIENTID_LEN; + int pos = 0; + int rc = 0; + /*remain length*/ + int remain_pos = 0; + char remain[4] = {0}; + size_t packetlen = 0; + size_t payloadlen = 0; + size_t start_user = 0; + size_t start_pwd = 0; char client_id[MQTT_CLIENTID_LEN + 1] = "curl"; const size_t clen = strlen("curl"); - char packet[32] = { - MQTT_MSG_CONNECT, /* packet type */ - 0x00, /* remaining length */ - 0x00, 0x04, /* protocol length */ - 'M','Q','T','T', /* protocol name */ - 0x04, /* protocol level */ - 0x02, /* CONNECT flag: CleanSession */ - 0x00, 0x3c, /* keep-alive 0 = disabled */ - 0x00, 0x00 /* payload1 length */ - }; - packet[1] = (packetlen - 2) & 0x7f; - packet[client_id_offset - 1] = MQTT_CLIENTID_LEN; + char *packet = NULL; + + /* extracting username from request */ + const char *username = data->state.aptr.user ? + data->state.aptr.user : ""; + const size_t ulen = strlen(username); + /* extracting password from request */ + const char *passwd = data->state.aptr.passwd ? + data->state.aptr.passwd : ""; + const size_t plen = strlen(passwd); + + payloadlen = ulen + plen + MQTT_CLIENTID_LEN + 2; + /* The plus 2 are for the MSB and LSB describing the length of the string to + * be added on the payload. Refer to spec 1.5.2 and 1.5.4 */ + if(ulen) + payloadlen += 2; + if(plen) + payloadlen += 2; + + /* getting how much occupy the remain length */ + remain_pos = mqtt_encode_len(remain, payloadlen + 10); + + /* 10 length of variable header and 1 the first byte of the fixed header */ + packetlen = payloadlen + 10 + remain_pos + 1; + + /* allocating packet */ + if(packetlen > 268435455) + return CURLE_WEIRD_SERVER_REPLY; + packet = malloc(packetlen); + if(!packet) + return CURLE_OUT_OF_MEMORY; + memset(packet, 0, packetlen); + + /* set initial values for CONN pack */ + pos = init_connpack(packet, remain, remain_pos); result = Curl_rand_hex(data, (unsigned char *)&client_id[clen], MQTT_CLIENTID_LEN - clen + 1); - memcpy(&packet[client_id_offset], client_id, MQTT_CLIENTID_LEN); + /* add client id */ + rc = add_client_id(client_id, strlen(client_id), packet, pos + 1); + if(rc) { + failf(data, "Client ID length mismatched: [%lu]", strlen(client_id)); + result = CURLE_WEIRD_SERVER_REPLY; + goto end; + } infof(data, "Using client id '%s'\n", client_id); + + /* position where starts the user payload */ + start_user = pos + 3 + MQTT_CLIENTID_LEN; + /* position where starts the password payload */ + start_pwd = start_user + ulen; + /* if user name was provided, add it to the packet */ + if(ulen) { + start_pwd += 2; + + rc = add_user(username, ulen, + (unsigned char *)packet, start_user, remain_pos); + if(rc) { + failf(data, "Username is too large: [%lu]", ulen); + result = CURLE_WEIRD_SERVER_REPLY; + goto end; + } + } + + /* if passwd was provided, add it to the packet */ + if(plen) { + rc = add_passwd(passwd, plen, packet, start_pwd, remain_pos); + if(rc) { + failf(data, "Password is too large: [%lu]", plen); + result = CURLE_WEIRD_SERVER_REPLY; + goto end; + } + } + if(!result) result = mqtt_send(data, packet, packetlen); + +end: + if(packet) + free(packet); + Curl_safefree(data->state.aptr.user); + Curl_safefree(data->state.aptr.passwd); return result; } @@ -228,21 +393,6 @@ static CURLcode mqtt_get_topic(struct Curl_easy *data, } -static int mqtt_encode_len(char *buf, size_t len) -{ - unsigned char encoded; - int i; - - for(i = 0; (len > 0) && (i<4); i++) { - encoded = len % 0x80; - len /= 0x80; - if(len) - encoded |= 0x80; - buf[i] = encoded; - } - - return i; -} static CURLcode mqtt_subscribe(struct Curl_easy *data) { diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index 5ee4d05b3..d39773354 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -228,8 +228,11 @@ test2064 test2065 test2066 test2067 test2068 test2069 test2070 \ test2071 test2072 test2073 test2074 test2075 test2076 test2077 \ test2078 \ test2080 test2081 \ +\ test2100 \ \ +test2200 test2201 test2202 test2203 test2204 test2205 \ +\ test3000 test3001 test3002 test3003 test3004 test3005 test3006 test3007 \ test3008 test3009 test3010 test3011 test3012 test3013 test3014 test3015 \ test3016 test3017 test3018 test3019 test3020 diff --git a/tests/data/test2200 b/tests/data/test2200 new file mode 100644 index 000000000..64d655009 --- /dev/null +++ b/tests/data/test2200 @@ -0,0 +1,62 @@ + + + +MQTT +MQTT SUBSCRIBE + + + +# +# Server-side + + +hello + + +00 04 31 31 39 30 68 65 6c 6c 6f 5b 4c 46 5d 0a + + +# error 5 - "Connection Refused, not authorized. Wrong data supplied" + +error-CONNACK 5 + + + +# +# Client-side + + +mqtt + + +mqtt + + +MQTT SUBSCRIBE with user and password + + +mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER -u fakeuser:fakepasswd + + + +# +# 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/^(.* 00044d51545404c2003c000c6375726c).*/$1/ + + +client CONNECT 2e 00044d51545404c2003c000c6375726c +server CONNACK 2 20020005 + + +# 8 is CURLE_WEIRD_SERVER_REPLY + +8 + + + diff --git a/tests/data/test2201 b/tests/data/test2201 new file mode 100644 index 000000000..7c804e801 --- /dev/null +++ b/tests/data/test2201 @@ -0,0 +1,50 @@ + + + +MQTT +MQTT PUBLISH + + + +# +# Server-side + + + + + +# +# Client-side + + +mqtt + + +mqtt + + +MQTT PUBLISH with user and password valid + + +mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER -d something -u testuser:testpasswd + + + +# +# 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/^(.* 00044d51545404c2003c000c6375726c).*/$1/ + + +client CONNECT 2e 00044d51545404c2003c000c6375726c +server CONNACK 2 20020000 +client PUBLISH f 000432323031736f6d657468696e67 +client DISCONNECT 0 e000 + + + diff --git a/tests/data/test2202 b/tests/data/test2202 new file mode 100644 index 000000000..9bb037993 --- /dev/null +++ b/tests/data/test2202 @@ -0,0 +1,59 @@ + + + +MQTT +MQTT PUBLISH + + + +# +# Server-side + + + + +# error 5 - "Connection Refused, not authorized. Wrong data supplied" + +error-CONNACK 5 + + + +# +# Client-side + + +mqtt + + +mqtt + + +MQTT PUBLISH with invalid user and password + + +mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER -d something -u fakeuser:fakepasswd + + + +# +# 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/^(.* 00044d51545404c2003c000c6375726c).*/$1/ + + +client CONNECT 2e 00044d51545404c2003c000c6375726c +server CONNACK 2 20020005 + + + +# 8 is CURLE_WEIRD_SERVER_REPLY + +8 + + + diff --git a/tests/data/test2203 b/tests/data/test2203 new file mode 100644 index 000000000..b47ca4d0e --- /dev/null +++ b/tests/data/test2203 @@ -0,0 +1,62 @@ + + + +MQTT +MQTT SUBSCRIBE + + + +# +# Server-side + + +hello + + +00 04 31 31 39 30 68 65 6c 6c 6f 5b 4c 46 5d 0a + + +# error 5 - "Connection Refused, not authorized. No user or password supplied" + +error-CONNACK 5 + + + +# +# Client-side + + +mqtt + + +mqtt + + +MQTT with error in CONNACK + + +mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER + + + +# +# 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 20020005 + + +# 8 is CURLE_WEIRD_SERVER_REPLY + +8 + + + diff --git a/tests/data/test2204 b/tests/data/test2204 new file mode 100644 index 000000000..fb03dd37a --- /dev/null +++ b/tests/data/test2204 @@ -0,0 +1,56 @@ + + + +MQTT +MQTT SUBSCRIBE + + + +# +# Server-side + + +hello + + +00 04 31 31 39 30 68 65 6c 6c 6f 5b 4c 46 5d 0a + + + +# +# Client-side + + +mqtt + + +mqtt + + +MQTT SUBSCRIBE with user and password + + +mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER -u testuser:testpasswd + + + +# +# 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/^(.* 00044d51545404c2003c000c6375726c).*/$1/ + + +client CONNECT 2e 00044d51545404c2003c000c6375726c +server CONNACK 2 20020000 +client SUBSCRIBE 9 000100043232303400 +server SUBACK 3 9003000100 +server PUBLISH c 300c00043232303468656c6c6f0a +server DISCONNECT 0 e000 + + + diff --git a/tests/data/test2205 b/tests/data/test2205 new file mode 100644 index 000000000..9bc8d3207 --- /dev/null +++ b/tests/data/test2205 @@ -0,0 +1,51 @@ + + + +MQTT +MQTT SUBSCRIBE + + + +# +# Server-side + + + + + +# +# Client-side + + +mqtt + + +mqtt + + +MQTT with very long user name + + +user = %repeat[65536 x a]%:fakepasswd + + +mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER -K log/input%TESTNUMBER + + + +# +# 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/^(.* 00044d51545404c2003c000c6375726c).*/$1/ + +# 8 is CURLE_WEIRD_SERVER_REPLY + +8 + + + diff --git a/tests/runtests.pl b/tests/runtests.pl index d018ace52..9afc97df0 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -584,7 +584,7 @@ sub get_disttests { if(($_ =~ /^#/) ||($_ !~ /test/)) { next; } - $disttests .= join("", $_); + $disttests .= $_; } close(D); } diff --git a/tests/server/mqttd.c b/tests/server/mqttd.c index 18339262e..a1b062414 100644 --- a/tests/server/mqttd.c +++ b/tests/server/mqttd.c @@ -490,11 +490,13 @@ static int fixedheader(curl_socket_t fd, static curl_socket_t mqttit(curl_socket_t fd) { - unsigned char buffer[10*1024]; + size_t buff_size = 10*1024; + unsigned char *buffer = NULL; ssize_t rc; unsigned char byte; unsigned short packet_id; size_t payload_len; + size_t client_id_length; unsigned int topic_len; size_t remaining_length = 0; size_t bytes = 0; /* remaining length field size in bytes */ @@ -502,6 +504,7 @@ static curl_socket_t mqttit(curl_socket_t fd) long testno; FILE *stream = NULL; + static const char protocol[7] = { 0x00, 0x04, /* protocol length */ 'M','Q','T','T', /* protocol name */ @@ -518,12 +521,36 @@ static curl_socket_t mqttit(curl_socket_t fd) if(testno) logmsg("Found test number %ld", testno); + buffer = malloc(buff_size); + if(!buffer) { + logmsg("Out of memory, unable to allocate buffer"); + goto end; + } + do { + unsigned char usr_flag = 0x80; + unsigned char passwd_flag = 0x40; + unsigned char conn_flags; + const size_t client_id_offset = 12; + size_t start_usr; + size_t start_passwd; + /* get the fixed header */ rc = fixedheader(fd, &byte, &remaining_length, &bytes); if(rc) break; + + if(remaining_length >= buff_size) { + buff_size = remaining_length; + buffer = realloc(buffer, buff_size); + if(!buffer) { + logmsg("Failed realloc of size %lu", buff_size); + goto end; + } + } + if(remaining_length) { + /* reading variable header and payload into buffer */ rc = sread(fd, (char *)buffer, remaining_length); if(rc > 0) { logmsg("READ %d bytes", rc); @@ -540,19 +567,40 @@ static curl_socket_t mqttit(curl_socket_t fd) goto end; } /* ignore the connect flag byte and two keepalive bytes */ - payload_len = (buffer[10] << 8) | buffer[11]; + /* first part of the payload is the client ID */ + client_id_length = payload_len; + + /* checking if user and password flags were set */ + conn_flags = buffer[7]; + + start_usr = client_id_offset + payload_len; + if(usr_flag == (unsigned char)(conn_flags & usr_flag)) { + logmsg("User flag is present in CONN flag"); + payload_len += (buffer[start_usr] << 8) | buffer[start_usr + 1]; + payload_len += 2; /* MSB and LSB for user length */ + } + + start_passwd = client_id_offset + payload_len; + if(passwd_flag == (char)(conn_flags & passwd_flag)) { + logmsg("Password flag is present in CONN flags"); + payload_len += (buffer[start_passwd] << 8) | buffer[start_passwd + 1]; + payload_len += 2; /* MSB and LSB for password length */ + } + + /* check the length of the payload */ if((ssize_t)payload_len != (rc - 12)) { logmsg("Payload length mismatch, expected %x got %x", rc - 12, payload_len); goto end; } - else if((payload_len + 1) > MAX_CLIENT_ID_LENGTH) { + /* check the length of the client ID */ + else if((client_id_length + 1) > MAX_CLIENT_ID_LENGTH) { logmsg("Too large client id"); goto end; } - memcpy(client_id, &buffer[12], payload_len); - client_id[payload_len] = 0; + memcpy(client_id, &buffer[12], client_id_length); + client_id[client_id_length] = 0; logmsg("MQTT client connect accepted: %s", client_id); @@ -650,6 +698,8 @@ static curl_socket_t mqttit(curl_socket_t fd) } while(1); end: + if(buffer) + free(buffer); if(dump) fclose(dump); if(stream)