]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
mqtt: add support for username and password
authorGealber Morales <mbasalo@nauta.cu>
Fri, 4 Jun 2021 07:25:38 +0000 (09:25 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Sat, 12 Jun 2021 21:50:13 +0000 (23:50 +0200)
Minor-edits-by: Daniel Stenberg
Added test 2200 to 2205

Closes #7243

lib/mqtt.c
tests/data/Makefile.inc
tests/data/test2200 [new file with mode: 0644]
tests/data/test2201 [new file with mode: 0644]
tests/data/test2202 [new file with mode: 0644]
tests/data/test2203 [new file with mode: 0644]
tests/data/test2204 [new file with mode: 0644]
tests/data/test2205 [new file with mode: 0644]
tests/runtests.pl
tests/server/mqttd.c

index d49a5f1cc82c9ee6dc36307c7d421477427cc70c..9a3ca222f14e901fa30355c63d2ffa6aa2c6c595 100644 (file)
@@ -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)
 {
index 5ee4d05b3c2ba19d0e0068ac44c5e06f3e5160cc..d39773354a6340c08a48ff2b6640ff0e72f30293 100644 (file)
@@ -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 (file)
index 0000000..64d6550
--- /dev/null
@@ -0,0 +1,62 @@
+<testcase>
+<info>
+<keywords>
+MQTT
+MQTT SUBSCRIBE
+</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>
+
+# error 5 - "Connection Refused, not authorized. Wrong data supplied"
+<servercmd>
+error-CONNACK 5
+</servercmd>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+mqtt
+</features>
+<server>
+mqtt
+</server>
+<name>
+MQTT SUBSCRIBE with user and password
+</name>
+<command option="binary-trace">
+mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER -u fakeuser:fakepasswd
+</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/^(.* 00044d51545404c2003c000c6375726c).*/$1/
+</strippart>
+<protocol>
+client CONNECT 2e 00044d51545404c2003c000c6375726c
+server CONNACK 2 20020005
+</protocol>
+
+# 8 is CURLE_WEIRD_SERVER_REPLY
+<errorcode>
+8
+</errorcode>
+</verify>
+</testcase>
diff --git a/tests/data/test2201 b/tests/data/test2201
new file mode 100644 (file)
index 0000000..7c804e8
--- /dev/null
@@ -0,0 +1,50 @@
+<testcase>
+<info>
+<keywords>
+MQTT
+MQTT PUBLISH
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data>
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+mqtt
+</features>
+<server>
+mqtt
+</server>
+<name>
+MQTT PUBLISH with user and password valid
+</name>
+<command option="binary-trace">
+mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER -d something -u testuser:testpasswd
+</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/^(.* 00044d51545404c2003c000c6375726c).*/$1/
+</strippart>
+<protocol>
+client CONNECT 2e 00044d51545404c2003c000c6375726c
+server CONNACK 2 20020000
+client PUBLISH f 000432323031736f6d657468696e67
+client DISCONNECT 0 e000
+</protocol>
+</verify>
+</testcase>
diff --git a/tests/data/test2202 b/tests/data/test2202
new file mode 100644 (file)
index 0000000..9bb0379
--- /dev/null
@@ -0,0 +1,59 @@
+<testcase>
+<info>
+<keywords>
+MQTT
+MQTT PUBLISH
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data>
+</data>
+
+# error 5 - "Connection Refused, not authorized. Wrong data supplied"
+<servercmd>
+error-CONNACK 5
+</servercmd>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+mqtt
+</features>
+<server>
+mqtt
+</server>
+<name>
+MQTT PUBLISH with invalid user and password
+</name>
+<command option="binary-trace">
+mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER -d something -u fakeuser:fakepasswd
+</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/^(.* 00044d51545404c2003c000c6375726c).*/$1/
+</strippart>
+<protocol>
+client CONNECT 2e 00044d51545404c2003c000c6375726c
+server CONNACK 2 20020005
+</protocol>
+
+
+# 8 is CURLE_WEIRD_SERVER_REPLY
+<errorcode>
+8
+</errorcode>
+</verify>
+</testcase>
diff --git a/tests/data/test2203 b/tests/data/test2203
new file mode 100644 (file)
index 0000000..b47ca4d
--- /dev/null
@@ -0,0 +1,62 @@
+<testcase>
+<info>
+<keywords>
+MQTT
+MQTT SUBSCRIBE
+</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>
+
+# error 5 - "Connection Refused, not authorized. No user or password supplied"
+<servercmd>
+error-CONNACK 5
+</servercmd>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+mqtt
+</features>
+<server>
+mqtt
+</server>
+<name>
+MQTT with error in CONNACK
+</name>
+<command option="binary-trace">
+mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER
+</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 20020005
+</protocol>
+
+# 8 is CURLE_WEIRD_SERVER_REPLY
+<errorcode>
+8
+</errorcode>
+</verify>
+</testcase>
diff --git a/tests/data/test2204 b/tests/data/test2204
new file mode 100644 (file)
index 0000000..fb03dd3
--- /dev/null
@@ -0,0 +1,56 @@
+<testcase>
+<info>
+<keywords>
+MQTT
+MQTT SUBSCRIBE
+</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>
+mqtt
+</features>
+<server>
+mqtt
+</server>
+<name>
+MQTT SUBSCRIBE with user and password
+</name>
+<command option="binary-trace">
+mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER -u testuser:testpasswd
+</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/^(.* 00044d51545404c2003c000c6375726c).*/$1/
+</strippart>
+<protocol>
+client CONNECT 2e 00044d51545404c2003c000c6375726c
+server CONNACK 2 20020000
+client SUBSCRIBE 9 000100043232303400
+server SUBACK 3 9003000100
+server PUBLISH c 300c00043232303468656c6c6f0a
+server DISCONNECT 0 e000
+</protocol>
+</verify>
+</testcase>
diff --git a/tests/data/test2205 b/tests/data/test2205
new file mode 100644 (file)
index 0000000..9bc8d32
--- /dev/null
@@ -0,0 +1,51 @@
+<testcase>
+<info>
+<keywords>
+MQTT
+MQTT SUBSCRIBE
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data>
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+mqtt
+</features>
+<server>
+mqtt
+</server>
+<name>
+MQTT with very long user name
+</name>
+<file name="log/input%TESTNUMBER">
+user = %repeat[65536 x a]%:fakepasswd
+</file>
+<command option="binary-trace">
+mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER -K log/input%TESTNUMBER
+</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/^(.* 00044d51545404c2003c000c6375726c).*/$1/
+</strippart>
+# 8 is CURLE_WEIRD_SERVER_REPLY
+<errorcode>
+8
+</errorcode>
+</verify>
+</testcase>
index d018ace52c1cc315cbd1a950a943f81bf73581c6..9afc97df004939a8e591157402055fb8b1b111df 100755 (executable)
@@ -584,7 +584,7 @@ sub get_disttests {
         if(($_ =~ /^#/) ||($_ !~ /test/)) {
             next;
         }
-        $disttests .= join("", $_);
+        $disttests .= $_;
     }
     close(D);
 }
index 18339262eb22ec4b03005bf3d41cbf641309c575..a1b062414355595cc471088ddf63c3f11fbf3a60 100644 (file)
@@ -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)