# Stream reassembly size for modbus, default is 0
stream-depth: 0
+
+MQTT
+~~~~
+
+MQTT messages could theoretically be up to 256MB in size, potentially
+containing a lot of payload data (such as properties, topics, or
+published payloads) that would end up parsed and logged. To acknowledge
+the fact that most MQTT messages, however, will be quite small and to
+reduce the potential for denial of service issues, it is possible to limit
+the maximum length of a message that we are willing to parse. Any message
+larger than the limit will just be logged with reduced metadata, and rules
+will only be evaluated against a subset of fields.
+The default is 1 MB.
+
+::
+
+ mqtt:
+ max-msg-length: 1mb
+
SMTP
~~~~~~
"blue_shift": 0
}
}
+
+
+Event type: MQTT
+----------------
+
+EVE-JSON output for MQTT consists of one object per MQTT transaction, with some common and various type-specific fields.
+
+Transactions
+~~~~~~~~~~~~
+
+A single MQTT communication can consist of multiple messages that need to be exchanged between broker and client.
+For example, some actions at higher QoS levels (> 0) usually involve a combination of requests and acknowledgement
+messages that are linked by a common identifier:
+
+ * ``CONNECT`` followed by ``CONNACK``
+ * ``PUBLISH`` followed by ``PUBACK`` (QoS 1) or ``PUBREC``/``PUBREL``/``PUBCOMP`` (QoS 2)
+ * ``SUBSCRIBE`` followed by ``SUBACK``
+ * ``UNSUBSCRIBE`` followed by ``UNSUBACK``
+
+The MQTT parser merges individual messages into one EVE output item if they belong to one transaction. In such cases,
+the source and destination information (IP/port) reflect the direction of the initial request, but contain messages
+from both sides.
+
+Example for a PUBLISH at QoS 2:
+
+::
+
+ {
+ "timestamp": "2020-05-19T18:00:39.016985+0200",
+ "flow_id": 1454127794305760,
+ "pcap_cnt": 65,
+ "event_type": "mqtt",
+ "src_ip": "0000:0000:0000:0000:0000:0000:0000:0001",
+ "src_port": 60105,
+ "dest_ip": "0000:0000:0000:0000:0000:0000:0000:0001",
+ "dest_port": 1883,
+ "proto": "TCP",
+ "mqtt": {
+ "publish": {
+ "qos": 2,
+ "retain": false,
+ "dup": false,
+ "topic": "house/bulbs/bulb1",
+ "message_id": 3,
+ "message": "OFF"
+ },
+ "pubrec": {
+ "qos": 0,
+ "retain": false,
+ "dup": false,
+ "message_id": 3
+ },
+ "pubrel": {
+ "qos": 1,
+ "retain": false,
+ "dup": false,
+ "message_id": 3
+ },
+ "pubcomp": {
+ "qos": 0,
+ "retain": false,
+ "dup": false,
+ "message_id": 3
+ }
+ }
+ }
+
+Note that some message types (aka control packet types), such as ``PINGREQ`` and ``PINGRESP``, have no type-specific
+data, nor do they have information that facilitate grouping into transactions. These will be logged as single items
+and only contain the common fields listed below.
+
+
+Common fields
+~~~~~~~~~~~~~
+
+Common fields from the MQTT fixed header:
+
+* "\*.qos": Quality of service level for the message, integer between 0 and 2.
+* "\*.retain": Boolean value of the MQTT 'retain' flag.
+* "\*.dup": Boolean value of the MQTT 'dup' (duplicate) flag.
+
+
+MQTT CONNECT fields
+~~~~~~~~~~~~~~~~~~~
+
+* "connect.protocol_string": Protocol string as defined in the spec, e.g. ``MQTT`` (MQTT 3.1.1 and later) or ``MQIsdp`` (MQTT 3.1).
+* "connect.protocol_version": Protocol version as defined in the specification:
+
+ * protocol version ``3``: MQTT 3.1
+ * protocol version ``4``: MQTT 3.1.1
+ * protocol version ``5``: MQTT 5.0
+
+* "connect.flags.username", "connect.flags.password": Set to `true` if credentials are submitted with the connect request.
+* "connect.flags.will": Set to `true` if a will is set.
+* "connect.flags.will_retain": Set to `true` if the will is to be retained on the broker.
+* "connect.will.clean_session": Set to `true` if the connection is to made with a clean session.
+* "connect.client_id": Client ID string submitted my the connecting client.
+* "connect.username", "connect.password": User/password authentication credentials submitted with the connect request. Passwords are only logged when the corresponding configuration setting is enabled (``mqtt.passwords: yes``).
+* "connect.will.topic": Topic to publish the will message to.
+* "connect.will.message": Message to be published on connection loss.
+* "connect.will.properties": (Optional, MQTT 5.0) Will properties set on this request. See `3.1.3.2 in the spec <https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901060>`_ for more information on will properties.
+* "connect.properties": (Optional, MQTT 5.0) CONNECT properties set on this request. See `3.1.2.11 in the spec <https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901046>`_ for more information on CONNECT properties.
+
+Example of MQTT CONNECT logging:
+
+::
+
+ "connect": {
+ "qos": 0,
+ "retain": false,
+ "dup": false,
+ "protocol_string": "MQTT",
+ "protocol_version": 5,
+ "flags": {
+ "username": true,
+ "password": true,
+ "will_retain": false,
+ "will": true,
+ "clean_session": true
+ },
+ "client_id": "client",
+ "username": "user",
+ "password": "pass",
+ "will": {
+ "topic": "willtopic",
+ "message": "willmessage",
+ "properties": {
+ "content_type": "mywilltype",
+ "correlation_data": "3c32aa4313b3e",
+ "message_expiry_interval": 133,
+ "payload_format_indicator": 144,
+ "response_topic": "response_topic1",
+ "userprop": "uservalue",
+ "will_delay_interval": 200
+ }
+ },
+ "properties": {
+ "maximum_packet_size": 11111,
+ "receive_maximum": 222,
+ "session_expiry_interval": 555,
+ "topic_alias_maximum": 666,
+ "userprop1": "userval1",
+ "userprop2": "userval2"
+ }
+ }
+
+MQTT CONNACK fields
+~~~~~~~~~~~~~~~~~~~
+
+* "connack.session_present": Set to `true` if a session is continued on connection.
+* "connack.return_code": Return code/reason code for this reply. See `3.2.2.2 in the spec <https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901079>`_ for more information on these codes.
+* "connect.properties": (Optional, MQTT 5.0) CONNACK properties set on this request. See `3.2.2.3 in the spec <https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901080>`_ for more information on CONNACK properties.
+
+Example of MQTT CONNACK logging:
+
+::
+
+ "connack": {
+ "qos": 0,
+ "retain": false,
+ "dup": false,
+ "session_present": false,
+ "return_code": 0,
+ "properties": {
+ "topic_alias_maximum": 10
+ }
+ }
+
+MQTT PUBLISH fields
+~~~~~~~~~~~~~~~~~~~
+
+* "publish.topic": Topic this message is published to.
+* "publish.message_id": (Only present if QOS level > 0) Message ID for this publication.
+* "publish.message": Message to be published.
+* "publish.properties": (Optional, MQTT 5.0) PUBLISH properties set on this request. See `3.3.2.3 in the spec <https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901109>`_ for more information on PUBLISH properties.
+
+Example of MQTT PUBLISH logging:
+
+::
+
+ "publish": {
+ "qos": 1,
+ "retain": false,
+ "dup": false,
+ "topic": "topic",
+ "message_id": 1,
+ "message": "baa baa sheep",
+ "properties": {
+ "content_type": "mytype",
+ "correlation_data": "3c32aa4313b3e",
+ "message_expiry_interval": 77,
+ "payload_format_indicator": 88,
+ "response_topic": "response_topic1",
+ "topic_alias": 5,
+ "userprop": "userval"
+ }
+ }
+
+MQTT PUBACK/PUBREL/PUBREC/PUBCOMP fields
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* "[puback|pubrel|pubrec|pubcomp].message_id": Original message ID this message refers to.
+* "[puback|pubrel|pubrec|pubcomp].reason_code": Return code/reason code for this reply. See the spec for more information on these codes.
+* "[puback|pubrel|pubrec|pubcomp].properties": (Optional, MQTT 5.0) Properties set on this request. See the spec for more information on these properties.
+
+Example of MQTT PUBACK/PUBREL/PUBREC/PUBCOMP logging:
+
+::
+
+ "puback": {
+ "qos": 0,
+ "retain": false,
+ "dup": false,
+ "message_id": 1,
+ "reason_code": 16
+ }
+
+MQTT SUBSCRIBE fields
+~~~~~~~~~~~~~~~~~~~~~
+
+* "subscribe.message_id": (Only present if QOS level > 0) Message ID for this subscription.
+* "subscribe.topics": Array of pairs describing the subscribed topics:
+
+ * "subscribe.topics[].topic": Topic to subscribe to.
+ * "subscribe.topics[].qos": QOS level to apply for when subscribing.
+
+* "subscribe.properties": (Optional, MQTT 5.0) SUBSCRIBE properties set on this request. See `3.8.2.1 in the spec <https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901164>`_ for more information on SUBSCRIBE properties.
+
+Example of MQTT SUBSCRIBE logging:
+
+::
+
+ "subscribe": {
+ "qos": 1,
+ "retain": false,
+ "dup": false,
+ "message_id": 1,
+ "topics": [
+ {
+ "topic": "topicX",
+ "qos": 0
+ },
+ {
+ "topic": "topicY",
+ "qos": 0
+ }
+ ]
+ }
+
+MQTT SUBACK fields
+~~~~~~~~~~~~~~~~~~
+
+* "suback.message_id": Original message ID this message refers to.
+* "suback.qos_granted": Array of QOS levels granted for the subscribed topics, in the order of the original request.
+* "suback.properties": (Optional, MQTT 5.0) SUBACK properties set on this request. See `3.9.2.1 in the spec <https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901174>`_ for more information on SUBACK properties.
+
+Example of MQTT SUBACK logging:
+
+::
+
+ "suback": {
+ "qos": 0,
+ "retain": false,
+ "dup": false,
+ "message_id": 1,
+ "qos_granted": [
+ 0,
+ 0
+ ]
+ }
+
+MQTT UNSUBSCRIBE fields
+~~~~~~~~~~~~~~~~~~~~~~~
+
+* "unsubscribe.message_id": (Only present if QOS level > 0) Message ID for this unsubscribe action.
+* "unsubscribe.topics": Array of topics to be unsubscribed from.
+* "unsubscribe.properties": (Optional, MQTT 5.0) UNSUBSCRIBE properties set on this request. See `3.10.2.1 in the spec <https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901182>`_ for more information on UNSUBSCRIBE properties.
+
+Example of MQTT UNSUBSCRIBE logging:
+
+::
+
+ "unsubscribe": {
+ "qos": 1,
+ "retain": false,
+ "dup": false,
+ "message_id": 1,
+ "topics": [
+ "topicX",
+ "topicY"
+ ]
+ }
+
+MQTT UNSUBACK fields
+~~~~~~~~~~~~~~~~~~~~
+
+* "unsuback.message_id": Original message ID this message refers to.
+
+Example of MQTT UNSUBACK logging:
+
+::
+
+ "unsuback": {
+ "qos": 0,
+ "retain": false,
+ "dup": false,
+ "message_id": 1
+ }
+
+MQTT AUTH fields (MQTT 5.0)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* "auth.reason_code": Return code/reason code for this message. See `3.15.2.1 in the spec <https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901220>`_ for more information on these codes.
+* "auth.properties": (Optional, MQTT 5.0) Properties set on this request. See `3.15.2.2 in the spec <https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901221>`_ for more information on these properties.
+
+Example of MQTT AUTH logging:
+
+::
+
+ "auth": {
+ "qos": 0,
+ "retain": false,
+ "dup": false,
+ "reason_code": 16
+ }
+
+MQTT DISCONNECT fields
+~~~~~~~~~~~~~~~~~~~~~~
+
+* "auth.reason_code": (Optional) Return code/reason code for this message. See `3.14.2.1 in the spec <https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901208>`_ for more information on these codes.
+* "auth.properties": (Optional, MQTT 5.0) Properties set on this request. See `3.14.2.2 in the spec <https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901209>`_ for more information on DISCONNECT properties.
+
+Example of MQTT DISCONNECT logging:
+
+::
+
+ "disconnect": {
+ "qos": 0,
+ "retain": false,
+ "dup": false,
+ "reason_code": 4,
+ "properties": {
+ "session_expiry_interval": 122,
+ }
+ }
+
+Truncated MQTT data
+~~~~~~~~~~~~~~~~~~~
+
+Messages exceeding the maximum message length limit (config setting ``app-layer.protocols.mqtt.max-msg-length``)
+will not be parsed entirely to reduce the danger of denial of service issues. In such cases, only reduced
+metadata will be included in the EVE-JSON output. Furthermore, since no message ID is parsed, such messages
+can not be placed into transactions, hence, they will always appear as a single transaction.
+
+These truncated events will -- besides basic communication metadata -- only contain the following
+fields:
+
+* "truncated": Set to `true` if the entry is truncated.
+* "skipped_length": Size of the original message.
+
+Example of a truncated MQTT PUBLISH message (with 10000 being the maximum length):
+
+::
+
+ {
+ "timestamp": "2020-06-23T16:25:48.729785+0200",
+ "flow_id": 1872904524326406,
+ "pcap_cnt": 107,
+ "event_type": "mqtt",
+ "src_ip": "0000:0000:0000:0000:0000:0000:0000:0001",
+ "src_port": 53335,
+ "dest_ip": "0000:0000:0000:0000:0000:0000:0000:0001",
+ "dest_port": 1883,
+ "proto": "TCP",
+ "mqtt": {
+ "publish": {
+ "qos": 0,
+ "retain": false,
+ "dup": false,
+ "truncated": true,
+ "skipped_length": 100011
+ }
+ }
+ }
base64-keywords
sip-keywords
rfb-keywords
+ mqtt-keywords
app-layer
xbits
thresholding
--- /dev/null
+MQTT Keywords
+=============
+
+Various keywords can be used for matching on fields in fixed and variable headers of MQTT messages as well as payload values.
+
+mqtt.protocol_version
+---------------------
+
+Match on the value of the MQTT protocol version field in the fixed header.
+
+The format of the keyword::
+
+ mqtt.protocol_version:<min>-<max>;
+ mqtt.protocol_version:[<|>]<number>;
+ mqtt.protocol_version:<value>;
+
+Examples:
+
+ mqtt.protocol_version:5;
+
+
+mqtt.type
+---------
+
+Match on the MQTT message type (also: control packet type).
+Valid values are :
+
+* ``CONNECT``
+* ``CONNACK``
+* ``PUBLISH``
+* ``PUBACK``
+* ``PUBREC``
+* ``PUBREL``
+* ``PUBCOMP``
+* ``SUBSCRIBE``
+* ``SUBACK``
+* ``UNSUBSCRIBE``
+* ``UNSUBACK``
+* ``PINGREQ``
+* ``PINGRESP``
+* ``DISCONNECT``
+* ``AUTH``
+* ``UNASSIGNED``
+
+where ``UNASSIGNED`` refers to message type code 0.
+
+Examples::
+
+ mqtt.type:CONNECT;
+ mqtt.type:PUBLISH;
+
+
+mqtt.flags
+----------
+
+Match on a combination of MQTT header flags, separated by commas (``,``). Flags may be prefixed by ``!`` to indicate negation, i.e. a flag prefixed by ``!`` must `not` be set to match.
+
+Valid flags are:
+
+* ``dup`` (duplicate message)
+* ``retain`` (message should be retained on the broker)
+
+Examples::
+
+ mqtt.flags:dup,!retain;
+ mqtt.flags:retain;
+
+
+mqtt.qos
+--------
+
+Match on the Quality of Service request code in the MQTT fixed header.
+Valid values are:
+
+* ``0`` (fire and forget)
+* ``1`` (at least one delivery)
+* ``2`` (exactly one delivery)
+
+Examples::
+
+ mqtt.qos:0;
+ mqtt.qos:2;
+
+
+mqtt.reason_code
+----------------
+
+Match on the numeric value of the reason code that is used in MQTT 5.0 for some message types. Please refer to the specification for the meaning of these values, which are often specific to the message type in question.
+
+Examples::
+
+ # match on attempts to unsubscribe from a non-subscribed topic
+ mqtt.type:UNSUBACK; mqtt.reason_code:17;
+
+ # match on publications that were accepted but there were no subscribers
+ mqtt.type:PUBACK; mqtt.reason_code:16;
+
+ # match on connection attempts by banned clients
+ mqtt.CONNACK; mqtt.reason_code:138;
+
+ # match on failed connection attempts due to bad credentials
+ mqtt.CONNACK; mqtt.reason_code:134;
+
+ # match on connections terminated by server shutdowns
+ mqtt.DISCONNECT; mqtt.reason_code:139;
+
+This keyword is also available under the alias ``mqtt.connack.return_code`` for completeness.
+
+
+mqtt.connack.session_present
+----------------------------
+
+Match on the MQTT CONNACK ``session_present`` flag. Values can be ``yes``, ``true``, ``no`` or ``false``.
+
+Examples::
+
+ mqtt.CONNACK; mqtt.connack.session_present:true;
+
+
+mqtt.connect.clientid
+---------------------
+
+Match on the self-assigned client ID in the MQTT CONNECT message.
+
+Examples::
+
+ mqtt.connect.clientid; pcre:"/^mosq.*/";
+ mqtt.connect.clientid; content:"myclient";
+
+``mqtt.connect.clientid`` is a 'sticky buffer' and can be used as ``fast_pattern``.
+
+
+mqtt.connect.flags
+------------------
+
+Match on a combination of MQTT CONNECT flags, separated by commas (``,``). Flags may be prefixed by ``!`` to indicate negation, i.e. a flag prefixed by ``!`` must `not` be set to match.
+
+Valid flags are:
+
+* ``username`` (message contains a username)
+* ``password`` (message contains a password)
+* ``will`` (message contains a will definition)
+* ``will_retain`` (will should be retained on broker)
+* ``clean_session`` (start with a clean session)
+
+Examples::
+
+ mqtt.connect.flags:username,password,!will;
+ mqtt.connect.flags:username,!password;
+ mqtt.connect.flags:clean_session;
+
+
+mqtt.connect.password
+---------------------
+
+Match on the password credential in the MQTT CONNECT message.
+
+Examples::
+
+ mqtt.connect.password; pcre:"/^123[0-9]*/";
+ mqtt.connect.password; content:"swordfish";
+
+``mqtt.connect.password`` is a 'sticky buffer' and can be used as ``fast_pattern``.
+
+
+mqtt.connect.username
+---------------------
+
+Match on the username credential in the MQTT CONNECT message.
+
+Examples::
+
+ mqtt.connect.username; content:"benson";
+
+``mqtt.connect.username`` is a 'sticky buffer' and can be used as ``fast_pattern``.
+
+
+mqtt.connect.willmessage
+------------------------
+
+Match on the will message in the MQTT CONNECT message, if a will is defined.
+
+Examples::
+
+ mqtt.connect.willmessage; pcre:"/^fooba[rz]/";
+ mqtt.connect.willmessage; content:"hunter2";
+
+``mqtt.connect.willmessage`` is a 'sticky buffer' and can be used as ``fast_pattern``.
+
+
+mqtt.connect.willtopic
+----------------------
+
+Match on the will topic in the MQTT CONNECT message, if a will is defined.
+
+Examples::
+
+ mqtt.connect.willtopic; pcre:"/^hunter[0-9]/";
+
+``mqtt.connect.willtopic`` is a 'sticky buffer' and can be used as ``fast_pattern``.
+
+
+mqtt.publish.message
+--------------------
+
+Match on the payload to be published in the MQTT PUBLISH message.
+
+Examples::
+
+ mqtt.type:PUBLISH; mqtt.publish.message; pcre:"/uid=[0-9]+/";
+ # match on published JPEG images
+ mqtt.type:PUBLISH; mqtt.publish.message; content:"|FF D8 FF E0|"; startswith;
+
+``mqtt.publish.message`` is a 'sticky buffer' and can be used as ``fast_pattern``.
+
+
+mqtt.publish.topic
+------------------
+
+Match on the topic to be published to in the MQTT PUBLISH message.
+
+Examples::
+
+ mqtt.publish.topic; content:"mytopic";
+
+``mqtt.publish.topic`` is a 'sticky buffer' and can be used as ``fast_pattern``.
+
+
+mqtt.subscribe.topic
+--------------------
+
+Match on any of the topics subscribed to in a MQTT SUBSCRIBE message.
+
+Examples::
+
+ mqtt.subscribe.topic; content:"mytopic";
+
+``mqtt.subscribe.topic`` is a 'sticky buffer' and can be used as ``fast_pattern``.
+
+
+mqtt.unsubscribe.topic
+----------------------
+
+Match on any of the topics unsubscribed from in a MQTT UNSUBSCRIBE message.
+
+Examples::
+
+ mqtt.unsubscribe.topic; content:"mytopic";
+
+``mqtt.unsubscribe.topic`` is a 'sticky buffer' and can be used as ``fast_pattern``.
+
+
+Additional information
+----------------------
+
+More information on the protocol can be found here:
+
+* MQTT 3.1: `<https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html>`_
+* MQTT 3.1.1: `<https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html>`_
+* MQTT 5.0: `<https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html>`_
--- /dev/null
+# MQTT app-layer event rules.
+#
+# These SIDs fall in the 2228000+ range. See:
+# http://doc.emergingthreats.net/bin/view/Main/SidAllocation and
+# https://redmine.openinfosecfoundation.org/projects/suricata/wiki/AppLayer
+
+alert mqtt any any -> any any (msg:"SURICATA MQTT CONNECT not seen before CONNACK"; app-layer-event:mqtt.missing_connect; classtype:protocol-command-decode; sid:2228000; rev:1;)
+alert mqtt any any -> any any (msg:"SURICATA MQTT PUBLISH not seen before PUBACK/PUBREL/PUBREC/PUBCOMP"; app-layer-event:mqtt.missing_publish; classtype:protocol-command-decode; sid:2228001; rev:1;)
+alert mqtt any any -> any any (msg:"SURICATA MQTT SUBSCRIBE not seen before SUBACK"; app-layer-event:mqtt.missing_subscribe; classtype:protocol-command-decode; sid:2228002; rev:1;)
+alert mqtt any any -> any any (msg:"SURICATA MQTT UNSUBSCRIBE not seen before UNSUBACK"; app-layer-event:mqtt.missing_unsubscribe; classtype:protocol-command-decode; sid:2228003; rev:1;)
+alert mqtt any any -> any any (msg:"SURICATA MQTT duplicate CONNECT"; app-layer-event:mqtt.double_connect; classtype:protocol-command-decode; sid:2228004; rev:1;)
+alert mqtt any any -> any any (msg:"SURICATA MQTT message seen before CONNECT/CONNACK completion"; app-layer-event:mqtt.unintroduced_message; classtype:protocol-command-decode; sid:2228005; rev:1;)
+alert mqtt any any -> any any (msg:"SURICATA MQTT invalid QOS level"; app-layer-event:mqtt.invalid_qos_level; classtype:protocol-command-decode; sid:2228006; rev:1;)
+alert mqtt any any -> any any (msg:"SURICATA MQTT missing message ID"; app-layer-event:mqtt.missing_msg_id; classtype:protocol-command-decode; sid:2228007; rev:1;)
+alert mqtt any any -> any any (msg:"SURICATA MQTT unassigned message type (0 or >15)"; app-layer-event:mqtt.unassigned_msg_type; classtype:protocol-command-decode; sid:2228008; rev:1;)
pub mod dhcp;
pub mod sip;
pub mod rfb;
+pub mod mqtt;
pub mod applayertemplate;
pub mod rdp;
pub mod x509;
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+// written by Sascha Steinbiss <sascha@steinbiss.name>
+
+use crate::mqtt::mqtt::{MQTTTransaction, MQTTState};
+use crate::mqtt::mqtt_message::{MQTTOperation, MQTTTypeCode};
+use std::ffi::CStr;
+use std::ptr;
+use std::str::FromStr;
+
+#[derive(FromPrimitive, Debug, Copy, Clone, PartialOrd, PartialEq)]
+#[allow(non_camel_case_types)]
+#[repr(u8)]
+pub enum MQTTFlagState {
+ MQTT_DONT_CARE = 0,
+ MQTT_MUST_BE_SET = 1,
+ MQTT_CANT_BE_SET = 2,
+}
+
+#[inline]
+fn check_flag_state(
+ flag_state: MQTTFlagState,
+ flag_value: bool,
+ ok: &mut bool,
+) {
+ match flag_state {
+ MQTTFlagState::MQTT_MUST_BE_SET => {
+ if !flag_value {
+ *ok = false;
+ }
+ },
+ MQTTFlagState::MQTT_CANT_BE_SET => {
+ if flag_value {
+ *ok = false;
+ }
+ },
+ _ => {}
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_has_type(
+ tx: &MQTTTransaction,
+ mtype: u8,
+) -> u8 {
+ for msg in tx.msg.iter() {
+ if mtype == msg.header.message_type as u8 {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_cstr_message_code(
+ str: *const std::os::raw::c_char,
+) -> std::os::raw::c_int {
+ unsafe {
+ let msgtype: &CStr = CStr::from_ptr(str);
+ if let Ok(s) = msgtype.to_str() {
+ if let Ok(x) = MQTTTypeCode::from_str(s) {
+ return x as i32;
+ }
+ }
+ }
+ return -1;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_has_flags(
+ tx: &MQTTTransaction,
+ qretain: MQTTFlagState,
+ qdup: MQTTFlagState,
+) -> u8 {
+ for msg in tx.msg.iter() {
+ let mut ok = true;
+ check_flag_state(qretain, msg.header.retain, &mut ok);
+ check_flag_state(qdup, msg.header.dup_flag, &mut ok);
+ if ok {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_has_qos(
+ tx: &MQTTTransaction,
+ qos: u8,
+) -> u8 {
+ for msg in tx.msg.iter() {
+ if qos == msg.header.qos_level {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_get_protocol_version(
+ state: &MQTTState,
+) -> u8 {
+ return state.protocol_version;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_has_connect_flags(
+ tx: &MQTTTransaction,
+ username: MQTTFlagState,
+ password: MQTTFlagState,
+ will: MQTTFlagState,
+ will_retain: MQTTFlagState,
+ clean_session: MQTTFlagState,
+) -> u8 {
+ for msg in tx.msg.iter() {
+ if let MQTTOperation::CONNECT(ref cv) = msg.op {
+ let mut ok = true;
+ check_flag_state(username, cv.username_flag, &mut ok);
+ check_flag_state(password, cv.password_flag, &mut ok);
+ check_flag_state(will, cv.will_flag, &mut ok);
+ check_flag_state(will_retain, cv.will_retain, &mut ok);
+ check_flag_state(clean_session, cv.clean_session, &mut ok);
+ if ok {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_get_connect_clientid(
+ tx: &MQTTTransaction,
+ buffer: *mut *const u8,
+ buffer_len: *mut u32,
+) -> u8 {
+ for msg in tx.msg.iter() {
+ if let MQTTOperation::CONNECT(ref cv) = msg.op {
+ let p = &cv.client_id;
+ if p.len() > 0 {
+ unsafe {
+ *buffer = p.as_ptr();
+ *buffer_len = p.len() as u32;
+ }
+ return 1;
+ }
+ }
+ }
+
+ unsafe {
+ *buffer = ptr::null();
+ *buffer_len = 0;
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_get_connect_username(
+ tx: &MQTTTransaction,
+ buffer: *mut *const u8,
+ buffer_len: *mut u32,
+) -> u8 {
+ for msg in tx.msg.iter() {
+ if let MQTTOperation::CONNECT(ref cv) = msg.op {
+ if let Some(p) = &cv.username {
+ if p.len() > 0 {
+ unsafe {
+ *buffer = p.as_ptr();
+ *buffer_len = p.len() as u32;
+ }
+ return 1;
+ }
+ }
+ }
+ }
+
+ unsafe {
+ *buffer = ptr::null();
+ *buffer_len = 0;
+ }
+
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_get_connect_password(
+ tx: &MQTTTransaction,
+ buffer: *mut *const u8,
+ buffer_len: *mut u32,
+) -> u8 {
+ for msg in tx.msg.iter() {
+ if let MQTTOperation::CONNECT(ref cv) = msg.op {
+ if let Some(p) = &cv.password {
+ if p.len() > 0 {
+ unsafe {
+ *buffer = p.as_ptr();
+ *buffer_len = p.len() as u32;
+ }
+ return 1;
+ }
+ }
+ }
+ }
+
+ unsafe {
+ *buffer = ptr::null();
+ *buffer_len = 0;
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_get_connect_willtopic(
+ tx: &MQTTTransaction,
+ buffer: *mut *const u8,
+ buffer_len: *mut u32,
+) -> u8 {
+ for msg in tx.msg.iter() {
+ if let MQTTOperation::CONNECT(ref cv) = msg.op {
+ if let Some(p) = &cv.will_topic {
+ if p.len() > 0 {
+ unsafe {
+ *buffer = p.as_ptr();
+ *buffer_len = p.len() as u32;
+ }
+ return 1;
+ }
+ }
+ }
+ }
+
+ unsafe {
+ *buffer = ptr::null();
+ *buffer_len = 0;
+ }
+
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_get_connect_willmessage(
+ tx: &MQTTTransaction,
+ buffer: *mut *const u8,
+ buffer_len: *mut u32,
+) -> u8 {
+ for msg in tx.msg.iter() {
+ if let MQTTOperation::CONNECT(ref cv) = msg.op {
+ if let Some(p) = &cv.will_message {
+ if p.len() > 0 {
+ unsafe {
+ *buffer = p.as_ptr();
+ *buffer_len = p.len() as u32;
+ }
+ return 1;
+ }
+ }
+ }
+ }
+
+ unsafe {
+ *buffer = ptr::null();
+ *buffer_len = 0;
+ }
+
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_get_connack_sessionpresent(
+ tx: &MQTTTransaction,
+ session_present: *mut bool,
+) -> u8 {
+ for msg in tx.msg.iter() {
+ if let MQTTOperation::CONNACK(ref ca) = msg.op {
+ unsafe {
+ *session_present = ca.session_present;
+ }
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_get_publish_topic(
+ tx: &MQTTTransaction,
+ buffer: *mut *const u8,
+ buffer_len: *mut u32,
+) -> u8 {
+ for msg in tx.msg.iter() {
+ if let MQTTOperation::PUBLISH(ref pubv) = msg.op {
+ let p = &pubv.topic;
+ if p.len() > 0 {
+ unsafe {
+ *buffer = p.as_ptr();
+ *buffer_len = p.len() as u32;
+ }
+ return 1;
+ }
+ }
+ }
+
+ unsafe {
+ *buffer = ptr::null();
+ *buffer_len = 0;
+ }
+
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_get_publish_message(
+ tx: &MQTTTransaction,
+ buffer: *mut *const u8,
+ buffer_len: *mut u32,
+) -> u8 {
+ for msg in tx.msg.iter() {
+ if let MQTTOperation::PUBLISH(ref pubv) = msg.op {
+ let p = &pubv.message;
+ if p.len() > 0 {
+ unsafe {
+ *buffer = p.as_ptr();
+ *buffer_len = p.len() as u32;
+ }
+ return 1;
+ }
+ }
+ }
+
+ unsafe {
+ *buffer = ptr::null();
+ *buffer_len = 0;
+ }
+
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_get_subscribe_topic(tx: &MQTTTransaction,
+ i: u16,
+ buf: *mut *const u8,
+ len: *mut u32)
+ -> u8
+{
+ let mut offset = 0;
+ for msg in tx.msg.iter() {
+ if let MQTTOperation::SUBSCRIBE(ref subv) = msg.op {
+ if (i as usize) < subv.topics.len() + offset {
+ let topic = &subv.topics[(i as usize) - offset];
+ if topic.topic_name.len() > 0 {
+ unsafe {
+ *len = topic.topic_name.len() as u32;
+ *buf = topic.topic_name.as_ptr();
+ }
+ return 1;
+ }
+ } else {
+ offset += subv.topics.len();
+ }
+ }
+ }
+
+ unsafe {
+ *buf = ptr::null();
+ *len = 0;
+ }
+
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_get_unsubscribe_topic(tx: &MQTTTransaction,
+ i: u16,
+ buf: *mut *const u8,
+ len: *mut u32)
+ -> u8
+{
+ let mut offset = 0;
+ for msg in tx.msg.iter() {
+ if let MQTTOperation::UNSUBSCRIBE(ref unsubv) = msg.op {
+ if (i as usize) < unsubv.topics.len() + offset {
+ let topic = &unsubv.topics[(i as usize) - offset];
+ if topic.len() > 0 {
+ unsafe {
+ *len = topic.len() as u32;
+ *buf = topic.as_ptr();
+ }
+ return 1;
+ }
+ } else {
+ offset += unsubv.topics.len();
+ }
+ }
+ }
+
+ unsafe {
+ *buf = ptr::null();
+ *len = 0;
+ }
+
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_get_reason_code(
+ tx: &MQTTTransaction,
+ result: *mut u8,
+) -> u8 {
+ for msg in tx.msg.iter() {
+ match msg.op {
+ MQTTOperation::PUBACK(ref v)
+ | MQTTOperation::PUBREL(ref v)
+ | MQTTOperation::PUBREC(ref v)
+ | MQTTOperation::PUBCOMP(ref v) => {
+ if let Some(rcode) = v.reason_code {
+ unsafe {
+ *result = rcode;
+ }
+ return 1;
+ }
+ }
+ MQTTOperation::AUTH(ref v) => {
+ unsafe {
+ *result = v.reason_code;
+ }
+ return 1;
+ }
+ MQTTOperation::CONNACK(ref v) => {
+ unsafe {
+ *result = v.return_code;
+ }
+ return 1;
+ }
+ MQTTOperation::DISCONNECT(ref v) => {
+ if let Some(rcode) = v.reason_code {
+ unsafe {
+ *result = rcode;
+ }
+ return 1;
+ }
+ }
+ _ => return 0
+ }
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_unsuback_has_reason_code(
+ tx: &MQTTTransaction,
+ code: u8,
+) -> u8 {
+ for msg in tx.msg.iter() {
+ if let MQTTOperation::UNSUBACK(ref unsuback) = msg.op {
+ if let Some(ref reason_codes) = unsuback.reason_codes {
+ for rc in reason_codes.iter() {
+ if *rc == code {
+ return 1;
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std;
+ use crate::mqtt::mqtt::MQTTTransaction;
+ use crate::mqtt::mqtt_message::*;
+ use crate::mqtt::parser::FixedHeader;
+
+ #[test]
+ fn test_multi_unsubscribe() {
+ let mut t = MQTTTransaction::new(MQTTMessage {
+ header: FixedHeader {
+ message_type: MQTTTypeCode::UNSUBSCRIBE,
+ dup_flag: false,
+ qos_level: 0,
+ retain: false,
+ remaining_length: 0,
+ },
+ op: MQTTOperation::UNSUBSCRIBE(MQTTUnsubscribeData {
+ message_id: 1,
+ topics: vec!["foo".to_string(), "baar".to_string()],
+ properties: None,
+ }),
+ });
+ t.msg.push(MQTTMessage {
+ header: FixedHeader {
+ message_type: MQTTTypeCode::UNSUBSCRIBE,
+ dup_flag: false,
+ qos_level: 0,
+ retain: false,
+ remaining_length: 0,
+ },
+ op: MQTTOperation::UNSUBSCRIBE(MQTTUnsubscribeData {
+ message_id: 1,
+ topics: vec!["fieee".to_string(), "baaaaz".to_string()],
+ properties: None,
+ }),
+ });
+ let mut s: *const u8 = std::ptr::null_mut();
+ let mut slen: u32 = 0;
+ let mut r = rs_mqtt_tx_get_unsubscribe_topic(&t, 0, &mut s, &mut slen);
+ assert_eq!(r, 1);
+ let mut topic = String::from_utf8_lossy(build_slice!(s, slen as usize));
+ assert_eq!(topic, "foo");
+ r = rs_mqtt_tx_get_unsubscribe_topic(&t, 1, &mut s, &mut slen);
+ assert_eq!(r, 1);
+ topic = String::from_utf8_lossy(build_slice!(s, slen as usize));
+ assert_eq!(topic, "baar");
+ r = rs_mqtt_tx_get_unsubscribe_topic(&t, 2, &mut s, &mut slen);
+ assert_eq!(r, 1);
+ topic = String::from_utf8_lossy(build_slice!(s, slen as usize));
+ assert_eq!(topic, "fieee");
+ r = rs_mqtt_tx_get_unsubscribe_topic(&t, 3, &mut s, &mut slen);
+ assert_eq!(r, 1);
+ topic = String::from_utf8_lossy(build_slice!(s, slen as usize));
+ assert_eq!(topic, "baaaaz");
+ r = rs_mqtt_tx_get_unsubscribe_topic(&t, 4, &mut s, &mut slen);
+ assert_eq!(r, 0);
+ }
+
+ #[test]
+ fn test_multi_subscribe() {
+ let mut t = MQTTTransaction::new(MQTTMessage {
+ header: FixedHeader {
+ message_type: MQTTTypeCode::SUBSCRIBE,
+ dup_flag: false,
+ qos_level: 0,
+ retain: false,
+ remaining_length: 0,
+ },
+ op: MQTTOperation::SUBSCRIBE(MQTTSubscribeData {
+ message_id: 1,
+ topics: vec![
+ MQTTSubscribeTopicData {
+ topic_name: "foo".to_string(),
+ qos: 0,
+ },
+ MQTTSubscribeTopicData {
+ topic_name: "baar".to_string(),
+ qos: 1,
+ }],
+ properties: None,
+ }),
+ });
+ t.msg.push(MQTTMessage {
+ header: FixedHeader {
+ message_type: MQTTTypeCode::SUBSCRIBE,
+ dup_flag: false,
+ qos_level: 0,
+ retain: false,
+ remaining_length: 0,
+ },
+ op: MQTTOperation::SUBSCRIBE(MQTTSubscribeData {
+ message_id: 1,
+ topics: vec![
+ MQTTSubscribeTopicData {
+ topic_name: "fieee".to_string(),
+ qos: 0,
+ },
+ MQTTSubscribeTopicData {
+ topic_name: "baaaaz".to_string(),
+ qos: 1,
+ }],
+ properties: None,
+ }),
+ });
+ let mut s: *const u8 = std::ptr::null_mut();
+ let mut slen: u32 = 0;
+ let mut r = rs_mqtt_tx_get_subscribe_topic(&t, 0, &mut s, &mut slen);
+ assert_eq!(r, 1);
+ let mut topic = String::from_utf8_lossy(build_slice!(s, slen as usize));
+ assert_eq!(topic, "foo");
+ r = rs_mqtt_tx_get_subscribe_topic(&t, 1, &mut s, &mut slen);
+ assert_eq!(r, 1);
+ topic = String::from_utf8_lossy(build_slice!(s, slen as usize));
+ assert_eq!(topic, "baar");
+ r = rs_mqtt_tx_get_subscribe_topic(&t, 2, &mut s, &mut slen);
+ assert_eq!(r, 1);
+ topic = String::from_utf8_lossy(build_slice!(s, slen as usize));
+ assert_eq!(topic, "fieee");
+ r = rs_mqtt_tx_get_subscribe_topic(&t, 3, &mut s, &mut slen);
+ assert_eq!(r, 1);
+ topic = String::from_utf8_lossy(build_slice!(s, slen as usize));
+ assert_eq!(topic, "baaaaz");
+ r = rs_mqtt_tx_get_subscribe_topic(&t, 4, &mut s, &mut slen);
+ assert_eq!(r, 0);
+ }
+}
\ No newline at end of file
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+// written by Sascha Steinbiss <sascha@steinbiss.name>
+
+use std;
+use super::mqtt::{MQTTTransaction, MQTTState};
+use crate::jsonbuilder::{JsonBuilder, JsonError};
+use crate::mqtt::mqtt_message::{MQTTOperation, MQTTSubscribeTopicData};
+use crate::mqtt::parser::{FixedHeader};
+
+pub const MQTT_LOG_PASSWORDS: u32 = BIT_U32!(0);
+
+#[inline]
+fn log_mqtt_topic(js: &mut JsonBuilder, t: &MQTTSubscribeTopicData) -> Result<(), JsonError>
+{
+ js.start_object()?;
+ js.set_string("topic", &t.topic_name)?;
+ js.set_uint("qos", t.qos as u64)?;
+ js.close()?;
+ return Ok(());
+}
+
+#[inline]
+fn log_mqtt_header(js: &mut JsonBuilder, hdr: &FixedHeader) -> Result<(), JsonError>
+{
+ js.set_uint("qos", hdr.qos_level as u64)?;
+ js.set_bool("retain", hdr.retain)?;
+ js.set_bool("dup", hdr.dup_flag)?;
+ return Ok(());
+}
+
+fn log_mqtt(tx: &MQTTTransaction, flags: u32, js: &mut JsonBuilder) -> Result<(), JsonError> {
+ js.open_object("mqtt")?;
+ for msg in tx.msg.iter() {
+ match msg.op {
+ MQTTOperation::CONNECT(ref conn) => {
+ js.open_object("connect")?;
+ log_mqtt_header(js, &msg.header)?;
+ js.set_string("protocol_string", &conn.protocol_string)?;
+ js.set_uint("protocol_version", conn.protocol_version as u64)?;
+ js.set_string("client_id", &conn.client_id)?;
+ js.open_object("flags")?;
+ js.set_bool("username", conn.username_flag)?;
+ js.set_bool("password", conn.password_flag)?;
+ js.set_bool("will_retain", conn.will_retain)?;
+ js.set_bool("will", conn.will_flag)?;
+ js.set_bool("clean_session", conn.clean_session)?;
+ js.close()?; // flags
+ if let Some(user) = &conn.username {
+ js.set_string("username", user)?;
+ }
+ if flags & MQTT_LOG_PASSWORDS != 0 {
+ if let Some(pass) = &conn.password {
+ js.set_string_from_bytes("password", pass)?;
+ }
+ }
+ if conn.will_flag {
+ js.open_object("will")?;
+ if let Some(will_topic) = &conn.will_topic {
+ js.set_string("topic", will_topic)?;
+ }
+ if let Some(will_message) = &conn.will_message {
+ js.set_string_from_bytes("message", will_message)?;
+ }
+ if let Some(will_properties) = &conn.will_properties {
+ js.open_object("properties")?;
+ for prop in will_properties {
+ prop.set_json(js)?;
+ }
+ js.close()?; // properties
+ }
+ js.close()?; // will
+ }
+ if let Some(properties) = &conn.properties {
+ js.open_object("properties")?;
+ for prop in properties {
+ prop.set_json(js)?;
+ }
+ js.close()?; // properties
+ }
+ js.close()?; // connect
+ }
+ MQTTOperation::CONNACK(ref connack) => {
+ js.open_object("connack")?;
+ log_mqtt_header(js, &msg.header)?;
+ js.set_bool("session_present", connack.session_present)?;
+ js.set_uint("return_code", connack.return_code as u64)?;
+ if let Some(properties) = &connack.properties {
+ js.open_object("properties")?;
+ for prop in properties {
+ prop.set_json(js)?;
+ }
+ js.close()?; // properties
+ }
+ js.close()?; // connack
+ }
+ MQTTOperation::PUBLISH(ref publish) => {
+ js.open_object("publish")?;
+ log_mqtt_header(js, &msg.header)?;
+ js.set_string("topic", &publish.topic)?;
+ if let Some(message_id) = publish.message_id {
+ js.set_uint("message_id", message_id as u64)?;
+ }
+ js.set_string_from_bytes("message", &publish.message)?;
+ if let Some(properties) = &publish.properties {
+ js.open_object("properties")?;
+ for prop in properties {
+ prop.set_json(js)?;
+ }
+ js.close()?; // properties
+ }
+ js.close()?; // publish
+ }
+ MQTTOperation::PUBACK(ref msgidonly) => {
+ js.open_object("puback")?;
+ log_mqtt_header(js, &msg.header)?;
+ js.set_uint("message_id", msgidonly.message_id as u64)?;
+ if let Some(reason_code) = &msgidonly.reason_code {
+ js.set_uint("reason_code", *reason_code as u64)?;
+ }
+ if let Some(properties) = &msgidonly.properties {
+ js.open_object("properties")?;
+ for prop in properties {
+ prop.set_json(js)?;
+ }
+ js.close()?; // properties
+ }
+ js.close()?; // puback
+ }
+ MQTTOperation::PUBREC(ref msgidonly) => {
+ js.open_object("pubrec")?;
+ log_mqtt_header(js, &msg.header)?;
+ js.set_uint("message_id", msgidonly.message_id as u64)?;
+ if let Some(reason_code) = &msgidonly.reason_code {
+ js.set_uint("reason_code", *reason_code as u64)?;
+ }
+ if let Some(properties) = &msgidonly.properties {
+ js.open_object("properties")?;
+ for prop in properties {
+ prop.set_json(js)?;
+ }
+ js.close()?; // properties
+ }
+ js.close()?; // pubrec
+ }
+ MQTTOperation::PUBREL(ref msgidonly) => {
+ js.open_object("pubrel")?;
+ log_mqtt_header(js, &msg.header)?;
+ js.set_uint("message_id", msgidonly.message_id as u64)?;
+ if let Some(reason_code) = &msgidonly.reason_code {
+ js.set_uint("reason_code", *reason_code as u64)?;
+ }
+ if let Some(properties) = &msgidonly.properties {
+ js.open_object("properties")?;
+ for prop in properties {
+ prop.set_json(js)?;
+ }
+ js.close()?; // properties
+ }
+ js.close()?; // pubrel
+ }
+ MQTTOperation::PUBCOMP(ref msgidonly) => {
+ js.open_object("pubcomp")?;
+ log_mqtt_header(js, &msg.header)?;
+ js.set_uint("message_id", msgidonly.message_id as u64)?;
+ if let Some(reason_code) = &msgidonly.reason_code {
+ js.set_uint("reason_code", *reason_code as u64)?;
+ }
+ if let Some(properties) = &msgidonly.properties {
+ js.open_object("properties")?;
+ for prop in properties {
+ prop.set_json(js)?;
+ }
+ js.close()?; // properties
+ }
+ js.close()?; // pubcomp
+ }
+ MQTTOperation::SUBSCRIBE(ref subs) => {
+ js.open_object("subscribe")?;
+ log_mqtt_header(js, &msg.header)?;
+ js.set_uint("message_id", subs.message_id as u64)?;
+ js.open_array("topics")?;
+ for t in &subs.topics {
+ log_mqtt_topic(js, t)?;
+ }
+ js.close()?; //topics
+ if let Some(properties) = &subs.properties {
+ js.open_object("properties")?;
+ for prop in properties {
+ prop.set_json(js)?;
+ }
+ js.close()?; // properties
+ }
+ js.close()?; // subscribe
+ }
+ MQTTOperation::SUBACK(ref suback) => {
+ js.open_object("suback")?;
+ log_mqtt_header(js, &msg.header)?;
+ js.set_uint("message_id", suback.message_id as u64)?;
+ js.open_array("qos_granted")?;
+ for t in &suback.qoss {
+ js.append_uint(*t as u64)?;
+ }
+ js.close()?; // qos_granted
+ js.close()?; // suback
+ }
+ MQTTOperation::UNSUBSCRIBE(ref unsub) => {
+ js.open_object("unsubscribe")?;
+ log_mqtt_header(js, &msg.header)?;
+ js.set_uint("message_id", unsub.message_id as u64)?;
+ js.open_array("topics")?;
+ for t in &unsub.topics {
+ js.append_string(t)?;
+ }
+ js.close()?; // topics
+ js.close()?; // unsubscribe
+ }
+ MQTTOperation::UNSUBACK(ref unsuback) => {
+ js.open_object("unsuback")?;
+ log_mqtt_header(js, &msg.header)?;
+ js.set_uint("message_id", unsuback.message_id as u64)?;
+ if let Some(codes) = &unsuback.reason_codes {
+ js.open_array("reason_codes")?;
+ for t in codes {
+ js.append_uint(*t as u64)?;
+ }
+ js.close()?; // reason_codes
+ }
+ js.close()?; // unsuback
+ }
+ MQTTOperation::PINGREQ => {
+ js.open_object("pingreq")?;
+ log_mqtt_header(js, &msg.header)?;
+ js.close()?; // pingreq
+ },
+ MQTTOperation::PINGRESP => {
+ js.open_object("pingresp")?;
+ log_mqtt_header(js, &msg.header)?;
+ js.close()?; // pingresp
+ },
+ MQTTOperation::AUTH(ref auth) => {
+ js.open_object("auth")?;
+ log_mqtt_header(js, &msg.header)?;
+ js.set_uint("reason_code", auth.reason_code as u64)?;
+ if let Some(properties) = &auth.properties {
+ js.open_object("properties")?;
+ for prop in properties {
+ prop.set_json(js)?;
+ }
+ js.close()?; // properties
+ }
+ js.close()?; // auth
+ },
+ MQTTOperation::DISCONNECT(ref disco) => {
+ js.open_object("disconnect")?;
+ log_mqtt_header(js, &msg.header)?;
+ if let Some(reason_code) = &disco.reason_code {
+ js.set_uint("reason_code", *reason_code as u64)?;
+ }
+ if let Some(properties) = &disco.properties {
+ js.open_object("properties")?;
+ for prop in properties {
+ prop.set_json(js)?;
+ }
+ js.close()?; // properties
+ }
+ js.close()?; // disconnect
+ },
+ MQTTOperation::TRUNCATED(ref trunc) => {
+ js.open_object(&trunc.original_message_type.to_lower_str())?;
+ log_mqtt_header(js, &msg.header)?;
+ js.set_bool("truncated", true)?;
+ js.set_uint("skipped_length", trunc.skipped_length as u64)?;
+ js.close()?; // truncated
+ },
+ MQTTOperation::UNASSIGNED => {},
+ }
+ }
+ js.close()?; // mqtt
+
+ return Ok(());
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_logger_log(_state: &mut MQTTState, tx: *mut std::os::raw::c_void, flags: u32, js: &mut JsonBuilder) -> bool {
+ let tx = cast_pointer!(tx, MQTTTransaction);
+ log_mqtt(tx, flags, js).is_ok()
+}
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+pub mod detect;
+pub mod logger;
+pub mod mqtt;
+pub mod mqtt_message;
+pub mod mqtt_property;
+pub mod parser;
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+// written by Sascha Steinbiss <sascha@steinbiss.name>
+
+use super::mqtt_message::*;
+use super::parser::*;
+use crate::applayer::{self, LoggerFlags};
+use crate::applayer::*;
+use crate::core::{self, AppProto, Flow, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP};
+use crate::log::*;
+use num_traits::FromPrimitive;
+use nom;
+use std;
+use std::ffi::{CStr,CString};
+use std::mem::transmute;
+
+// Used as a special pseudo packet identifier to denote the first CONNECT
+// packet in a connection. Note that there is no risk of collision with a
+// parsed packet identifier because in the protocol these are only 16 bit
+// unsigned.
+const MQTT_CONNECT_PKT_ID: u32 = std::u32::MAX;
+// Maximum message length in bytes. If the length of a message exceeds
+// this value, it will be truncated. Default: 1MB.
+static mut MAX_MSG_LEN: u32 = 1048576;
+
+static mut ALPROTO_MQTT: AppProto = ALPROTO_UNKNOWN;
+
+#[derive(FromPrimitive, Debug)]
+#[repr(u32)]
+pub enum MQTTEvent {
+ MissingConnect = 0,
+ MissingPublish,
+ MissingSubscribe,
+ MissingUnsubscribe,
+ DoubleConnect,
+ UnintroducedMessage,
+ InvalidQosLevel,
+ MissingMsgId,
+ UnassignedMsgtype,
+}
+
+#[derive(Debug)]
+pub struct MQTTTransaction {
+ tx_id: u64,
+ pkt_id: Option<u32>,
+ pub msg: Vec<MQTTMessage>,
+ complete: bool,
+ toclient: bool,
+ toserver: bool,
+
+ logged: LoggerFlags,
+ de_state: Option<*mut core::DetectEngineState>,
+ events: *mut core::AppLayerDecoderEvents,
+ tx_data: applayer::AppLayerTxData,
+}
+
+impl MQTTTransaction {
+ pub fn new(msg: MQTTMessage) -> MQTTTransaction {
+ let mut m = MQTTTransaction {
+ tx_id: 0,
+ pkt_id: None,
+ complete: false,
+ logged: LoggerFlags::new(),
+ msg: Vec::new(),
+ toclient: false,
+ toserver: false,
+ de_state: None,
+ events: std::ptr::null_mut(),
+ tx_data: applayer::AppLayerTxData::new(),
+ };
+ m.msg.push(msg);
+ return m;
+ }
+
+ pub fn free(&mut self) {
+ if self.events != std::ptr::null_mut() {
+ core::sc_app_layer_decoder_events_free_events(&mut self.events);
+ }
+ if let Some(state) = self.de_state {
+ core::sc_detect_engine_state_free(state);
+ }
+ }
+}
+
+impl Drop for MQTTTransaction {
+ fn drop(&mut self) {
+ self.free();
+ }
+}
+
+pub struct MQTTState {
+ tx_id: u64,
+ pub protocol_version: u8,
+ transactions: Vec<MQTTTransaction>,
+ connected: bool,
+ skip_request: usize,
+ skip_response: usize,
+ max_msg_len: usize,
+}
+
+impl MQTTState {
+ pub fn new() -> Self {
+ Self {
+ tx_id: 0,
+ protocol_version: 0,
+ transactions: Vec::new(),
+ connected: false,
+ skip_request: 0,
+ skip_response: 0,
+ max_msg_len: unsafe { MAX_MSG_LEN as usize },
+ }
+ }
+
+ fn free_tx(&mut self, tx_id: u64) {
+ let len = self.transactions.len();
+ let mut found = false;
+ let mut index = 0;
+ for i in 0..len {
+ let tx = &self.transactions[i];
+ if tx.tx_id == tx_id + 1 {
+ found = true;
+ index = i;
+ break;
+ }
+ }
+ if found {
+ self.transactions.remove(index);
+ }
+ }
+
+ pub fn get_tx(&mut self, tx_id: u64) -> Option<&MQTTTransaction> {
+ for tx in &mut self.transactions {
+ if tx.tx_id == tx_id + 1 {
+ return Some(tx);
+ }
+ }
+ return None;
+ }
+
+ pub fn get_tx_by_pkt_id(&mut self, pkt_id: u32) -> Option<&mut MQTTTransaction> {
+ for tx in &mut self.transactions {
+ if !tx.complete {
+ if let Some(mpktid) = tx.pkt_id {
+ if mpktid == pkt_id {
+ return Some(tx);
+ }
+ }
+ }
+ }
+ return None;
+ }
+
+ fn new_tx(&mut self, msg: MQTTMessage, toclient: bool) -> MQTTTransaction {
+ let mut tx = MQTTTransaction::new(msg);
+ self.tx_id += 1;
+ tx.tx_id = self.tx_id;
+ if toclient {
+ tx.toclient = true;
+ } else {
+ tx.toserver = true;
+ }
+ return tx;
+ }
+
+ // Handle a MQTT message depending on the direction and state.
+ // Note that we are trying to only have one mutable reference to msg
+ // and its components, however, since we are in a large match operation,
+ // we cannot pass around and/or store more references or move things
+ // without having to introduce lifetimes etc.
+ // This is the reason for the code duplication below. Maybe there is a
+ // more concise way to do it, but this works for now.
+ fn handle_msg(&mut self, msg: MQTTMessage, toclient: bool) {
+ match msg.op {
+ MQTTOperation::CONNECT(ref conn) => {
+ self.protocol_version = conn.protocol_version;
+ if self.connected {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::DoubleConnect);
+ self.transactions.push(tx);
+ } else {
+ let mut tx = self.new_tx(msg, toclient);
+ tx.pkt_id = Some(MQTT_CONNECT_PKT_ID);
+ self.transactions.push(tx);
+ }
+ },
+ MQTTOperation::PUBLISH(ref publish) => {
+ if !self.connected {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage);
+ self.transactions.push(tx);
+ return;
+ }
+ match msg.header.qos_level {
+ 0 => {
+ // with QOS level 0, we do not need to wait for a
+ // response
+ let mut tx = self.new_tx(msg, toclient);
+ tx.complete = true;
+ self.transactions.push(tx);
+ },
+ 1..=2 => {
+ if let Some(pkt_id) = publish.message_id {
+ let mut tx = self.new_tx(msg, toclient);
+ tx.pkt_id = Some(pkt_id as u32);
+ self.transactions.push(tx);
+ } else {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::MissingMsgId);
+ self.transactions.push(tx);
+ }
+ },
+ _ => {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::InvalidQosLevel);
+ self.transactions.push(tx);
+ }
+ }
+ },
+ MQTTOperation::SUBSCRIBE(ref subscribe) => {
+ if !self.connected {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage);
+ self.transactions.push(tx);
+ return;
+ }
+ let pkt_id = subscribe.message_id as u32;
+ match msg.header.qos_level {
+ 0 => {
+ // with QOS level 0, we do not need to wait for a
+ // response
+ let mut tx = self.new_tx(msg, toclient);
+ tx.complete = true;
+ self.transactions.push(tx);
+ },
+ 1..=2 => {
+ let mut tx = self.new_tx(msg, toclient);
+ tx.pkt_id = Some(pkt_id);
+ self.transactions.push(tx);
+ },
+ _ => {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::InvalidQosLevel);
+ self.transactions.push(tx);
+ }
+ }
+ },
+ MQTTOperation::UNSUBSCRIBE(ref unsubscribe) => {
+ if !self.connected {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage);
+ self.transactions.push(tx);
+ return;
+ }
+ let pkt_id = unsubscribe.message_id as u32;
+ match msg.header.qos_level {
+ 0 => {
+ // with QOS level 0, we do not need to wait for a
+ // response
+ let mut tx = self.new_tx(msg, toclient);
+ tx.complete = true;
+ self.transactions.push(tx);
+ },
+ 1..=2 => {
+ let mut tx = self.new_tx(msg, toclient);
+ tx.pkt_id = Some(pkt_id);
+ self.transactions.push(tx);
+ },
+ _ => {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::InvalidQosLevel);
+ self.transactions.push(tx);
+ }
+ }
+ },
+ MQTTOperation::CONNACK(ref _connack) => {
+ if let Some(tx) = self.get_tx_by_pkt_id(MQTT_CONNECT_PKT_ID) {
+ (*tx).msg.push(msg);
+ (*tx).complete = true;
+ (*tx).pkt_id = None;
+ self.connected = true;
+ } else {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::MissingConnect);
+ self.transactions.push(tx);
+ }
+ },
+ MQTTOperation::PUBREC(ref v)
+ | MQTTOperation::PUBREL(ref v) => {
+ if !self.connected {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage);
+ self.transactions.push(tx);
+ return;
+ }
+ if let Some(tx) = self.get_tx_by_pkt_id(v.message_id as u32) {
+ (*tx).msg.push(msg);
+ } else {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::MissingPublish);
+ self.transactions.push(tx);
+ }
+ },
+ MQTTOperation::PUBACK(ref v)
+ | MQTTOperation::PUBCOMP(ref v) => {
+ if !self.connected {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage);
+ self.transactions.push(tx);
+ return;
+ }
+ if let Some(tx) = self.get_tx_by_pkt_id(v.message_id as u32) {
+ (*tx).msg.push(msg);
+ (*tx).complete = true;
+ (*tx).pkt_id = None;
+ } else {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::MissingPublish);
+ self.transactions.push(tx);
+ }
+ },
+ MQTTOperation::SUBACK(ref suback) => {
+ if !self.connected {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage);
+ self.transactions.push(tx);
+ return;
+ }
+ if let Some(tx) = self.get_tx_by_pkt_id(suback.message_id as u32) {
+ (*tx).msg.push(msg);
+ (*tx).complete = true;
+ (*tx).pkt_id = None;
+ } else {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::MissingSubscribe);
+ self.transactions.push(tx);
+ }
+ },
+ MQTTOperation::UNSUBACK(ref unsuback) => {
+ if !self.connected {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage);
+ self.transactions.push(tx);
+ return;
+ }
+ if let Some(tx) = self.get_tx_by_pkt_id(unsuback.message_id as u32) {
+ (*tx).msg.push(msg);
+ (*tx).complete = true;
+ (*tx).pkt_id = None;
+ } else {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::MissingUnsubscribe);
+ self.transactions.push(tx);
+ }
+ },
+ MQTTOperation::UNASSIGNED => {
+ let mut tx = self.new_tx(msg, toclient);
+ tx.complete = true;
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::UnassignedMsgtype);
+ self.transactions.push(tx);
+ },
+ MQTTOperation::TRUNCATED(_) => {
+ let mut tx = self.new_tx(msg, toclient);
+ tx.complete = true;
+ self.transactions.push(tx);
+ },
+ MQTTOperation::AUTH(_)
+ | MQTTOperation::DISCONNECT(_) => {
+ if !self.connected {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage);
+ self.transactions.push(tx);
+ return;
+ }
+ let mut tx = self.new_tx(msg, toclient);
+ tx.complete = true;
+ self.transactions.push(tx);
+ },
+ MQTTOperation::PINGREQ
+ | MQTTOperation::PINGRESP => {
+ if !self.connected {
+ let mut tx = self.new_tx(msg, toclient);
+ &mut MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage);
+ self.transactions.push(tx);
+ return;
+ }
+ let mut tx = self.new_tx(msg, toclient);
+ tx.complete = true;
+ self.transactions.push(tx);
+ }
+ }
+ }
+
+ fn parse_request(&mut self, input: &[u8]) -> AppLayerResult {
+ let mut current = input;
+ if input.len() == 0 {
+ return AppLayerResult::ok();
+ }
+
+ let mut consumed = 0;
+ SCLogDebug!("skip_request {} input len {}", self.skip_request, input.len());
+ if self.skip_request > 0 {
+ if input.len() <= self.skip_request {
+ SCLogDebug!("reducing skip_request by {}", input.len());
+ self.skip_request -= input.len();
+ return AppLayerResult::ok();
+ } else {
+ current = &input[self.skip_request..];
+ SCLogDebug!("skip end reached, skipping {} :{:?}", self.skip_request, current);
+ consumed = self.skip_request;
+ self.skip_request = 0;
+ }
+ }
+
+
+ while current.len() > 0 {
+ let mut skipped = false;
+ SCLogDebug!("request: handling {}", current.len());
+ match parse_message(current, self.protocol_version, self.max_msg_len) {
+ Ok((rem, msg)) => {
+ SCLogDebug!("request msg {:?}", msg);
+ if let MQTTOperation::TRUNCATED(ref trunc) = msg.op {
+ SCLogDebug!("found truncated with skipped {} current len {}", trunc.skipped_length, current.len());
+ if trunc.skipped_length >= current.len() {
+ skipped = true;
+ self.skip_request = trunc.skipped_length - current.len();
+ } else {
+ current = ¤t[trunc.skipped_length..];
+ self.skip_request = 0;
+ }
+ }
+ self.handle_msg(msg, false);
+ if skipped {
+ return AppLayerResult::ok();
+ }
+ consumed += current.len() - rem.len();
+ current = rem;
+ }
+ Err(nom::Err::Incomplete(_)) => {
+ SCLogDebug!("incomplete request: consumed {} needed {} (input len {})", consumed, (current.len() + 1), input.len());
+ return AppLayerResult::incomplete(consumed as u32, (current.len() + 1) as u32);
+ }
+ Err(_) => {
+ return AppLayerResult::err();
+ }
+ }
+ }
+
+ return AppLayerResult::ok();
+ }
+
+ fn parse_response(&mut self, input: &[u8]) -> AppLayerResult {
+ let mut current = input;
+ if input.len() == 0 {
+ return AppLayerResult::ok();
+ }
+
+ let mut consumed = 0;
+ SCLogDebug!("skip_response {} input len {}", self.skip_response, current.len());
+ if self.skip_response > 0 {
+ if input.len() <= self.skip_response {
+ self.skip_response -= current.len();
+ return AppLayerResult::ok();
+ } else {
+ current = &input[self.skip_response..];
+ SCLogDebug!("skip end reached, skipping {} :{:?}", self.skip_request, current);
+ consumed = self.skip_response;
+ self.skip_response = 0;
+ }
+ }
+
+ while current.len() > 0 {
+ let mut skipped = false;
+ SCLogDebug!("response: handling {}", current.len());
+ match parse_message(current, self.protocol_version, self.max_msg_len as usize) {
+ Ok((rem, msg)) => {
+ SCLogDebug!("response msg {:?}", msg);
+ if let MQTTOperation::TRUNCATED(ref trunc) = msg.op {
+ SCLogDebug!("found truncated with skipped {} current len {}", trunc.skipped_length, current.len());
+ if trunc.skipped_length >= current.len() {
+ skipped = true;
+ self.skip_response = trunc.skipped_length - current.len();
+ } else {
+ current = ¤t[trunc.skipped_length..];
+ self.skip_response = 0;
+ }
+ SCLogDebug!("skip_response now {}", self.skip_response);
+ }
+ self.handle_msg(msg, true);
+ if skipped {
+ return AppLayerResult::ok();
+ }
+ consumed += current.len() - rem.len();
+ current = rem;
+ }
+ Err(nom::Err::Incomplete(_)) => {
+ SCLogDebug!("incomplete response: consumed {} needed {} (input len {})", consumed, (current.len() + 1), input.len());
+ return AppLayerResult::incomplete(consumed as u32, (current.len() + 1) as u32);
+ }
+ Err(_) => {
+ return AppLayerResult::err();
+ }
+ }
+ }
+
+ return AppLayerResult::ok();
+ }
+
+ fn set_event(tx: &mut MQTTTransaction, event: MQTTEvent) {
+ let ev = event as u8;
+ core::sc_app_layer_decoder_events_set_event_raw(&mut tx.events, ev);
+ }
+
+ fn tx_iterator(
+ &mut self,
+ min_tx_id: u64,
+ state: &mut u64,
+ ) -> Option<(&MQTTTransaction, u64, bool)> {
+ let mut index = *state as usize;
+ let len = self.transactions.len();
+
+ while index < len {
+ let tx = &self.transactions[index];
+ if tx.tx_id < min_tx_id + 1 {
+ index += 1;
+ continue;
+ }
+ *state = index as u64;
+ return Some((tx, tx.tx_id - 1, (len - index) > 1));
+ }
+
+ return None;
+ }
+}
+
+// C exports.
+
+export_tx_get_detect_state!(rs_mqtt_tx_get_detect_state, MQTTTransaction);
+export_tx_set_detect_state!(rs_mqtt_tx_set_detect_state, MQTTTransaction);
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_probing_parser(
+ _flow: *const Flow,
+ _direction: u8,
+ input: *const u8,
+ input_len: u32,
+ _rdir: *mut u8,
+) -> AppProto {
+ let buf = build_slice!(input, input_len as usize);
+ match parse_fixed_header(buf) {
+ Ok((_, hdr)) => {
+ // reject unassigned message type
+ if hdr.message_type == MQTTTypeCode::UNASSIGNED {
+ return unsafe { ALPROTO_FAILED } ;
+ }
+ // with 2 being the highest valid QoS level
+ if hdr.qos_level > 2 {
+ return unsafe { ALPROTO_FAILED };
+ }
+ return unsafe { ALPROTO_MQTT };
+ },
+ Err(nom::Err::Incomplete(_)) => ALPROTO_UNKNOWN,
+ Err(_) => unsafe { ALPROTO_FAILED }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_state_new() -> *mut std::os::raw::c_void {
+ let state = MQTTState::new();
+ let boxed = Box::new(state);
+ return unsafe { transmute(boxed) };
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_state_free(state: *mut std::os::raw::c_void) {
+ let _drop: Box<MQTTState> = unsafe { transmute(state) };
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) {
+ let state = cast_pointer!(state, MQTTState);
+ state.free_tx(tx_id);
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_parse_request(
+ _flow: *const Flow,
+ state: *mut std::os::raw::c_void,
+ _pstate: *mut std::os::raw::c_void,
+ input: *const u8,
+ input_len: u32,
+ _data: *const std::os::raw::c_void,
+ _flags: u8,
+) -> AppLayerResult {
+ let state = cast_pointer!(state, MQTTState);
+ let buf = build_slice!(input, input_len as usize);
+ return state.parse_request(buf).into();
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_parse_response(
+ _flow: *const Flow,
+ state: *mut std::os::raw::c_void,
+ _pstate: *mut std::os::raw::c_void,
+ input: *const u8,
+ input_len: u32,
+ _data: *const std::os::raw::c_void,
+ _flags: u8,
+) -> AppLayerResult {
+ let state = cast_pointer!(state, MQTTState);
+ let buf = build_slice!(input, input_len as usize);
+ return state.parse_response(buf).into();
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_state_get_tx(
+ state: *mut std::os::raw::c_void,
+ tx_id: u64,
+) -> *mut std::os::raw::c_void {
+ let state = cast_pointer!(state, MQTTState);
+ match state.get_tx(tx_id) {
+ Some(tx) => {
+ return unsafe { transmute(tx) };
+ }
+ None => {
+ return std::ptr::null_mut();
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 {
+ let state = cast_pointer!(state, MQTTState);
+ return state.tx_id;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_state_progress_completion_status(_direction: u8) -> std::os::raw::c_int {
+ return 1;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_is_toclient(tx: *const std::os::raw::c_void) -> std::os::raw::c_int {
+ let tx = cast_pointer!(tx, MQTTTransaction);
+ if tx.toclient {
+ return 1;
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_get_alstate_progress(
+ tx: *mut std::os::raw::c_void,
+ direction: u8,
+) -> std::os::raw::c_int {
+ let tx = cast_pointer!(tx, MQTTTransaction);
+ if tx.complete {
+ if direction == core::STREAM_TOSERVER {
+ if tx.toserver {
+ return 1;
+ }
+ } else if direction == core::STREAM_TOCLIENT {
+ if tx.toclient {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_get_logged(
+ _state: *mut std::os::raw::c_void,
+ tx: *mut std::os::raw::c_void,
+) -> u32 {
+ let tx = cast_pointer!(tx, MQTTTransaction);
+ return tx.logged.get();
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_tx_set_logged(
+ _state: *mut std::os::raw::c_void,
+ tx: *mut std::os::raw::c_void,
+ logged: u32,
+) {
+ let tx = cast_pointer!(tx, MQTTTransaction);
+ tx.logged.set(logged);
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_state_get_events(
+ tx: *mut std::os::raw::c_void,
+) -> *mut core::AppLayerDecoderEvents {
+ let tx = cast_pointer!(tx, MQTTTransaction);
+ return tx.events;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_state_get_event_info_by_id(event_id: std::os::raw::c_int,
+ event_name: *mut *const std::os::raw::c_char,
+ event_type: *mut core::AppLayerEventType)
+ -> i8
+{
+ if let Some(e) = FromPrimitive::from_i32(event_id as i32) {
+ let estr = match e {
+ MQTTEvent::MissingConnect => { "missing_connect\0" },
+ MQTTEvent::MissingPublish => { "missing_publish\0" },
+ MQTTEvent::MissingSubscribe => { "missing_subscribe\0" },
+ MQTTEvent::MissingUnsubscribe => { "missing_unsubscribe\0" },
+ MQTTEvent::DoubleConnect => { "double_connect\0" },
+ MQTTEvent::UnintroducedMessage => { "unintroduced_message\0" },
+ MQTTEvent::InvalidQosLevel => { "invalid_qos_level\0" },
+ MQTTEvent::MissingMsgId => { "missing_msg_id\0" },
+ MQTTEvent::UnassignedMsgtype => { "unassigned_msg_type\0" },
+ };
+ unsafe{
+ *event_name = estr.as_ptr() as *const std::os::raw::c_char;
+ *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION;
+ };
+ 0
+ } else {
+ -1
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_state_get_event_info(event_name: *const std::os::raw::c_char,
+ event_id: *mut std::os::raw::c_int,
+ event_type: *mut core::AppLayerEventType)
+ -> std::os::raw::c_int
+{
+ if event_name == std::ptr::null() { return -1; }
+ let c_event_name: &CStr = unsafe { CStr::from_ptr(event_name) };
+ let event = match c_event_name.to_str() {
+ Ok(s) => {
+ match s {
+ "missing_connect" => MQTTEvent::MissingConnect as i32,
+ "missing_publish" => MQTTEvent::MissingPublish as i32,
+ "missing_subscribe" => MQTTEvent::MissingSubscribe as i32,
+ "missing_unsubscribe" => MQTTEvent::MissingUnsubscribe as i32,
+ "double_connect" => MQTTEvent::DoubleConnect as i32,
+ "unintroduced_message" => MQTTEvent::UnintroducedMessage as i32,
+ "invalid_qos_level" => MQTTEvent::InvalidQosLevel as i32,
+ "missing_msg_id" => MQTTEvent::MissingMsgId as i32,
+ "unassigned_msg_type" => MQTTEvent::UnassignedMsgtype as i32,
+ _ => -1, // unknown event
+ }
+ },
+ Err(_) => -1, // UTF-8 conversion failed
+ };
+ unsafe{
+ *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION;
+ *event_id = event as std::os::raw::c_int;
+ };
+ 0
+}
+
+#[no_mangle]
+pub extern "C" fn rs_mqtt_state_get_tx_iterator(
+ _ipproto: u8,
+ _alproto: AppProto,
+ state: *mut std::os::raw::c_void,
+ min_tx_id: u64,
+ _max_tx_id: u64,
+ istate: &mut u64,
+) -> applayer::AppLayerGetTxIterTuple {
+ let state = cast_pointer!(state, MQTTState);
+ match state.tx_iterator(min_tx_id, istate) {
+ Some((tx, out_tx_id, has_next)) => {
+ let c_tx = unsafe { transmute(tx) };
+ let ires = applayer::AppLayerGetTxIterTuple::with_values(c_tx, out_tx_id, has_next);
+ return ires;
+ }
+ None => {
+ return applayer::AppLayerGetTxIterTuple::not_found();
+ }
+ }
+}
+
+// Parser name as a C style string.
+const PARSER_NAME: &'static [u8] = b"mqtt\0";
+
+export_tx_data_get!(rs_mqtt_get_tx_data, MQTTTransaction);
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_mqtt_register_parser(cfg_max_msg_len: u32) {
+ let default_port = CString::new("[1883]").unwrap();
+ let max_msg_len = &mut MAX_MSG_LEN;
+ *max_msg_len = cfg_max_msg_len;
+ let parser = RustParser {
+ name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char,
+ default_port: default_port.as_ptr(),
+ ipproto: IPPROTO_TCP,
+ probe_ts: Some(rs_mqtt_probing_parser),
+ probe_tc: Some(rs_mqtt_probing_parser),
+ min_depth: 0,
+ max_depth: 16,
+ state_new: rs_mqtt_state_new,
+ state_free: rs_mqtt_state_free,
+ tx_free: rs_mqtt_state_tx_free,
+ parse_ts: rs_mqtt_parse_request,
+ parse_tc: rs_mqtt_parse_response,
+ get_tx_count: rs_mqtt_state_get_tx_count,
+ get_tx: rs_mqtt_state_get_tx,
+ tx_get_comp_st: rs_mqtt_state_progress_completion_status,
+ tx_get_progress: rs_mqtt_tx_get_alstate_progress,
+ get_de_state: rs_mqtt_tx_get_detect_state,
+ set_de_state: rs_mqtt_tx_set_detect_state,
+ get_events: Some(rs_mqtt_state_get_events),
+ get_eventinfo: Some(rs_mqtt_state_get_event_info),
+ get_eventinfo_byid: Some(rs_mqtt_state_get_event_info_by_id),
+ localstorage_new: None,
+ localstorage_free: None,
+ get_files: None,
+ get_tx_iterator: Some(rs_mqtt_state_get_tx_iterator),
+ get_tx_data: rs_mqtt_get_tx_data,
+ apply_tx_config: None,
+ flags: 0,
+ };
+
+ let ip_proto_str = CString::new("tcp").unwrap();
+
+ if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
+ let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
+ ALPROTO_MQTT = alproto;
+ if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
+ let _ = AppLayerRegisterParser(&parser, alproto);
+ }
+ } else {
+ SCLogDebug!("Protocol detector and parser disabled for MQTT.");
+ }
+}
--- /dev/null
+
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+// written by Sascha Steinbiss <sascha@steinbiss.name>
+
+use crate::mqtt::mqtt_property::*;
+use crate::mqtt::parser::*;
+use std::fmt;
+
+#[derive(Debug)]
+pub struct MQTTMessage {
+ pub header: FixedHeader,
+ pub op: MQTTOperation,
+}
+
+#[derive(Debug)]
+pub enum MQTTOperation {
+ UNASSIGNED,
+ CONNECT(MQTTConnectData),
+ CONNACK(MQTTConnackData),
+ PUBLISH(MQTTPublishData),
+ PUBACK(MQTTMessageIdOnly),
+ PUBREC(MQTTMessageIdOnly),
+ PUBREL(MQTTMessageIdOnly),
+ PUBCOMP(MQTTMessageIdOnly),
+ SUBSCRIBE(MQTTSubscribeData),
+ SUBACK(MQTTSubackData),
+ UNSUBSCRIBE(MQTTUnsubscribeData),
+ UNSUBACK(MQTTUnsubackData),
+ AUTH(MQTTAuthData),
+ PINGREQ,
+ PINGRESP,
+ DISCONNECT(MQTTDisconnectData),
+ // TRUNCATED is special, representing a message that was not parsed
+ // in its entirety due to size constraints. There is no equivalent in
+ // the MQTT specification.
+ TRUNCATED(MQTTTruncatedData),
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, PartialEq, PartialOrd, FromPrimitive, Debug)]
+pub enum MQTTTypeCode {
+ UNASSIGNED = 0,
+ CONNECT = 1,
+ CONNACK = 2,
+ PUBLISH = 3,
+ PUBACK = 4,
+ PUBREC = 5,
+ PUBREL = 6,
+ PUBCOMP = 7,
+ SUBSCRIBE = 8,
+ SUBACK = 9,
+ UNSUBSCRIBE = 10,
+ UNSUBACK = 11,
+ PINGREQ = 12,
+ PINGRESP = 13,
+ DISCONNECT = 14,
+ AUTH = 15,
+}
+
+impl MQTTTypeCode {
+ pub fn to_lower_str(&self) -> String {
+ self.to_string().to_lowercase()
+ }
+}
+
+impl fmt::Display for MQTTTypeCode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+impl std::str::FromStr for MQTTTypeCode {
+ type Err = String;
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let su = s.to_uppercase();
+ let su_slice: &str = &*su;
+ match su_slice {
+ "CONNECT" => Ok(MQTTTypeCode::CONNECT),
+ "CONNACK" => Ok(MQTTTypeCode::CONNACK),
+ "PUBLISH" => Ok(MQTTTypeCode::PUBLISH),
+ "PUBACK" => Ok(MQTTTypeCode::PUBACK),
+ "PUBREC" => Ok(MQTTTypeCode::PUBREC),
+ "PUBREL" => Ok(MQTTTypeCode::PUBREL),
+ "PUBCOMP" => Ok(MQTTTypeCode::PUBCOMP),
+ "SUBSCRIBE" => Ok(MQTTTypeCode::SUBSCRIBE),
+ "SUBACK" => Ok(MQTTTypeCode::SUBACK),
+ "UNSUBSCRIBE" => Ok(MQTTTypeCode::UNSUBSCRIBE),
+ "UNSUBACK" => Ok(MQTTTypeCode::UNSUBACK),
+ "PINGREQ" => Ok(MQTTTypeCode::PINGREQ),
+ "PINGRESP" => Ok(MQTTTypeCode::PINGRESP),
+ "DISCONNECT" => Ok(MQTTTypeCode::DISCONNECT),
+ "AUTH" => Ok(MQTTTypeCode::AUTH),
+ _ => Err(format!("'{}' is not a valid value for MQTTTypeCode", s)),
+ }
+ }
+}
+
+
+#[derive(Debug)]
+pub struct MQTTConnectData {
+ pub protocol_string: String,
+ pub protocol_version: u8,
+ pub username_flag: bool,
+ pub password_flag: bool,
+ pub will_retain: bool,
+ pub will_qos: u8,
+ pub will_flag: bool,
+ pub clean_session: bool,
+ pub keepalive: u16,
+ pub client_id: String,
+ pub will_topic: Option<String>,
+ pub will_message: Option<Vec<u8>>,
+ pub username: Option<String>,
+ pub password: Option<Vec<u8>>,
+ pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0
+ pub will_properties: Option<Vec<MQTTProperty>>, // MQTT 5.0
+}
+
+#[derive(Debug)]
+pub struct MQTTConnackData {
+ pub return_code: u8,
+ pub session_present: bool, // MQTT 3.1.1
+ pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0
+}
+
+#[derive(Debug)]
+pub struct MQTTPublishData {
+ pub topic: String,
+ pub message_id: Option<u16>,
+ pub message: Vec<u8>,
+ pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0
+}
+
+#[derive(Debug)]
+pub struct MQTTMessageIdOnly {
+ pub message_id: u16,
+ pub reason_code: Option<u8>, // MQTT 5.0
+ pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0
+}
+
+#[derive(Debug)]
+pub struct MQTTSubscribeTopicData {
+ pub topic_name: String,
+ pub qos: u8,
+}
+
+#[derive(Debug)]
+pub struct MQTTSubscribeData {
+ pub message_id: u16,
+ pub topics: Vec<MQTTSubscribeTopicData>,
+ pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0
+}
+
+#[derive(Debug)]
+pub struct MQTTSubackData {
+ pub message_id: u16,
+ pub qoss: Vec<u8>,
+ pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0
+}
+
+#[derive(Debug)]
+pub struct MQTTUnsubscribeData {
+ pub message_id: u16,
+ pub topics: Vec<String>,
+ pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0
+}
+
+#[derive(Debug)]
+pub struct MQTTUnsubackData {
+ pub message_id: u16,
+ pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0
+ pub reason_codes: Option<Vec<u8>>, // MQTT 5.0
+}
+
+#[derive(Debug)]
+pub struct MQTTAuthData {
+ pub reason_code: u8, // MQTT 5.0
+ pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0
+}
+
+#[derive(Debug)]
+pub struct MQTTDisconnectData {
+ pub reason_code: Option<u8>, // MQTT 5.0
+ pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0
+}
+
+#[derive(Debug)]
+pub struct MQTTTruncatedData {
+ pub original_message_type: MQTTTypeCode,
+ pub skipped_length: usize,
+}
\ No newline at end of file
--- /dev/null
+
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+// written by Sascha Steinbiss <sascha@steinbiss.name>
+
+use crate::mqtt::parser::*;
+use crate::jsonbuilder::{JsonBuilder, JsonError};
+use nom::number::streaming::*;
+use nom::*;
+
+// TODO: It might be useful to also add detection on property presence and
+// content, e.g. mqtt.property: AUTHENTICATION_METHOD.
+#[derive(Debug)]
+#[allow(non_camel_case_types)]
+pub enum MQTTProperty {
+ UNKNOWN,
+ PAYLOAD_FORMAT_INDICATOR(u8),
+ MESSAGE_EXPIRY_INTERVAL(u32),
+ CONTENT_TYPE(String),
+ RESPONSE_TOPIC(String),
+ CORRELATION_DATA(Vec<u8>),
+ SUBSCRIPTION_IDENTIFIER(u32),
+ SESSION_EXPIRY_INTERVAL(u32),
+ ASSIGNED_CLIENT_IDENTIFIER(String),
+ SERVER_KEEP_ALIVE(u16),
+ AUTHENTICATION_METHOD(String),
+ AUTHENTICATION_DATA(Vec<u8>),
+ REQUEST_PROBLEM_INFORMATION(u8),
+ WILL_DELAY_INTERVAL(u32),
+ REQUEST_RESPONSE_INFORMATION(u8),
+ RESPONSE_INFORMATION(String),
+ SERVER_REFERENCE(String),
+ REASON_STRING(String),
+ RECEIVE_MAXIMUM(u16),
+ TOPIC_ALIAS_MAXIMUM(u16),
+ TOPIC_ALIAS(u16),
+ MAXIMUM_QOS(u8),
+ RETAIN_AVAILABLE(u8),
+ USER_PROPERTY((String, String)),
+ MAXIMUM_PACKET_SIZE(u32),
+ WILDCARD_SUBSCRIPTION_AVAILABLE(u8),
+ SUBSCRIPTION_IDENTIFIER_AVAILABLE(u8),
+ SHARED_SUBSCRIPTION_AVAILABLE(u8),
+}
+
+impl crate::mqtt::mqtt_property::MQTTProperty {
+ pub fn set_json(&self, js: &mut JsonBuilder) -> Result<(), JsonError> {
+ match self {
+ crate::mqtt::mqtt_property::MQTTProperty::PAYLOAD_FORMAT_INDICATOR(v) => {
+ js.set_uint("payload_format_indicator", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::MESSAGE_EXPIRY_INTERVAL(v) => {
+ js.set_uint("message_expiry_interval", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::CONTENT_TYPE(v) => {
+ js.set_string("content_type", &v)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::RESPONSE_TOPIC(v) => {
+ js.set_string("response_topic", &v)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::CORRELATION_DATA(v) => {
+ js.set_string_from_bytes("correlation_data", &v)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::SUBSCRIPTION_IDENTIFIER(v) => {
+ js.set_uint("subscription_identifier", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::SESSION_EXPIRY_INTERVAL(v) => {
+ js.set_uint("session_expiry_interval", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::ASSIGNED_CLIENT_IDENTIFIER(v) => {
+ js.set_string("assigned_client_identifier", &v)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::SERVER_KEEP_ALIVE(v) => {
+ js.set_uint("server_keep_alive", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::AUTHENTICATION_METHOD(v) => {
+ js.set_string("authentication_method", &v)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::AUTHENTICATION_DATA(v) => {
+ js.set_string_from_bytes("authentication_data", &v)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::REQUEST_PROBLEM_INFORMATION(v) => {
+ js.set_uint("request_problem_information", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::WILL_DELAY_INTERVAL(v) => {
+ js.set_uint("will_delay_interval", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::REQUEST_RESPONSE_INFORMATION(v) => {
+ js.set_uint("request_response_information", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::RESPONSE_INFORMATION(v) => {
+ js.set_string("response_information", &v)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::SERVER_REFERENCE(v) => {
+ js.set_string("server_reference", &v)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::REASON_STRING(v) => {
+ js.set_string("reason_string", &v)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::RECEIVE_MAXIMUM(v) => {
+ js.set_uint("receive_maximum", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::TOPIC_ALIAS_MAXIMUM(v) => {
+ js.set_uint("topic_alias_maximum", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::TOPIC_ALIAS(v) => {
+ js.set_uint("topic_alias", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::MAXIMUM_QOS(v) => {
+ js.set_uint("maximum_qos", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::RETAIN_AVAILABLE(v) => {
+ js.set_uint("retain_available", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::USER_PROPERTY((k, v)) => {
+ js.set_string(k, &v)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::MAXIMUM_PACKET_SIZE(v) => {
+ js.set_uint("maximum_packet_size", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::WILDCARD_SUBSCRIPTION_AVAILABLE(v) => {
+ js.set_uint("wildcard_subscription_available", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::SUBSCRIPTION_IDENTIFIER_AVAILABLE(v) => {
+ js.set_uint("subscription_identifier_available", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::SHARED_SUBSCRIPTION_AVAILABLE(v) => {
+ js.set_uint("shared_subscription_available", *v as u64)?;
+ }
+ crate::mqtt::mqtt_property::MQTTProperty::UNKNOWN => {
+ // pass
+ }
+ }
+ Ok(())
+ }
+}
+
+#[inline]
+pub fn parse_qualified_property(input: &[u8], identifier: u32) -> IResult<&[u8], MQTTProperty> {
+ match identifier {
+ 1 => match be_u8(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::PAYLOAD_FORMAT_INDICATOR(val))),
+ Err(e) => return Err(e),
+ },
+ 2 => match be_u32(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::MESSAGE_EXPIRY_INTERVAL(val))),
+ Err(e) => return Err(e),
+ },
+ 3 => match parse_mqtt_string(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::CONTENT_TYPE(val))),
+ Err(e) => return Err(e),
+ },
+ 8 => match parse_mqtt_string(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::RESPONSE_TOPIC(val))),
+ Err(e) => return Err(e),
+ },
+ 9 => match parse_mqtt_binary_data(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::CORRELATION_DATA(val))),
+ Err(e) => return Err(e),
+ },
+ 11 => match parse_mqtt_variable_integer(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::SUBSCRIPTION_IDENTIFIER(val))),
+ Err(e) => return Err(e),
+ },
+ 17 => match be_u32(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::SESSION_EXPIRY_INTERVAL(val))),
+ Err(e) => return Err(e),
+ },
+ 18 => match parse_mqtt_string(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::ASSIGNED_CLIENT_IDENTIFIER(val))),
+ Err(e) => return Err(e),
+ },
+ 19 => match be_u16(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::SERVER_KEEP_ALIVE(val))),
+ Err(e) => return Err(e),
+ },
+ 21 => match parse_mqtt_string(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::AUTHENTICATION_METHOD(val))),
+ Err(e) => return Err(e),
+ },
+ 22 => match parse_mqtt_binary_data(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::AUTHENTICATION_DATA(val))),
+ Err(e) => return Err(e),
+ },
+ 23 => match be_u8(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::REQUEST_PROBLEM_INFORMATION(val))),
+ Err(e) => return Err(e),
+ },
+ 24 => match be_u32(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::WILL_DELAY_INTERVAL(val))),
+ Err(e) => return Err(e),
+ },
+ 25 => match be_u8(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::REQUEST_RESPONSE_INFORMATION(val))),
+ Err(e) => return Err(e),
+ },
+ 26 => match parse_mqtt_string(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::RESPONSE_INFORMATION(val))),
+ Err(e) => return Err(e),
+ },
+ 28 => match parse_mqtt_string(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::SERVER_REFERENCE(val))),
+ Err(e) => return Err(e),
+ },
+ 31 => match parse_mqtt_string(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::REASON_STRING(val))),
+ Err(e) => return Err(e),
+ },
+ 33 => match be_u16(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::RECEIVE_MAXIMUM(val))),
+ Err(e) => return Err(e),
+ },
+ 34 => match be_u16(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::TOPIC_ALIAS_MAXIMUM(val))),
+ Err(e) => return Err(e),
+ },
+ 35 => match be_u16(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::TOPIC_ALIAS(val))),
+ Err(e) => return Err(e),
+ },
+ 36 => match be_u8(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::MAXIMUM_QOS(val))),
+ Err(e) => return Err(e),
+ },
+ 37 => match be_u8(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::RETAIN_AVAILABLE(val))),
+ Err(e) => return Err(e),
+ },
+ 38 => match parse_mqtt_string_pair(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::USER_PROPERTY(val))),
+ Err(e) => return Err(e),
+ },
+ 39 => match be_u32(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::MAXIMUM_PACKET_SIZE(val))),
+ Err(e) => return Err(e),
+ },
+ 40 => match be_u8(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::WILDCARD_SUBSCRIPTION_AVAILABLE(val))),
+ Err(e) => return Err(e),
+ },
+ 41 => match be_u8(input) {
+ Ok((rem, val)) => {
+ return Ok((rem, MQTTProperty::SUBSCRIPTION_IDENTIFIER_AVAILABLE(val)))
+ }
+ Err(e) => return Err(e),
+ },
+ 42 => match be_u8(input) {
+ Ok((rem, val)) => return Ok((rem, MQTTProperty::SHARED_SUBSCRIPTION_AVAILABLE(val))),
+ Err(e) => return Err(e),
+ },
+ _ => {
+ return Ok((input, MQTTProperty::UNKNOWN));
+ }
+ }
+}
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+// written by Sascha Steinbiss <sascha@steinbiss.name>
+
+use crate::mqtt::mqtt_message::*;
+use crate::mqtt::mqtt_property::*;
+use nom::combinator::rest;
+use nom::number::streaming::*;
+use nom::*;
+use num_traits::FromPrimitive;
+
+#[derive(Debug)]
+pub struct FixedHeader {
+ pub message_type: MQTTTypeCode,
+ pub dup_flag: bool,
+ pub qos_level: u8,
+ pub retain: bool,
+ pub remaining_length: u32,
+}
+
+// PARSING HELPERS
+
+#[inline]
+fn is_continuation_bit_set(b: u8) -> bool {
+ return (b & 128) != 0;
+}
+
+#[inline]
+fn convert_varint(continued: Vec<u8>, last: u8) -> u32 {
+ let mut multiplier = 1u32;
+ let mut value = 0u32;
+ for val in &continued {
+ value = value + ((val & 127) as u32 * multiplier);
+ multiplier = multiplier * 128u32;
+ }
+ value = value + ((last & 127) as u32 * multiplier);
+ return value;
+}
+
+#[inline]
+fn varint_length(val: usize) -> usize {
+ match val {
+ 0 ..= 127 => 1,
+ 128 ..= 16383 => 2,
+ 16384 ..= 2097151 => 3,
+ 2097152 ..= 268435455 => 4,
+ _ => 0,
+ }
+}
+
+// DATA TYPES
+
+named!(#[inline], pub parse_mqtt_string<String>,
+ do_parse!(
+ length: be_u16
+ >> content: take!(length)
+ >> (
+ String::from_utf8_lossy(&content).to_string()
+ )
+ ));
+
+named!(#[inline], pub parse_mqtt_variable_integer<u32>,
+ do_parse!(
+ continued_part: take_while!(is_continuation_bit_set)
+ >> non_continued_part: be_u8
+ >> (
+ convert_varint(continued_part.to_vec(), non_continued_part)
+ )
+ ));
+
+named!(#[inline], pub parse_mqtt_binary_data<Vec<u8>>,
+ do_parse!(
+ length: be_u16
+ >> data: take!(length)
+ >> (
+ data.to_vec()
+ )
+ ));
+
+named!(#[inline], pub parse_mqtt_string_pair<(String, String)>,
+ do_parse!(
+ name: parse_mqtt_string
+ >> value: parse_mqtt_string
+ >> (
+ (name, value)
+ )
+ ));
+
+// MESSAGE COMPONENTS
+
+named!(#[inline], pub parse_property<MQTTProperty>,
+ do_parse!(
+ identifier: parse_mqtt_variable_integer
+ >> value: call!(parse_qualified_property, identifier)
+ >> (
+ value
+ )
+ ));
+
+#[inline]
+fn parse_properties(input: &[u8], precond: bool) -> IResult<&[u8], Option<Vec<MQTTProperty>>> {
+ // do not try to parse anything when precondition is not met
+ if !precond {
+ return Ok((input, None));
+ }
+ // parse properties length
+ match parse_mqtt_variable_integer(input) {
+ Ok((rem, mut proplen)) => {
+ if proplen == 0 {
+ // no properties
+ return Ok((rem, None));
+ }
+ // parse properties
+ let mut props = Vec::<MQTTProperty>::new();
+ let mut newrem = rem;
+ while proplen > 0 {
+ match parse_property(newrem) {
+ Ok((rem, val)) => {
+ props.push(val);
+ let curparselen = (newrem.len() - rem.len()) as u32;
+ proplen -= curparselen;
+ newrem = &rem;
+ }
+ Err(e) => return Err(e),
+ }
+ }
+ return Ok((newrem, Some(props)));
+ }
+ Err(e) => return Err(e),
+ }
+}
+
+#[inline]
+fn parse_fixed_header_flags(i: &[u8]) -> IResult<&[u8], (u8, u8, u8, u8)> {
+ bits!(
+ i,
+ tuple!(
+ take_bits!(4u8),
+ take_bits!(1u8),
+ take_bits!(2u8),
+ take_bits!(1u8)
+ )
+ )
+}
+
+#[inline]
+fn parse_message_type(code: u8) -> MQTTTypeCode {
+ match code {
+ 0..=15 => {
+ if let Some(t) = FromPrimitive::from_u8(code) {
+ return t
+ } else {
+ return MQTTTypeCode::UNASSIGNED
+ }
+ },
+ _ => {
+ // unreachable state in parser: we only pass values parsed from take_bits!(4u8)
+ debug_validate_fail!("can't have message codes >15 from 4 bits");
+ MQTTTypeCode::UNASSIGNED
+ }
+ }
+}
+
+named!(#[inline], pub parse_fixed_header<FixedHeader>,
+ do_parse!(
+ flags: parse_fixed_header_flags
+ >> remaining_length: parse_mqtt_variable_integer
+ >> (
+ FixedHeader {
+ message_type: parse_message_type(flags.0),
+ dup_flag: flags.1 != 0,
+ qos_level: flags.2 as u8,
+ retain: flags.3 != 0,
+ remaining_length: remaining_length,
+ }
+ )
+ ));
+
+#[inline]
+fn parse_connect_variable_flags(i: &[u8]) -> IResult<&[u8], (u8, u8, u8, u8, u8, u8, u8)> {
+ bits!(
+ i,
+ tuple!(
+ take_bits!(1u8),
+ take_bits!(1u8),
+ take_bits!(1u8),
+ take_bits!(2u8),
+ take_bits!(1u8),
+ take_bits!(1u8),
+ take_bits!(1u8)
+ )
+ )
+}
+
+named!(#[inline], pub parse_connect<MQTTConnectData>,
+ do_parse!(
+ protocol_string: parse_mqtt_string
+ >> protocol_version: be_u8
+ >> flags: parse_connect_variable_flags
+ >> keepalive: be_u16
+ >> properties: call!(parse_properties, protocol_version == 5)
+ >> client_id: parse_mqtt_string
+ >> will_properties: call!(parse_properties, protocol_version == 5 && flags.4 != 0)
+ >> will_topic: cond!(flags.4 != 0, parse_mqtt_string)
+ >> will_message: cond!(flags.4 != 0, parse_mqtt_binary_data)
+ >> username: cond!(flags.0 != 0, parse_mqtt_string)
+ >> password: cond!(flags.1 != 0, parse_mqtt_binary_data)
+ >> (
+ MQTTConnectData {
+ protocol_string: protocol_string,
+ protocol_version: protocol_version,
+ username_flag: flags.0 != 0,
+ password_flag: flags.1 != 0,
+ will_retain: flags.2 != 0,
+ will_qos: flags.3 as u8,
+ will_flag: flags.4 != 0,
+ clean_session: flags.5 != 0,
+ keepalive: keepalive,
+ client_id: client_id,
+ will_topic: will_topic,
+ will_message: will_message,
+ username: username,
+ password: password,
+ properties: properties,
+ will_properties: will_properties,
+ }
+ )
+ ));
+
+named_args!(pub parse_connack(protocol_version: u8)<MQTTConnackData>,
+ do_parse!(
+ topic_name_compression_response: be_u8
+ >> retcode: be_u8
+ >> properties: call!(parse_properties, protocol_version == 5)
+ >> (
+ MQTTConnackData {
+ session_present: (topic_name_compression_response & 1) != 0,
+ return_code: retcode,
+ properties: properties,
+ }
+ )
+ ));
+
+named_args!(pub parse_publish(protocol_version: u8, has_id: bool)<MQTTPublishData>,
+ do_parse!(
+ topic: parse_mqtt_string
+ >> message_id: cond!(has_id, be_u16)
+ >> properties: call!(parse_properties, protocol_version == 5)
+ >> message: rest
+ >> (
+ MQTTPublishData {
+ topic: topic,
+ message_id: message_id,
+ message: message.to_vec(),
+ properties: properties,
+ }
+ )
+ ));
+
+#[inline]
+fn parse_msgidonly(
+ input: &[u8],
+ protocol_version: u8,
+) -> IResult<&[u8], MQTTMessageIdOnly> {
+ if protocol_version < 5 {
+ // before v5 we don't even have to care about reason codes
+ // and properties, lucky us
+ return parse_msgidonly_v3(input);
+ }
+ let remaining_len = input.len();
+ match be_u16(input) {
+ Ok((rem, message_id)) => {
+ if remaining_len == 2 {
+ // from the spec: " The Reason Code and Property Length can be
+ // omitted if the Reason Code is 0x00 (Success) and there are
+ // no Properties. In this case the message has a Remaining
+ // Length of 2."
+ return Ok((
+ rem,
+ MQTTMessageIdOnly {
+ message_id: message_id,
+ reason_code: Some(0),
+ properties: None,
+ },
+ ));
+ }
+ match be_u8(rem) {
+ Ok((rem, reason_code)) => {
+ // We are checking for 3 because in that case we have a
+ // header plus reason code, but no properties.
+ if remaining_len == 3 {
+ // no properties
+ return Ok((
+ rem,
+ MQTTMessageIdOnly {
+ message_id: message_id,
+ reason_code: Some(reason_code),
+ properties: None,
+ },
+ ));
+ }
+ match parse_properties(rem, true) {
+ Ok((rem, properties)) => {
+ return Ok((
+ rem,
+ MQTTMessageIdOnly {
+ message_id: message_id,
+ reason_code: Some(reason_code),
+ properties: properties,
+ },
+ ));
+ }
+ Err(e) => return Err(e),
+ }
+ }
+ Err(e) => return Err(e),
+ }
+ }
+ Err(e) => return Err(e),
+ }
+}
+
+named!(#[inline], pub parse_msgidonly_v3<MQTTMessageIdOnly>,
+ do_parse!(
+ message_id: be_u16
+ >> (
+ MQTTMessageIdOnly {
+ message_id: message_id,
+ reason_code: None,
+ properties: None,
+ }
+ )
+ ));
+
+named!(#[inline], pub parse_subscribe_topic<MQTTSubscribeTopicData>,
+ do_parse!(
+ topic: parse_mqtt_string
+ >> qos: be_u8
+ >> (
+ MQTTSubscribeTopicData {
+ topic_name: topic,
+ qos: qos,
+ }
+ )
+ ));
+
+named_args!(pub parse_subscribe(protocol_version: u8)<MQTTSubscribeData>,
+ do_parse!(
+ message_id: be_u16
+ >> properties: call!(parse_properties, protocol_version == 5)
+ >> topics: many1!(complete!(parse_subscribe_topic))
+ >> (
+ MQTTSubscribeData {
+ message_id: message_id,
+ topics: topics,
+ properties: properties,
+ }
+ )
+ ));
+
+named_args!(pub parse_suback(protocol_version: u8)<MQTTSubackData>,
+ do_parse!(
+ message_id: be_u16
+ >> properties: call!(parse_properties, protocol_version == 5)
+ >> qoss: rest
+ >> (
+ MQTTSubackData {
+ message_id: message_id,
+ qoss: qoss.to_vec(),
+ properties: properties,
+ }
+ )
+ ));
+
+named_args!(pub parse_unsubscribe(protocol_version: u8)<MQTTUnsubscribeData>,
+ do_parse!(
+ message_id: be_u16
+ >> properties: call!(parse_properties, protocol_version == 5)
+ >> topics: many0!(complete!(parse_mqtt_string))
+ >> (
+ MQTTUnsubscribeData {
+ message_id: message_id,
+ topics: topics,
+ properties: properties,
+ }
+ )
+ ));
+
+named_args!(pub parse_unsuback(protocol_version: u8)<MQTTUnsubackData>,
+ do_parse!(
+ message_id: be_u16
+ >> properties: call!(parse_properties, protocol_version == 5)
+ >> reason_codes: many0!(complete!(be_u8))
+ >> (
+ MQTTUnsubackData {
+ message_id: message_id,
+ properties: properties,
+ reason_codes: Some(reason_codes),
+ }
+ )
+ ));
+
+#[inline]
+fn parse_disconnect(
+ input: &[u8],
+ remaining_len: usize,
+ protocol_version: u8,
+) -> IResult<&[u8], MQTTDisconnectData> {
+ if protocol_version < 5 {
+ return Ok((
+ input,
+ MQTTDisconnectData {
+ reason_code: None,
+ properties: None,
+ },
+ ));
+ }
+ if remaining_len == 0 {
+ // The Reason Code and Property Length can be omitted if the Reason
+ // Code is 0x00 (Normal disconnection) and there are no Properties.
+ // In this case the DISCONNECT has a Remaining Length of 0.
+ return Ok((
+ input,
+ MQTTDisconnectData {
+ reason_code: Some(0),
+ properties: None,
+ },
+ ));
+ }
+ match be_u8(input) {
+ Ok((rem, reason_code)) => {
+ // We are checking for 1 because in that case we have a
+ // header plus reason code, but no properties.
+ if remaining_len == 1 {
+ // no properties
+ return Ok((
+ rem,
+ MQTTDisconnectData {
+ reason_code: Some(0),
+ properties: None,
+ },
+ ));
+ }
+ match parse_properties(rem, true) {
+ Ok((rem, properties)) => {
+ return Ok((
+ rem,
+ MQTTDisconnectData {
+ reason_code: Some(reason_code),
+ properties: properties,
+ },
+ ));
+ }
+ Err(e) => return Err(e),
+ }
+ }
+ Err(e) => return Err(e),
+ }
+}
+
+named!(#[inline], pub parse_auth<MQTTAuthData>,
+ do_parse!(
+ reason_code: be_u8
+ >> properties: call!(parse_properties, true)
+ >> (
+ MQTTAuthData {
+ reason_code: reason_code,
+ properties: properties,
+ }
+ )
+ ));
+
+pub fn parse_message(input: &[u8], protocol_version: u8, max_msg_size: usize) -> IResult<&[u8], MQTTMessage> {
+ // Parse the fixed header first. This is identical across versions and can
+ // be between 2 and 5 bytes long.
+ match parse_fixed_header(input) {
+ Ok((fullrem, header)) => {
+ let len = header.remaining_length as usize;
+ // This is the length of the fixed header that we need to skip
+ // before returning the remainder. It is the sum of the length
+ // of the flag byte (1) and the length of the message length
+ // varint.
+ let skiplen = 1 + varint_length(len);
+ let message_type = header.message_type;
+ assert_eq!(skiplen, input.len() - fullrem.len());
+
+ // If the remaining length (message length) exceeds the specified
+ // limit, we return a special truncation message type, containing
+ // no parsed metadata but just the skipped length and the message
+ // type.
+ if len > max_msg_size {
+ let msg = MQTTMessage {
+ header: header,
+ op: MQTTOperation::TRUNCATED(MQTTTruncatedData {
+ original_message_type: message_type,
+ skipped_length: len + skiplen,
+ }),
+ };
+ // In this case we return the full input buffer, since this is
+ // what the skipped_length value also refers to: header _and_
+ // remaining length.
+ return Ok((&input, msg));
+ }
+
+ // We have not exceeded the maximum length limit, but still do not
+ // have enough data in the input buffer to handle the full
+ // message. Signal this by returning an Incomplete IResult value.
+ if fullrem.len() < len {
+ return Err(Err::Incomplete(Needed::Size(len - fullrem.len())));
+ }
+
+ // Parse the contents of the buffer into a single message.
+ // We reslice the remainder into the portion that we are interested
+ // in, according to the length we just parsed. This helps with the
+ // complete! parsers, where we would otherwise need to keep track
+ // of the already parsed length.
+ let rem = &fullrem[..len];
+ match message_type {
+ MQTTTypeCode::CONNECT => match parse_connect(rem) {
+ Ok((_rem, conn)) => {
+ let msg = MQTTMessage {
+ header: header,
+ op: MQTTOperation::CONNECT(conn),
+ };
+ Ok((&input[skiplen+len..], msg))
+ }
+ Err(e) => Err(e),
+ },
+ MQTTTypeCode::CONNACK => match parse_connack(rem, protocol_version) {
+ Ok((_rem, connack)) => {
+ let msg = MQTTMessage {
+ header: header,
+ op: MQTTOperation::CONNACK(connack),
+ };
+ Ok((&input[skiplen+len..], msg))
+ }
+ Err(e) => Err(e),
+ },
+ MQTTTypeCode::PUBLISH => match parse_publish(rem, protocol_version, header.qos_level > 0) {
+ Ok((_rem, publish)) => {
+ let msg = MQTTMessage {
+ header: header,
+ op: MQTTOperation::PUBLISH(publish),
+ };
+ Ok((&input[skiplen+len..], msg))
+ }
+ Err(e) => Err(e),
+ },
+ MQTTTypeCode::PUBACK | MQTTTypeCode::PUBREC | MQTTTypeCode::PUBREL | MQTTTypeCode::PUBCOMP => {
+ match parse_msgidonly(rem, protocol_version) {
+ Ok((_rem, msgidonly)) => {
+ let msg = MQTTMessage {
+ header: header,
+ op: match message_type {
+ MQTTTypeCode::PUBACK => MQTTOperation::PUBACK(msgidonly),
+ MQTTTypeCode::PUBREC => MQTTOperation::PUBREC(msgidonly),
+ MQTTTypeCode::PUBREL => MQTTOperation::PUBREL(msgidonly),
+ MQTTTypeCode::PUBCOMP => MQTTOperation::PUBCOMP(msgidonly),
+ _ => MQTTOperation::UNASSIGNED,
+ },
+ };
+ Ok((&input[skiplen+len..], msg))
+ }
+ Err(e) => Err(e),
+ }
+ },
+ MQTTTypeCode::SUBSCRIBE => match parse_subscribe(rem, protocol_version) {
+ Ok((_rem, subs)) => {
+ let msg = MQTTMessage {
+ header: header,
+ op: MQTTOperation::SUBSCRIBE(subs),
+ };
+ Ok((&input[skiplen+len..], msg))
+ }
+ Err(e) => Err(e),
+ },
+ MQTTTypeCode::SUBACK => match parse_suback(rem, protocol_version) {
+ Ok((_rem, suback)) => {
+ let msg = MQTTMessage {
+ header: header,
+ op: MQTTOperation::SUBACK(suback),
+ };
+ Ok((&input[skiplen+len..], msg))
+ }
+ Err(e) => Err(e),
+ },
+ MQTTTypeCode::UNSUBSCRIBE => match parse_unsubscribe(rem, protocol_version) {
+ Ok((_rem, unsub)) => {
+ let msg = MQTTMessage {
+ header: header,
+ op: MQTTOperation::UNSUBSCRIBE(unsub),
+ };
+ Ok((&input[skiplen+len..], msg))
+ }
+ Err(e) => Err(e),
+ },
+ MQTTTypeCode::UNSUBACK => match parse_unsuback(rem, protocol_version) {
+ Ok((_rem, unsuback)) => {
+ let msg = MQTTMessage {
+ header: header,
+ op: MQTTOperation::UNSUBACK(unsuback),
+ };
+ Ok((&input[skiplen+len..], msg))
+ }
+ Err(e) => Err(e),
+ },
+ MQTTTypeCode::PINGREQ | MQTTTypeCode::PINGRESP => {
+ let msg = MQTTMessage {
+ header: header,
+ op: match message_type {
+ MQTTTypeCode::PINGREQ => MQTTOperation::PINGREQ,
+ MQTTTypeCode::PINGRESP => MQTTOperation::PINGRESP,
+ _ => MQTTOperation::UNASSIGNED,
+ },
+ };
+ return Ok((&rem[skiplen+len..], msg));
+ }
+ MQTTTypeCode::DISCONNECT => match parse_disconnect(rem, len, protocol_version) {
+ Ok((_rem, disco)) => {
+ let msg = MQTTMessage {
+ header: header,
+ op: MQTTOperation::DISCONNECT(disco),
+ };
+ Ok((&input[skiplen+len..], msg))
+ }
+ Err(e) => Err(e),
+ },
+ MQTTTypeCode::AUTH => match parse_auth(rem) {
+ Ok((_rem, auth)) => {
+ let msg = MQTTMessage {
+ header: header,
+ op: MQTTOperation::AUTH(auth),
+ };
+ Ok((&input[skiplen+len..], msg))
+ }
+ Err(e) => Err(e),
+ },
+ // Unassigned message type code. Unlikely to happen with
+ // regular traffic, might be an indication for broken or
+ // crafted MQTT traffic.
+ _ => {
+ let msg = MQTTMessage {
+ header: header,
+ op: MQTTOperation::UNASSIGNED,
+ };
+ return Ok((&rem[len..], msg));
+ }
+ }
+ }
+ Err(err) => {
+ return Err(err);
+ }
+ }
+}
app-layer-ikev2.c app-layer-ikev2.h \
app-layer-krb5.c app-layer-krb5.h \
app-layer-rfb.c app-layer-rfb.h \
+app-layer-mqtt.c app-layer-mqtt.h \
app-layer-template.c app-layer-template.h \
app-layer-template-rust.c app-layer-template-rust.h \
app-layer-rdp.c app-layer-rdp.h \
detect-template2.c detect-template2.h \
detect-ftpdata.c detect-ftpdata.h \
detect-template-rust-buffer.c detect-template-rust-buffer.h \
+detect-mqtt-type.c detect-mqtt-type.h \
+detect-mqtt-flags.c detect-mqtt-flags.h \
+detect-mqtt-qos.c detect-mqtt-qos.h \
+detect-mqtt-protocol-version.c detect-mqtt-protocol-version.h \
+detect-mqtt-reason-code.c detect-mqtt-reason-code.h \
+detect-mqtt-connect-flags.c detect-mqtt-connect-flags.h \
+detect-mqtt-connect-clientid.c detect-mqtt-connect-clientid.h \
+detect-mqtt-connect-username.c detect-mqtt-connect-username.h \
+detect-mqtt-connect-password.c detect-mqtt-connect-password.h \
+detect-mqtt-connect-willtopic.c detect-mqtt-connect-willtopic.h \
+detect-mqtt-connect-willmessage.c detect-mqtt-connect-willmessage.h \
+detect-mqtt-connack-sessionpresent.c detect-mqtt-connack-sessionpresent.h \
+detect-mqtt-publish-message.c detect-mqtt-publish-message.h \
+detect-mqtt-publish-topic.c detect-mqtt-publish-topic.h \
+detect-mqtt-subscribe-topic.c detect-mqtt-subscribe-topic.h \
+detect-mqtt-unsubscribe-topic.c detect-mqtt-unsubscribe-topic.h \
detect-template-buffer.c detect-template-buffer.h \
detect-threshold.c detect-threshold.h \
detect-tls.c detect-tls.h \
output-json-dhcp.c output-json-dhcp.h \
output-json-snmp.c output-json-snmp.h \
output-json-rfb.c output-json-rfb.h \
+output-json-mqtt.c output-json-mqtt.h \
output-json-template.c output-json-template.h \
output-json-template-rust.c output-json-template-rust.h \
output-json-rdp.c output-json-rdp.h \
printf(" alproto: ALPROTO_TEMPLATE_RUST\n");
else if (pp_pe->alproto == ALPROTO_RFB)
printf(" alproto: ALPROTO_RFB\n");
+ else if (pp_pe->alproto == ALPROTO_MQTT)
+ printf(" alproto: ALPROTO_MQTT\n");
else if (pp_pe->alproto == ALPROTO_TEMPLATE)
printf(" alproto: ALPROTO_TEMPLATE\n");
else if (pp_pe->alproto == ALPROTO_DNP3)
printf(" alproto: ALPROTO_TEMPLATE_RUST\n");
else if (pp_pe->alproto == ALPROTO_RFB)
printf(" alproto: ALPROTO_RFB\n");
+ else if (pp_pe->alproto == ALPROTO_MQTT)
+ printf(" alproto: ALPROTO_MQTT\n");
else if (pp_pe->alproto == ALPROTO_TEMPLATE)
printf(" alproto: ALPROTO_TEMPLATE\n");
else if (pp_pe->alproto == ALPROTO_DNP3)
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#include "suricata-common.h"
+#include "stream.h"
+#include "conf.h"
+
+#include "util-misc.h"
+#include "util-unittest.h"
+
+#include "app-layer-detect-proto.h"
+#include "app-layer-parser.h"
+
+#include "app-layer-mqtt.h"
+#include "rust.h"
+
+void RegisterMQTTParsers(void)
+{
+ SCLogDebug("Registering Rust mqtt parser.");
+ uint32_t max_msg_len = 1048576; /* default: 1MB */
+
+ if (AppLayerParserConfParserEnabled("tcp", "mqtt")) {
+ ConfNode *p = ConfGetNode("app-layer.protocols.mqtt.max-msg-length");
+ if (p != NULL) {
+ uint32_t value;
+ if (ParseSizeStringU32(p->val, &value) < 0) {
+ SCLogError(SC_ERR_SIZE_PARSE, "invalid value for max-msg-length: %s", p->val);
+ } else {
+ max_msg_len = value;
+ }
+ }
+ rs_mqtt_register_parser(max_msg_len);
+ }
+#ifdef UNITTESTS
+ AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_MQTT,
+ MQTTParserRegisterTests);
+#endif
+}
+
+void MQTTParserRegisterTests(void)
+{
+#ifdef UNITTESTS
+#endif
+}
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __APP_LAYER_MQTT_H__
+#define __APP_LAYER_MQTT_H__
+
+void RegisterMQTTParsers(void);
+void MQTTParserRegisterTests(void);
+
+#endif /* __APP_LAYER_MQTT_H__ */
#include "app-layer-snmp.h"
#include "app-layer-sip.h"
#include "app-layer-rfb.h"
+#include "app-layer-mqtt.h"
#include "app-layer-template.h"
#include "app-layer-template-rust.h"
#include "app-layer-rdp.h"
RegisterSIPParsers();
RegisterTemplateRustParsers();
RegisterRFBParsers();
+ RegisterMQTTParsers();
RegisterTemplateParsers();
RegisterRdpParsers();
break;
case ALPROTO_RFB:
proto_name = "rfb";
+ break;
+ case ALPROTO_MQTT:
+ proto_name = "mqtt";
break;
case ALPROTO_TEMPLATE:
proto_name = "template";
if (strcmp(proto_name,"snmp")==0) return ALPROTO_SNMP;
if (strcmp(proto_name,"sip")==0) return ALPROTO_SIP;
if (strcmp(proto_name,"rfb")==0) return ALPROTO_RFB;
+ if (strcmp(proto_name,"mqtt")==0) return ALPROTO_MQTT;
if (strcmp(proto_name,"template")==0) return ALPROTO_TEMPLATE;
if (strcmp(proto_name,"template-rust")==0) return ALPROTO_TEMPLATE_RUST;
if (strcmp(proto_name,"rdp")==0) return ALPROTO_RDP;
ALPROTO_SNMP,
ALPROTO_SIP,
ALPROTO_RFB,
+ ALPROTO_MQTT,
ALPROTO_TEMPLATE,
ALPROTO_TEMPLATE_RUST,
ALPROTO_RDP,
#include "detect-snmp-version.h"
#include "detect-snmp-community.h"
#include "detect-snmp-pdu_type.h"
+#include "detect-mqtt-type.h"
+#include "detect-mqtt-flags.h"
+#include "detect-mqtt-qos.h"
+#include "detect-mqtt-protocol-version.h"
+#include "detect-mqtt-reason-code.h"
+#include "detect-mqtt-connect-flags.h"
+#include "detect-mqtt-connect-clientid.h"
+#include "detect-mqtt-connect-username.h"
+#include "detect-mqtt-connect-password.h"
+#include "detect-mqtt-connect-willtopic.h"
+#include "detect-mqtt-connect-willmessage.h"
+#include "detect-mqtt-connack-sessionpresent.h"
+#include "detect-mqtt-publish-topic.h"
+#include "detect-mqtt-publish-message.h"
+#include "detect-mqtt-subscribe-topic.h"
+#include "detect-mqtt-unsubscribe-topic.h"
+
#include "detect-template-buffer.h"
#include "detect-bypass.h"
#include "detect-ftpdata.h"
DetectSNMPVersionRegister();
DetectSNMPCommunityRegister();
DetectSNMPPduTypeRegister();
+ DetectMQTTTypeRegister();
+ DetectMQTTFlagsRegister();
+ DetectMQTTQosRegister();
+ DetectMQTTProtocolVersionRegister();
+ DetectMQTTReasonCodeRegister();
+ DetectMQTTConnectFlagsRegister();
+ DetectMQTTConnectClientIDRegister();
+ DetectMQTTConnectUsernameRegister();
+ DetectMQTTConnectPasswordRegister();
+ DetectMQTTConnectWillTopicRegister();
+ DetectMQTTConnectWillMessageRegister();
+ DetectMQTTConnackSessionPresentRegister();
+ DetectMQTTPublishTopicRegister();
+ DetectMQTTPublishMessageRegister();
+ DetectMQTTSubscribeTopicRegister();
+ DetectMQTTUnsubscribeTopicRegister();
+
DetectTemplateBufferRegister();
DetectBypassRegister();
DetectConfigRegister();
DETECT_AL_SNMP_VERSION,
DETECT_AL_SNMP_COMMUNITY,
DETECT_AL_SNMP_PDU_TYPE,
+ DETECT_AL_MQTT_TYPE,
+ DETECT_AL_MQTT_FLAGS,
+ DETECT_AL_MQTT_QOS,
+ DETECT_AL_MQTT_PROTOCOL_VERSION,
+ DETECT_AL_MQTT_REASON_CODE,
+ DETECT_AL_MQTT_CONNECT_FLAGS,
+ DETECT_AL_MQTT_CONNECT_CLIENTID,
+ DETECT_AL_MQTT_CONNECT_USERNAME,
+ DETECT_AL_MQTT_CONNECT_PASSWORD,
+ DETECT_AL_MQTT_CONNECT_WILLTOPIC,
+ DETECT_AL_MQTT_CONNECT_WILLMESSAGE,
+ DETECT_AL_MQTT_CONNACK_SESSION_PRESENT,
+ DETECT_AL_MQTT_PUBLISH_TOPIC,
+ DETECT_AL_MQTT_PUBLISH_MESSAGE,
+ DETECT_AL_MQTT_SUBSCRIBE_TOPIC,
+ DETECT_AL_MQTT_UNSUBSCRIBE_TOPIC,
DETECT_AL_TEMPLATE_BUFFER,
DETECT_BYPASS,
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#include "suricata-common.h"
+#include "conf.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-content-inspection.h"
+#include "detect-mqtt-connack-sessionpresent.h"
+#include "util-unittest.h"
+
+#include "rust-bindings.h"
+
+#define PARSE_REGEX "^true|false|yes|no$"
+static DetectParseRegex parse_regex;
+
+static int mqtt_connack_session_present_id = 0;
+
+static int DetectMQTTConnackSessionPresentMatch(DetectEngineThreadCtx *det_ctx,
+ Flow *f, uint8_t flags, void *state,
+ void *txv, const Signature *s,
+ const SigMatchCtx *ctx);
+static int DetectMQTTConnackSessionPresentSetup (DetectEngineCtx *, Signature *, const char *);
+void MQTTConnackSessionPresentRegisterTests(void);
+void DetectMQTTConnackSessionPresentFree(DetectEngineCtx *de_ctx, void *);
+
+static int DetectEngineInspectMQTTConnackSessionPresentGeneric(ThreadVars *tv,
+ DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const Signature *s, const SigMatchData *smd,
+ Flow *f, uint8_t flags, void *alstate,
+ void *txv, uint64_t tx_id);
+
+/**
+ * \brief Registration function for mqtt.connack.session_present: keyword
+ */
+void DetectMQTTConnackSessionPresentRegister (void)
+{
+ sigmatch_table[DETECT_AL_MQTT_CONNACK_SESSION_PRESENT].name = "mqtt.connack.session_present";
+ sigmatch_table[DETECT_AL_MQTT_CONNACK_SESSION_PRESENT].desc = "match MQTT CONNACK session present flag";
+ sigmatch_table[DETECT_AL_MQTT_CONNACK_SESSION_PRESENT].url = "/rules/mqtt-keywords.html#mqtt-connack-session-present";
+ sigmatch_table[DETECT_AL_MQTT_CONNACK_SESSION_PRESENT].AppLayerTxMatch = DetectMQTTConnackSessionPresentMatch;
+ sigmatch_table[DETECT_AL_MQTT_CONNACK_SESSION_PRESENT].Setup = DetectMQTTConnackSessionPresentSetup;
+ sigmatch_table[DETECT_AL_MQTT_CONNACK_SESSION_PRESENT].Free = DetectMQTTConnackSessionPresentFree;
+#ifdef UNITTESTS
+ sigmatch_table[DETECT_AL_MQTT_CONNACK_SESSION_PRESENT].RegisterTests = MQTTConnackSessionPresentRegisterTests;
+#endif
+
+ DetectSetupParseRegexes(PARSE_REGEX, &parse_regex);
+
+ DetectAppLayerInspectEngineRegister("mqtt.connack.session_present",
+ ALPROTO_MQTT, SIG_FLAG_TOSERVER, 1,
+ DetectEngineInspectMQTTConnackSessionPresentGeneric);
+
+ mqtt_connack_session_present_id = DetectBufferTypeGetByName("mqtt.connack.session_present");
+}
+
+static int DetectEngineInspectMQTTConnackSessionPresentGeneric(ThreadVars *tv,
+ DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const Signature *s, const SigMatchData *smd,
+ Flow *f, uint8_t flags, void *alstate,
+ void *txv, uint64_t tx_id)
+{
+ return DetectEngineInspectGenericList(tv, de_ctx, det_ctx, s, smd,
+ f, flags, alstate, txv, tx_id);
+}
+
+/**
+ * \internal
+ * \brief Function to match session_present flag of an MQTT CONNACK message
+ *
+ * \param det_ctx Pointer to the pattern matcher thread.
+ * \param f Pointer to the current flow.
+ * \param flags Flags.
+ * \param state App layer state.
+ * \param txv Pointer to the transaction.
+ * \param s Pointer to the Signature.
+ * \param ctx Pointer to the sigmatch that we will cast into DetectMQTTConnackSessionPresentData.
+ *
+ * \retval 0 no match.
+ * \retval 1 match.
+ */
+static int DetectMQTTConnackSessionPresentMatch(DetectEngineThreadCtx *det_ctx,
+ Flow *f, uint8_t flags, void *state,
+ void *txv, const Signature *s,
+ const SigMatchCtx *ctx)
+{
+ const bool *de = (const bool *)ctx;
+ bool value = false;
+
+ if (!de)
+ return 0;
+
+ if (rs_mqtt_tx_get_connack_sessionpresent(txv, &value) ==0 ) {
+ return 0;
+ }
+ if (value != *de) {
+ return 0;
+ }
+
+ return 1;
+}
+
+/**
+ * \internal
+ * \brief This function is used to parse options passed via mqtt.connack.session_present: keyword
+ *
+ * \param rawstr Pointer to the user provided options
+ *
+ * \retval de pointer to DetectMQTTConnackSessionPresentData on success
+ * \retval NULL on failure
+ */
+static bool *DetectMQTTConnackSessionPresentParse(const char *rawstr)
+{
+ bool *de = NULL;
+ de = SCMalloc(sizeof(bool));
+ if (unlikely(de == NULL))
+ return NULL;
+ *de = false;
+
+ if (strcmp(rawstr, "yes") == 0) {
+ *de = true;
+ } else if (strcmp(rawstr, "true") == 0) {
+ *de = true;
+ } else if (strcmp(rawstr, "no") == 0) {
+ *de = false;
+ } else if (strcmp(rawstr, "false") == 0) {
+ *de = false;
+ } else {
+ SCLogError(SC_ERR_UNKNOWN_VALUE, "invalid session_present flag definition: %s", rawstr);
+ goto error;
+ }
+
+ return de;
+
+error:
+ if (de != NULL)
+ SCFree(de);
+ return NULL;
+}
+
+/**
+ * \internal
+ * \brief this function is used to add the parsed type query into the current signature
+ *
+ * \param de_ctx pointer to the Detection Engine Context
+ * \param s pointer to the Current Signature
+ * \param rawstr pointer to the user provided options
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+static int DetectMQTTConnackSessionPresentSetup (DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
+{
+ bool *de = NULL;
+ SigMatch *sm = NULL;
+
+ if (DetectSignatureSetAppProto(s, ALPROTO_MQTT) < 0)
+ return -1;
+
+ de = DetectMQTTConnackSessionPresentParse(rawstr);
+ if (de == NULL)
+ goto error;
+
+ sm = SigMatchAlloc();
+ if (sm == NULL)
+ goto error;
+
+ sm->type = DETECT_AL_MQTT_CONNACK_SESSION_PRESENT;
+ sm->ctx = (SigMatchCtx *)de;
+
+ SigMatchAppendSMToList(s, sm, mqtt_connack_session_present_id);
+
+ return 0;
+
+error:
+ if (de != NULL)
+ SCFree(de);
+ if (sm != NULL)
+ SCFree(sm);
+ return -1;
+}
+
+/**
+ * \internal
+ * \brief this function will free memory associated with DetectMQTTConnackSessionPresentData
+ *
+ * \param de pointer to DetectMQTTConnackSessionPresentData
+ */
+void DetectMQTTConnackSessionPresentFree(DetectEngineCtx *de_ctx, void *de_ptr)
+{
+ if (de_ptr != NULL)
+ SCFree(de_ptr);
+}
+
+/*
+ * ONLY TESTS BELOW THIS COMMENT
+ */
+
+#ifdef UNITTESTS
+/**
+ * \test MQTTConnackSessionPresentTestParse01 is a test for a valid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTConnackSessionPresentTestParse01 (void)
+{
+ bool *de = NULL;
+
+ de = DetectMQTTConnackSessionPresentParse("yes");
+ FAIL_IF_NULL(de);
+ DetectMQTTConnackSessionPresentFree(NULL, de);
+
+ de = DetectMQTTConnackSessionPresentParse("true");
+ FAIL_IF_NULL(de);
+ DetectMQTTConnackSessionPresentFree(NULL, de);
+
+ de = DetectMQTTConnackSessionPresentParse("false");
+ FAIL_IF_NULL(de);
+ DetectMQTTConnackSessionPresentFree(NULL, de);
+
+ de = DetectMQTTConnackSessionPresentParse("no");
+ FAIL_IF_NULL(de);
+ DetectMQTTConnackSessionPresentFree(NULL, de);
+
+ PASS;
+}
+
+/**
+ * \test MQTTConnackSessionPresentTestParse02 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTConnackSessionPresentTestParse02 (void)
+{
+ bool *de = NULL;
+ de = DetectMQTTConnackSessionPresentParse("nix");
+ if (de) {
+ DetectMQTTConnackSessionPresentFree(NULL, de);
+ FAIL;
+ }
+
+ PASS;
+}
+
+/**
+ * \test MQTTConnackSessionPresentTestParse03 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTConnackSessionPresentTestParse03 (void)
+{
+ bool *de = NULL;
+ de = DetectMQTTConnackSessionPresentParse("");
+ if (de) {
+ DetectMQTTConnackSessionPresentFree(NULL, de);
+ FAIL;
+ }
+
+ PASS;
+}
+
+/**
+ * \test MQTTConnackSessionPresentTestParse04 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTConnackSessionPresentTestParse04 (void)
+{
+ bool *de = NULL;
+ de = DetectMQTTConnackSessionPresentParse(",");
+ if (de) {
+ DetectMQTTConnackSessionPresentFree(NULL, de);
+ FAIL;
+ }
+
+ PASS;
+}
+
+
+#endif /* UNITTESTS */
+
+/**
+ * \brief this function registers unit tests for MQTTConnackSessionPresent
+ */
+void MQTTConnackSessionPresentRegisterTests(void)
+{
+#ifdef UNITTESTS
+ UtRegisterTest("MQTTConnackSessionPresentTestParse01", MQTTConnackSessionPresentTestParse01);
+ UtRegisterTest("MQTTConnackSessionPresentTestParse02", MQTTConnackSessionPresentTestParse02);
+ UtRegisterTest("MQTTConnackSessionPresentTestParse03", MQTTConnackSessionPresentTestParse03);
+ UtRegisterTest("MQTTConnackSessionPresentTestParse04", MQTTConnackSessionPresentTestParse04);
+#endif /* UNITTESTS */
+}
\ No newline at end of file
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __DETECT_MQTT_CONNACK_SESSIONPRESENT_H__
+#define __DETECT_MQTT_CONNACK_SESSIONPRESENT_H__
+
+void DetectMQTTConnackSessionPresentRegister(void);
+
+#endif /* __DETECT_MQTT_CONNACK_SESSIONPRESENT_H__ */
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ *
+ * Implements the mqtt.connect.clientid sticky buffer
+ */
+
+#include "suricata-common.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+#include "detect-engine-prefilter.h"
+#include "detect-mqtt-connect-clientid.h"
+#include "rust.h"
+
+#define KEYWORD_NAME "mqtt.connect.clientid"
+#define KEYWORD_DOC "mqtt-keywords.html#mqtt-connect-clientid"
+#define BUFFER_NAME "mqtt.connect.clientid"
+#define BUFFER_DESC "MQTT CONNECT client ID"
+static int g_buffer_id = 0;
+
+static int DetectMQTTConnectClientIDSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
+{
+ if (DetectBufferSetActiveList(s, g_buffer_id) < 0)
+ return -1;
+
+ if (DetectSignatureSetAppProto(s, ALPROTO_MQTT) < 0)
+ return -1;
+
+ return 0;
+}
+
+static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx,
+ const DetectEngineTransforms *transforms,
+ Flow *_f, const uint8_t _flow_flags,
+ void *txv, const int list_id)
+{
+ InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id);
+ if (buffer->inspect == NULL) {
+ const uint8_t *b = NULL;
+ uint32_t b_len = 0;
+
+ if (rs_mqtt_tx_get_connect_clientid(txv, &b, &b_len) != 1)
+ return NULL;
+ if (b == NULL || b_len == 0)
+ return NULL;
+
+ InspectionBufferSetup(buffer, b, b_len);
+ InspectionBufferApplyTransforms(buffer, transforms);
+ }
+ return buffer;
+}
+
+void DetectMQTTConnectClientIDRegister(void)
+{
+ /* mqtt.connect.clientid sticky buffer */
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_CLIENTID].name = KEYWORD_NAME;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_CLIENTID].desc = "sticky buffer to match on the MQTT CONNECT client ID";
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_CLIENTID].url = "/rules/" KEYWORD_DOC;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_CLIENTID].Setup = DetectMQTTConnectClientIDSetup;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_CLIENTID].flags |= SIGMATCH_NOOPT;
+
+ DetectAppLayerInspectEngineRegister2(BUFFER_NAME, ALPROTO_MQTT,
+ SIG_FLAG_TOSERVER, 0,
+ DetectEngineInspectBufferGeneric, GetData);
+
+ DetectAppLayerMpmRegister2(BUFFER_NAME, SIG_FLAG_TOSERVER, 2,
+ PrefilterGenericMpmRegister, GetData, ALPROTO_MQTT,
+ 1);
+
+ DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC);
+
+ g_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME);
+
+ SCLogDebug("registering " BUFFER_NAME " rule option");
+}
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __DETECT_MQTT_CONNECT_CLIENTID_H__
+#define __DETECT_MQTT_CONNECT_CLIENTID_H__
+
+void DetectMQTTConnectClientIDRegister(void);
+
+#endif /* __DETECT_MQTT_CONNECT_CLIENTID_H__ */
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#include "suricata-common.h"
+#include "conf.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-content-inspection.h"
+#include "detect-mqtt-connect-flags.h"
+#include "util-unittest.h"
+
+#include "rust-bindings.h"
+
+#define PARSE_REGEX "(?: *,?!?(?:username|password|will|will_retain|clean_session))+"
+static DetectParseRegex parse_regex;
+
+static int mqtt_connect_flags_id = 0;
+
+static int DetectMQTTConnectFlagsMatch(DetectEngineThreadCtx *det_ctx,
+ Flow *f, uint8_t flags, void *state,
+ void *txv, const Signature *s,
+ const SigMatchCtx *ctx);
+static int DetectMQTTConnectFlagsSetup (DetectEngineCtx *, Signature *, const char *);
+void MQTTConnectFlagsRegisterTests(void);
+void DetectMQTTConnectFlagsFree(DetectEngineCtx *de_ctx, void *);
+
+static int DetectEngineInspectMQTTConnectFlagsGeneric(ThreadVars *tv,
+ DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const Signature *s, const SigMatchData *smd,
+ Flow *f, uint8_t flags, void *alstate,
+ void *txv, uint64_t tx_id);
+
+typedef struct DetectMQTTConnectFlagsData_ {
+ MQTTFlagState username,
+ password,
+ will,
+ will_retain,
+ clean_session;
+} DetectMQTTConnectFlagsData;
+
+/**
+ * \brief Registration function for mqtt.connect.flags: keyword
+ */
+void DetectMQTTConnectFlagsRegister (void)
+{
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_FLAGS].name = "mqtt.connect.flags";
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_FLAGS].desc = "match MQTT CONNECT variable header flags";
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_FLAGS].url = "/rules/mqtt-keywords.html#mqtt-connect-flags";
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_FLAGS].AppLayerTxMatch = DetectMQTTConnectFlagsMatch;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_FLAGS].Setup = DetectMQTTConnectFlagsSetup;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_FLAGS].Free = DetectMQTTConnectFlagsFree;
+#ifdef UNITTESTS
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_FLAGS].RegisterTests = MQTTConnectFlagsRegisterTests;
+#endif
+
+ DetectSetupParseRegexes(PARSE_REGEX, &parse_regex);
+
+ DetectAppLayerInspectEngineRegister("mqtt.connect.flags",
+ ALPROTO_MQTT, SIG_FLAG_TOSERVER, 1,
+ DetectEngineInspectMQTTConnectFlagsGeneric);
+
+ mqtt_connect_flags_id = DetectBufferTypeGetByName("mqtt.connect.flags");
+}
+
+static int DetectEngineInspectMQTTConnectFlagsGeneric(ThreadVars *tv,
+ DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const Signature *s, const SigMatchData *smd,
+ Flow *f, uint8_t flags, void *alstate,
+ void *txv, uint64_t tx_id)
+{
+ return DetectEngineInspectGenericList(tv, de_ctx, det_ctx, s, smd,
+ f, flags, alstate, txv, tx_id);
+}
+
+/**
+ * \internal
+ * \brief Function to match variable header flags of an MQTT CONNECT Tx
+ *
+ * \param det_ctx Pointer to the pattern matcher thread.
+ * \param f Pointer to the current flow.
+ * \param flags Flags.
+ * \param state App layer state.
+ * \param txv Pointer to the transaction.
+ * \param s Pointer to the Signature.
+ * \param ctx Pointer to the sigmatch that we will cast into DetectMQTTConnectFlagsData.
+ *
+ * \retval 0 no match.
+ * \retval 1 match.
+ */
+static int DetectMQTTConnectFlagsMatch(DetectEngineThreadCtx *det_ctx,
+ Flow *f, uint8_t flags, void *state,
+ void *txv, const Signature *s,
+ const SigMatchCtx *ctx)
+{
+ const DetectMQTTConnectFlagsData *de = (const DetectMQTTConnectFlagsData *)ctx;
+
+ if (!de)
+ return 0;
+
+ return rs_mqtt_tx_has_connect_flags(txv, de->username, de->password, de->will,
+ de->will_retain, de->clean_session);
+ }
+
+/**
+ * \internal
+ * \brief This function is used to parse options passed via mqtt.connect.flags: keyword
+ *
+ * \param rawstr Pointer to the user provided options
+ *
+ * \retval de pointer to DetectMQTTConnectFlagsData on success
+ * \retval NULL on failure
+ */
+static DetectMQTTConnectFlagsData *DetectMQTTConnectFlagsParse(const char *rawstr)
+{
+ DetectMQTTConnectFlagsData *de = NULL;
+ int ret = 0;
+ int ov[MAX_SUBSTRINGS];
+
+ ret = DetectParsePcreExec(&parse_regex, rawstr, 0, 0, ov, MAX_SUBSTRINGS);
+ if (ret < 1) {
+ SCLogError(SC_ERR_PCRE_MATCH, "invalid flag definition: %s", rawstr);
+ return NULL;
+ }
+
+ de = SCCalloc(1, sizeof(DetectMQTTConnectFlagsData));
+ if (unlikely(de == NULL))
+ return NULL;
+ de->username = de->password = de->will = MQTT_DONT_CARE;
+ de->will_retain = de->clean_session = MQTT_DONT_CARE;
+
+ char copy[strlen(rawstr)+1];
+ strlcpy(copy, rawstr, sizeof(copy));
+ char *xsaveptr = NULL;
+ char *flagv = strtok_r(copy, ",", &xsaveptr);
+ while (flagv != NULL) {
+ while (*flagv != '\0' && isblank(*flagv)) {
+ flagv++;
+ }
+ if (strlen(flagv) < 2) {
+ SCLogError(SC_ERR_UNKNOWN_VALUE, "malformed flag value: %s", flagv);
+ goto error;
+ } else {
+ int offset = 0;
+ MQTTFlagState fs_to_set = MQTT_MUST_BE_SET;
+ if (flagv[0] == '!') {
+ /* negated flag */
+ offset = 1; /* skip negation operator during comparison */
+ fs_to_set = MQTT_CANT_BE_SET;
+ }
+ if (strcmp(flagv+offset, "username") == 0) {
+ if (de->username != MQTT_DONT_CARE) {
+ SCLogError(SC_ERR_INVALID_VALUE, "duplicate flag definition: %s", flagv);
+ goto error;
+ }
+ de->username = fs_to_set;
+ } else if (strcmp(flagv+offset, "password") == 0) {
+ if (de->password != MQTT_DONT_CARE) {
+ SCLogError(SC_ERR_INVALID_VALUE, "duplicate flag definition: %s", flagv);
+ goto error;
+ }
+ de->password = fs_to_set;
+ } else if (strcmp(flagv+offset, "will") == 0) {
+ if (de->will != MQTT_DONT_CARE) {
+ SCLogError(SC_ERR_INVALID_VALUE, "duplicate flag definition: %s", flagv);
+ goto error;
+ }
+ de->will = fs_to_set;
+ } else if (strcmp(flagv+offset, "will_retain") == 0) {
+ if (de->will_retain != MQTT_DONT_CARE) {
+ SCLogError(SC_ERR_INVALID_VALUE, "duplicate flag definition: %s", flagv);
+ goto error;
+ }
+ de->will_retain = fs_to_set;
+ } else if (strcmp(flagv+offset, "clean_session") == 0) {
+ if (de->clean_session != MQTT_DONT_CARE) {
+ SCLogError(SC_ERR_INVALID_VALUE, "duplicate flag definition: %s", flagv);
+ goto error;
+ }
+ de->clean_session = fs_to_set;
+ } else {
+ SCLogError(SC_ERR_UNKNOWN_VALUE, "invalid flag definition: %s", flagv);
+ goto error;
+ }
+ }
+ flagv = strtok_r(NULL, ",", &xsaveptr);
+ }
+
+ return de;
+
+error:
+ if (de != NULL)
+ SCFree(de);
+ return NULL;
+}
+
+/**
+ * \internal
+ * \brief this function is used to add the parsed type query into the current signature
+ *
+ * \param de_ctx pointer to the Detection Engine Context
+ * \param s pointer to the Current Signature
+ * \param rawstr pointer to the user provided options
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+static int DetectMQTTConnectFlagsSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
+{
+ DetectMQTTConnectFlagsData *de = NULL;
+ SigMatch *sm = NULL;
+
+ if (DetectSignatureSetAppProto(s, ALPROTO_MQTT) < 0)
+ return -1;
+
+ de = DetectMQTTConnectFlagsParse(rawstr);
+ if (de == NULL)
+ goto error;
+
+ sm = SigMatchAlloc();
+ if (sm == NULL)
+ goto error;
+
+ sm->type = DETECT_AL_MQTT_CONNECT_FLAGS;
+ sm->ctx = (SigMatchCtx *)de;
+
+ SigMatchAppendSMToList(s, sm, mqtt_connect_flags_id);
+
+ return 0;
+
+error:
+ if (de != NULL)
+ SCFree(de);
+ if (sm != NULL)
+ SCFree(sm);
+ return -1;
+}
+
+/**
+ * \internal
+ * \brief this function will free memory associated with DetectMQTTConnectFlagsData
+ *
+ * \param de pointer to DetectMQTTConnectFlagsData
+ */
+void DetectMQTTConnectFlagsFree(DetectEngineCtx *de_ctx, void *de_ptr)
+{
+ if (de_ptr != NULL)
+ SCFree(de_ptr);
+}
+
+/*
+ * ONLY TESTS BELOW THIS COMMENT
+ */
+
+#ifdef UNITTESTS
+/**
+ * \test MQTTConnectFlagsTestParse01 is a test for a valid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTConnectFlagsTestParse01 (void)
+{
+ DetectMQTTConnectFlagsData *de = NULL;
+ de = DetectMQTTConnectFlagsParse("username");
+ FAIL_IF_NULL(de);
+ DetectMQTTConnectFlagsFree(NULL, de);
+
+ de = DetectMQTTConnectFlagsParse("username,password,will,will_retain,clean_session");
+ FAIL_IF_NULL(de);
+ DetectMQTTConnectFlagsFree(NULL, de);
+
+ de = DetectMQTTConnectFlagsParse("!username,!password,!will,!will_retain,!clean_session");
+ FAIL_IF_NULL(de);
+ DetectMQTTConnectFlagsFree(NULL, de);
+
+ de = DetectMQTTConnectFlagsParse(" username,password");
+ FAIL_IF_NULL(de);
+ DetectMQTTConnectFlagsFree(NULL, de);
+
+ PASS;
+}
+
+/**
+ * \test MQTTConnectFlagsTestParse02 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTConnectFlagsTestParse02 (void)
+{
+ DetectMQTTConnectFlagsData *de = NULL;
+ de = DetectMQTTConnectFlagsParse("foobar");
+ if (de) {
+ DetectMQTTConnectFlagsFree(NULL, de);
+ FAIL;
+ }
+
+ PASS;
+}
+
+/**
+ * \test MQTTConnectFlagsTestParse03 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTConnectFlagsTestParse03 (void)
+{
+ DetectMQTTConnectFlagsData *de = NULL;
+ de = DetectMQTTConnectFlagsParse("will,!");
+ if (de) {
+ DetectMQTTConnectFlagsFree(NULL, de);
+ FAIL;
+ }
+
+ PASS;
+}
+
+/**
+ * \test MQTTConnectFlagsTestParse04 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTConnectFlagsTestParse04 (void)
+{
+ DetectMQTTConnectFlagsData *de = NULL;
+ de = DetectMQTTConnectFlagsParse("");
+ if (de) {
+ DetectMQTTConnectFlagsFree(NULL, de);
+ FAIL;
+ }
+
+ PASS;
+}
+
+/**
+ * \test MQTTConnectFlagsTestParse05 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTConnectFlagsTestParse05 (void)
+{
+ DetectMQTTConnectFlagsData *de = NULL;
+ de = DetectMQTTConnectFlagsParse("username, username");
+ if (de) {
+ DetectMQTTConnectFlagsFree(NULL, de);
+ FAIL;
+ }
+ de = DetectMQTTConnectFlagsParse("!username, username");
+ if (de) {
+ DetectMQTTConnectFlagsFree(NULL, de);
+ FAIL;
+ }
+ de = DetectMQTTConnectFlagsParse("!username,password,!password");
+ if (de) {
+ DetectMQTTConnectFlagsFree(NULL, de);
+ FAIL;
+ }
+ de = DetectMQTTConnectFlagsParse("will, username,password, !will, will");
+ if (de) {
+ DetectMQTTConnectFlagsFree(NULL, de);
+ FAIL;
+ }
+
+ PASS;
+}
+
+
+#endif /* UNITTESTS */
+
+/**
+ * \brief this function registers unit tests for MQTTConnectFlags
+ */
+void MQTTConnectFlagsRegisterTests(void)
+{
+#ifdef UNITTESTS
+ UtRegisterTest("MQTTConnectFlagsTestParse01", MQTTConnectFlagsTestParse01);
+ UtRegisterTest("MQTTConnectFlagsTestParse02", MQTTConnectFlagsTestParse02);
+ UtRegisterTest("MQTTConnectFlagsTestParse03", MQTTConnectFlagsTestParse03);
+ UtRegisterTest("MQTTConnectFlagsTestParse04", MQTTConnectFlagsTestParse04);
+ UtRegisterTest("MQTTConnectFlagsTestParse05", MQTTConnectFlagsTestParse05);
+#endif /* UNITTESTS */
+}
\ No newline at end of file
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __DETECT_MQTT_CONNECT_FLAGS_H__
+#define __DETECT_MQTT_CONNECT_FLAGS_H__
+
+void DetectMQTTConnectFlagsRegister(void);
+
+#endif /* __DETECT_MQTT_CONNECT_FLAGS_H__ */
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ *
+ * Implements the mqtt.connect.password sticky buffer
+ */
+
+#include "suricata-common.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+#include "detect-engine-prefilter.h"
+#include "detect-mqtt-connect-password.h"
+#include "rust.h"
+
+#define KEYWORD_NAME "mqtt.connect.password"
+#define KEYWORD_DOC "mqtt-keywords.html#mqtt-connect-password"
+#define BUFFER_NAME "mqtt.connect.password"
+#define BUFFER_DESC "MQTT CONNECT password"
+static int g_buffer_id = 0;
+
+static int DetectMQTTConnectPasswordSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
+{
+ if (DetectBufferSetActiveList(s, g_buffer_id) < 0)
+ return -1;
+
+ if (DetectSignatureSetAppProto(s, ALPROTO_MQTT) < 0)
+ return -1;
+
+ return 0;
+}
+
+static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx,
+ const DetectEngineTransforms *transforms,
+ Flow *_f, const uint8_t _flow_flags,
+ void *txv, const int list_id)
+{
+ InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id);
+ if (buffer->inspect == NULL) {
+ const uint8_t *b = NULL;
+ uint32_t b_len = 0;
+
+ if (rs_mqtt_tx_get_connect_password(txv, &b, &b_len) != 1)
+ return NULL;
+ if (b == NULL || b_len == 0)
+ return NULL;
+
+ InspectionBufferSetup(buffer, b, b_len);
+ InspectionBufferApplyTransforms(buffer, transforms);
+ }
+ return buffer;
+}
+
+void DetectMQTTConnectPasswordRegister(void)
+{
+ /* mqtt.connect.password sticky buffer */
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_PASSWORD].name = KEYWORD_NAME;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_PASSWORD].desc = "sticky buffer to match on the MQTT CONNECT password";
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_PASSWORD].url = "/rules/" KEYWORD_DOC;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_PASSWORD].Setup = DetectMQTTConnectPasswordSetup;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_PASSWORD].flags |= SIGMATCH_NOOPT;
+
+ DetectAppLayerInspectEngineRegister2(BUFFER_NAME, ALPROTO_MQTT,
+ SIG_FLAG_TOSERVER, 0,
+ DetectEngineInspectBufferGeneric, GetData);
+
+ DetectAppLayerMpmRegister2(BUFFER_NAME, SIG_FLAG_TOSERVER, 2,
+ PrefilterGenericMpmRegister, GetData, ALPROTO_MQTT,
+ 1);
+
+ DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC);
+
+ g_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME);
+
+ SCLogDebug("registering " BUFFER_NAME " rule option");
+}
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __DETECT_MQTT_CONNECT_PASSWORD_H__
+#define __DETECT_MQTT_CONNECT_PASSWORD_H__
+
+void DetectMQTTConnectPasswordRegister(void);
+
+#endif /* __DETECT_MQTT_CONNECT_PASSWORD_H__ */
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ *
+ * Implements the mqtt.connect.username sticky buffer
+ */
+
+#include "suricata-common.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+#include "detect-engine-prefilter.h"
+#include "detect-mqtt-connect-username.h"
+#include "rust.h"
+
+#define KEYWORD_NAME "mqtt.connect.username"
+#define KEYWORD_DOC "mqtt-keywords.html#mqtt-connect-username"
+#define BUFFER_NAME "mqtt.connect.username"
+#define BUFFER_DESC "MQTT CONNECT username"
+static int g_buffer_id = 0;
+
+static int DetectMQTTConnectUsernameSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
+{
+ if (DetectBufferSetActiveList(s, g_buffer_id) < 0)
+ return -1;
+
+ if (DetectSignatureSetAppProto(s, ALPROTO_MQTT) < 0)
+ return -1;
+
+ return 0;
+}
+
+static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx,
+ const DetectEngineTransforms *transforms,
+ Flow *_f, const uint8_t _flow_flags,
+ void *txv, const int list_id)
+{
+ InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id);
+ if (buffer->inspect == NULL) {
+ const uint8_t *b = NULL;
+ uint32_t b_len = 0;
+
+ if (rs_mqtt_tx_get_connect_username(txv, &b, &b_len) != 1)
+ return NULL;
+ if (b == NULL || b_len == 0)
+ return NULL;
+
+ InspectionBufferSetup(buffer, b, b_len);
+ InspectionBufferApplyTransforms(buffer, transforms);
+ }
+ return buffer;
+}
+
+void DetectMQTTConnectUsernameRegister(void)
+{
+ /* mqtt.connect.username sticky buffer */
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_USERNAME].name = KEYWORD_NAME;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_USERNAME].desc = "sticky buffer to match on the MQTT CONNECT username";
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_USERNAME].url = "/rules/" KEYWORD_DOC;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_USERNAME].Setup = DetectMQTTConnectUsernameSetup;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_USERNAME].flags |= SIGMATCH_NOOPT;
+
+ DetectAppLayerInspectEngineRegister2(BUFFER_NAME, ALPROTO_MQTT,
+ SIG_FLAG_TOSERVER, 0,
+ DetectEngineInspectBufferGeneric, GetData);
+
+ DetectAppLayerMpmRegister2(BUFFER_NAME, SIG_FLAG_TOSERVER, 2,
+ PrefilterGenericMpmRegister, GetData, ALPROTO_MQTT,
+ 1);
+
+ DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC);
+
+ g_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME);
+
+ SCLogDebug("registering " BUFFER_NAME " rule option");
+}
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __DETECT_MQTT_CONNECT_USERNAME_H__
+#define __DETECT_MQTT_CONNECT_USERNAME_H__
+
+void DetectMQTTConnectUsernameRegister(void);
+
+#endif /* __DETECT_MQTT_CONNECT_USERNAME_H__ */
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ *
+ * Implements the mqtt.connect.willmessage sticky buffer
+ */
+
+#include "suricata-common.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+#include "detect-engine-prefilter.h"
+#include "detect-mqtt-connect-willmessage.h"
+#include "rust.h"
+
+#define KEYWORD_NAME "mqtt.connect.willmessage"
+#define KEYWORD_DOC "mqtt-keywords.html#mqtt-connect-willmessage"
+#define BUFFER_NAME "mqtt.connect.willmessage"
+#define BUFFER_DESC "MQTT CONNECT will message"
+static int g_buffer_id = 0;
+
+static int DetectMQTTConnectWillMessageSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
+{
+ if (DetectBufferSetActiveList(s, g_buffer_id) < 0)
+ return -1;
+
+ if (DetectSignatureSetAppProto(s, ALPROTO_MQTT) < 0)
+ return -1;
+
+ return 0;
+}
+
+static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx,
+ const DetectEngineTransforms *transforms,
+ Flow *_f, const uint8_t _flow_flags,
+ void *txv, const int list_id)
+{
+ InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id);
+ if (buffer->inspect == NULL) {
+ const uint8_t *b = NULL;
+ uint32_t b_len = 0;
+
+ if (rs_mqtt_tx_get_connect_willmessage(txv, &b, &b_len) != 1)
+ return NULL;
+ if (b == NULL || b_len == 0)
+ return NULL;
+
+ InspectionBufferSetup(buffer, b, b_len);
+ InspectionBufferApplyTransforms(buffer, transforms);
+ }
+ return buffer;
+}
+
+void DetectMQTTConnectWillMessageRegister(void)
+{
+ /* mqtt.connect.willmessage sticky buffer */
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_WILLMESSAGE].name = KEYWORD_NAME;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_WILLMESSAGE].desc = "sticky buffer to match on the MQTT CONNECT will message";
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_WILLMESSAGE].url = "/rules/" KEYWORD_DOC;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_WILLMESSAGE].Setup = DetectMQTTConnectWillMessageSetup;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_WILLMESSAGE].flags |= SIGMATCH_NOOPT;
+
+ DetectAppLayerInspectEngineRegister2(BUFFER_NAME, ALPROTO_MQTT,
+ SIG_FLAG_TOSERVER, 0,
+ DetectEngineInspectBufferGeneric, GetData);
+
+ DetectAppLayerMpmRegister2(BUFFER_NAME, SIG_FLAG_TOSERVER, 2,
+ PrefilterGenericMpmRegister, GetData, ALPROTO_MQTT,
+ 1);
+
+ DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC);
+
+ g_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME);
+
+ SCLogDebug("registering " BUFFER_NAME " rule option");
+}
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __DETECT_MQTT_CONNECT_WILLMESSAGE_H__
+#define __DETECT_MQTT_CONNECT_WILLMESSAGE_H__
+
+void DetectMQTTConnectWillMessageRegister(void);
+
+#endif /* __DETECT_MQTT_CONNECT_WILLMESSAGE_H__ */
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ *
+ * Implements the mqtt.connect.willtopic sticky buffer
+ */
+
+#include "suricata-common.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+#include "detect-engine-prefilter.h"
+#include "detect-mqtt-connect-willtopic.h"
+#include "rust.h"
+
+#define KEYWORD_NAME "mqtt.connect.willtopic"
+#define KEYWORD_DOC "mqtt-keywords.html#mqtt-connect-willtopic"
+#define BUFFER_NAME "mqtt.connect.willtopic"
+#define BUFFER_DESC "MQTT CONNECT will topic"
+static int g_buffer_id = 0;
+
+static int DetectMQTTConnectWillTopicSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
+{
+ if (DetectBufferSetActiveList(s, g_buffer_id) < 0)
+ return -1;
+
+ if (DetectSignatureSetAppProto(s, ALPROTO_MQTT) < 0)
+ return -1;
+
+ return 0;
+}
+
+static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx,
+ const DetectEngineTransforms *transforms,
+ Flow *_f, const uint8_t _flow_flags,
+ void *txv, const int list_id)
+{
+ InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id);
+ if (buffer->inspect == NULL) {
+ const uint8_t *b = NULL;
+ uint32_t b_len = 0;
+
+ if (rs_mqtt_tx_get_connect_willtopic(txv, &b, &b_len) != 1)
+ return NULL;
+ if (b == NULL || b_len == 0)
+ return NULL;
+
+ InspectionBufferSetup(buffer, b, b_len);
+ InspectionBufferApplyTransforms(buffer, transforms);
+ }
+ return buffer;
+}
+
+void DetectMQTTConnectWillTopicRegister(void)
+{
+ /* mqtt.connect.willtopic sticky buffer */
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_WILLTOPIC].name = KEYWORD_NAME;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_WILLTOPIC].desc = "sticky buffer to match on the MQTT CONNECT will topic";
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_WILLTOPIC].url = "/rules/" KEYWORD_DOC;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_WILLTOPIC].Setup = DetectMQTTConnectWillTopicSetup;
+ sigmatch_table[DETECT_AL_MQTT_CONNECT_WILLTOPIC].flags |= SIGMATCH_NOOPT;
+
+ DetectAppLayerInspectEngineRegister2(BUFFER_NAME, ALPROTO_MQTT,
+ SIG_FLAG_TOSERVER, 0,
+ DetectEngineInspectBufferGeneric, GetData);
+
+ DetectAppLayerMpmRegister2(BUFFER_NAME, SIG_FLAG_TOSERVER, 2,
+ PrefilterGenericMpmRegister, GetData, ALPROTO_MQTT,
+ 1);
+
+ DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC);
+
+ g_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME);
+
+ SCLogDebug("registering " BUFFER_NAME " rule option");
+}
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __DETECT_MQTT_CONNECT_WILLTOPIC_H__
+#define __DETECT_MQTT_CONNECT_WILLTOPIC_H__
+
+void DetectMQTTConnectWillTopicRegister(void);
+
+#endif /* __DETECT_MQTT_CONNECT_WILLTOPIC_H__ */
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#include "suricata-common.h"
+#include "conf.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-content-inspection.h"
+#include "detect-mqtt-flags.h"
+#include "util-unittest.h"
+
+#include "rust-bindings.h"
+
+#define PARSE_REGEX "(?: *,?!?(?:retain|dup))+"
+static DetectParseRegex parse_regex;
+
+static int mqtt_flags_id = 0;
+
+static int DetectMQTTFlagsMatch(DetectEngineThreadCtx *det_ctx,
+ Flow *f, uint8_t flags, void *state,
+ void *txv, const Signature *s,
+ const SigMatchCtx *ctx);
+static int DetectMQTTFlagsSetup (DetectEngineCtx *, Signature *, const char *);
+void MQTTFlagsRegisterTests(void);
+void DetectMQTTFlagsFree(DetectEngineCtx *de_ctx, void *);
+
+static int DetectEngineInspectMQTTFlagsGeneric(ThreadVars *tv,
+ DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const Signature *s, const SigMatchData *smd,
+ Flow *f, uint8_t flags, void *alstate,
+ void *txv, uint64_t tx_id);
+
+typedef struct DetectMQTTFlagsData_ {
+ MQTTFlagState retain, dup;
+} DetectMQTTFlagsData;
+
+/**
+ * \brief Registration function for mqtt.flags: keyword
+ */
+void DetectMQTTFlagsRegister (void)
+{
+ sigmatch_table[DETECT_AL_MQTT_FLAGS].name = "mqtt.flags";
+ sigmatch_table[DETECT_AL_MQTT_FLAGS].desc = "match MQTT fixed header flags";
+ sigmatch_table[DETECT_AL_MQTT_FLAGS].url = "/rules/mqtt-keywords.html#mqtt-flags";
+ sigmatch_table[DETECT_AL_MQTT_FLAGS].AppLayerTxMatch = DetectMQTTFlagsMatch;
+ sigmatch_table[DETECT_AL_MQTT_FLAGS].Setup = DetectMQTTFlagsSetup;
+ sigmatch_table[DETECT_AL_MQTT_FLAGS].Free = DetectMQTTFlagsFree;
+#ifdef UNITTESTS
+ sigmatch_table[DETECT_AL_MQTT_FLAGS].RegisterTests = MQTTFlagsRegisterTests;
+#endif
+
+ DetectSetupParseRegexes(PARSE_REGEX, &parse_regex);
+
+ DetectAppLayerInspectEngineRegister("mqtt.flags",
+ ALPROTO_MQTT, SIG_FLAG_TOSERVER, 1,
+ DetectEngineInspectMQTTFlagsGeneric);
+
+ mqtt_flags_id = DetectBufferTypeGetByName("mqtt.flags");
+}
+
+static int DetectEngineInspectMQTTFlagsGeneric(ThreadVars *tv,
+ DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const Signature *s, const SigMatchData *smd,
+ Flow *f, uint8_t flags, void *alstate,
+ void *txv, uint64_t tx_id)
+{
+ return DetectEngineInspectGenericList(tv, de_ctx, det_ctx, s, smd,
+ f, flags, alstate, txv, tx_id);
+}
+
+/**
+ * \internal
+ * \brief Function to match fixed header flags of an MQTT Tx
+ *
+ * \param det_ctx Pointer to the pattern matcher thread.
+ * \param f Pointer to the current flow.
+ * \param flags Flags.
+ * \param state App layer state.
+ * \param txv Pointer to the transaction.
+ * \param s Pointer to the Signature.
+ * \param ctx Pointer to the sigmatch that we will cast into DetectMQTTFlagsData.
+ *
+ * \retval 0 no match.
+ * \retval 1 match.
+ */
+static int DetectMQTTFlagsMatch(DetectEngineThreadCtx *det_ctx,
+ Flow *f, uint8_t flags, void *state,
+ void *txv, const Signature *s,
+ const SigMatchCtx *ctx)
+{
+ const DetectMQTTFlagsData *de = (const DetectMQTTFlagsData *)ctx;
+
+ if (!de)
+ return 0;
+
+ return rs_mqtt_tx_has_flags(txv, de->retain, de->dup);
+}
+
+/**
+ * \internal
+ * \brief This function is used to parse options passed via mqtt.flags: keyword
+ *
+ * \param rawstr Pointer to the user provided options
+ *
+ * \retval de pointer to DetectMQTTFlagsData on success
+ * \retval NULL on failure
+ */
+static DetectMQTTFlagsData *DetectMQTTFlagsParse(const char *rawstr)
+{
+ DetectMQTTFlagsData *de = NULL;
+ int ret = 0;
+ int ov[MAX_SUBSTRINGS];
+
+ ret = DetectParsePcreExec(&parse_regex, rawstr, 0, 0, ov, MAX_SUBSTRINGS);
+ if (ret < 1) {
+ SCLogError(SC_ERR_PCRE_MATCH, "invalid flag definition: %s", rawstr);
+ return NULL;
+ }
+
+ de = SCCalloc(1, sizeof(DetectMQTTFlagsData));
+ if (unlikely(de == NULL))
+ return NULL;
+ de->retain = de->dup = MQTT_DONT_CARE;
+
+ char copy[strlen(rawstr)+1];
+ strlcpy(copy, rawstr, sizeof(copy));
+ char *xsaveptr = NULL;
+
+ /* Iterate through comma-separated string... */
+ char *flagv = strtok_r(copy, ",", &xsaveptr);
+ while (flagv != NULL) {
+ /* skip blanks */
+ while (*flagv != '\0' && isblank(*flagv)) {
+ flagv++;
+ }
+ if (strlen(flagv) < 2) {
+ /* flags have a minimum length */
+ SCLogError(SC_ERR_UNKNOWN_VALUE, "malformed flag value: %s", flagv);
+ goto error;
+ } else {
+ int offset = 0;
+ MQTTFlagState fs_to_set = MQTT_MUST_BE_SET;
+ if (flagv[0] == '!') {
+ /* negated flag */
+ offset = 1; /* skip negation operator during comparison */
+ fs_to_set = MQTT_CANT_BE_SET;
+ }
+ if (strcmp(flagv+offset, "dup") == 0) {
+ if (de->dup != MQTT_DONT_CARE) {
+ SCLogError(SC_ERR_INVALID_VALUE, "duplicate flag definition: %s", flagv);
+ goto error;
+ }
+ de->dup = fs_to_set;
+ } else if (strcmp(flagv+offset, "retain") == 0) {
+ if (de->retain != MQTT_DONT_CARE) {
+ SCLogError(SC_ERR_INVALID_VALUE, "duplicate flag definition: %s", flagv);
+ goto error;
+ }
+ de->retain = fs_to_set;
+ } else {
+ SCLogError(SC_ERR_UNKNOWN_VALUE, "invalid flag definition: %s", flagv);
+ goto error;
+ }
+ }
+ flagv = strtok_r(NULL, ",", &xsaveptr);
+ }
+
+ return de;
+
+error:
+ if (de != NULL)
+ SCFree(de);
+ return NULL;
+}
+
+/**
+ * \internal
+ * \brief this function is used to add the parsed type query into the current signature
+ *
+ * \param de_ctx pointer to the Detection Engine Context
+ * \param s pointer to the Current Signature
+ * \param rawstr pointer to the user provided options
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+static int DetectMQTTFlagsSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
+{
+ DetectMQTTFlagsData *de = NULL;
+ SigMatch *sm = NULL;
+
+ if (DetectSignatureSetAppProto(s, ALPROTO_MQTT) < 0)
+ return -1;
+
+ de = DetectMQTTFlagsParse(rawstr);
+ if (de == NULL)
+ goto error;
+
+ sm = SigMatchAlloc();
+ if (sm == NULL)
+ goto error;
+
+ sm->type = DETECT_AL_MQTT_FLAGS;
+ sm->ctx = (SigMatchCtx *)de;
+
+ SigMatchAppendSMToList(s, sm, mqtt_flags_id);
+
+ return 0;
+
+error:
+ if (de != NULL)
+ SCFree(de);
+ if (sm != NULL)
+ SCFree(sm);
+ return -1;
+}
+
+/**
+ * \internal
+ * \brief this function will free memory associated with DetectMQTTFlagsData
+ *
+ * \param de pointer to DetectMQTTFlagsData
+ */
+void DetectMQTTFlagsFree(DetectEngineCtx *de_ctx, void *de_ptr)
+{
+ if (de_ptr != NULL)
+ SCFree(de_ptr);
+}
+
+/*
+ * ONLY TESTS BELOW THIS COMMENT
+ */
+
+#ifdef UNITTESTS
+/**
+ * \test MQTTFlagsTestParse01 is a test for a valid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTFlagsTestParse01 (void)
+{
+ DetectMQTTFlagsData *de = NULL;
+
+ de = DetectMQTTFlagsParse("retain");
+ FAIL_IF_NULL(de);
+ DetectMQTTFlagsFree(NULL, de);
+
+ de = DetectMQTTFlagsParse("dup");
+ FAIL_IF_NULL(de);
+ DetectMQTTFlagsFree(NULL, de);
+
+ de = DetectMQTTFlagsParse("retain,dup");
+ FAIL_IF_NULL(de);
+ DetectMQTTFlagsFree(NULL, de);
+
+ de = DetectMQTTFlagsParse("dup, retain");
+ FAIL_IF_NULL(de);
+ DetectMQTTFlagsFree(NULL, de);
+
+ PASS;
+}
+
+/**
+ * \test MQTTFlagsTestParse02 is a test for a valid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTFlagsTestParse02 (void)
+{
+ DetectMQTTFlagsData *de = NULL;
+ de = DetectMQTTFlagsParse("retain,!dup");
+ FAIL_IF_NULL(de);
+ DetectMQTTFlagsFree(NULL, de);
+
+ PASS;
+}
+
+/**
+ * \test MQTTFlagsTestParse03 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTFlagsTestParse03 (void)
+{
+ DetectMQTTFlagsData *de = NULL;
+ de = DetectMQTTFlagsParse("ref");
+ if (de) {
+ DetectMQTTFlagsFree(NULL, de);
+ FAIL;
+ }
+
+ PASS;
+}
+
+/**
+ * \test MQTTFlagsTestParse04 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTFlagsTestParse04 (void)
+{
+ DetectMQTTFlagsData *de = NULL;
+ de = DetectMQTTFlagsParse("dup,!");
+ if (de) {
+ DetectMQTTFlagsFree(NULL, de);
+ FAIL;
+ }
+
+ PASS;
+}
+
+/**
+ * \test MQTTFlagsTestParse05 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTFlagsTestParse05 (void)
+{
+ DetectMQTTFlagsData *de = NULL;
+ de = DetectMQTTFlagsParse("dup,!dup");
+ if (de) {
+ DetectMQTTFlagsFree(NULL, de);
+ FAIL;
+ }
+
+ de = DetectMQTTFlagsParse("!retain,retain");
+ if (de) {
+ DetectMQTTFlagsFree(NULL, de);
+ FAIL;
+ }
+
+ PASS;
+}
+
+
+#endif /* UNITTESTS */
+
+/**
+ * \brief this function registers unit tests for MQTTFlags
+ */
+void MQTTFlagsRegisterTests(void)
+{
+#ifdef UNITTESTS
+ UtRegisterTest("MQTTFlagsTestParse01", MQTTFlagsTestParse01);
+ UtRegisterTest("MQTTFlagsTestParse02", MQTTFlagsTestParse02);
+ UtRegisterTest("MQTTFlagsTestParse03", MQTTFlagsTestParse03);
+ UtRegisterTest("MQTTFlagsTestParse04", MQTTFlagsTestParse04);
+ UtRegisterTest("MQTTFlagsTestParse05", MQTTFlagsTestParse05);
+#endif /* UNITTESTS */
+}
\ No newline at end of file
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __DETECT_MQTT_FLAGS_H__
+#define __DETECT_MQTT_FLAGS_H__
+
+void DetectMQTTFlagsRegister(void);
+
+#endif /* __DETECT_MQTT_FLAGS_H__ */
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#include "suricata-common.h"
+#include "conf.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-content-inspection.h"
+#include "detect-engine-uint.h"
+#include "detect-mqtt-protocol-version.h"
+#include "util-byte.h"
+#include "util-unittest.h"
+
+#include "rust-bindings.h"
+
+static int mqtt_protocol_version_id = 0;
+
+static int DetectMQTTProtocolVersionMatch(DetectEngineThreadCtx *det_ctx,
+ Flow *f, uint8_t flags, void *state,
+ void *txv, const Signature *s,
+ const SigMatchCtx *ctx);
+static int DetectMQTTProtocolVersionSetup (DetectEngineCtx *, Signature *, const char *);
+void MQTTProtocolVersionRegisterTests(void);
+void DetectMQTTProtocolVersionFree(DetectEngineCtx *de_ctx, void *);
+
+static int DetectEngineInspectMQTTProtocolVersionGeneric(ThreadVars *tv,
+ DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const Signature *s, const SigMatchData *smd,
+ Flow *f, uint8_t flags, void *alstate,
+ void *txv, uint64_t tx_id);
+
+/**
+ * \brief Registration function for mqtt.protocol_version: keyword
+ */
+void DetectMQTTProtocolVersionRegister (void)
+{
+ sigmatch_table[DETECT_AL_MQTT_PROTOCOL_VERSION].name = "mqtt.protocol_version";
+ sigmatch_table[DETECT_AL_MQTT_PROTOCOL_VERSION].desc = "match MQTT protocol version";
+ sigmatch_table[DETECT_AL_MQTT_PROTOCOL_VERSION].url = "/rules/mqtt-keywords.html#mqtt-protocol-version";
+ sigmatch_table[DETECT_AL_MQTT_PROTOCOL_VERSION].AppLayerTxMatch = DetectMQTTProtocolVersionMatch;
+ sigmatch_table[DETECT_AL_MQTT_PROTOCOL_VERSION].Setup = DetectMQTTProtocolVersionSetup;
+ sigmatch_table[DETECT_AL_MQTT_PROTOCOL_VERSION].Free = DetectMQTTProtocolVersionFree;
+#ifdef UNITTESTS
+ sigmatch_table[DETECT_AL_MQTT_PROTOCOL_VERSION].RegisterTests = MQTTProtocolVersionRegisterTests;
+#endif
+
+ DetectAppLayerInspectEngineRegister("mqtt.protocol_version",
+ ALPROTO_MQTT, SIG_FLAG_TOSERVER, 1,
+ DetectEngineInspectMQTTProtocolVersionGeneric);
+
+ mqtt_protocol_version_id = DetectBufferTypeGetByName("mqtt.protocol_version");
+}
+
+static int DetectEngineInspectMQTTProtocolVersionGeneric(ThreadVars *tv,
+ DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const Signature *s, const SigMatchData *smd,
+ Flow *f, uint8_t flags, void *alstate,
+ void *txv, uint64_t tx_id)
+{
+ return DetectEngineInspectGenericList(tv, de_ctx, det_ctx, s, smd,
+ f, flags, alstate, txv, tx_id);
+}
+
+/**
+ * \internal
+ * \brief Function to match protocol version of an MQTT Tx
+ *
+ * \param det_ctx Pointer to the pattern matcher thread.
+ * \param f Pointer to the current flow.
+ * \param flags Flags.
+ * \param state App layer state.
+ * \param txv Pointer to the transaction.
+ * \param s Pointer to the Signature.
+ * \param ctx Pointer to the sigmatch that we will cast into DetectMQTTProtocolVersionData.
+ *
+ * \retval 0 no match.
+ * \retval 1 match.
+ */
+static int DetectMQTTProtocolVersionMatch(DetectEngineThreadCtx *det_ctx,
+ Flow *f, uint8_t flags, void *state,
+ void *txv, const Signature *s,
+ const SigMatchCtx *ctx)
+{
+ const DetectU8Data *de = (const DetectU8Data *)ctx;
+ uint8_t version;
+
+ version = rs_mqtt_tx_get_protocol_version(state);
+
+ return DetectU8Match(version, de);
+}
+
+/**
+ * \internal
+ * \brief this function is used to add the parsed sigmatch into the current signature
+ *
+ * \param de_ctx pointer to the Detection Engine Context
+ * \param s pointer to the Current Signature
+ * \param rawstr pointer to the user provided options
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+static int DetectMQTTProtocolVersionSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
+{
+ SigMatch *sm = NULL;
+ DetectU8Data *de = NULL;
+
+ if (DetectSignatureSetAppProto(s, ALPROTO_MQTT) < 0)
+ return -1;
+
+ de = DetectU8Parse(rawstr);
+ if (de == NULL)
+ return -1;
+
+ sm = SigMatchAlloc();
+ if (sm == NULL)
+ goto error;
+
+ sm->type = DETECT_AL_MQTT_PROTOCOL_VERSION;
+ sm->ctx = (SigMatchCtx *)de;
+
+ SigMatchAppendSMToList(s, sm, mqtt_protocol_version_id);
+
+ return 0;
+
+error:
+ if (de != NULL)
+ SCFree(de);
+ if (sm != NULL)
+ SCFree(sm);
+ return -1;
+}
+
+/**
+ * \internal
+ * \brief this function will free memory associated with DetectMQTTProtocolVersionData
+ *
+ * \param de pointer to DetectMQTTProtocolVersionData
+ */
+void DetectMQTTProtocolVersionFree(DetectEngineCtx *de_ctx, void *de_ptr)
+{
+ if (de_ptr != NULL)
+ SCFree(de_ptr);
+}
+
+/*
+ * ONLY TESTS BELOW THIS COMMENT
+ */
+
+#ifdef UNITTESTS
+/**
+ * \test MQTTProtocolVersionTestParse01 is a test for a valid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTProtocolVersionTestParse01 (void)
+{
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ FAIL_IF_NULL(de_ctx);
+
+ Signature *sig = DetectEngineAppendSig(de_ctx,
+ "alert ip any any -> any any (mqtt.protocol_version:3; sid:1; rev:1;)");
+ FAIL_IF_NULL(sig);
+
+ sig = DetectEngineAppendSig(de_ctx,
+ "alert ip any any -> any any (mqtt.protocol_version:3; sid:2; rev:1;)");
+ FAIL_IF_NULL(sig);
+
+ DetectEngineCtxFree(de_ctx);
+
+ PASS;
+}
+
+/**
+ * \test MQTTProtocolVersionTestParse02 is a test for a valid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTProtocolVersionTestParse02 (void)
+{
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ FAIL_IF_NULL(de_ctx);
+
+ Signature *sig = DetectEngineAppendSig(de_ctx,
+ "alert ip any any -> any any (mqtt.protocol_version:>3; sid:1; rev:1;)");
+ FAIL_IF_NULL(sig);
+
+ sig = DetectEngineAppendSig(de_ctx,
+ "alert ip any any -> any any (mqtt.protocol_version:<44; sid:2; rev:1;)");
+ FAIL_IF_NULL(sig);
+
+ DetectEngineCtxFree(de_ctx);
+
+ PASS;
+}
+
+/**
+ * \test MQTTProtocolVersionTestParse03 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTProtocolVersionTestParse03 (void)
+{
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ FAIL_IF_NULL(de_ctx);
+
+ Signature *sig = DetectEngineAppendSig(de_ctx,
+ "alert ip any any -> any any (mqtt.protocol_version:; sid:1; rev:1;)");
+ FAIL_IF_NOT_NULL(sig);
+
+ DetectEngineCtxFree(de_ctx);
+
+ PASS;
+}
+
+/**
+ * \test MQTTProtocolVersionTestParse04 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTProtocolVersionTestParse04 (void)
+{
+ DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+ FAIL_IF_NULL(de_ctx);
+
+ Signature *sig = DetectEngineAppendSig(de_ctx,
+ "alert ip any any -> any any (mqtt.protocol_version:<444; sid:1; rev:1;)");
+ FAIL_IF_NOT_NULL(sig);
+
+ DetectEngineCtxFree(de_ctx);
+
+ PASS;
+}
+
+#endif /* UNITTESTS */
+
+/**
+ * \brief this function registers unit tests for MQTTProtocolVersion
+ */
+void MQTTProtocolVersionRegisterTests(void)
+{
+#ifdef UNITTESTS
+ UtRegisterTest("MQTTProtocolVersionTestParse01", MQTTProtocolVersionTestParse01);
+ UtRegisterTest("MQTTProtocolVersionTestParse02", MQTTProtocolVersionTestParse02);
+ UtRegisterTest("MQTTProtocolVersionTestParse03", MQTTProtocolVersionTestParse03);
+ UtRegisterTest("MQTTProtocolVersionTestParse04", MQTTProtocolVersionTestParse04);
+#endif /* UNITTESTS */
+}
\ No newline at end of file
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __DETECT_MQTT_PROTOCOL_VERSION_H__
+#define __DETECT_MQTT_PROTOCOL_VERSION_H__
+
+void DetectMQTTProtocolVersionRegister(void);
+
+#endif /* __DETECT_MQTT_PROTOCOL_VERSION_H__ */
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ *
+ * Implements the mqtt.publish.message sticky buffer
+ */
+
+#include "suricata-common.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+#include "detect-engine-prefilter.h"
+#include "detect-mqtt-publish-message.h"
+#include "rust.h"
+
+#define KEYWORD_NAME "mqtt.publish.message"
+#define KEYWORD_DOC "mqtt-keywords.html#mqtt-publish-message"
+#define BUFFER_NAME "mqtt.publish.message"
+#define BUFFER_DESC "MQTT PUBLISH message"
+static int g_buffer_id = 0;
+
+static int DetectMQTTPublishMessageSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
+{
+ if (DetectBufferSetActiveList(s, g_buffer_id) < 0)
+ return -1;
+
+ if (DetectSignatureSetAppProto(s, ALPROTO_MQTT) < 0)
+ return -1;
+
+ return 0;
+}
+
+static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx,
+ const DetectEngineTransforms *transforms,
+ Flow *_f, const uint8_t _flow_flags,
+ void *txv, const int list_id)
+{
+ InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id);
+ if (buffer->inspect == NULL) {
+ const uint8_t *b = NULL;
+ uint32_t b_len = 0;
+
+ if (rs_mqtt_tx_get_publish_message(txv, &b, &b_len) != 1)
+ return NULL;
+ if (b == NULL || b_len == 0)
+ return NULL;
+
+ InspectionBufferSetup(buffer, b, b_len);
+ InspectionBufferApplyTransforms(buffer, transforms);
+ }
+ return buffer;
+}
+
+void DetectMQTTPublishMessageRegister(void)
+{
+ /* mqtt.publish.message sticky buffer */
+ sigmatch_table[DETECT_AL_MQTT_PUBLISH_MESSAGE].name = KEYWORD_NAME;
+ sigmatch_table[DETECT_AL_MQTT_PUBLISH_MESSAGE].desc = "sticky buffer to match on the MQTT PUBLISH message";
+ sigmatch_table[DETECT_AL_MQTT_PUBLISH_MESSAGE].url = "/rules/" KEYWORD_DOC;
+ sigmatch_table[DETECT_AL_MQTT_PUBLISH_MESSAGE].Setup = DetectMQTTPublishMessageSetup;
+ sigmatch_table[DETECT_AL_MQTT_PUBLISH_MESSAGE].flags |= SIGMATCH_NOOPT;
+
+ DetectAppLayerInspectEngineRegister2(BUFFER_NAME, ALPROTO_MQTT,
+ SIG_FLAG_TOSERVER, 0,
+ DetectEngineInspectBufferGeneric, GetData);
+
+ DetectAppLayerMpmRegister2(BUFFER_NAME, SIG_FLAG_TOSERVER, 2,
+ PrefilterGenericMpmRegister, GetData, ALPROTO_MQTT,
+ 1);
+
+ DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC);
+
+ g_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME);
+
+ SCLogDebug("registering " BUFFER_NAME " rule option");
+}
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __DETECT_MQTT_PUBLISH_MESSAGE_H__
+#define __DETECT_MQTT_PUBLISH_MESSAGE_H__
+
+void DetectMQTTPublishMessageRegister(void);
+
+#endif /* __DETECT_MQTT_PUBLISH_MESSAGE_H__ */
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ *
+ * Implements the mqtt.publish.topic sticky buffer
+ */
+
+#include "suricata-common.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+#include "detect-engine-prefilter.h"
+#include "detect-mqtt-publish-topic.h"
+#include "rust.h"
+
+#define KEYWORD_NAME "mqtt.publish.topic"
+#define KEYWORD_DOC "mqtt-keywords.html#mqtt-publish-topic"
+#define BUFFER_NAME "mqtt.publish.topic"
+#define BUFFER_DESC "MQTT PUBLISH topic"
+static int g_buffer_id = 0;
+
+static int DetectMQTTPublishTopicSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
+{
+ if (DetectBufferSetActiveList(s, g_buffer_id) < 0)
+ return -1;
+
+ if (DetectSignatureSetAppProto(s, ALPROTO_MQTT) < 0)
+ return -1;
+
+ return 0;
+}
+
+static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx,
+ const DetectEngineTransforms *transforms,
+ Flow *_f, const uint8_t _flow_flags,
+ void *txv, const int list_id)
+{
+ InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id);
+ if (buffer->inspect == NULL) {
+ const uint8_t *b = NULL;
+ uint32_t b_len = 0;
+
+ if (rs_mqtt_tx_get_publish_topic(txv, &b, &b_len) != 1)
+ return NULL;
+ if (b == NULL || b_len == 0)
+ return NULL;
+
+ InspectionBufferSetup(buffer, b, b_len);
+ InspectionBufferApplyTransforms(buffer, transforms);
+ }
+ return buffer;
+}
+
+void DetectMQTTPublishTopicRegister(void)
+{
+ /* mqtt.publish.topic sticky buffer */
+ sigmatch_table[DETECT_AL_MQTT_PUBLISH_TOPIC].name = KEYWORD_NAME;
+ sigmatch_table[DETECT_AL_MQTT_PUBLISH_TOPIC].desc = "sticky buffer to match on the MQTT PUBLISH topic";
+ sigmatch_table[DETECT_AL_MQTT_PUBLISH_TOPIC].url = "/rules/" KEYWORD_DOC;
+ sigmatch_table[DETECT_AL_MQTT_PUBLISH_TOPIC].Setup = DetectMQTTPublishTopicSetup;
+ sigmatch_table[DETECT_AL_MQTT_PUBLISH_TOPIC].flags |= SIGMATCH_NOOPT;
+
+ DetectAppLayerInspectEngineRegister2(BUFFER_NAME, ALPROTO_MQTT,
+ SIG_FLAG_TOSERVER, 0,
+ DetectEngineInspectBufferGeneric, GetData);
+
+ DetectAppLayerMpmRegister2(BUFFER_NAME, SIG_FLAG_TOSERVER, 2,
+ PrefilterGenericMpmRegister, GetData, ALPROTO_MQTT,
+ 1);
+
+ DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC);
+
+ g_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME);
+
+ SCLogDebug("registering " BUFFER_NAME " rule option");
+}
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __DETECT_MQTT_PUBLISH_TOPIC_H__
+#define __DETECT_MQTT_PUBLISH_TOPIC_H__
+
+void DetectMQTTPublishTopicRegister(void);
+
+#endif /* __DETECT_MQTT_PUBLISH_TOPIC_H__ */
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#include "suricata-common.h"
+#include "conf.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-content-inspection.h"
+#include "detect-mqtt-qos.h"
+#include "util-byte.h"
+#include "util-unittest.h"
+
+#include "rust-bindings.h"
+
+static int mqtt_qos_id = 0;
+
+static int DetectMQTTQosMatch(DetectEngineThreadCtx *det_ctx,
+ Flow *f, uint8_t flags, void *state,
+ void *txv, const Signature *s,
+ const SigMatchCtx *ctx);
+static int DetectMQTTQosSetup (DetectEngineCtx *, Signature *, const char *);
+void MQTTQosRegisterTests(void);
+void DetectMQTTQosFree(DetectEngineCtx *de_ctx, void *);
+
+static int DetectEngineInspectMQTTQosGeneric(ThreadVars *tv,
+ DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const Signature *s, const SigMatchData *smd,
+ Flow *f, uint8_t flags, void *alstate,
+ void *txv, uint64_t tx_id);
+
+/**
+ * \brief Registration function for mqtt.qos: keyword
+ */
+void DetectMQTTQosRegister (void)
+{
+ sigmatch_table[DETECT_AL_MQTT_QOS].name = "mqtt.qos";
+ sigmatch_table[DETECT_AL_MQTT_QOS].desc = "match MQTT fixed header QOS level";
+ sigmatch_table[DETECT_AL_MQTT_QOS].url = "/rules/mqtt-keywords.html#mqtt-qos";
+ sigmatch_table[DETECT_AL_MQTT_QOS].AppLayerTxMatch = DetectMQTTQosMatch;
+ sigmatch_table[DETECT_AL_MQTT_QOS].Setup = DetectMQTTQosSetup;
+ sigmatch_table[DETECT_AL_MQTT_QOS].Free = DetectMQTTQosFree;
+#ifdef UNITTESTS
+ sigmatch_table[DETECT_AL_MQTT_QOS].RegisterTests = MQTTQosRegisterTests;
+#endif
+
+ DetectAppLayerInspectEngineRegister("mqtt.qos",
+ ALPROTO_MQTT, SIG_FLAG_TOSERVER, 1,
+ DetectEngineInspectMQTTQosGeneric);
+
+ mqtt_qos_id = DetectBufferTypeGetByName("mqtt.qos");
+}
+
+static int DetectEngineInspectMQTTQosGeneric(ThreadVars *tv,
+ DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const Signature *s, const SigMatchData *smd,
+ Flow *f, uint8_t flags, void *alstate,
+ void *txv, uint64_t tx_id)
+{
+ return DetectEngineInspectGenericList(tv, de_ctx, det_ctx, s, smd,
+ f, flags, alstate, txv, tx_id);
+}
+
+/**
+ * \internal
+ * \brief Function to match fixed header QOS field of an MQTT Tx
+ *
+ * \param det_ctx Pointer to the pattern matcher thread.
+ * \param f Pointer to the current flow.
+ * \param flags Flags.
+ * \param state App layer state.
+ * \param txv Pointer to the transaction.
+ * \param s Pointer to the Signature.
+ * \param ctx Pointer to the sigmatch that we will cast into uint8_t.
+ *
+ * \retval 0 no match.
+ * \retval 1 match.
+ */
+static int DetectMQTTQosMatch(DetectEngineThreadCtx *det_ctx,
+ Flow *f, uint8_t flags, void *state,
+ void *txv, const Signature *s,
+ const SigMatchCtx *ctx)
+{
+ const uint8_t *de = (const uint8_t *)ctx;
+
+ if (!de)
+ return 0;
+
+ return rs_mqtt_tx_has_qos(txv, *de);
+}
+
+/**
+ * \internal
+ * \brief This function is used to parse options passed via mqtt.qos: keyword
+ *
+ * \param rawstr Pointer to the user provided options
+ *
+ * \retval de pointer to DetectMQTTQosData on success
+ * \retval NULL on failure
+ */
+static uint8_t *DetectMQTTQosParse(const char *rawstr)
+{
+ uint8_t *de = NULL;
+ int ret = 0;
+ uint8_t val;
+
+ ret = StringParseU8RangeCheck(&val, 10, 0, rawstr, 0, 2);
+ if (ret < 0) {
+ SCLogError(SC_ERR_UNKNOWN_VALUE, "invalid MQTT QOS level: %s", rawstr);
+ return NULL;
+ }
+
+ de = SCMalloc(sizeof(uint8_t));
+ if (unlikely(de == NULL))
+ return NULL;
+ *de = val;
+
+ return de;
+}
+
+/**
+ * \internal
+ * \brief this function is used to add the parsed sigmatch into the current signature
+ *
+ * \param de_ctx pointer to the Detection Engine Context
+ * \param s pointer to the Current Signature
+ * \param rawstr pointer to the user provided options
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+static int DetectMQTTQosSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
+{
+ uint8_t *de = NULL;
+ SigMatch *sm = NULL;
+
+ if (DetectSignatureSetAppProto(s, ALPROTO_MQTT) < 0)
+ return -1;
+
+ de = DetectMQTTQosParse(rawstr);
+ if (de == NULL)
+ goto error;
+
+ sm = SigMatchAlloc();
+ if (sm == NULL)
+ goto error;
+
+ sm->type = DETECT_AL_MQTT_QOS;
+ sm->ctx = (SigMatchCtx *)de;
+
+ SigMatchAppendSMToList(s, sm, mqtt_qos_id);
+
+ return 0;
+
+error:
+ if (de != NULL)
+ SCFree(de);
+ if (sm != NULL)
+ SCFree(sm);
+ return -1;
+}
+
+/**
+ * \internal
+ * \brief this function will free memory associated with DetectMQTTQosData
+ *
+ * \param de pointer to DetectMQTTQosData
+ */
+void DetectMQTTQosFree(DetectEngineCtx *de_ctx, void *de_ptr)
+{
+ if (de_ptr != NULL)
+ SCFree(de_ptr);
+}
+
+/*
+ * ONLY TESTS BELOW THIS COMMENT
+ */
+
+#ifdef UNITTESTS
+/**
+ * \test MQTTQosTestParse01 is a test for a valid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTQosTestParse01 (void)
+{
+ uint8_t *de = NULL;
+
+ de = DetectMQTTQosParse("0");
+ FAIL_IF_NULL(de);
+ FAIL_IF_NOT(*de == 0);
+ DetectMQTTQosFree(NULL, de);
+
+ de = DetectMQTTQosParse(" 0");
+ FAIL_IF_NULL(de);
+ FAIL_IF_NOT(*de == 0);
+ DetectMQTTQosFree(NULL, de);
+
+ de = DetectMQTTQosParse("1");
+ FAIL_IF_NULL(de);
+ FAIL_IF_NOT(*de == 1);
+ DetectMQTTQosFree(NULL, de);
+
+ de = DetectMQTTQosParse("2");
+ FAIL_IF_NULL(de);
+ FAIL_IF_NOT(*de == 2);
+ DetectMQTTQosFree(NULL, de);
+
+ PASS;
+}
+
+/**
+ * \test MQTTQosTestParse02 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTQosTestParse02 (void)
+{
+ uint8_t *de = NULL;
+ de = DetectMQTTQosParse("3");
+ if (de) {
+ DetectMQTTQosFree(NULL, de);
+ FAIL;
+ }
+
+ PASS;
+}
+
+/**
+ * \test MQTTQosTestParse04 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTQosTestParse03 (void)
+{
+ uint8_t *de = NULL;
+ de = DetectMQTTQosParse("12");
+ if (de) {
+ DetectMQTTQosFree(NULL, de);
+ FAIL;
+ }
+
+ PASS;
+}
+
+
+#endif /* UNITTESTS */
+
+/**
+ * \brief this function registers unit tests for MQTTQos
+ */
+void MQTTQosRegisterTests(void)
+{
+#ifdef UNITTESTS
+ UtRegisterTest("MQTTQosTestParse01", MQTTQosTestParse01);
+ UtRegisterTest("MQTTQosTestParse02", MQTTQosTestParse02);
+ UtRegisterTest("MQTTQosTestParse03", MQTTQosTestParse03);
+#endif /* UNITTESTS */
+}
\ No newline at end of file
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __DETECT_MQTT_QOS_H__
+#define __DETECT_MQTT_QOS_H__
+
+void DetectMQTTQosRegister(void);
+
+#endif /* __DETECT_MQTT_QOS_H__ */
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#include "suricata-common.h"
+#include "conf.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-content-inspection.h"
+#include "detect-mqtt-reason-code.h"
+#include "util-byte.h"
+#include "util-unittest.h"
+
+#include "rust-bindings.h"
+
+#define PARSE_REGEX "^\\s*\\d+\\s*$"
+static DetectParseRegex parse_regex;
+
+static int mqtt_reason_code_id = 0;
+
+static int DetectMQTTReasonCodeMatch(DetectEngineThreadCtx *det_ctx,
+ Flow *f, uint8_t flags, void *state,
+ void *txv, const Signature *s,
+ const SigMatchCtx *ctx);
+static int DetectMQTTReasonCodeSetup (DetectEngineCtx *, Signature *, const char *);
+void MQTTReasonCodeRegisterTests(void);
+void DetectMQTTReasonCodeFree(DetectEngineCtx *de_ctx, void *);
+
+static int DetectEngineInspectMQTTReasonCodeGeneric(ThreadVars *tv,
+ DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const Signature *s, const SigMatchData *smd,
+ Flow *f, uint8_t flags, void *alstate,
+ void *txv, uint64_t tx_id);
+
+/**
+ * \brief Registration function for mqtt.reason_code: keyword
+ */
+void DetectMQTTReasonCodeRegister (void)
+{
+ sigmatch_table[DETECT_AL_MQTT_REASON_CODE].name = "mqtt.reason_code";
+ sigmatch_table[DETECT_AL_MQTT_REASON_CODE].alias = "mqtt.connack.return_code";
+ sigmatch_table[DETECT_AL_MQTT_REASON_CODE].desc = "match MQTT 5.0+ reason code";
+ sigmatch_table[DETECT_AL_MQTT_REASON_CODE].url = "/rules/mqtt-keywords.html#mqtt-reason-code";
+ sigmatch_table[DETECT_AL_MQTT_REASON_CODE].AppLayerTxMatch = DetectMQTTReasonCodeMatch;
+ sigmatch_table[DETECT_AL_MQTT_REASON_CODE].Setup = DetectMQTTReasonCodeSetup;
+ sigmatch_table[DETECT_AL_MQTT_REASON_CODE].Free = DetectMQTTReasonCodeFree;
+#ifdef UNITTESTS
+ sigmatch_table[DETECT_AL_MQTT_REASON_CODE].RegisterTests = MQTTReasonCodeRegisterTests;
+#endif
+
+ DetectSetupParseRegexes(PARSE_REGEX, &parse_regex);
+
+ DetectAppLayerInspectEngineRegister("mqtt.reason_code",
+ ALPROTO_MQTT, SIG_FLAG_TOSERVER, 1,
+ DetectEngineInspectMQTTReasonCodeGeneric);
+
+ mqtt_reason_code_id = DetectBufferTypeGetByName("mqtt.reason_code");
+}
+
+static int DetectEngineInspectMQTTReasonCodeGeneric(ThreadVars *tv,
+ DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const Signature *s, const SigMatchData *smd,
+ Flow *f, uint8_t flags, void *alstate,
+ void *txv, uint64_t tx_id)
+{
+ return DetectEngineInspectGenericList(tv, de_ctx, det_ctx, s, smd,
+ f, flags, alstate, txv, tx_id);
+}
+
+/**
+ * \internal
+ * \brief Function to match reason code of an MQTT 5.0 Tx
+ *
+ * \param det_ctx Pointer to the pattern matcher thread.
+ * \param f Pointer to the current flow.
+ * \param flags Flags.
+ * \param state App layer state.
+ * \param txv Pointer to the transaction.
+ * \param s Pointer to the Signature.
+ * \param ctx Pointer to the sigmatch that we will cast into DetectMQTTReasonCodeData.
+ *
+ * \retval 0 no match.
+ * \retval 1 match.
+ */
+static int DetectMQTTReasonCodeMatch(DetectEngineThreadCtx *det_ctx,
+ Flow *f, uint8_t flags, void *state,
+ void *txv, const Signature *s,
+ const SigMatchCtx *ctx)
+{
+ const uint8_t *de = (const uint8_t *)ctx;
+ uint8_t code;
+
+ if (!de)
+ return 0;
+
+ if (rs_mqtt_tx_get_reason_code(txv, &code) == 0) {
+ /* this function does not return a code that needs to be compared,
+ so we can just return the result of the check implemented in
+ Rust */
+ return rs_mqtt_tx_unsuback_has_reason_code(txv, *de);
+ } else {
+ if (code == *de)
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * \internal
+ * \brief This function is used to parse options passed via mqtt.reason_code: keyword
+ *
+ * \param rawstr Pointer to the user provided options
+ *
+ * \retval de pointer to DetectMQTTReasonCodeData on success
+ * \retval NULL on failure
+ */
+static uint8_t *DetectMQTTReasonCodeParse(const char *rawstr)
+{
+ uint8_t *de = NULL;
+ int ret = 0;
+ uint8_t val;
+
+ ret = StringParseUint8(&val, 10, 0, rawstr);
+ if (ret < 0) {
+ SCLogError(SC_ERR_UNKNOWN_VALUE, "invalid MQTT reason code: %s", rawstr);
+ return NULL;
+ }
+
+ de = SCMalloc(sizeof(uint8_t));
+ if (unlikely(de == NULL))
+ return NULL;
+ *de = (uint8_t) val;
+
+ return de;
+}
+
+/**
+ * \internal
+ * \brief this function is used to add the parsed sigmatch into the current signature
+ *
+ * \param de_ctx pointer to the Detection Engine Context
+ * \param s pointer to the Current Signature
+ * \param rawstr pointer to the user provided options
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+static int DetectMQTTReasonCodeSetup (DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
+{
+ uint8_t *de = NULL;
+ SigMatch *sm = NULL;
+
+ if (DetectSignatureSetAppProto(s, ALPROTO_MQTT) < 0)
+ return -1;
+
+ de = DetectMQTTReasonCodeParse(rawstr);
+ if (de == NULL)
+ goto error;
+
+ sm = SigMatchAlloc();
+ if (sm == NULL)
+ goto error;
+
+ sm->type = DETECT_AL_MQTT_REASON_CODE;
+ sm->ctx = (SigMatchCtx *)de;
+
+ SigMatchAppendSMToList(s, sm, mqtt_reason_code_id);
+
+ return 0;
+
+error:
+ if (de != NULL)
+ SCFree(de);
+ if (sm != NULL)
+ SCFree(sm);
+ return -1;
+}
+
+/**
+ * \internal
+ * \brief this function will free memory associated with DetectMQTTReasonCodeData
+ *
+ * \param de pointer to DetectMQTTReasonCodeData
+ */
+void DetectMQTTReasonCodeFree(DetectEngineCtx *de_ctx, void *de_ptr)
+{
+ if (de_ptr != NULL)
+ SCFree(de_ptr);
+}
+
+/*
+ * ONLY TESTS BELOW THIS COMMENT
+ */
+
+#ifdef UNITTESTS
+/**
+ * \test MQTTReasonCodeTestParse01 is a test for a valid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTReasonCodeTestParse01 (void)
+{
+ uint8_t *de = NULL;
+
+ de = DetectMQTTReasonCodeParse("3");
+ FAIL_IF_NULL(de);
+ FAIL_IF_NOT(*de == 3);
+ DetectMQTTReasonCodeFree(NULL, de);
+
+ de = DetectMQTTReasonCodeParse(" 4");
+ FAIL_IF_NULL(de);
+ FAIL_IF_NOT(*de == 4);
+ DetectMQTTReasonCodeFree(NULL, de);
+
+ de = DetectMQTTReasonCodeParse(" 5");
+ FAIL_IF_NULL(de);
+ FAIL_IF_NOT(*de == 5);
+ DetectMQTTReasonCodeFree(NULL, de);
+
+ de = DetectMQTTReasonCodeParse("255");
+ FAIL_IF_NULL(de);
+ FAIL_IF_NOT(*de == 255);
+ DetectMQTTReasonCodeFree(NULL, de);
+
+ PASS;
+}
+
+/**
+ * \test MQTTReasonCodeTestParse02 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTReasonCodeTestParse02 (void)
+{
+ uint8_t *de = NULL;
+ de = DetectMQTTReasonCodeParse("6X");
+ if (de) {
+ DetectMQTTReasonCodeFree(NULL, de);
+ FAIL;
+ }
+
+ PASS;
+}
+
+/**
+ * \test MQTTReasonCodeTestParse03 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTReasonCodeTestParse03 (void)
+{
+ uint8_t *de = NULL;
+ de = DetectMQTTReasonCodeParse("");
+ if (de) {
+ DetectMQTTReasonCodeFree(NULL, de);
+ FAIL;
+ }
+
+ PASS;
+}
+
+/**
+ * \test MQTTReasonCodeTestParse04 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTReasonCodeTestParse04 (void)
+{
+ uint8_t *de = NULL;
+ de = DetectMQTTReasonCodeParse("256");
+ if (de) {
+ DetectMQTTReasonCodeFree(NULL, de);
+ FAIL;
+ }
+
+ PASS;
+}
+
+
+
+#endif /* UNITTESTS */
+
+/**
+ * \brief this function registers unit tests for MQTTReasonCode
+ */
+void MQTTReasonCodeRegisterTests(void)
+{
+#ifdef UNITTESTS
+ UtRegisterTest("MQTTReasonCodeTestParse01", MQTTReasonCodeTestParse01);
+ UtRegisterTest("MQTTReasonCodeTestParse02", MQTTReasonCodeTestParse02);
+ UtRegisterTest("MQTTReasonCodeTestParse03", MQTTReasonCodeTestParse03);
+ UtRegisterTest("MQTTReasonCodeTestParse04", MQTTReasonCodeTestParse04);
+#endif /* UNITTESTS */
+}
\ No newline at end of file
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __DETECT_MQTT_REASON_CODE_H__
+#define __DETECT_MQTT_REASON_CODE_H__
+
+void DetectMQTTReasonCodeRegister(void);
+
+#endif /* __DETECT_MQTT_REASON_CODE_H__ */
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#include "suricata-common.h"
+
+#include "app-layer.h"
+#include "app-layer-parser.h"
+
+#include "conf.h"
+#include "debug.h"
+#include "decode.h"
+#include "detect.h"
+#include "detect-content.h"
+#include "detect-parse.h"
+#include "detect-pcre.h"
+#include "detect-engine.h"
+#include "detect-engine-content-inspection.h"
+#include "detect-engine-mpm.h"
+#include "detect-engine-prefilter.h"
+#include "detect-mqtt-subscribe-topic.h"
+#include "util-unittest.h"
+#include "util-unittest-helper.h"
+
+#include "rust-bindings.h"
+
+#include "threads.h"
+
+#include "flow.h"
+#include "flow-util.h"
+#include "flow-var.h"
+
+#include "util-debug.h"
+#include "util-unittest.h"
+#include "util-spm.h"
+#include "util-print.h"
+
+static int DetectMQTTSubscribeTopicSetup(DetectEngineCtx *, Signature *, const char *);
+
+static int g_mqtt_subscribe_topic_buffer_id = 0;
+
+struct MQTTSubscribeTopicGetDataArgs {
+ int local_id;
+ void *txv;
+};
+
+static InspectionBuffer *MQTTSubscribeTopicGetData(DetectEngineThreadCtx *det_ctx,
+ const DetectEngineTransforms *transforms,
+ Flow *f, struct MQTTSubscribeTopicGetDataArgs *cbdata, int list_id, bool first)
+{
+ SCEnter();
+
+ InspectionBufferMultipleForList *fb = InspectionBufferGetMulti(det_ctx, list_id);
+ InspectionBuffer *buffer = InspectionBufferMultipleForListGet(fb, cbdata->local_id);
+ if (buffer == NULL)
+ return NULL;
+ if (!first && buffer->inspect != NULL)
+ return buffer;
+
+ const uint8_t *data;
+ uint32_t data_len;
+ if (rs_mqtt_tx_get_subscribe_topic(cbdata->txv, (uint16_t)cbdata->local_id,
+ &data, &data_len) == 0) {
+ return NULL;
+ }
+
+ InspectionBufferSetup(buffer, data, data_len);
+ InspectionBufferApplyTransforms(buffer, transforms);
+
+ SCReturnPtr(buffer, "InspectionBuffer");
+}
+
+static int DetectEngineInspectMQTTSubscribeTopic(
+ DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const DetectEngineAppInspectionEngine *engine,
+ const Signature *s,
+ Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id)
+{
+ int local_id = 0;
+
+ const DetectEngineTransforms *transforms = NULL;
+ if (!engine->mpm) {
+ transforms = engine->v2.transforms;
+ }
+
+ while(1) {
+ struct MQTTSubscribeTopicGetDataArgs cbdata = { local_id, txv, };
+ InspectionBuffer *buffer = MQTTSubscribeTopicGetData(det_ctx,
+ transforms, f, &cbdata, engine->sm_list, false);
+ if (buffer == NULL || buffer->inspect == NULL)
+ break;
+
+ det_ctx->buffer_offset = 0;
+ det_ctx->discontinue_matching = 0;
+ det_ctx->inspection_recursion_counter = 0;
+
+ const int match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd,
+ NULL, f,
+ (uint8_t *)buffer->inspect,
+ buffer->inspect_len,
+ buffer->inspect_offset, DETECT_CI_FLAGS_SINGLE,
+ DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE);
+ if (match == 1) {
+ return DETECT_ENGINE_INSPECT_SIG_MATCH;
+ }
+ local_id++;
+ }
+ return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
+}
+
+typedef struct PrefilterMpmMQTTSubscribeTopic {
+ int list_id;
+ const MpmCtx *mpm_ctx;
+ const DetectEngineTransforms *transforms;
+} PrefilterMpmMQTTSubscribeTopic;
+
+/** \brief MQTTSubscribeTopic MQTTSubscribeTopic Mpm prefilter callback
+ *
+ * \param det_ctx detection engine thread ctx
+ * \param p packet to inspect
+ * \param f flow to inspect
+ * \param txv tx to inspect
+ * \param pectx inspection context
+ */
+static void PrefilterTxMQTTSubscribeTopic(DetectEngineThreadCtx *det_ctx,
+ const void *pectx,
+ Packet *p, Flow *f, void *txv,
+ const uint64_t idx, const uint8_t flags)
+{
+ SCEnter();
+
+ const PrefilterMpmMQTTSubscribeTopic *ctx = (const PrefilterMpmMQTTSubscribeTopic *)pectx;
+ const MpmCtx *mpm_ctx = ctx->mpm_ctx;
+ const int list_id = ctx->list_id;
+
+ int local_id = 0;
+ while(1) {
+ struct MQTTSubscribeTopicGetDataArgs cbdata = { local_id, txv };
+ InspectionBuffer *buffer = MQTTSubscribeTopicGetData(det_ctx, ctx->transforms,
+ f, &cbdata, list_id, true);
+ if (buffer == NULL)
+ break;
+
+ if (buffer->inspect_len >= mpm_ctx->minlen) {
+ (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx,
+ &det_ctx->mtcu, &det_ctx->pmq,
+ buffer->inspect, buffer->inspect_len);
+ }
+ local_id++;
+ }
+}
+
+static void PrefilterMpmMQTTSubscribeTopicFree(void *ptr)
+{
+ if (ptr != NULL)
+ SCFree(ptr);
+}
+
+static int PrefilterMpmMQTTSubscribeTopicRegister(DetectEngineCtx *de_ctx,
+ SigGroupHead *sgh, MpmCtx *mpm_ctx,
+ const DetectBufferMpmRegistery *mpm_reg, int list_id)
+{
+ PrefilterMpmMQTTSubscribeTopic *pectx = SCCalloc(1, sizeof(*pectx));
+ if (pectx == NULL)
+ return -1;
+ pectx->list_id = list_id;
+ pectx->mpm_ctx = mpm_ctx;
+ pectx->transforms = &mpm_reg->transforms;
+
+ return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxMQTTSubscribeTopic,
+ mpm_reg->app_v2.alproto, mpm_reg->app_v2.tx_min_progress,
+ pectx, PrefilterMpmMQTTSubscribeTopicFree, mpm_reg->pname);
+}
+
+/**
+ * \brief Registration function for keyword: mqtt.subscribe.topic
+ */
+void DetectMQTTSubscribeTopicRegister (void)
+{
+ sigmatch_table[DETECT_AL_MQTT_SUBSCRIBE_TOPIC].name = "mqtt.subscribe.topic";
+ sigmatch_table[DETECT_AL_MQTT_SUBSCRIBE_TOPIC].desc = "sticky buffer to match MQTT SUBSCRIBE topic";
+ sigmatch_table[DETECT_AL_MQTT_SUBSCRIBE_TOPIC].url = "/rules/mqtt-keywords.html#mqtt-subscribe-topic";
+ sigmatch_table[DETECT_AL_MQTT_SUBSCRIBE_TOPIC].Setup = DetectMQTTSubscribeTopicSetup;
+ sigmatch_table[DETECT_AL_MQTT_SUBSCRIBE_TOPIC].flags |= SIGMATCH_NOOPT;
+ sigmatch_table[DETECT_AL_MQTT_SUBSCRIBE_TOPIC].flags |= SIGMATCH_INFO_STICKY_BUFFER;
+
+
+ DetectAppLayerMpmRegister2("mqtt.subscribe.topic", SIG_FLAG_TOSERVER, 1,
+ PrefilterMpmMQTTSubscribeTopicRegister, NULL,
+ ALPROTO_MQTT, 1);
+
+ DetectAppLayerInspectEngineRegister2("mqtt.subscribe.topic",
+ ALPROTO_MQTT, SIG_FLAG_TOSERVER, 1,
+ DetectEngineInspectMQTTSubscribeTopic, NULL);
+
+ DetectBufferTypeSetDescriptionByName("mqtt.subscribe.topic",
+ "subscribe topic query");
+
+ g_mqtt_subscribe_topic_buffer_id = DetectBufferTypeGetByName("mqtt.subscribe.topic");
+}
+
+/**
+ * \brief setup the sticky buffer keyword used in the rule
+ *
+ * \param de_ctx Pointer to the Detection Engine Context
+ * \param s Pointer to the Signature to which the current keyword belongs
+ * \param str Should hold an empty string always
+ *
+ * \retval 0 On success
+ * \retval -1 On failure
+ */
+
+static int DetectMQTTSubscribeTopicSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str)
+{
+ if (DetectBufferSetActiveList(s, g_mqtt_subscribe_topic_buffer_id) < 0)
+ return -1;
+ if (DetectSignatureSetAppProto(s, ALPROTO_MQTT) < 0)
+ return -1;
+ return 0;
+}
\ No newline at end of file
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __DETECT_MQTT_SUBSCRIBE_TOPIC_H__
+#define __DETECT_MQTT_SUBSCRIBE_TOPIC_H__
+
+void DetectMQTTSubscribeTopicRegister(void);
+
+#endif /* __DETECT_MQTT_SUBSCRIBE_TOPIC_H__ */
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#include "suricata-common.h"
+#include "conf.h"
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-content-inspection.h"
+#include "detect-mqtt-type.h"
+#include "util-unittest.h"
+
+#include "rust-bindings.h"
+
+static int mqtt_type_id = 0;
+
+static int DetectMQTTTypeMatch(DetectEngineThreadCtx *det_ctx,
+ Flow *f, uint8_t flags, void *state,
+ void *txv, const Signature *s,
+ const SigMatchCtx *ctx);
+static int DetectMQTTTypeSetup (DetectEngineCtx *, Signature *, const char *);
+void MQTTTypeRegisterTests(void);
+void DetectMQTTTypeFree(DetectEngineCtx *de_ctx, void *);
+
+static int DetectEngineInspectMQTTTypeGeneric(ThreadVars *tv,
+ DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const Signature *s, const SigMatchData *smd,
+ Flow *f, uint8_t flags, void *alstate,
+ void *txv, uint64_t tx_id);
+
+/**
+ * \brief Registration function for ipopts: keyword
+ */
+void DetectMQTTTypeRegister (void)
+{
+ sigmatch_table[DETECT_AL_MQTT_TYPE].name = "mqtt.type";
+ sigmatch_table[DETECT_AL_MQTT_TYPE].desc = "match MQTT control packet type";
+ sigmatch_table[DETECT_AL_MQTT_TYPE].url = "/rules/mqtt-keywords.html#mqtt-type";
+ sigmatch_table[DETECT_AL_MQTT_TYPE].AppLayerTxMatch = DetectMQTTTypeMatch;
+ sigmatch_table[DETECT_AL_MQTT_TYPE].Setup = DetectMQTTTypeSetup;
+ sigmatch_table[DETECT_AL_MQTT_TYPE].Free = DetectMQTTTypeFree;
+#ifdef UNITTESTS
+ sigmatch_table[DETECT_AL_MQTT_TYPE].RegisterTests = MQTTTypeRegisterTests;
+#endif
+
+ DetectAppLayerInspectEngineRegister("mqtt.type",
+ ALPROTO_MQTT, SIG_FLAG_TOSERVER, 1,
+ DetectEngineInspectMQTTTypeGeneric);
+
+ mqtt_type_id = DetectBufferTypeGetByName("mqtt.type");
+}
+
+static int DetectEngineInspectMQTTTypeGeneric(ThreadVars *tv,
+ DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const Signature *s, const SigMatchData *smd,
+ Flow *f, uint8_t flags, void *alstate,
+ void *txv, uint64_t tx_id)
+{
+ return DetectEngineInspectGenericList(tv, de_ctx, det_ctx, s, smd,
+ f, flags, alstate, txv, tx_id);
+}
+
+/**
+ * \internal
+ * \brief Function to match control packet type of an MQTT Tx
+ *
+ * \param det_ctx Pointer to the pattern matcher thread.
+ * \param f Pointer to the current flow.
+ * \param flags Flags.
+ * \param state App layer state.
+ * \param txv Pointer to the transaction.
+ * \param s Pointer to the Signature.
+ * \param ctx Pointer to the sigmatch that we will cast into DetectMQTTTypeData.
+ *
+ * \retval 0 no match.
+ * \retval 1 match.
+ */
+static int DetectMQTTTypeMatch(DetectEngineThreadCtx *det_ctx,
+ Flow *f, uint8_t flags, void *state,
+ void *txv, const Signature *s,
+ const SigMatchCtx *ctx)
+{
+ const uint8_t *de = (const uint8_t *)ctx;
+
+ if (!de)
+ return 0;
+
+ return rs_mqtt_tx_has_type(txv, *de);
+}
+
+/**
+ * \internal
+ * \brief This function is used to parse options passed via mqtt.type: keyword
+ *
+ * \param rawstr Pointer to the user provided options
+ *
+ * \retval de pointer to DetectMQTTTypeData on success
+ * \retval NULL on failure
+ */
+static uint8_t *DetectMQTTTypeParse(const char *rawstr)
+{
+ uint8_t *de = NULL;
+ int ret = 0;
+
+ ret = rs_mqtt_cstr_message_code(rawstr);
+ // negative value denotes invalid input
+ if(ret < 0) {
+ SCLogError(SC_ERR_UNKNOWN_VALUE, "unknown mqtt.type value %s", rawstr);
+ goto error;
+ }
+
+ de = SCMalloc(sizeof(uint8_t));
+ if (unlikely(de == NULL))
+ goto error;
+
+ *de = (uint8_t) ret;
+
+ return de;
+
+error:
+ if (de != NULL)
+ SCFree(de);
+ return NULL;
+}
+
+/**
+ * \internal
+ * \brief this function is used to add the parsed type query into the current signature
+ *
+ * \param de_ctx pointer to the Detection Engine Context
+ * \param s pointer to the Current Signature
+ * \param rawstr pointer to the user provided options
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+static int DetectMQTTTypeSetup (DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
+{
+ uint8_t *de = NULL;
+ SigMatch *sm = NULL;
+
+ if (DetectSignatureSetAppProto(s, ALPROTO_MQTT) < 0)
+ return -1;
+
+ de = DetectMQTTTypeParse(rawstr);
+ if (de == NULL)
+ goto error;
+
+ sm = SigMatchAlloc();
+ if (sm == NULL)
+ goto error;
+
+ sm->type = DETECT_AL_MQTT_TYPE;
+ sm->ctx = (SigMatchCtx *)de;
+
+ SigMatchAppendSMToList(s, sm, mqtt_type_id);
+
+ return 0;
+
+error:
+ if (de != NULL)
+ SCFree(de);
+ if (sm != NULL)
+ SCFree(sm);
+ return -1;
+}
+
+/**
+ * \internal
+ * \brief this function will free memory associated with DetectMQTTTypeData
+ *
+ * \param de pointer to DetectMQTTTypeData
+ */
+void DetectMQTTTypeFree(DetectEngineCtx *de_ctx, void *de_ptr)
+{
+ if (de_ptr != NULL)
+ SCFree(de_ptr);
+}
+
+/*
+ * ONLY TESTS BELOW THIS COMMENT
+ */
+
+#ifdef UNITTESTS
+/**
+ * \test MQTTTypeTestParse01 is a test for a valid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTTypeTestParse01 (void)
+{
+ uint8_t *de = NULL;
+ de = DetectMQTTTypeParse("CONNECT");
+ FAIL_IF_NULL(de);
+ FAIL_IF_NOT(*de == 1);
+ DetectMQTTTypeFree(NULL, de);
+
+ de = DetectMQTTTypeParse("PINGRESP");
+ FAIL_IF_NULL(de);
+ FAIL_IF_NOT(*de == 13);
+ DetectMQTTTypeFree(NULL, de);
+
+ PASS;
+}
+
+/**
+ * \test MQTTTypeTestParse02 is a test for a valid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTTypeTestParse02 (void)
+{
+ uint8_t *de = NULL;
+ de = DetectMQTTTypeParse("auth");
+ FAIL_IF_NULL(de);
+ FAIL_IF_NOT(*de == 15);
+ DetectMQTTTypeFree(NULL, de);
+
+ PASS;
+}
+
+/**
+ * \test MQTTTypeTestParse03 is a test for an invalid value
+ *
+ * \retval 1 on success
+ * \retval 0 on failure
+ */
+static int MQTTTypeTestParse03 (void)
+{
+ uint8_t *de = NULL;
+ de = DetectMQTTTypeParse("invalidopt");
+ if (de) {
+ DetectMQTTTypeFree(NULL, de);
+ FAIL;
+ }
+
+ de = DetectMQTTTypeParse("unassigned");
+ if (de) {
+ DetectMQTTTypeFree(NULL, de);
+ FAIL;
+ }
+
+ PASS;
+}
+
+#endif /* UNITTESTS */
+
+/**
+ * \brief this function registers unit tests for MQTTType
+ */
+void MQTTTypeRegisterTests(void)
+{
+#ifdef UNITTESTS
+ UtRegisterTest("MQTTTypeTestParse01", MQTTTypeTestParse01);
+ UtRegisterTest("MQTTTypeTestParse02", MQTTTypeTestParse02);
+ UtRegisterTest("MQTTTypeTestParse03", MQTTTypeTestParse03);
+#endif /* UNITTESTS */
+}
\ No newline at end of file
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __DETECT_MQTT_TYPE_H__
+#define __DETECT_MQTT_TYPE_H__
+
+void DetectMQTTTypeRegister(void);
+
+#endif /* __DETECT_MQTT_TYPE_H__ */
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#include "suricata-common.h"
+
+#include "app-layer.h"
+#include "app-layer-parser.h"
+
+#include "conf.h"
+#include "debug.h"
+#include "decode.h"
+#include "detect.h"
+#include "detect-content.h"
+#include "detect-parse.h"
+#include "detect-pcre.h"
+#include "detect-engine.h"
+#include "detect-engine-content-inspection.h"
+#include "detect-engine-mpm.h"
+#include "detect-engine-prefilter.h"
+#include "detect-mqtt-unsubscribe-topic.h"
+#include "util-unittest.h"
+#include "util-unittest-helper.h"
+
+#include "rust-bindings.h"
+
+#include "threads.h"
+
+#include "flow.h"
+#include "flow-util.h"
+#include "flow-var.h"
+
+#include "util-debug.h"
+#include "util-unittest.h"
+#include "util-spm.h"
+#include "util-print.h"
+
+static int DetectMQTTUnsubscribeTopicSetup(DetectEngineCtx *, Signature *, const char *);
+
+static int g_mqtt_unsubscribe_topic_buffer_id = 0;
+
+struct MQTTUnsubscribeTopicGetDataArgs {
+ int local_id;
+ void *txv;
+};
+
+static InspectionBuffer *MQTTUnsubscribeTopicGetData(DetectEngineThreadCtx *det_ctx,
+ const DetectEngineTransforms *transforms,
+ Flow *f, struct MQTTUnsubscribeTopicGetDataArgs *cbdata, int list_id, bool first)
+{
+ SCEnter();
+
+ InspectionBufferMultipleForList *fb = InspectionBufferGetMulti(det_ctx, list_id);
+ InspectionBuffer *buffer = InspectionBufferMultipleForListGet(fb, cbdata->local_id);
+ if (buffer == NULL)
+ return NULL;
+ if (!first && buffer->inspect != NULL)
+ return buffer;
+
+ const uint8_t *data;
+ uint32_t data_len;
+ if (rs_mqtt_tx_get_unsubscribe_topic(cbdata->txv, (uint16_t)cbdata->local_id,
+ &data, &data_len) == 0) {
+ return NULL;
+ }
+
+ InspectionBufferSetup(buffer, data, data_len);
+ InspectionBufferApplyTransforms(buffer, transforms);
+
+ SCReturnPtr(buffer, "InspectionBuffer");
+}
+
+static int DetectEngineInspectMQTTUnsubscribeTopic(
+ DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+ const DetectEngineAppInspectionEngine *engine,
+ const Signature *s,
+ Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id)
+{
+ int local_id = 0;
+
+ const DetectEngineTransforms *transforms = NULL;
+ if (!engine->mpm) {
+ transforms = engine->v2.transforms;
+ }
+
+ while(1) {
+ struct MQTTUnsubscribeTopicGetDataArgs cbdata = { local_id, txv, };
+ InspectionBuffer *buffer = MQTTUnsubscribeTopicGetData(det_ctx,
+ transforms, f, &cbdata, engine->sm_list, false);
+ if (buffer == NULL || buffer->inspect == NULL)
+ break;
+
+ det_ctx->buffer_offset = 0;
+ det_ctx->discontinue_matching = 0;
+ det_ctx->inspection_recursion_counter = 0;
+
+ const int match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd,
+ NULL, f,
+ (uint8_t *)buffer->inspect,
+ buffer->inspect_len,
+ buffer->inspect_offset, DETECT_CI_FLAGS_SINGLE,
+ DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE);
+ if (match == 1) {
+ return DETECT_ENGINE_INSPECT_SIG_MATCH;
+ }
+ local_id++;
+ }
+ return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
+}
+
+typedef struct PrefilterMpmMQTTUnsubscribeTopic {
+ int list_id;
+ const MpmCtx *mpm_ctx;
+ const DetectEngineTransforms *transforms;
+} PrefilterMpmMQTTUnsubscribeTopic;
+
+/** \brief MQTTUnsubscribeTopic MQTTUnsubscribeTopic Mpm prefilter callback
+ *
+ * \param det_ctx detection engine thread ctx
+ * \param p packet to inspect
+ * \param f flow to inspect
+ * \param txv tx to inspect
+ * \param pectx inspection context
+ */
+static void PrefilterTxMQTTUnsubscribeTopic(DetectEngineThreadCtx *det_ctx,
+ const void *pectx,
+ Packet *p, Flow *f, void *txv,
+ const uint64_t idx, const uint8_t flags)
+{
+ SCEnter();
+
+ const PrefilterMpmMQTTUnsubscribeTopic *ctx = (const PrefilterMpmMQTTUnsubscribeTopic *)pectx;
+ const MpmCtx *mpm_ctx = ctx->mpm_ctx;
+ const int list_id = ctx->list_id;
+
+ int local_id = 0;
+ while(1) {
+ struct MQTTUnsubscribeTopicGetDataArgs cbdata = { local_id, txv };
+ InspectionBuffer *buffer = MQTTUnsubscribeTopicGetData(det_ctx, ctx->transforms,
+ f, &cbdata, list_id, true);
+ if (buffer == NULL)
+ break;
+
+ if (buffer->inspect_len >= mpm_ctx->minlen) {
+ (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx,
+ &det_ctx->mtcu, &det_ctx->pmq,
+ buffer->inspect, buffer->inspect_len);
+ }
+ local_id++;
+ }
+}
+
+static void PrefilterMpmMQTTUnsubscribeTopicFree(void *ptr)
+{
+ if (ptr != NULL)
+ SCFree(ptr);
+}
+
+static int PrefilterMpmMQTTUnsubscribeTopicRegister(DetectEngineCtx *de_ctx,
+ SigGroupHead *sgh, MpmCtx *mpm_ctx,
+ const DetectBufferMpmRegistery *mpm_reg, int list_id)
+{
+ PrefilterMpmMQTTUnsubscribeTopic *pectx = SCCalloc(1, sizeof(*pectx));
+ if (pectx == NULL)
+ return -1;
+ pectx->list_id = list_id;
+ pectx->mpm_ctx = mpm_ctx;
+ pectx->transforms = &mpm_reg->transforms;
+
+ return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxMQTTUnsubscribeTopic,
+ mpm_reg->app_v2.alproto, mpm_reg->app_v2.tx_min_progress,
+ pectx, PrefilterMpmMQTTUnsubscribeTopicFree, mpm_reg->pname);
+}
+
+/**
+ * \brief Registration function for keyword: mqtt.unsubscribe.topic
+ */
+void DetectMQTTUnsubscribeTopicRegister (void)
+{
+ sigmatch_table[DETECT_AL_MQTT_UNSUBSCRIBE_TOPIC].name = "mqtt.unsubscribe.topic";
+ sigmatch_table[DETECT_AL_MQTT_UNSUBSCRIBE_TOPIC].desc = "sticky buffer to match MQTT UNSUBSCRIBE topic";
+ sigmatch_table[DETECT_AL_MQTT_UNSUBSCRIBE_TOPIC].url = "/rules/mqtt-keywords.html#mqtt-unsubscribe-topic";
+ sigmatch_table[DETECT_AL_MQTT_UNSUBSCRIBE_TOPIC].Setup = DetectMQTTUnsubscribeTopicSetup;
+ sigmatch_table[DETECT_AL_MQTT_UNSUBSCRIBE_TOPIC].flags |= SIGMATCH_NOOPT;
+ sigmatch_table[DETECT_AL_MQTT_UNSUBSCRIBE_TOPIC].flags |= SIGMATCH_INFO_STICKY_BUFFER;
+
+
+ DetectAppLayerMpmRegister2("mqtt.unsubscribe.topic", SIG_FLAG_TOSERVER, 1,
+ PrefilterMpmMQTTUnsubscribeTopicRegister, NULL,
+ ALPROTO_MQTT, 1);
+
+ DetectAppLayerInspectEngineRegister2("mqtt.unsubscribe.topic",
+ ALPROTO_MQTT, SIG_FLAG_TOSERVER, 1,
+ DetectEngineInspectMQTTUnsubscribeTopic, NULL);
+
+ DetectBufferTypeSetDescriptionByName("mqtt.unsubscribe.topic",
+ "unsubscribe topic query");
+
+ g_mqtt_unsubscribe_topic_buffer_id = DetectBufferTypeGetByName("mqtt.unsubscribe.topic");
+}
+
+/**
+ * \brief setup the sticky buffer keyword used in the rule
+ *
+ * \param de_ctx Pointer to the Detection Engine Context
+ * \param s Pointer to the Signature to which the current keyword belongs
+ * \param str Should hold an empty string always
+ *
+ * \retval 0 On success
+ * \retval -1 On failure
+ */
+
+static int DetectMQTTUnsubscribeTopicSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str)
+{
+ if (DetectBufferSetActiveList(s, g_mqtt_unsubscribe_topic_buffer_id) < 0)
+ return -1;
+ if (DetectSignatureSetAppProto(s, ALPROTO_MQTT) < 0)
+ return -1;
+ return 0;
+}
\ No newline at end of file
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __DETECT_MQTT_UNSUBSCRIBE_TOPIC_H__
+#define __DETECT_MQTT_UNSUBSCRIBE_TOPIC_H__
+
+void DetectMQTTUnsubscribeTopicRegister(void);
+
+#endif /* __DETECT_MQTT_UNSUBSCRIBE_TOPIC_H__ */
#include "output-json-flow.h"
#include "output-json-sip.h"
#include "output-json-rfb.h"
+#include "output-json-mqtt.h"
#include "util-byte.h"
#include "util-privs.h"
case ALPROTO_DNS:
AlertJsonDns(p->flow, tx_id, jb);
break;
+ case ALPROTO_MQTT:
+ jb_get_mark(jb, &mark);
+ if (!JsonMQTTAddMetadata(p->flow, tx_id, jb)) {
+ jb_restore_mark(jb, &mark);
+ }
+ break;
default:
break;
}
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#include "suricata-common.h"
+#include "debug.h"
+#include "detect.h"
+#include "pkt-var.h"
+#include "conf.h"
+
+#include "threads.h"
+#include "threadvars.h"
+#include "tm-threads.h"
+
+#include "util-unittest.h"
+#include "util-buffer.h"
+#include "util-debug.h"
+#include "util-byte.h"
+
+#include "output.h"
+#include "output-json.h"
+
+#include "app-layer.h"
+#include "app-layer-parser.h"
+
+#include "app-layer-mqtt.h"
+#include "output-json-mqtt.h"
+#include "rust.h"
+
+#define MQTT_LOG_PASSWORDS BIT_U32(0)
+#define MQTT_DEFAULTS (MQTT_LOG_PASSWORDS)
+
+typedef struct LogMQTTFileCtx_ {
+ LogFileCtx *file_ctx;
+ uint32_t flags;
+} LogMQTTFileCtx;
+
+typedef struct LogMQTTLogThread_ {
+ LogMQTTFileCtx *mqttlog_ctx;
+ uint32_t count;
+ MemBuffer *buffer;
+} LogMQTTLogThread;
+
+bool JsonMQTTAddMetadata(const Flow *f, uint64_t tx_id, JsonBuilder *js)
+{
+ MQTTState *state = FlowGetAppState(f);
+ if (state) {
+ MQTTTransaction *tx = AppLayerParserGetTx(f->proto, ALPROTO_MQTT, state, tx_id);
+ if (tx) {
+ return rs_mqtt_logger_log(state, tx, MQTT_DEFAULTS, js);
+ }
+ }
+
+ return false;
+}
+
+static int JsonMQTTLogger(ThreadVars *tv, void *thread_data,
+ const Packet *p, Flow *f, void *state, void *tx, uint64_t tx_id)
+{
+ LogMQTTLogThread *thread = thread_data;
+ enum OutputJsonLogDirection dir;
+
+ if (rs_mqtt_tx_is_toclient((MQTTTransaction*) tx)) {
+ dir = LOG_DIR_FLOW_TOCLIENT;
+ } else {
+ dir = LOG_DIR_FLOW_TOSERVER;
+ }
+
+ JsonBuilder *js = CreateEveHeader(p, dir, "mqtt", NULL);
+ if (unlikely(js == NULL)) {
+ return TM_ECODE_FAILED;
+ }
+
+ if (!rs_mqtt_logger_log(state, tx, thread->mqttlog_ctx->flags, js))
+ goto error;
+
+ MemBufferReset(thread->buffer);
+ OutputJsonBuilderBuffer(js, thread->mqttlog_ctx->file_ctx, &thread->buffer);
+ jb_free(js);
+
+ return TM_ECODE_OK;
+
+error:
+ jb_free(js);
+ return TM_ECODE_FAILED;
+}
+
+static void OutputMQTTLogDeInitCtxSub(OutputCtx *output_ctx)
+{
+ LogMQTTFileCtx *mqttlog_ctx = (LogMQTTFileCtx *)output_ctx->data;
+ SCFree(mqttlog_ctx);
+ SCFree(output_ctx);
+}
+
+static void JsonMQTTLogParseConfig(ConfNode *conf, LogMQTTFileCtx *mqttlog_ctx)
+{
+ const char *query = ConfNodeLookupChildValue(conf, "passwords");
+ if (query != NULL) {
+ if (ConfValIsTrue(query)) {
+ mqttlog_ctx->flags |= MQTT_LOG_PASSWORDS;
+ } else {
+ mqttlog_ctx->flags &= ~MQTT_LOG_PASSWORDS;
+ }
+ } else {
+ mqttlog_ctx->flags |= MQTT_LOG_PASSWORDS;
+ }
+}
+
+static OutputInitResult OutputMQTTLogInitSub(ConfNode *conf,
+ OutputCtx *parent_ctx)
+{
+ OutputInitResult result = { NULL, false };
+ OutputJsonCtx *ajt = parent_ctx->data;
+
+ LogMQTTFileCtx *mqttlog_ctx = SCCalloc(1, sizeof(*mqttlog_ctx));
+ if (unlikely(mqttlog_ctx == NULL)) {
+ return result;
+ }
+ mqttlog_ctx->file_ctx = ajt->file_ctx;
+
+ OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx));
+ if (unlikely(output_ctx == NULL)) {
+ SCFree(mqttlog_ctx);
+ return result;
+ }
+ output_ctx->data = mqttlog_ctx;
+ output_ctx->DeInit = OutputMQTTLogDeInitCtxSub;
+
+ JsonMQTTLogParseConfig(conf, mqttlog_ctx);
+
+ AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_MQTT);
+
+ result.ctx = output_ctx;
+ result.ok = true;
+ return result;
+}
+
+static TmEcode JsonMQTTLogThreadInit(ThreadVars *t, const void *initdata, void **data)
+{
+ LogMQTTLogThread *thread = SCCalloc(1, sizeof(*thread));
+ if (unlikely(thread == NULL)) {
+ return TM_ECODE_FAILED;
+ }
+
+ if (initdata == NULL) {
+ SCLogDebug("Error getting context for EveLogMQTT. \"initdata\" is NULL.");
+ SCFree(thread);
+ return TM_ECODE_FAILED;
+ }
+
+ thread->buffer = MemBufferCreateNew(JSON_OUTPUT_BUFFER_SIZE);
+ if (unlikely(thread->buffer == NULL)) {
+ SCFree(thread);
+ return TM_ECODE_FAILED;
+ }
+
+ thread->mqttlog_ctx = ((OutputCtx *)initdata)->data;
+ *data = (void *)thread;
+
+ return TM_ECODE_OK;
+}
+
+static TmEcode JsonMQTTLogThreadDeinit(ThreadVars *t, void *data)
+{
+ LogMQTTLogThread *thread = (LogMQTTLogThread *)data;
+ if (thread == NULL) {
+ return TM_ECODE_OK;
+ }
+ if (thread->buffer != NULL) {
+ MemBufferFree(thread->buffer);
+ }
+ SCFree(thread);
+ return TM_ECODE_OK;
+}
+
+void JsonMQTTLogRegister(void)
+{
+ OutputRegisterTxSubModule(LOGGER_JSON_MQTT, "eve-log",
+ "JsonMQTTLog", "eve-log.mqtt",
+ OutputMQTTLogInitSub, ALPROTO_MQTT, JsonMQTTLogger,
+ JsonMQTTLogThreadInit, JsonMQTTLogThreadDeinit, NULL);
+}
--- /dev/null
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Sascha Steinbiss <sascha@steinbiss.name>
+ */
+
+#ifndef __OUTPUT_JSON_MQTT_H__
+#define __OUTPUT_JSON_MQTT_H__
+
+void JsonMQTTLogRegister(void);
+bool JsonMQTTAddMetadata(const Flow *f, uint64_t tx_id, JsonBuilder *js);
+
+#endif /* __OUTPUT_JSON_MQTT_H__ */
#include "output-json-snmp.h"
#include "output-json-sip.h"
#include "output-json-rfb.h"
+#include "output-json-mqtt.h"
#include "output-json-template.h"
#include "output-json-template-rust.h"
#include "output-json-rdp.h"
JsonSIPLogRegister();
/* RFB JSON logger. */
JsonRFBLogRegister();
+ /* MQTT JSON logger. */
+ JsonMQTTLogRegister();
/* Template JSON logger. */
JsonTemplateLogRegister();
/* Template Rust JSON logger. */
LOGGER_JSON_SIP,
LOGGER_JSON_TEMPLATE_RUST,
LOGGER_JSON_RFB,
+ LOGGER_JSON_MQTT,
LOGGER_JSON_TEMPLATE,
LOGGER_JSON_RDP,
LOGGER_JSON_DCERPC,
CASE_CODE (LOGGER_JSON_SIP);
CASE_CODE (LOGGER_JSON_TEMPLATE_RUST);
CASE_CODE (LOGGER_JSON_RFB);
+ CASE_CODE (LOGGER_JSON_MQTT);
CASE_CODE (LOGGER_JSON_TEMPLATE);
CASE_CODE (LOGGER_JSON_RDP);
CASE_CODE (LOGGER_JSON_DCERPC);
header: X-Forwarded-For
types:
+ - mqtt:
+ # passwords: yes # enable output of passwords
- alert:
# payload: yes # enable dumping payload in Base64
# payload-buffer-size: 4kb # max size of payload buffer to output in eve-log
enabled: yes
detection-ports:
dp: 5900, 5901, 5902, 5903, 5904, 5905, 5906, 5907, 5908, 5909
+ # MQTT, disabled by default.
+ mqtt:
+ # enabled: no
+ # max-msg-length: 1mb
krb5:
enabled: yes
snmp: