mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2024-12-24 13:42:16 +00:00
1295798139
In MQTTv3.1, protocol name is "MQIsdp" and protocol level is 3. The mqtt converters(mqtt_is_valid and mqtt_field_value) did not work for clients on mqttv3.1 because the mqtt_parse_connect() marked the CONNECT message invalid if either the protocol name is not "MQTT" or the protocol version is other than v3.1.1 or v5.0. To fix it, we have added the mqttv3.1 protocol name and version as part of the checks. This patch fixes the mqtt converters to support mqttv3.1 clients as well (issue #1600). It must be backported to 2.4.
1282 lines
47 KiB
C
1282 lines
47 KiB
C
/*
|
|
* MQTT Protocol
|
|
*
|
|
* Copyright 2020 Baptiste Assmann <bedis9@gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*
|
|
*/
|
|
|
|
#include <haproxy/chunk.h>
|
|
#include <haproxy/mqtt.h>
|
|
|
|
uint8_t mqtt_cpt_flags[MQTT_CPT_ENTRIES] = {
|
|
[MQTT_CPT_INVALID] = 0x00,
|
|
[MQTT_CPT_CONNECT] = 0x00,
|
|
[MQTT_CPT_CONNACK] = 0x00,
|
|
|
|
/* MQTT_CPT_PUBLISH flags can have different values (DUP, QoS, RETAIN), must be
|
|
* check more carefully
|
|
*/
|
|
[MQTT_CPT_PUBLISH] = 0x0F,
|
|
|
|
[MQTT_CPT_PUBACK] = 0x00,
|
|
[MQTT_CPT_PUBREC] = 0x00,
|
|
[MQTT_CPT_PUBREL] = 0x02,
|
|
[MQTT_CPT_PUBCOMP] = 0x00,
|
|
[MQTT_CPT_SUBSCRIBE] = 0x02,
|
|
[MQTT_CPT_SUBACK] = 0x00,
|
|
[MQTT_CPT_UNSUBSCRIBE] = 0x02,
|
|
[MQTT_CPT_UNSUBACK] = 0x00,
|
|
[MQTT_CPT_PINGREQ] = 0x00,
|
|
[MQTT_CPT_PINGRESP] = 0x00,
|
|
[MQTT_CPT_DISCONNECT] = 0x00,
|
|
[MQTT_CPT_AUTH] = 0x00,
|
|
};
|
|
|
|
const struct ist mqtt_fields_string[MQTT_FN_ENTRIES] = {
|
|
[MQTT_FN_INVALID] = IST(""),
|
|
|
|
/* it's MQTT 3.1, 3.1.1 and 5.0, those fields have no unique id, so we use strings */
|
|
[MQTT_FN_FLAGS] = IST("flags"),
|
|
[MQTT_FN_REASON_CODE] = IST("reason_code"), /* MQTT 3.1 and 3.1.1: return_code */
|
|
[MQTT_FN_PROTOCOL_NAME] = IST("protocol_name"),
|
|
[MQTT_FN_PROTOCOL_VERSION] = IST("protocol_version"), /* MQTT 3.1.1: protocol_level */
|
|
[MQTT_FN_CLIENT_IDENTIFIER] = IST("client_identifier"),
|
|
[MQTT_FN_WILL_TOPIC] = IST("will_topic"),
|
|
[MQTT_FN_WILL_PAYLOAD] = IST("will_payload"), /* MQTT 3.1 and 3.1.1: will_message */
|
|
[MQTT_FN_USERNAME] = IST("username"),
|
|
[MQTT_FN_PASSWORD] = IST("password"),
|
|
[MQTT_FN_KEEPALIVE] = IST("keepalive"),
|
|
/* from here, it's MQTT 5.0 only */
|
|
[MQTT_FN_PAYLOAD_FORMAT_INDICATOR] = IST("1"),
|
|
[MQTT_FN_MESSAGE_EXPIRY_INTERVAL] = IST("2"),
|
|
[MQTT_FN_CONTENT_TYPE] = IST("3"),
|
|
[MQTT_FN_RESPONSE_TOPIC] = IST("8"),
|
|
[MQTT_FN_CORRELATION_DATA] = IST("9"),
|
|
[MQTT_FN_SUBSCRIPTION_IDENTIFIER] = IST("11"),
|
|
[MQTT_FN_SESSION_EXPIRY_INTERVAL] = IST("17"),
|
|
[MQTT_FN_ASSIGNED_CLIENT_IDENTIFIER] = IST("18"),
|
|
[MQTT_FN_SERVER_KEEPALIVE] = IST("19"),
|
|
[MQTT_FN_AUTHENTICATION_METHOD] = IST("21"),
|
|
[MQTT_FN_AUTHENTICATION_DATA] = IST("22"),
|
|
[MQTT_FN_REQUEST_PROBLEM_INFORMATION] = IST("23"),
|
|
[MQTT_FN_DELAY_INTERVAL] = IST("24"),
|
|
[MQTT_FN_REQUEST_RESPONSE_INFORMATION] = IST("25"),
|
|
[MQTT_FN_RESPONSE_INFORMATION] = IST("26"),
|
|
[MQTT_FN_SERVER_REFERENCE] = IST("28"),
|
|
[MQTT_FN_REASON_STRING] = IST("31"),
|
|
[MQTT_FN_RECEIVE_MAXIMUM] = IST("33"),
|
|
[MQTT_FN_TOPIC_ALIAS_MAXIMUM] = IST("34"),
|
|
[MQTT_FN_TOPIC_ALIAS] = IST("35"),
|
|
[MQTT_FN_MAXIMUM_QOS] = IST("36"),
|
|
[MQTT_FN_RETAIN_AVAILABLE] = IST("37"),
|
|
[MQTT_FN_USER_PROPERTY] = IST("38"),
|
|
[MQTT_FN_MAXIMUM_PACKET_SIZE] = IST("39"),
|
|
[MQTT_FN_WILDCARD_SUBSCRIPTION_AVAILABLE] = IST("40"),
|
|
[MQTT_FN_SUBSCRIPTION_IDENTIFIERS_AVAILABLE] = IST("41"),
|
|
[MQTT_FN_SHARED_SUBSCRIPTION_AVAILABLE] = IST("42"),
|
|
};
|
|
|
|
/* list of supported capturable field names for each MQTT control packet type */
|
|
const uint64_t mqtt_fields_per_packet[MQTT_CPT_ENTRIES] = {
|
|
[MQTT_CPT_INVALID] = 0,
|
|
|
|
[MQTT_CPT_CONNECT] = MQTT_FN_BIT_PROTOCOL_NAME | MQTT_FN_BIT_PROTOCOL_VERSION |
|
|
MQTT_FN_BIT_FLAGS | MQTT_FN_BIT_KEEPALIVE |
|
|
MQTT_FN_BIT_SESSION_EXPIRY_INTERVAL | MQTT_FN_BIT_RECEIVE_MAXIMUM |
|
|
MQTT_FN_BIT_MAXIMUM_PACKET_SIZE | MQTT_FN_BIT_TOPIC_ALIAS_MAXIMUM |
|
|
MQTT_FN_BIT_REQUEST_RESPONSE_INFORMATION | MQTT_FN_BIT_REQUEST_PROBLEM_INFORMATION |
|
|
MQTT_FN_BIT_USER_PROPERTY | MQTT_FN_BIT_AUTHENTICATION_METHOD |
|
|
MQTT_FN_BIT_AUTHENTICATION_DATA | MQTT_FN_BIT_CLIENT_IDENTIFIER |
|
|
MQTT_FN_BIT_DELAY_INTERVAL | MQTT_FN_BIT_PAYLOAD_FORMAT_INDICATOR |
|
|
MQTT_FN_BIT_MESSAGE_EXPIRY_INTERVAL | MQTT_FN_BIT_CONTENT_TYPE |
|
|
MQTT_FN_BIT_RESPONSE_TOPIC | MQTT_FN_BIT_CORRELATION_DATA |
|
|
MQTT_FN_BIT_USER_PROPERTY | MQTT_FN_BIT_WILL_TOPIC |
|
|
MQTT_FN_BIT_WILL_PAYLOAD | MQTT_FN_BIT_USERNAME |
|
|
MQTT_FN_BIT_PASSWORD,
|
|
|
|
[MQTT_CPT_CONNACK] = MQTT_FN_BIT_FLAGS | MQTT_FN_BIT_PROTOCOL_VERSION |
|
|
MQTT_FN_BIT_REASON_CODE | MQTT_FN_BIT_SESSION_EXPIRY_INTERVAL |
|
|
MQTT_FN_BIT_RECEIVE_MAXIMUM | MQTT_FN_BIT_MAXIMUM_QOS |
|
|
MQTT_FN_BIT_RETAIN_AVAILABLE | MQTT_FN_BIT_MAXIMUM_PACKET_SIZE |
|
|
MQTT_FN_BIT_ASSIGNED_CLIENT_IDENTIFIER | MQTT_FN_BIT_TOPIC_ALIAS_MAXIMUM |
|
|
MQTT_FN_BIT_REASON_STRING | MQTT_FN_BIT_WILDCARD_SUBSCRIPTION_AVAILABLE |
|
|
MQTT_FN_BIT_SUBSCRIPTION_IDENTIFIERS_AVAILABLE| MQTT_FN_BIT_SHARED_SUBSCRIPTION_AVAILABLE |
|
|
MQTT_FN_BIT_SERVER_KEEPALIVE | MQTT_FN_BIT_RESPONSE_INFORMATION |
|
|
MQTT_FN_BIT_SERVER_REFERENCE | MQTT_FN_BIT_USER_PROPERTY |
|
|
MQTT_FN_BIT_AUTHENTICATION_METHOD | MQTT_FN_BIT_AUTHENTICATION_DATA,
|
|
|
|
[MQTT_CPT_PUBLISH] = MQTT_FN_BIT_PAYLOAD_FORMAT_INDICATOR | MQTT_FN_BIT_MESSAGE_EXPIRY_INTERVAL |
|
|
MQTT_FN_BIT_CONTENT_TYPE | MQTT_FN_BIT_RESPONSE_TOPIC |
|
|
MQTT_FN_BIT_CORRELATION_DATA | MQTT_FN_BIT_SUBSCRIPTION_IDENTIFIER |
|
|
MQTT_FN_BIT_TOPIC_ALIAS | MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_PUBACK] = MQTT_FN_BIT_REASON_CODE | MQTT_FN_BIT_REASON_STRING | MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_PUBREC] = MQTT_FN_BIT_REASON_CODE | MQTT_FN_BIT_REASON_STRING | MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_PUBREL] = MQTT_FN_BIT_REASON_CODE | MQTT_FN_BIT_REASON_STRING | MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_PUBCOMP] = MQTT_FN_BIT_REASON_CODE | MQTT_FN_BIT_REASON_STRING | MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_SUBSCRIBE] = MQTT_FN_BIT_SUBSCRIPTION_IDENTIFIER | MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_SUBACK] = MQTT_FN_BIT_REASON_STRING | MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_UNSUBSCRIBE] = MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_UNSUBACK] = MQTT_FN_BIT_REASON_STRING | MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_PINGREQ] = 0,
|
|
|
|
[MQTT_CPT_PINGRESP] = 0,
|
|
|
|
[MQTT_CPT_DISCONNECT] = MQTT_FN_BIT_REASON_CODE | MQTT_FN_BIT_SESSION_EXPIRY_INTERVAL |
|
|
MQTT_FN_BIT_SERVER_REFERENCE | MQTT_FN_BIT_REASON_STRING |
|
|
MQTT_FN_BIT_USER_PROPERTY,
|
|
|
|
[MQTT_CPT_AUTH] = MQTT_FN_BIT_AUTHENTICATION_METHOD | MQTT_FN_BIT_AUTHENTICATION_DATA |
|
|
MQTT_FN_BIT_REASON_STRING | MQTT_FN_BIT_USER_PROPERTY,
|
|
};
|
|
|
|
/* Checks the first byte of a message to read the fixed header and extract the
|
|
* packet type and flags. <parser> is supposed to point to the fix header byte.
|
|
*
|
|
* Fix header looks like:
|
|
* +-------+-----------+-----------+-----------+---------+----------+----------+---------+------------+
|
|
* | bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
|
* +-------+-----------+-----------+-----------+---------+----------+----------+---------+------------+
|
|
* | field | MQTT Control Packet Type | Flags specific to each Control Packet type |
|
|
* +-------+---------------------------------------------+--------------------------------------------+
|
|
*
|
|
* On success, <ptk> is updated with the packet type and flags and the new parser
|
|
* state is returned. On error, IST_NULL is returned.
|
|
*/
|
|
static inline struct ist mqtt_read_fixed_hdr(struct ist parser, struct mqtt_pkt *pkt)
|
|
{
|
|
uint8_t type = (uint8_t)*istptr(parser);
|
|
uint8_t ptype = (type & 0xF0) >> 4;
|
|
uint8_t flags = type & 0x0F;
|
|
|
|
if (ptype == MQTT_CPT_INVALID || ptype >= MQTT_CPT_ENTRIES || flags != mqtt_cpt_flags[ptype])
|
|
return IST_NULL;
|
|
|
|
pkt->fixed_hdr.type = ptype;
|
|
pkt->fixed_hdr.flags = flags;
|
|
return istnext(parser);
|
|
}
|
|
|
|
/* Reads a one byte integer. more information here :
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901007
|
|
*
|
|
* <parser> is supposed to point to the first byte of the integer. On success
|
|
* the integer is stored in <*i>, if provided, and the new parser state is returned. On
|
|
* error, IST_NULL is returned.
|
|
*/
|
|
static inline struct ist mqtt_read_1byte_int(struct ist parser, uint8_t *i)
|
|
{
|
|
if (istlen(parser) < 1)
|
|
return IST_NULL;
|
|
if (i)
|
|
*i = (uint8_t)*istptr(parser);
|
|
parser = istnext(parser);
|
|
return parser;
|
|
}
|
|
|
|
/* Reads a two byte integer. more information here :
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901008
|
|
*
|
|
* <parser> is supposed to point to the first byte of the integer. On success
|
|
* the integer is stored in <*i>, if provided, and the new parser state is returned. On
|
|
* error, IST_NULL is returned.
|
|
*/
|
|
static inline struct ist mqtt_read_2byte_int(struct ist parser, uint16_t *i)
|
|
{
|
|
if (istlen(parser) < 2)
|
|
return IST_NULL;
|
|
if (i) {
|
|
*i = (uint8_t)*istptr(parser) << 8;
|
|
*i += (uint8_t)*(istptr(parser) + 1);
|
|
}
|
|
parser = istadv(parser, 2);
|
|
return parser;
|
|
}
|
|
|
|
/* Reads a four byte integer. more information here :
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901009
|
|
*
|
|
* <parser> is supposed to point to the first byte of the integer. On success
|
|
* the integer is stored in <*i>, if provided, and the new parser state is returned. On
|
|
* error, IST_NULL is returned.
|
|
*/
|
|
static inline struct ist mqtt_read_4byte_int(struct ist parser, uint32_t *i)
|
|
{
|
|
if (istlen(parser) < 4)
|
|
return IST_NULL;
|
|
if (i) {
|
|
*i = (uint8_t)*istptr(parser) << 24;
|
|
*i += (uint8_t)*(istptr(parser) + 1) << 16;
|
|
*i += (uint8_t)*(istptr(parser) + 2) << 8;
|
|
*i += (uint8_t)*(istptr(parser) + 3);
|
|
}
|
|
parser = istadv(parser, 4);
|
|
return parser;
|
|
}
|
|
|
|
/* Reads a variable byte integer. more information here :
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718023
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901011
|
|
*
|
|
* It is encoded using a variable length encoding scheme which uses a single
|
|
* byte for values up to 127. Larger values are handled as follows. The least
|
|
* significant seven bits of each byte encode the data, and the most significant
|
|
* bit is used to indicate that there are following bytes in the representation.
|
|
* Thus each byte encodes 128 values and a "continuation bit".
|
|
*
|
|
* The maximum number of bytes in the Remaining Length field is four
|
|
* (MQTT_REMAINING_LENGHT_MAX_SIZE).
|
|
*
|
|
* <parser> is supposed to point to the first byte of the integer. On success
|
|
* the integer is stored in <*i> and the new parser state is returned. On
|
|
* error, IST_NULL is returned.
|
|
*/
|
|
static inline struct ist mqtt_read_varint(struct ist parser, uint32_t *i)
|
|
{
|
|
int off, m;
|
|
|
|
off = m = 0;
|
|
if (i)
|
|
*i = 0;
|
|
for (off = 0; off < MQTT_REMAINING_LENGHT_MAX_SIZE && istlen(parser); off++) {
|
|
uint8_t byte = (uint8_t)*istptr(parser);
|
|
|
|
if (i) {
|
|
*i += (byte & 127) << m;
|
|
m += 7; /* preparing <m> for next byte */
|
|
}
|
|
parser = istnext(parser);
|
|
|
|
/* we read the latest byte for the remaining length field */
|
|
if (byte <= 127)
|
|
break;
|
|
}
|
|
|
|
if (off == MQTT_REMAINING_LENGHT_MAX_SIZE)
|
|
return IST_NULL;
|
|
return parser;
|
|
}
|
|
|
|
/* Reads a MQTT string. more information here :
|
|
* http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718016
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901010
|
|
*
|
|
* In MQTT, strings are prefixed by their size, encoded over 2 bytes:
|
|
* byte 1: length MSB
|
|
* byte 2: length LSB
|
|
* byte 3: string
|
|
* ...
|
|
*
|
|
* string size is MSB * 256 + LSB
|
|
*
|
|
* <parser> is supposed to point to the first byte of the string. On success the
|
|
* string is stored in <*str>, if provided, and the new parser state is
|
|
* returned. On error, IST_NULL is returned.
|
|
*/
|
|
static inline struct ist mqtt_read_string(struct ist parser, struct ist *str)
|
|
{
|
|
uint16_t len = 0;
|
|
|
|
/* read and compute the string length */
|
|
if (istlen(parser) < 2)
|
|
goto error;
|
|
|
|
parser = mqtt_read_2byte_int(parser, &len);
|
|
if (!isttest(parser) || istlen(parser) < len)
|
|
goto error;
|
|
|
|
if (str) {
|
|
str->ptr = istptr(parser);
|
|
str->len = len;
|
|
}
|
|
|
|
return istadv(parser, len);
|
|
|
|
error:
|
|
return IST_NULL;
|
|
}
|
|
|
|
/* Helper function to convert a unsigned integer to a string. The result is
|
|
* written in <buf>. On success, the written size is returned, otherwise, on
|
|
* error, 0 is returned.
|
|
*/
|
|
static inline size_t mqtt_uint2str(struct buffer *buf, uint32_t i)
|
|
{
|
|
char *end;
|
|
|
|
end = ultoa_o(i, buf->area, buf->size);
|
|
if (!end)
|
|
return 0;
|
|
buf->data = end - buf->area;
|
|
return buf->data;
|
|
}
|
|
|
|
/* Extracts the value of a <fieldname_id> of type <type> from a given MQTT
|
|
* message <msg>. IST_NULL is returned if an error occurred while parsing or if
|
|
* the field could not be found. If more data are required, the message with a
|
|
* length set to 0 is returned. If the field is found, the response is returned
|
|
* as a struct ist.
|
|
*/
|
|
struct ist mqtt_field_value(struct ist msg, int type, int fieldname_id)
|
|
{
|
|
struct buffer *trash = get_trash_chunk();
|
|
struct mqtt_pkt mpkt;
|
|
struct ist res;
|
|
|
|
switch (mqtt_validate_message(msg, &mpkt)) {
|
|
case MQTT_VALID_MESSAGE:
|
|
if (mpkt.fixed_hdr.type != type)
|
|
goto not_found_or_invalid;
|
|
break;
|
|
case MQTT_NEED_MORE_DATA:
|
|
goto need_more;
|
|
case MQTT_INVALID_MESSAGE:
|
|
goto not_found_or_invalid;
|
|
}
|
|
|
|
switch (type) {
|
|
case MQTT_CPT_CONNECT:
|
|
switch (fieldname_id) {
|
|
case MQTT_FN_FLAGS:
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.flags))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_PROTOCOL_NAME:
|
|
if (!istlen(mpkt.data.connect.var_hdr.protocol_name))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.var_hdr.protocol_name;
|
|
goto end;
|
|
|
|
case MQTT_FN_PROTOCOL_VERSION:
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.protocol_version))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_CLIENT_IDENTIFIER:
|
|
if (!istlen(mpkt.data.connect.payload.client_identifier))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.payload.client_identifier;
|
|
goto end;
|
|
|
|
case MQTT_FN_WILL_TOPIC:
|
|
if (!istlen(mpkt.data.connect.payload.will_topic))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.payload.will_topic;
|
|
goto end;
|
|
|
|
case MQTT_FN_WILL_PAYLOAD:
|
|
if (!istlen(mpkt.data.connect.payload.will_payload))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.payload.will_payload;
|
|
goto end;
|
|
|
|
case MQTT_FN_USERNAME:
|
|
if (!istlen(mpkt.data.connect.payload.username))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.payload.username;
|
|
goto end;
|
|
|
|
case MQTT_FN_PASSWORD:
|
|
if (!istlen(mpkt.data.connect.payload.password))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.payload.password;
|
|
goto end;
|
|
|
|
case MQTT_FN_KEEPALIVE:
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.keepalive))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_PAYLOAD_FORMAT_INDICATOR:
|
|
if ((mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0) ||
|
|
!(mpkt.data.connect.var_hdr.flags & MQTT_CONNECT_FL_WILL))
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.payload.will_props.payload_format_indicator))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_MESSAGE_EXPIRY_INTERVAL:
|
|
if ((mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0) ||
|
|
!(mpkt.data.connect.var_hdr.flags & MQTT_CONNECT_FL_WILL))
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.payload.will_props.message_expiry_interval))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_CONTENT_TYPE:
|
|
if ((mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0) ||
|
|
!(mpkt.data.connect.var_hdr.flags & MQTT_CONNECT_FL_WILL))
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connect.payload.will_props.content_type))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.payload.will_props.content_type;
|
|
goto end;
|
|
|
|
case MQTT_FN_RESPONSE_TOPIC:
|
|
if ((mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0) ||
|
|
!(mpkt.data.connect.var_hdr.flags & MQTT_CONNECT_FL_WILL))
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connect.payload.will_props.response_topic))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.payload.will_props.response_topic;
|
|
goto end;
|
|
|
|
case MQTT_FN_CORRELATION_DATA:
|
|
if ((mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0) ||
|
|
!(mpkt.data.connect.var_hdr.flags & MQTT_CONNECT_FL_WILL))
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connect.payload.will_props.correlation_data))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.payload.will_props.correlation_data;
|
|
goto end;
|
|
|
|
case MQTT_FN_SESSION_EXPIRY_INTERVAL:
|
|
if (mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.props.session_expiry_interval))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_AUTHENTICATION_METHOD:
|
|
if (mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connect.var_hdr.props.authentication_method))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.var_hdr.props.authentication_method;
|
|
goto end;
|
|
|
|
case MQTT_FN_AUTHENTICATION_DATA:
|
|
if (mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connect.var_hdr.props.authentication_data))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connect.var_hdr.props.authentication_data;
|
|
goto end;
|
|
|
|
case MQTT_FN_REQUEST_PROBLEM_INFORMATION:
|
|
if (mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.props.request_problem_information))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_DELAY_INTERVAL:
|
|
if ((mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0) ||
|
|
!(mpkt.data.connect.var_hdr.flags & MQTT_CONNECT_FL_WILL))
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.payload.will_props.delay_interval))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_REQUEST_RESPONSE_INFORMATION:
|
|
if (mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.props.request_response_information))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_RECEIVE_MAXIMUM:
|
|
if (mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.props.receive_maximum))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_TOPIC_ALIAS_MAXIMUM:
|
|
if (mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.props.topic_alias_maximum))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_MAXIMUM_PACKET_SIZE:
|
|
if (mpkt.data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connect.var_hdr.props.maximum_packet_size))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
default:
|
|
goto not_found_or_invalid;
|
|
}
|
|
break;
|
|
|
|
case MQTT_CPT_CONNACK:
|
|
switch (fieldname_id) {
|
|
case MQTT_FN_FLAGS:
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.flags))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_REASON_CODE:
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.reason_code))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_PROTOCOL_VERSION:
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.protocol_version))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_SESSION_EXPIRY_INTERVAL:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.session_expiry_interval))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_ASSIGNED_CLIENT_IDENTIFIER:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connack.var_hdr.props.assigned_client_identifier))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connack.var_hdr.props.assigned_client_identifier;
|
|
goto end;
|
|
|
|
case MQTT_FN_SERVER_KEEPALIVE:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.server_keepalive))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_AUTHENTICATION_METHOD:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connack.var_hdr.props.authentication_method))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connack.var_hdr.props.authentication_method;
|
|
goto end;
|
|
|
|
case MQTT_FN_AUTHENTICATION_DATA:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connack.var_hdr.props.authentication_data))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connack.var_hdr.props.authentication_data;
|
|
goto end;
|
|
|
|
case MQTT_FN_RESPONSE_INFORMATION:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connack.var_hdr.props.response_information))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connack.var_hdr.props.response_information;
|
|
goto end;
|
|
|
|
case MQTT_FN_SERVER_REFERENCE:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connack.var_hdr.props.server_reference))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connack.var_hdr.props.server_reference;
|
|
goto end;
|
|
|
|
case MQTT_FN_REASON_STRING:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!istlen(mpkt.data.connack.var_hdr.props.reason_string))
|
|
goto not_found_or_invalid;
|
|
res = mpkt.data.connack.var_hdr.props.reason_string;
|
|
goto end;
|
|
|
|
case MQTT_FN_RECEIVE_MAXIMUM:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.receive_maximum))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_TOPIC_ALIAS_MAXIMUM:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.topic_alias_maximum))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_MAXIMUM_QOS:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.maximum_qos))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_RETAIN_AVAILABLE:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.retain_available))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_MAXIMUM_PACKET_SIZE:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.maximum_packet_size))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_WILDCARD_SUBSCRIPTION_AVAILABLE:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.wildcard_subscription_available))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_SUBSCRIPTION_IDENTIFIERS_AVAILABLE:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.subscription_identifiers_available))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
case MQTT_FN_SHARED_SUBSCRIPTION_AVAILABLE:
|
|
if (mpkt.data.connack.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto not_found_or_invalid;
|
|
if (!mqtt_uint2str(trash, mpkt.data.connack.var_hdr.props.shared_subsription_available))
|
|
goto not_found_or_invalid;
|
|
res = ist2(trash->area, trash->data);
|
|
goto end;
|
|
|
|
default:
|
|
goto not_found_or_invalid;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
goto not_found_or_invalid;
|
|
}
|
|
|
|
end:
|
|
return res;
|
|
|
|
need_more:
|
|
return ist2(istptr(msg), 0);
|
|
|
|
not_found_or_invalid:
|
|
return IST_NULL;
|
|
}
|
|
|
|
/* Parses a CONNECT packet :
|
|
* https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#connect
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901033
|
|
*
|
|
* <parser> should point right after the MQTT fixed header. The remaining length
|
|
* was already checked, thus missing data is an error. On success, the result of
|
|
* the parsing is stored in <mpkt>.
|
|
*
|
|
* Returns:
|
|
* MQTT_INVALID_MESSAGE if the CONNECT message is invalid
|
|
* MQTT_VALID_MESSAGE if the CONNECT message looks valid
|
|
*/
|
|
static int mqtt_parse_connect(struct ist parser, struct mqtt_pkt *mpkt)
|
|
{
|
|
/* The parser length is stored to be sure exactly consumed the announced
|
|
* remaining length. */
|
|
size_t orig_len = istlen(parser);
|
|
int ret = MQTT_INVALID_MESSAGE;
|
|
|
|
/*
|
|
* parsing variable header
|
|
*/
|
|
/* read protocol_name */
|
|
parser = mqtt_read_string(parser, &mpkt->data.connect.var_hdr.protocol_name);
|
|
if (!isttest(parser) || !(isteqi(mpkt->data.connect.var_hdr.protocol_name, ist("MQTT")) || isteqi(mpkt->data.connect.var_hdr.protocol_name, ist("MQIsdp"))))
|
|
goto end;
|
|
|
|
/* read protocol_version */
|
|
parser = mqtt_read_1byte_int(parser, &mpkt->data.connect.var_hdr.protocol_version);
|
|
if (!isttest(parser))
|
|
goto end;
|
|
if (mpkt->data.connect.var_hdr.protocol_version != MQTT_VERSION_3_1 &&
|
|
mpkt->data.connect.var_hdr.protocol_version != MQTT_VERSION_3_1_1 &&
|
|
mpkt->data.connect.var_hdr.protocol_version != MQTT_VERSION_5_0)
|
|
goto end;
|
|
|
|
/* read flags */
|
|
/* bit 1 is 'reserved' and must be set to 0 in CONNECT message flags */
|
|
parser = mqtt_read_1byte_int(parser, &mpkt->data.connect.var_hdr.flags);
|
|
if (!isttest(parser) || (mpkt->data.connect.var_hdr.flags & MQTT_CONNECT_FL_RESERVED))
|
|
goto end;
|
|
|
|
/* if WILL flag must be set to have WILL_QOS flag or WILL_RETAIN set */
|
|
if ((mpkt->data.connect.var_hdr.flags & (MQTT_CONNECT_FL_WILL|MQTT_CONNECT_FL_WILL_QOS|MQTT_CONNECT_FL_WILL_RETAIN)) == MQTT_CONNECT_FL_WILL_QOS)
|
|
goto end;
|
|
|
|
/* read keepalive */
|
|
parser = mqtt_read_2byte_int(parser, &mpkt->data.connect.var_hdr.keepalive);
|
|
if (!isttest(parser))
|
|
goto end;
|
|
|
|
/* read properties, only available in MQTT_VERSION_5_0 */
|
|
if (mpkt->data.connect.var_hdr.protocol_version == MQTT_VERSION_5_0) {
|
|
struct ist props;
|
|
unsigned int user_prop_idx = 0;
|
|
uint64_t fields = 0;
|
|
uint32_t plen = 0;
|
|
|
|
parser = mqtt_read_varint(parser, &plen);
|
|
if (!isttest(parser) || istlen(parser) < plen)
|
|
goto end;
|
|
props = ist2(istptr(parser), plen);
|
|
parser = istadv(parser, props.len);
|
|
|
|
while (istlen(props) > 0) {
|
|
switch (*istptr(props)) {
|
|
case MQTT_PROP_SESSION_EXPIRY_INTERVAL:
|
|
if (fields & MQTT_FN_BIT_SESSION_EXPIRY_INTERVAL)
|
|
goto end;
|
|
props = mqtt_read_4byte_int(istnext(props), &mpkt->data.connect.var_hdr.props.session_expiry_interval);
|
|
fields |= MQTT_FN_BIT_SESSION_EXPIRY_INTERVAL;
|
|
break;
|
|
|
|
case MQTT_PROP_RECEIVE_MAXIMUM:
|
|
if (fields & MQTT_FN_BIT_RECEIVE_MAXIMUM)
|
|
goto end;
|
|
props = mqtt_read_2byte_int(istnext(props), &mpkt->data.connect.var_hdr.props.receive_maximum);
|
|
/* cannot be 0 */
|
|
if (!mpkt->data.connect.var_hdr.props.receive_maximum)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_RECEIVE_MAXIMUM;
|
|
break;
|
|
|
|
case MQTT_PROP_MAXIMUM_PACKET_SIZE:
|
|
if (fields & MQTT_FN_BIT_MAXIMUM_PACKET_SIZE)
|
|
goto end;
|
|
props = mqtt_read_4byte_int(istnext(props), &mpkt->data.connect.var_hdr.props.maximum_packet_size);
|
|
/* cannot be 0 */
|
|
if (!mpkt->data.connect.var_hdr.props.maximum_packet_size)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_MAXIMUM_PACKET_SIZE;
|
|
break;
|
|
|
|
case MQTT_PROP_TOPIC_ALIAS_MAXIMUM:
|
|
if (fields & MQTT_FN_BIT_TOPIC_ALIAS)
|
|
goto end;
|
|
props = mqtt_read_2byte_int(istnext(props), &mpkt->data.connect.var_hdr.props.topic_alias_maximum);
|
|
fields |= MQTT_FN_BIT_TOPIC_ALIAS;
|
|
break;
|
|
|
|
case MQTT_PROP_REQUEST_RESPONSE_INFORMATION:
|
|
if (fields & MQTT_FN_BIT_REQUEST_RESPONSE_INFORMATION)
|
|
goto end;
|
|
props = mqtt_read_1byte_int(istnext(props), &mpkt->data.connect.var_hdr.props.request_response_information);
|
|
/* can have only 2 values: 0 or 1 */
|
|
if (mpkt->data.connect.var_hdr.props.request_response_information > 1)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_REQUEST_RESPONSE_INFORMATION;
|
|
break;
|
|
|
|
case MQTT_PROP_REQUEST_PROBLEM_INFORMATION:
|
|
if (fields & MQTT_FN_BIT_REQUEST_PROBLEM_INFORMATION)
|
|
goto end;
|
|
props = mqtt_read_1byte_int(istnext(props), &mpkt->data.connect.var_hdr.props.request_problem_information);
|
|
/* can have only 2 values: 0 or 1 */
|
|
if (mpkt->data.connect.var_hdr.props.request_problem_information > 1)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_REQUEST_PROBLEM_INFORMATION;
|
|
break;
|
|
|
|
case MQTT_PROP_USER_PROPERTIES:
|
|
/* if we reached MQTT_PROP_USER_PROPERTY_ENTRIES already, then
|
|
* we start writing over the first property */
|
|
if (user_prop_idx >= MQTT_PROP_USER_PROPERTY_ENTRIES)
|
|
user_prop_idx = 0;
|
|
|
|
/* read user property name and value */
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connect.var_hdr.props.user_props[user_prop_idx].name);
|
|
if (!isttest(props))
|
|
goto end;
|
|
props = mqtt_read_string(props, &mpkt->data.connect.var_hdr.props.user_props[user_prop_idx].value);
|
|
++user_prop_idx;
|
|
break;
|
|
|
|
case MQTT_PROP_AUTHENTICATION_METHOD:
|
|
if (fields & MQTT_FN_BIT_AUTHENTICATION_METHOD)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connect.var_hdr.props.authentication_method);
|
|
fields |= MQTT_FN_BIT_AUTHENTICATION_METHOD;
|
|
break;
|
|
|
|
case MQTT_PROP_AUTHENTICATION_DATA:
|
|
if (fields & MQTT_FN_BIT_AUTHENTICATION_DATA)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connect.var_hdr.props.authentication_data);
|
|
fields |= MQTT_FN_BIT_AUTHENTICATION_DATA;
|
|
break;
|
|
|
|
default:
|
|
goto end;
|
|
}
|
|
|
|
if (!isttest(props))
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
/* cannot have auth data without auth method */
|
|
if (!istlen(mpkt->data.connect.var_hdr.props.authentication_method) &&
|
|
istlen(mpkt->data.connect.var_hdr.props.authentication_data))
|
|
goto end;
|
|
|
|
/* parsing payload
|
|
*
|
|
* Content of payload is related to flags parsed above and the field order is pre-defined:
|
|
* Client Identifier, Will Topic, Will Message, User Name, Password
|
|
*/
|
|
/* read client identifier */
|
|
parser = mqtt_read_string(parser, &mpkt->data.connect.payload.client_identifier);
|
|
if (!isttest(parser))
|
|
goto end;
|
|
|
|
/* read Will Properties, for MQTT v5 only
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901060
|
|
*/
|
|
if ((mpkt->data.connect.var_hdr.protocol_version == MQTT_VERSION_5_0) &&
|
|
(mpkt->data.connect.var_hdr.flags & MQTT_CONNECT_FL_WILL)) {
|
|
struct ist props;
|
|
unsigned int user_prop_idx = 0;
|
|
uint64_t fields = 0;
|
|
uint32_t plen = 0;
|
|
|
|
parser = mqtt_read_varint(parser, &plen);
|
|
if (!isttest(parser) || istlen(parser) < plen)
|
|
goto end;
|
|
props = ist2(istptr(parser), plen);
|
|
parser = istadv(parser, props.len);
|
|
|
|
while (istlen(props) > 0) {
|
|
switch (*istptr(props)) {
|
|
case MQTT_PROP_WILL_DELAY_INTERVAL:
|
|
if (fields & MQTT_FN_BIT_DELAY_INTERVAL)
|
|
goto end;
|
|
props = mqtt_read_4byte_int(istnext(props), &mpkt->data.connect.payload.will_props.delay_interval);
|
|
fields |= MQTT_FN_BIT_DELAY_INTERVAL;
|
|
break;
|
|
|
|
case MQTT_PROP_PAYLOAD_FORMAT_INDICATOR:
|
|
if (fields & MQTT_FN_BIT_PAYLOAD_FORMAT_INDICATOR)
|
|
goto end;
|
|
props = mqtt_read_1byte_int(istnext(props), &mpkt->data.connect.payload.will_props.payload_format_indicator);
|
|
/* can have only 2 values: 0 or 1 */
|
|
if (mpkt->data.connect.payload.will_props.payload_format_indicator > 1)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_PAYLOAD_FORMAT_INDICATOR;
|
|
break;
|
|
|
|
case MQTT_PROP_MESSAGE_EXPIRY_INTERVAL:
|
|
if (fields & MQTT_FN_BIT_MESSAGE_EXPIRY_INTERVAL)
|
|
goto end;
|
|
props = mqtt_read_4byte_int(istnext(props), &mpkt->data.connect.payload.will_props.message_expiry_interval);
|
|
fields |= MQTT_FN_BIT_MESSAGE_EXPIRY_INTERVAL;
|
|
break;
|
|
|
|
case MQTT_PROP_CONTENT_TYPE:
|
|
if (fields & MQTT_FN_BIT_CONTENT_TYPE)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connect.payload.will_props.content_type);
|
|
fields |= MQTT_FN_BIT_CONTENT_TYPE;
|
|
break;
|
|
|
|
case MQTT_PROP_RESPONSE_TOPIC:
|
|
if (fields & MQTT_FN_BIT_RESPONSE_TOPIC)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connect.payload.will_props.response_topic);
|
|
fields |= MQTT_FN_BIT_RESPONSE_TOPIC;
|
|
break;
|
|
|
|
case MQTT_PROP_CORRELATION_DATA:
|
|
if (fields & MQTT_FN_BIT_CORRELATION_DATA)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connect.payload.will_props.correlation_data);
|
|
fields |= MQTT_FN_BIT_CORRELATION_DATA;
|
|
break;
|
|
|
|
case MQTT_PROP_USER_PROPERTIES:
|
|
/* if we reached MQTT_PROP_USER_PROPERTY_ENTRIES already, then
|
|
* we start writing over the first property */
|
|
if (user_prop_idx >= MQTT_PROP_USER_PROPERTY_ENTRIES)
|
|
user_prop_idx = 0;
|
|
|
|
/* read user property name and value */
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connect.payload.will_props.user_props[user_prop_idx].name);
|
|
if (!isttest(props))
|
|
goto end;
|
|
props = mqtt_read_string(props, &mpkt->data.connect.payload.will_props.user_props[user_prop_idx].value);
|
|
++user_prop_idx;
|
|
break;
|
|
|
|
default:
|
|
goto end;
|
|
}
|
|
|
|
if (!isttest(props))
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
/* read Will Topic and Will Message (MQTT 3.1.1) or Payload (MQTT 5.0) */
|
|
if (mpkt->data.connect.var_hdr.flags & MQTT_CONNECT_FL_WILL) {
|
|
parser = mqtt_read_string(parser, &mpkt->data.connect.payload.will_topic);
|
|
if (!isttest(parser))
|
|
goto end;
|
|
parser = mqtt_read_string(parser, &mpkt->data.connect.payload.will_payload);
|
|
if (!isttest(parser))
|
|
goto end;
|
|
}
|
|
|
|
/* read User Name */
|
|
if (mpkt->data.connect.var_hdr.flags & MQTT_CONNECT_FL_USERNAME) {
|
|
parser = mqtt_read_string(parser, &mpkt->data.connect.payload.username);
|
|
if (!isttest(parser))
|
|
goto end;
|
|
}
|
|
|
|
/* read Password */
|
|
if (mpkt->data.connect.var_hdr.flags & MQTT_CONNECT_FL_PASSWORD) {
|
|
parser = mqtt_read_string(parser, &mpkt->data.connect.payload.password);
|
|
if (!isttest(parser))
|
|
goto end;
|
|
}
|
|
|
|
if ((orig_len - istlen(parser)) == mpkt->fixed_hdr.remaining_length)
|
|
ret = MQTT_VALID_MESSAGE;
|
|
|
|
end:
|
|
return ret;
|
|
}
|
|
|
|
/* Parses a CONNACK packet :
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718033
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901074
|
|
*
|
|
* <parser> should point right after the MQTT fixed header. The remaining length
|
|
* was already checked, thus missing data is an error. On success, the result of
|
|
* the parsing is stored in <mpkt>.
|
|
*
|
|
* Returns:
|
|
* MQTT_INVALID_MESSAGE if the CONNECT message is invalid
|
|
* MQTT_VALID_MESSAGE if the CONNECT message looks valid
|
|
*/
|
|
static int mqtt_parse_connack(struct ist parser, struct mqtt_pkt *mpkt)
|
|
{
|
|
/* The parser length is stored to be sure exactly consumed the announced
|
|
* remaining length. */
|
|
size_t orig_len = istlen(parser);
|
|
int ret = MQTT_INVALID_MESSAGE;
|
|
|
|
if (istlen(parser) < 2)
|
|
goto end;
|
|
else if (istlen(parser) == 2)
|
|
mpkt->data.connack.var_hdr.protocol_version = MQTT_VERSION_3_1_1;
|
|
else
|
|
mpkt->data.connack.var_hdr.protocol_version = MQTT_VERSION_5_0;
|
|
|
|
/*
|
|
* parsing variable header
|
|
*/
|
|
/* read flags */
|
|
/* bits 7 to 1 on flags are reserved and must be 0 */
|
|
parser = mqtt_read_1byte_int(parser, &mpkt->data.connack.var_hdr.flags);
|
|
if (!isttest(parser) || (mpkt->data.connack.var_hdr.flags & 0xFE))
|
|
goto end;
|
|
|
|
/* read reason_code */
|
|
parser = mqtt_read_1byte_int(parser, &mpkt->data.connack.var_hdr.reason_code);
|
|
if (!isttest(parser))
|
|
goto end;
|
|
|
|
/* we can leave here for MQTT 3.1.1 */
|
|
if (mpkt->data.connack.var_hdr.protocol_version == MQTT_VERSION_3_1_1) {
|
|
if ((orig_len - istlen(parser)) == mpkt->fixed_hdr.remaining_length)
|
|
ret = MQTT_VALID_MESSAGE;
|
|
goto end;
|
|
}
|
|
|
|
/* read properties, only available in MQTT_VERSION_5_0 */
|
|
if (mpkt->data.connack.var_hdr.protocol_version == MQTT_VERSION_5_0) {
|
|
struct ist props;
|
|
unsigned int user_prop_idx = 0;
|
|
uint64_t fields = 0;
|
|
uint32_t plen = 0;
|
|
|
|
parser = mqtt_read_varint(parser, &plen);
|
|
if (!isttest(parser) || istlen(parser) < plen)
|
|
goto end;
|
|
props = ist2(istptr(parser), plen);
|
|
parser = istadv(parser, props.len);
|
|
|
|
while (istlen(props) > 0) {
|
|
switch (*istptr(props)) {
|
|
case MQTT_PROP_SESSION_EXPIRY_INTERVAL:
|
|
if (fields & MQTT_FN_BIT_SESSION_EXPIRY_INTERVAL)
|
|
goto end;
|
|
props = mqtt_read_4byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.session_expiry_interval);
|
|
fields |= MQTT_FN_BIT_SESSION_EXPIRY_INTERVAL;
|
|
break;
|
|
|
|
case MQTT_PROP_RECEIVE_MAXIMUM:
|
|
if (fields & MQTT_FN_BIT_RECEIVE_MAXIMUM)
|
|
goto end;
|
|
props = mqtt_read_2byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.receive_maximum);
|
|
/* cannot be 0 */
|
|
if (!mpkt->data.connack.var_hdr.props.receive_maximum)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_RECEIVE_MAXIMUM;
|
|
break;
|
|
|
|
case MQTT_PROP_MAXIMUM_QOS:
|
|
if (fields & MQTT_FN_BIT_MAXIMUM_QOS)
|
|
goto end;
|
|
props = mqtt_read_1byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.maximum_qos);
|
|
/* can have only 2 values: 0 or 1 */
|
|
if (mpkt->data.connack.var_hdr.props.maximum_qos > 1)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_MAXIMUM_QOS;
|
|
break;
|
|
|
|
case MQTT_PROP_RETAIN_AVAILABLE:
|
|
if (fields & MQTT_FN_BIT_RETAIN_AVAILABLE)
|
|
goto end;
|
|
props = mqtt_read_1byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.retain_available);
|
|
/* can have only 2 values: 0 or 1 */
|
|
if (mpkt->data.connack.var_hdr.props.retain_available > 1)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_RETAIN_AVAILABLE;
|
|
break;
|
|
|
|
case MQTT_PROP_MAXIMUM_PACKET_SIZE:
|
|
if (fields & MQTT_FN_BIT_MAXIMUM_PACKET_SIZE)
|
|
goto end;
|
|
props = mqtt_read_4byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.maximum_packet_size);
|
|
/* cannot be 0 */
|
|
if (!mpkt->data.connack.var_hdr.props.maximum_packet_size)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_MAXIMUM_PACKET_SIZE;
|
|
break;
|
|
|
|
case MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER:
|
|
if (fields & MQTT_FN_BIT_ASSIGNED_CLIENT_IDENTIFIER)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connack.var_hdr.props.assigned_client_identifier);
|
|
if (!istlen(mpkt->data.connack.var_hdr.props.assigned_client_identifier))
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_ASSIGNED_CLIENT_IDENTIFIER;
|
|
break;
|
|
|
|
case MQTT_PROP_TOPIC_ALIAS_MAXIMUM:
|
|
if (fields & MQTT_FN_BIT_TOPIC_ALIAS_MAXIMUM)
|
|
goto end;
|
|
props = mqtt_read_2byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.topic_alias_maximum);
|
|
fields |= MQTT_FN_BIT_TOPIC_ALIAS_MAXIMUM;
|
|
break;
|
|
|
|
case MQTT_PROP_REASON_STRING:
|
|
if (fields & MQTT_FN_BIT_REASON_STRING)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connack.var_hdr.props.reason_string);
|
|
fields |= MQTT_FN_BIT_REASON_STRING;
|
|
break;
|
|
|
|
case MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE:
|
|
if (fields & MQTT_FN_BIT_WILDCARD_SUBSCRIPTION_AVAILABLE)
|
|
goto end;
|
|
props = mqtt_read_1byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.wildcard_subscription_available);
|
|
/* can have only 2 values: 0 or 1 */
|
|
if (mpkt->data.connack.var_hdr.props.wildcard_subscription_available > 1)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_WILDCARD_SUBSCRIPTION_AVAILABLE;
|
|
break;
|
|
|
|
case MQTT_PROP_SUBSCRIPTION_IDENTIFIERS_AVAILABLE:
|
|
if (fields & MQTT_FN_BIT_SUBSCRIPTION_IDENTIFIER)
|
|
goto end;
|
|
props = mqtt_read_1byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.subscription_identifiers_available);
|
|
/* can have only 2 values: 0 or 1 */
|
|
if (mpkt->data.connack.var_hdr.props.subscription_identifiers_available > 1)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_SUBSCRIPTION_IDENTIFIER;
|
|
break;
|
|
|
|
case MQTT_PROP_SHARED_SUBSRIPTION_AVAILABLE:
|
|
if (fields & MQTT_FN_BIT_SHARED_SUBSCRIPTION_AVAILABLE)
|
|
goto end;
|
|
props = mqtt_read_1byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.shared_subsription_available);
|
|
/* can have only 2 values: 0 or 1 */
|
|
if (mpkt->data.connack.var_hdr.props.shared_subsription_available > 1)
|
|
goto end;
|
|
fields |= MQTT_FN_BIT_SHARED_SUBSCRIPTION_AVAILABLE;
|
|
break;
|
|
|
|
case MQTT_PROP_SERVER_KEEPALIVE:
|
|
if (fields & MQTT_FN_BIT_SERVER_KEEPALIVE)
|
|
goto end;
|
|
props = mqtt_read_2byte_int(istnext(props), &mpkt->data.connack.var_hdr.props.server_keepalive);
|
|
fields |= MQTT_FN_BIT_SERVER_KEEPALIVE;
|
|
break;
|
|
|
|
case MQTT_PROP_RESPONSE_INFORMATION:
|
|
if (fields & MQTT_FN_BIT_RESPONSE_INFORMATION)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connack.var_hdr.props.response_information);
|
|
fields |= MQTT_FN_BIT_RESPONSE_INFORMATION;
|
|
break;
|
|
|
|
case MQTT_PROP_SERVER_REFERENCE:
|
|
if (fields & MQTT_FN_BIT_SERVER_REFERENCE)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connack.var_hdr.props.server_reference);
|
|
fields |= MQTT_FN_BIT_SERVER_REFERENCE;
|
|
break;
|
|
|
|
case MQTT_PROP_USER_PROPERTIES:
|
|
/* if we reached MQTT_PROP_USER_PROPERTY_ENTRIES already, then
|
|
* we start writing over the first property */
|
|
if (user_prop_idx >= MQTT_PROP_USER_PROPERTY_ENTRIES)
|
|
user_prop_idx = 0;
|
|
|
|
/* read user property name and value */
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connack.var_hdr.props.user_props[user_prop_idx].name);
|
|
if (!isttest(props))
|
|
goto end;
|
|
props = mqtt_read_string(props, &mpkt->data.connack.var_hdr.props.user_props[user_prop_idx].value);
|
|
++user_prop_idx;
|
|
break;
|
|
|
|
case MQTT_PROP_AUTHENTICATION_METHOD:
|
|
if (fields & MQTT_FN_BIT_AUTHENTICATION_METHOD)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connack.var_hdr.props.authentication_method);
|
|
fields |= MQTT_FN_BIT_AUTHENTICATION_METHOD;
|
|
break;
|
|
|
|
case MQTT_PROP_AUTHENTICATION_DATA:
|
|
if (fields & MQTT_FN_BIT_AUTHENTICATION_DATA)
|
|
goto end;
|
|
props = mqtt_read_string(istnext(props), &mpkt->data.connack.var_hdr.props.authentication_data);
|
|
fields |= MQTT_FN_BIT_AUTHENTICATION_DATA;
|
|
break;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
if (!isttest(props))
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
if ((orig_len - istlen(parser)) == mpkt->fixed_hdr.remaining_length)
|
|
ret = MQTT_VALID_MESSAGE;
|
|
end:
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Parses and validates a MQTT packet
|
|
* https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028
|
|
*
|
|
* For now, due to HAProxy limitation, only validation of CONNECT and CONNACK packets
|
|
* are supported.
|
|
*
|
|
* - check FIXED_HDR
|
|
* - check remaining length
|
|
* - check variable headers and payload
|
|
*
|
|
* if <mpkt> is not NULL, then this structure will be filled up as well. An
|
|
* unsupported packet type is considered as invalid. It is not a problem for now
|
|
* because only the first packet on each side can be parsed (CONNECT for the
|
|
* client and CONNACK for the server).
|
|
*
|
|
* Returns:
|
|
* MQTT_INVALID_MESSAGE if the message is invalid
|
|
* MQTT_NEED_MORE_DATA if we need more data to fully validate the message
|
|
* MQTT_VALID_MESSAGE if the message looks valid
|
|
*/
|
|
int mqtt_validate_message(const struct ist msg, struct mqtt_pkt *mpkt)
|
|
{
|
|
struct ist parser;
|
|
struct mqtt_pkt tmp_mpkt;
|
|
int ret = MQTT_INVALID_MESSAGE;
|
|
|
|
if (!mpkt)
|
|
mpkt = &tmp_mpkt;
|
|
memset(mpkt, 0, sizeof(*mpkt));
|
|
|
|
parser = msg;
|
|
if (istlen(msg) < MQTT_MIN_PKT_SIZE) {
|
|
ret = MQTT_NEED_MORE_DATA;
|
|
goto end;
|
|
}
|
|
|
|
/* parse the MQTT fixed header */
|
|
parser = mqtt_read_fixed_hdr(parser, mpkt);
|
|
if (!isttest(parser)) {
|
|
ret = MQTT_INVALID_MESSAGE;
|
|
goto end;
|
|
}
|
|
|
|
/* Now parsing "remaining length" field */
|
|
parser = mqtt_read_varint(parser, &mpkt->fixed_hdr.remaining_length);
|
|
if (!isttest(parser)) {
|
|
ret = MQTT_INVALID_MESSAGE;
|
|
goto end;
|
|
}
|
|
|
|
if (istlen(parser) < mpkt->fixed_hdr.remaining_length)
|
|
return MQTT_NEED_MORE_DATA;
|
|
|
|
/* Now parsing the variable header and payload, which is based on the packet type */
|
|
switch (mpkt->fixed_hdr.type) {
|
|
case MQTT_CPT_CONNECT:
|
|
ret = mqtt_parse_connect(parser, mpkt);
|
|
break;
|
|
case MQTT_CPT_CONNACK:
|
|
ret = mqtt_parse_connack(parser, mpkt);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
end:
|
|
return ret;
|
|
}
|