/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 | } |