/src/suricata7/rust/src/snmp/snmp.rs
Line | Count | Source |
1 | | /* Copyright (C) 2017-2021 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 Pierre Chifflier <chifflier@wzdftpd.net> |
19 | | |
20 | | use crate::snmp::snmp_parser::*; |
21 | | use crate::core::{self, *}; |
22 | | use crate::applayer::{self, *}; |
23 | | use std; |
24 | | use std::ffi::CString; |
25 | | |
26 | | use asn1_rs::Oid; |
27 | | use der_parser::ber::BerObjectContent; |
28 | | use der_parser::der::parse_der_sequence; |
29 | | use nom7::{Err, IResult}; |
30 | | use nom7::error::{ErrorKind, make_error}; |
31 | | |
32 | | #[derive(AppLayerEvent)] |
33 | | pub enum SNMPEvent { |
34 | | MalformedData, |
35 | | UnknownSecurityModel, |
36 | | VersionMismatch, |
37 | | } |
38 | | |
39 | | #[derive(Default)] |
40 | | pub struct SNMPState<'a> { |
41 | | state_data: AppLayerStateData, |
42 | | |
43 | | /// SNMP protocol version |
44 | | pub version: u32, |
45 | | |
46 | | /// List of transactions for this session |
47 | | transactions: Vec<SNMPTransaction<'a>>, |
48 | | |
49 | | /// tx counter for assigning incrementing id's to tx's |
50 | | tx_id: u64, |
51 | | } |
52 | | |
53 | | pub struct SNMPPduInfo<'a> { |
54 | | pub pdu_type: PduType, |
55 | | |
56 | | pub err: ErrorStatus, |
57 | | |
58 | | pub trap_type: Option<(TrapType,Oid<'a>,NetworkAddress)>, |
59 | | |
60 | | pub vars: Vec<Oid<'a>>, |
61 | | } |
62 | | |
63 | | pub struct SNMPTransaction<'a> { |
64 | | /// PDU version |
65 | | pub version: u32, |
66 | | |
67 | | /// PDU info, if present (and cleartext) |
68 | | pub info: Option<SNMPPduInfo<'a>>, |
69 | | |
70 | | /// Community, if present (SNMPv2) |
71 | | pub community: Option<String>, |
72 | | |
73 | | /// USM info, if present (SNMPv3) |
74 | | pub usm: Option<String>, |
75 | | |
76 | | /// True if transaction was encrypted |
77 | | pub encrypted: bool, |
78 | | |
79 | | /// The internal transaction id |
80 | | id: u64, |
81 | | |
82 | | tx_data: applayer::AppLayerTxData, |
83 | | } |
84 | | |
85 | | impl Transaction for SNMPTransaction<'_> { |
86 | 303k | fn id(&self) -> u64 { |
87 | 303k | self.id |
88 | 303k | } |
89 | | } |
90 | | |
91 | | impl<'a> SNMPState<'a> { |
92 | 5.88k | pub fn new() -> SNMPState<'a> { |
93 | 5.88k | Default::default() |
94 | 5.88k | } |
95 | | } |
96 | | |
97 | | impl<'a> Default for SNMPPduInfo<'a> { |
98 | 137k | fn default() -> SNMPPduInfo<'a> { |
99 | 137k | SNMPPduInfo{ |
100 | 137k | pdu_type: PduType(0), |
101 | 137k | err: ErrorStatus::NoError, |
102 | 137k | trap_type: None, |
103 | 137k | vars: Vec::new() |
104 | 137k | } |
105 | 137k | } |
106 | | } |
107 | | |
108 | | impl<'a> State<SNMPTransaction<'a>> for SNMPState<'a> { |
109 | 179k | fn get_transaction_count(&self) -> usize { |
110 | 179k | self.transactions.len() |
111 | 179k | } |
112 | | |
113 | 151k | fn get_transaction_by_index(&self, index: usize) -> Option<&SNMPTransaction<'a>> { |
114 | 151k | self.transactions.get(index) |
115 | 151k | } |
116 | | } |
117 | | |
118 | | impl<'a> SNMPState<'a> { |
119 | 137k | fn add_pdu_info(&mut self, pdu: &SnmpPdu<'a>, tx: &mut SNMPTransaction<'a>) { |
120 | 137k | let mut pdu_info = SNMPPduInfo { |
121 | 137k | pdu_type: pdu.pdu_type(), |
122 | 137k | ..Default::default() |
123 | 137k | }; |
124 | 137k | match *pdu { |
125 | 129k | SnmpPdu::Generic(ref pdu) => { |
126 | 129k | pdu_info.err = pdu.err; |
127 | 129k | }, |
128 | 7.31k | SnmpPdu::Bulk(_) => { |
129 | 7.31k | }, |
130 | 928 | SnmpPdu::TrapV1(ref t) => { |
131 | 928 | pdu_info.trap_type = Some((t.generic_trap,t.enterprise.clone(),t.agent_addr)); |
132 | 928 | } |
133 | | } |
134 | | |
135 | 137k | for var in pdu.vars_iter() { |
136 | 24.3k | pdu_info.vars.push(var.oid.to_owned()); |
137 | 24.3k | } |
138 | 137k | tx.info = Some(pdu_info); |
139 | 137k | } |
140 | | |
141 | 129k | fn handle_snmp_v12(&mut self, msg: SnmpMessage<'a>, _direction: Direction) -> i32 { |
142 | 129k | let mut tx = self.new_tx(_direction); |
143 | | // in the message, version is encoded as 0 (version 1) or 1 (version 2) |
144 | 129k | if self.version != msg.version + 1 { |
145 | 128k | SCLogDebug!("SNMP version mismatch: expected {}, received {}", self.version, msg.version+1); |
146 | 128k | self.set_event_tx(&mut tx, SNMPEvent::VersionMismatch); |
147 | 128k | } |
148 | 129k | self.add_pdu_info(&msg.pdu, &mut tx); |
149 | 129k | tx.community = Some(msg.community); |
150 | 129k | self.transactions.push(tx); |
151 | 129k | 0 |
152 | 129k | } |
153 | | |
154 | 11.0k | fn handle_snmp_v3(&mut self, msg: SnmpV3Message<'a>, _direction: Direction) -> i32 { |
155 | 11.0k | let mut tx = self.new_tx(_direction); |
156 | 11.0k | if self.version != msg.version { |
157 | 2.99k | SCLogDebug!("SNMP version mismatch: expected {}, received {}", self.version, msg.version); |
158 | 2.99k | self.set_event_tx(&mut tx, SNMPEvent::VersionMismatch); |
159 | 8.03k | } |
160 | 11.0k | match msg.data { |
161 | 7.94k | ScopedPduData::Plaintext(pdu) => { |
162 | 7.94k | self.add_pdu_info(&pdu.data, &mut tx); |
163 | 7.94k | }, |
164 | 3.07k | _ => { |
165 | 3.07k | tx.encrypted = true; |
166 | 3.07k | } |
167 | | } |
168 | 11.0k | match msg.security_params { |
169 | 7.58k | SecurityParameters::USM(usm) => { |
170 | 7.58k | tx.usm = Some(usm.msg_user_name); |
171 | 7.58k | }, |
172 | 3.44k | _ => { |
173 | 3.44k | self.set_event_tx(&mut tx, SNMPEvent::UnknownSecurityModel); |
174 | 3.44k | } |
175 | | } |
176 | 11.0k | self.transactions.push(tx); |
177 | 11.0k | 0 |
178 | 11.0k | } |
179 | | |
180 | | /// Parse an SNMP request message |
181 | | /// |
182 | | /// Returns 0 if successful, or -1 on error |
183 | 156k | fn parse(&mut self, i: &'a [u8], direction: Direction) -> i32 { |
184 | 156k | if self.version == 0 { |
185 | 136k | if let Ok((_, x)) = parse_pdu_envelope_version(i) { |
186 | 691 | self.version = x; |
187 | 135k | } |
188 | 20.0k | } |
189 | 156k | match parse_snmp_generic_message(i) { |
190 | 112k | Ok((_rem,SnmpGenericMessage::V1(msg))) | |
191 | 129k | Ok((_rem,SnmpGenericMessage::V2(msg))) => self.handle_snmp_v12(msg, direction), |
192 | 11.0k | Ok((_rem,SnmpGenericMessage::V3(msg))) => self.handle_snmp_v3(msg, direction), |
193 | 15.6k | Err(_e) => { |
194 | | SCLogDebug!("parse_snmp failed: {:?}", _e); |
195 | 15.6k | self.set_event(SNMPEvent::MalformedData); |
196 | 15.6k | -1 |
197 | | }, |
198 | | } |
199 | 156k | } |
200 | | |
201 | 5.88k | fn free(&mut self) { |
202 | | // All transactions are freed when the `transactions` object is freed. |
203 | | // But let's be explicit |
204 | 5.88k | self.transactions.clear(); |
205 | 5.88k | } |
206 | | |
207 | 140k | fn new_tx(&mut self, direction: Direction) -> SNMPTransaction<'a> { |
208 | 140k | self.tx_id += 1; |
209 | 140k | SNMPTransaction::new(direction, self.version, self.tx_id) |
210 | 140k | } |
211 | | |
212 | 32 | fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&SNMPTransaction<'_>> { |
213 | 32 | self.transactions.iter().rev().find(|&tx| tx.id == tx_id + 1) |
214 | 32 | } |
215 | | |
216 | 140k | fn free_tx(&mut self, tx_id: u64) { |
217 | 140k | let tx = self.transactions.iter().position(|tx| tx.id == tx_id + 1); |
218 | 140k | debug_assert!(tx.is_some()); |
219 | 140k | if let Some(idx) = tx { |
220 | 140k | let _ = self.transactions.remove(idx); |
221 | 140k | } |
222 | 140k | } |
223 | | |
224 | | /// Set an event. The event is set on the most recent transaction. |
225 | 15.6k | fn set_event(&mut self, event: SNMPEvent) { |
226 | 15.6k | if let Some(tx) = self.transactions.last_mut() { |
227 | 0 | tx.tx_data.set_event(event as u8); |
228 | 15.6k | } |
229 | 15.6k | } |
230 | | |
231 | | /// Set an event on a specific transaction. |
232 | 134k | fn set_event_tx(&self, tx: &mut SNMPTransaction, event: SNMPEvent) { |
233 | 134k | tx.tx_data.set_event(event as u8); |
234 | 134k | } |
235 | | } |
236 | | |
237 | | impl<'a> SNMPTransaction<'a> { |
238 | 140k | pub fn new(direction: Direction, version: u32, id: u64) -> SNMPTransaction<'a> { |
239 | 140k | SNMPTransaction { |
240 | 140k | version, |
241 | 140k | info: None, |
242 | 140k | community: None, |
243 | 140k | usm: None, |
244 | 140k | encrypted: false, |
245 | 140k | id, |
246 | 140k | tx_data: applayer::AppLayerTxData::for_direction(direction), |
247 | 140k | } |
248 | 140k | } |
249 | | } |
250 | | |
251 | | /// Returns *mut SNMPState |
252 | | #[no_mangle] |
253 | 5.88k | pub extern "C" fn rs_snmp_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { |
254 | 5.88k | let state = SNMPState::new(); |
255 | 5.88k | let boxed = Box::new(state); |
256 | 5.88k | return Box::into_raw(boxed) as *mut _; |
257 | 5.88k | } |
258 | | |
259 | | /// Params: |
260 | | /// - state: *mut SNMPState as void pointer |
261 | | #[no_mangle] |
262 | 5.88k | pub extern "C" fn rs_snmp_state_free(state: *mut std::os::raw::c_void) { |
263 | 5.88k | let mut snmp_state = unsafe{ Box::from_raw(state as *mut SNMPState) }; |
264 | 5.88k | snmp_state.free(); |
265 | 5.88k | } |
266 | | |
267 | | #[no_mangle] |
268 | 83.7k | pub unsafe extern "C" fn rs_snmp_parse_request(_flow: *const core::Flow, |
269 | 83.7k | state: *mut std::os::raw::c_void, |
270 | 83.7k | _pstate: *mut std::os::raw::c_void, |
271 | 83.7k | stream_slice: StreamSlice, |
272 | 83.7k | _data: *const std::os::raw::c_void, |
273 | 83.7k | ) -> AppLayerResult { |
274 | 83.7k | let state = cast_pointer!(state,SNMPState); |
275 | 83.7k | state.parse(stream_slice.as_slice(), Direction::ToServer).into() |
276 | 83.7k | } |
277 | | |
278 | | #[no_mangle] |
279 | 72.6k | pub unsafe extern "C" fn rs_snmp_parse_response(_flow: *const core::Flow, |
280 | 72.6k | state: *mut std::os::raw::c_void, |
281 | 72.6k | _pstate: *mut std::os::raw::c_void, |
282 | 72.6k | stream_slice: StreamSlice, |
283 | 72.6k | _data: *const std::os::raw::c_void, |
284 | 72.6k | ) -> AppLayerResult { |
285 | 72.6k | let state = cast_pointer!(state,SNMPState); |
286 | 72.6k | state.parse(stream_slice.as_slice(), Direction::ToClient).into() |
287 | 72.6k | } |
288 | | |
289 | | #[no_mangle] |
290 | 32 | pub unsafe extern "C" fn rs_snmp_state_get_tx(state: *mut std::os::raw::c_void, |
291 | 32 | tx_id: u64) |
292 | 32 | -> *mut std::os::raw::c_void |
293 | | { |
294 | 32 | let state = cast_pointer!(state,SNMPState); |
295 | 32 | match state.get_tx_by_id(tx_id) { |
296 | 28 | Some(tx) => tx as *const _ as *mut _, |
297 | 4 | None => std::ptr::null_mut(), |
298 | | } |
299 | 32 | } |
300 | | |
301 | | #[no_mangle] |
302 | 476k | pub unsafe extern "C" fn rs_snmp_state_get_tx_count(state: *mut std::os::raw::c_void) |
303 | 476k | -> u64 |
304 | | { |
305 | 476k | let state = cast_pointer!(state,SNMPState); |
306 | 476k | state.tx_id |
307 | 476k | } |
308 | | |
309 | | #[no_mangle] |
310 | 140k | pub unsafe extern "C" fn rs_snmp_state_tx_free(state: *mut std::os::raw::c_void, |
311 | 140k | tx_id: u64) |
312 | | { |
313 | 140k | let state = cast_pointer!(state,SNMPState); |
314 | 140k | state.free_tx(tx_id); |
315 | 140k | } |
316 | | |
317 | | #[no_mangle] |
318 | 300k | pub extern "C" fn rs_snmp_tx_get_alstate_progress(_tx: *mut std::os::raw::c_void, |
319 | 300k | _direction: u8) |
320 | 300k | -> std::os::raw::c_int |
321 | | { |
322 | 300k | 1 |
323 | 300k | } |
324 | | |
325 | | static mut ALPROTO_SNMP : AppProto = ALPROTO_UNKNOWN; |
326 | | |
327 | | // Read PDU sequence and extract version, if similar to SNMP definition |
328 | 139k | fn parse_pdu_envelope_version(i:&[u8]) -> IResult<&[u8],u32> { |
329 | 139k | match parse_der_sequence(i) { |
330 | 85.0k | Ok((_,x)) => { |
331 | | #[allow(clippy::single_match)] |
332 | 85.0k | match x.content { |
333 | 85.0k | BerObjectContent::Sequence(ref v) => { |
334 | 85.0k | if v.len() == 3 { |
335 | 1.85k | match v[0].as_u32() { |
336 | 176 | Ok(0) => { return Ok((i,1)); }, // possibly SNMPv1 |
337 | 465 | Ok(1) => { return Ok((i,2)); }, // possibly SNMPv2c |
338 | 1.21k | _ => () |
339 | | } |
340 | 83.2k | } else if v.len() == 4 && v[0].as_u32() == Ok(3) { |
341 | 807 | return Ok((i,3)); // possibly SNMPv3 |
342 | 82.4k | } |
343 | | }, |
344 | 0 | _ => () |
345 | | }; |
346 | 83.6k | Err(Err::Error(make_error(i, ErrorKind::Verify))) |
347 | | }, |
348 | 490 | Err(Err::Incomplete(i)) => Err(Err::Incomplete(i)), |
349 | | Err(Err::Failure(_)) | |
350 | 53.9k | Err(Err::Error(_)) => Err(Err::Error(make_error(i,ErrorKind::Verify))) |
351 | | } |
352 | 139k | } |
353 | | |
354 | | #[no_mangle] |
355 | 3.11k | pub unsafe extern "C" fn rs_snmp_probing_parser(_flow: *const Flow, |
356 | 3.11k | _direction: u8, |
357 | 3.11k | input:*const u8, |
358 | 3.11k | input_len: u32, |
359 | 3.11k | _rdir: *mut u8) -> AppProto { |
360 | 3.11k | if input.is_null() { |
361 | 0 | return ALPROTO_UNKNOWN; |
362 | 3.11k | } |
363 | 3.11k | let slice = build_slice!(input,input_len as usize); |
364 | 3.11k | let alproto = ALPROTO_SNMP; |
365 | 3.11k | if slice.len() < 4 { return ALPROTO_UNKNOWN; } |
366 | 3.11k | match parse_pdu_envelope_version(slice) { |
367 | 757 | Ok((_,_)) => alproto, |
368 | 127 | Err(Err::Incomplete(_)) => ALPROTO_UNKNOWN, |
369 | 2.23k | _ => ALPROTO_FAILED, |
370 | | } |
371 | 3.11k | } |
372 | | |
373 | | export_tx_data_get!(rs_snmp_get_tx_data, SNMPTransaction); |
374 | | export_state_data_get!(rs_snmp_get_state_data, SNMPState); |
375 | | |
376 | | const PARSER_NAME : &[u8] = b"snmp\0"; |
377 | | |
378 | | #[no_mangle] |
379 | 34 | pub unsafe extern "C" fn rs_register_snmp_parser() { |
380 | 34 | let default_port = CString::new("161").unwrap(); |
381 | 34 | let mut parser = RustParser { |
382 | 34 | name : PARSER_NAME.as_ptr() as *const std::os::raw::c_char, |
383 | 34 | default_port : default_port.as_ptr(), |
384 | 34 | ipproto : core::IPPROTO_UDP, |
385 | 34 | probe_ts : Some(rs_snmp_probing_parser), |
386 | 34 | probe_tc : Some(rs_snmp_probing_parser), |
387 | 34 | min_depth : 0, |
388 | 34 | max_depth : 16, |
389 | 34 | state_new : rs_snmp_state_new, |
390 | 34 | state_free : rs_snmp_state_free, |
391 | 34 | tx_free : rs_snmp_state_tx_free, |
392 | 34 | parse_ts : rs_snmp_parse_request, |
393 | 34 | parse_tc : rs_snmp_parse_response, |
394 | 34 | get_tx_count : rs_snmp_state_get_tx_count, |
395 | 34 | get_tx : rs_snmp_state_get_tx, |
396 | 34 | tx_comp_st_ts : 1, |
397 | 34 | tx_comp_st_tc : 1, |
398 | 34 | tx_get_progress : rs_snmp_tx_get_alstate_progress, |
399 | 34 | get_eventinfo : Some(SNMPEvent::get_event_info), |
400 | 34 | get_eventinfo_byid : Some(SNMPEvent::get_event_info_by_id), |
401 | 34 | localstorage_new : None, |
402 | 34 | localstorage_free : None, |
403 | 34 | get_tx_files : None, |
404 | 34 | get_tx_iterator : Some(applayer::state_get_tx_iterator::<SNMPState, SNMPTransaction>), |
405 | 34 | get_tx_data : rs_snmp_get_tx_data, |
406 | 34 | get_state_data : rs_snmp_get_state_data, |
407 | 34 | apply_tx_config : None, |
408 | 34 | flags : 0, |
409 | 34 | truncate : None, |
410 | 34 | get_frame_id_by_name: None, |
411 | 34 | get_frame_name_by_id: None, |
412 | 34 | }; |
413 | 34 | let ip_proto_str = CString::new("udp").unwrap(); |
414 | 34 | if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { |
415 | | // port 161 |
416 | 34 | let alproto = AppLayerRegisterProtocolDetection(&parser, 1); |
417 | | // store the allocated ID for the probe function |
418 | 34 | ALPROTO_SNMP = alproto; |
419 | 34 | if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { |
420 | 34 | let _ = AppLayerRegisterParser(&parser, alproto); |
421 | 34 | } |
422 | | // port 162 |
423 | 34 | let default_port_traps = CString::new("162").unwrap(); |
424 | 34 | parser.default_port = default_port_traps.as_ptr(); |
425 | 34 | let _ = AppLayerRegisterProtocolDetection(&parser, 1); |
426 | 34 | if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { |
427 | 34 | let _ = AppLayerRegisterParser(&parser, alproto); |
428 | 34 | } |
429 | 0 | } else { |
430 | 0 | SCLogDebug!("Protocol detector and parser disabled for SNMP."); |
431 | 0 | } |
432 | 34 | } |