Coverage Report

Created: 2026-11-12 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata8/rust/src/mqtt/logger.rs
Line
Count
Source
1
/* Copyright (C) 2020 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17
18
// written by Sascha Steinbiss <sascha@steinbiss.name>
19
20
use super::mqtt::MQTTTransaction;
21
use crate::jsonbuilder::{JsonBuilder, JsonError};
22
use crate::mqtt::mqtt_message::{MQTTOperation, MQTTSubscribeTopicData};
23
use crate::mqtt::mqtt_property::MQTTProperty;
24
use crate::mqtt::parser::FixedHeader;
25
use std;
26
27
pub const MQTT_LOG_PASSWORDS: u32 = BIT_U32!(0);
28
29
#[inline]
30
0
fn log_mqtt_topic(
31
0
    js: &mut JsonBuilder, t: &MQTTSubscribeTopicData, max_log_len: usize,
32
0
) -> Result<(), JsonError> {
33
0
    js.start_object()?;
34
0
    js.set_string_limited("topic", &t.topic_name, max_log_len)?;
35
0
    js.set_uint("qos", t.qos as u64)?;
36
0
    js.close()?;
37
0
    return Ok(());
38
0
}
39
40
#[inline]
41
0
fn log_mqtt_header(js: &mut JsonBuilder, hdr: &FixedHeader) -> Result<(), JsonError> {
42
0
    js.set_uint("qos", hdr.qos_level as u64)?;
43
0
    js.set_bool("retain", hdr.retain)?;
44
0
    js.set_bool("dup", hdr.dup_flag)?;
45
0
    return Ok(());
46
0
}
47
48
0
fn log_mqtt_user_properties(
49
0
    js: &mut JsonBuilder, props: &[MQTTProperty], max_log_len: usize,
50
0
) -> Result<(), JsonError> {
51
0
    let mut open = false;
52
0
    for prop in props {
53
0
        if let MQTTProperty::USER_PROPERTY((k, v)) = prop {
54
0
            if !open {
55
0
                js.open_array("user_properties")?;
56
0
                open = true;
57
0
            }
58
0
            js.start_object()?;
59
0
            js.set_string_limited("key", k, max_log_len)?;
60
0
            js.set_string_limited("value", v, max_log_len)?;
61
0
            js.close()?;
62
0
        }
63
    }
64
0
    if open {
65
0
        js.close()?; // user_properties
66
0
    }
67
0
    return Ok(());
68
0
}
69
70
// Logs a list of properties as a "properties" object. User properties are
71
// logged separately, as an array of [key, value] pairs, since their keys are
72
// peer-controlled and can occur more than once.
73
#[inline]
74
0
fn log_mqtt_properties(
75
0
    js: &mut JsonBuilder, props: &[MQTTProperty], max_log_len: usize,
76
0
) -> Result<(), JsonError> {
77
0
    js.open_object("properties")?;
78
0
    for prop in props {
79
0
        prop.set_json(js, max_log_len)?;
80
    }
81
0
    log_mqtt_user_properties(js, props, max_log_len)?;
82
0
    js.close()?; // properties
83
0
    return Ok(());
84
0
}
85
86
15
fn log_mqtt(
87
15
    tx: &MQTTTransaction, flags: u32, max_log_len: usize, js: &mut JsonBuilder,
88
15
) -> Result<(), JsonError> {
89
15
    js.open_object("mqtt")?;
90
15
    for msg in tx.msg.iter() {
91
15
        match msg.op {
92
0
            MQTTOperation::CONNECT(ref conn) => {
93
0
                js.open_object("connect")?;
94
0
                log_mqtt_header(js, &msg.header)?;
95
0
                js.set_string("protocol_string", &conn.protocol_string)?;
96
0
                js.set_uint("protocol_version", conn.protocol_version as u64)?;
97
0
                js.set_string_limited("client_id", &conn.client_id, max_log_len)?;
98
0
                js.open_object("flags")?;
99
0
                js.set_bool("username", conn.username_flag)?;
100
0
                js.set_bool("password", conn.password_flag)?;
101
0
                js.set_bool("will_retain", conn.will_retain)?;
102
0
                js.set_bool("will", conn.will_flag)?;
103
0
                js.set_bool("clean_session", conn.clean_session)?;
104
0
                js.close()?; // flags
105
0
                if let Some(user) = &conn.username {
106
0
                    js.set_string_limited("username", user, max_log_len)?;
107
0
                }
108
0
                if flags & MQTT_LOG_PASSWORDS != 0 {
109
0
                    if let Some(pass) = &conn.password {
110
0
                        js.set_string_from_bytes("password", pass)?;
111
0
                    }
112
0
                }
113
0
                if conn.will_flag {
114
0
                    js.open_object("will")?;
115
0
                    if let Some(will_topic) = &conn.will_topic {
116
0
                        js.set_string_limited("topic", will_topic, max_log_len)?;
117
0
                    }
118
0
                    if let Some(will_message) = &conn.will_message {
119
0
                        js.set_string_from_bytes_limited(
120
0
                            "message",
121
0
                            will_message,
122
0
                            max_log_len,
123
0
                        )?;
124
0
                    }
125
0
                    if let Some(will_properties) = &conn.will_properties {
126
0
                        log_mqtt_properties(js, will_properties, max_log_len)?;
127
0
                    }
128
0
                    js.close()?; // will
129
0
                }
130
0
                if let Some(properties) = &conn.properties {
131
0
                    log_mqtt_properties(js, properties, max_log_len)?;
132
0
                }
133
0
                js.close()?; // connect
134
            }
135
0
            MQTTOperation::CONNACK(ref connack) => {
136
0
                js.open_object("connack")?;
137
0
                log_mqtt_header(js, &msg.header)?;
138
0
                js.set_bool("session_present", connack.session_present)?;
139
0
                js.set_uint("return_code", connack.return_code as u64)?;
140
0
                if let Some(properties) = &connack.properties {
141
0
                    log_mqtt_properties(js, properties, max_log_len)?;
142
0
                }
143
0
                js.close()?; // connack
144
            }
145
0
            MQTTOperation::PUBLISH(ref publish) => {
146
0
                js.open_object("publish")?;
147
0
                log_mqtt_header(js, &msg.header)?;
148
0
                js.set_string_limited("topic", &publish.topic, max_log_len)?;
149
0
                if let Some(message_id) = publish.message_id {
150
0
                    js.set_uint("message_id", message_id as u64)?;
151
0
                }
152
0
                js.set_string_from_bytes_limited(
153
0
                    "message",
154
0
                    &publish.message,
155
0
                    max_log_len,
156
0
                )?;
157
0
                if let Some(properties) = &publish.properties {
158
0
                    log_mqtt_properties(js, properties, max_log_len)?;
159
0
                }
160
0
                js.close()?; // publish
161
            }
162
0
            MQTTOperation::PUBACK(ref msgidonly) => {
163
0
                js.open_object("puback")?;
164
0
                log_mqtt_header(js, &msg.header)?;
165
0
                js.set_uint("message_id", msgidonly.message_id as u64)?;
166
0
                if let Some(reason_code) = &msgidonly.reason_code {
167
0
                    js.set_uint("reason_code", *reason_code as u64)?;
168
0
                }
169
0
                if let Some(properties) = &msgidonly.properties {
170
0
                    log_mqtt_properties(js, properties, max_log_len)?;
171
0
                }
172
0
                js.close()?; // puback
173
            }
174
0
            MQTTOperation::PUBREC(ref msgidonly) => {
175
0
                js.open_object("pubrec")?;
176
0
                log_mqtt_header(js, &msg.header)?;
177
0
                js.set_uint("message_id", msgidonly.message_id as u64)?;
178
0
                if let Some(reason_code) = &msgidonly.reason_code {
179
0
                    js.set_uint("reason_code", *reason_code as u64)?;
180
0
                }
181
0
                if let Some(properties) = &msgidonly.properties {
182
0
                    log_mqtt_properties(js, properties, max_log_len)?;
183
0
                }
184
0
                js.close()?; // pubrec
185
            }
186
0
            MQTTOperation::PUBREL(ref msgidonly) => {
187
0
                js.open_object("pubrel")?;
188
0
                log_mqtt_header(js, &msg.header)?;
189
0
                js.set_uint("message_id", msgidonly.message_id as u64)?;
190
0
                if let Some(reason_code) = &msgidonly.reason_code {
191
0
                    js.set_uint("reason_code", *reason_code as u64)?;
192
0
                }
193
0
                if let Some(properties) = &msgidonly.properties {
194
0
                    log_mqtt_properties(js, properties, max_log_len)?;
195
0
                }
196
0
                js.close()?; // pubrel
197
            }
198
0
            MQTTOperation::PUBCOMP(ref msgidonly) => {
199
0
                js.open_object("pubcomp")?;
200
0
                log_mqtt_header(js, &msg.header)?;
201
0
                js.set_uint("message_id", msgidonly.message_id as u64)?;
202
0
                if let Some(reason_code) = &msgidonly.reason_code {
203
0
                    js.set_uint("reason_code", *reason_code as u64)?;
204
0
                }
205
0
                if let Some(properties) = &msgidonly.properties {
206
0
                    log_mqtt_properties(js, properties, max_log_len)?;
207
0
                }
208
0
                js.close()?; // pubcomp
209
            }
210
0
            MQTTOperation::SUBSCRIBE(ref subs) => {
211
0
                js.open_object("subscribe")?;
212
0
                log_mqtt_header(js, &msg.header)?;
213
0
                js.set_uint("message_id", subs.message_id as u64)?;
214
0
                js.open_array("topics")?;
215
0
                let mut topic_chars : usize = 0;
216
                // We only log topics until the length of the concatenation of
217
                // the topic names exceed the log limit set.
218
0
                for t in &subs.topics {
219
0
                    if topic_chars + std::cmp::min(t.topic_name.len(), max_log_len) > max_log_len {
220
0
                        log_mqtt_topic(js, t, max_log_len - topic_chars)?;
221
0
                        break;
222
0
                    }
223
0
                    log_mqtt_topic(js, t, max_log_len)?;
224
0
                    topic_chars += std::cmp::min(t.topic_name.len(), max_log_len);
225
                }
226
0
                js.close()?; //topics
227
0
                if let Some(properties) = &subs.properties {
228
0
                    log_mqtt_properties(js, properties, max_log_len)?;
229
0
                }
230
0
                js.close()?; // subscribe
231
            }
232
0
            MQTTOperation::SUBACK(ref suback) => {
233
0
                js.open_object("suback")?;
234
0
                log_mqtt_header(js, &msg.header)?;
235
0
                js.set_uint("message_id", suback.message_id as u64)?;
236
0
                js.open_array("qos_granted")?;
237
0
                for t in &suback.qoss {
238
0
                    js.append_uint(*t as u64)?;
239
                }
240
0
                js.close()?; // qos_granted
241
0
                js.close()?; // suback
242
            }
243
0
            MQTTOperation::UNSUBSCRIBE(ref unsub) => {
244
0
                js.open_object("unsubscribe")?;
245
0
                log_mqtt_header(js, &msg.header)?;
246
0
                js.set_uint("message_id", unsub.message_id as u64)?;
247
0
                js.open_array("topics")?;
248
0
                for t in &unsub.topics {
249
0
                    js.append_string(t)?;
250
                }
251
0
                js.close()?; // topics
252
0
                js.close()?; // unsubscribe
253
            }
254
0
            MQTTOperation::UNSUBACK(ref unsuback) => {
255
0
                js.open_object("unsuback")?;
256
0
                log_mqtt_header(js, &msg.header)?;
257
0
                js.set_uint("message_id", unsuback.message_id as u64)?;
258
0
                if let Some(codes) = &unsuback.reason_codes {
259
0
                    if !codes.is_empty() {
260
0
                        js.open_array("reason_codes")?;
261
0
                        for t in codes {
262
0
                            js.append_uint(*t as u64)?;
263
                        }
264
0
                        js.close()?; // reason_codes
265
0
                    }
266
0
                }
267
0
                js.close()?; // unsuback
268
            }
269
            MQTTOperation::PINGREQ => {
270
0
                js.open_object("pingreq")?;
271
0
                log_mqtt_header(js, &msg.header)?;
272
0
                js.close()?; // pingreq
273
            }
274
            MQTTOperation::PINGRESP => {
275
0
                js.open_object("pingresp")?;
276
0
                log_mqtt_header(js, &msg.header)?;
277
0
                js.close()?; // pingresp
278
            }
279
0
            MQTTOperation::AUTH(ref auth) => {
280
0
                js.open_object("auth")?;
281
0
                log_mqtt_header(js, &msg.header)?;
282
0
                js.set_uint("reason_code", auth.reason_code as u64)?;
283
0
                if let Some(properties) = &auth.properties {
284
0
                    log_mqtt_properties(js, properties, max_log_len)?;
285
0
                }
286
0
                js.close()?; // auth
287
            }
288
0
            MQTTOperation::DISCONNECT(ref disco) => {
289
0
                js.open_object("disconnect")?;
290
0
                log_mqtt_header(js, &msg.header)?;
291
0
                if let Some(reason_code) = &disco.reason_code {
292
0
                    js.set_uint("reason_code", *reason_code as u64)?;
293
0
                }
294
0
                if let Some(properties) = &disco.properties {
295
0
                    log_mqtt_properties(js, properties, max_log_len)?;
296
0
                }
297
0
                js.close()?; // disconnect
298
            }
299
0
            MQTTOperation::TRUNCATED(ref trunc) => {
300
0
                js.open_object(&trunc.original_message_type.to_lower_str())?;
301
0
                log_mqtt_header(js, &msg.header)?;
302
0
                js.set_bool("truncated", true)?;
303
0
                js.set_uint("skipped_length", trunc.skipped_length as u64)?;
304
0
                js.close()?; // truncated
305
            }
306
15
            MQTTOperation::UNASSIGNED => {}
307
        }
308
    }
309
15
    js.close()?; // mqtt
310
311
15
    return Ok(());
312
15
}
313
314
#[no_mangle]
315
15
pub unsafe extern "C" fn SCMqttLoggerLog(
316
15
    tx: *mut std::os::raw::c_void, flags: u32, max_log_len: u32, js: &mut JsonBuilder,
317
15
) -> bool {
318
15
    let tx = cast_pointer!(tx, MQTTTransaction);
319
15
    log_mqtt(tx, flags, max_log_len as usize, js).is_ok()
320
15
}