/src/suricata7/rust/src/ntp/ntp.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 | | extern crate ntp_parser; |
21 | | use self::ntp_parser::*; |
22 | | use crate::core; |
23 | | use crate::core::{AppProto,Flow,ALPROTO_UNKNOWN,ALPROTO_FAILED,Direction}; |
24 | | use crate::applayer::{self, *}; |
25 | | use std; |
26 | | use std::ffi::CString; |
27 | | |
28 | | use nom7::Err; |
29 | | |
30 | | #[derive(AppLayerEvent)] |
31 | | pub enum NTPEvent { |
32 | | UnsolicitedResponse , |
33 | | MalformedData, |
34 | | NotRequest, |
35 | | NotResponse, |
36 | | } |
37 | | |
38 | | #[derive(Default)] |
39 | | pub struct NTPState { |
40 | | state_data: AppLayerStateData, |
41 | | |
42 | | /// List of transactions for this session |
43 | | transactions: Vec<NTPTransaction>, |
44 | | |
45 | | /// Events counter |
46 | | events: u16, |
47 | | |
48 | | /// tx counter for assigning incrementing id's to tx's |
49 | | tx_id: u64, |
50 | | } |
51 | | |
52 | | #[derive(Debug, Default)] |
53 | | pub struct NTPTransaction { |
54 | | /// The NTP reference ID |
55 | | pub xid: u32, |
56 | | |
57 | | /// The internal transaction id |
58 | | id: u64, |
59 | | |
60 | | tx_data: applayer::AppLayerTxData, |
61 | | } |
62 | | |
63 | | impl Transaction for NTPTransaction { |
64 | 77.3k | fn id(&self) -> u64 { |
65 | 77.3k | self.id |
66 | 77.3k | } |
67 | | } |
68 | | |
69 | | impl NTPState { |
70 | 1.62k | pub fn new() -> Self { |
71 | 1.62k | Default::default() |
72 | 1.62k | } |
73 | | } |
74 | | |
75 | | impl State<NTPTransaction> for NTPState { |
76 | 42.9k | fn get_transaction_count(&self) -> usize { |
77 | 42.9k | self.transactions.len() |
78 | 42.9k | } |
79 | | |
80 | 38.6k | fn get_transaction_by_index(&self, index: usize) -> Option<&NTPTransaction> { |
81 | 38.6k | self.transactions.get(index) |
82 | 38.6k | } |
83 | | } |
84 | | |
85 | | impl NTPState { |
86 | | /// Parse an NTP request message |
87 | | /// |
88 | | /// Returns 0 if successful, or -1 on error |
89 | 44.5k | fn parse(&mut self, i: &[u8], direction: Direction) -> i32 { |
90 | 44.5k | match parse_ntp(i) { |
91 | 44.1k | Ok((_,ref msg)) => { |
92 | | // SCLogDebug!("parse_ntp: {:?}",msg); |
93 | 44.1k | let (mode, ref_id) = match msg { |
94 | 37.0k | NtpPacket::V3(pkt) => (pkt.mode, pkt.ref_id), |
95 | 7.02k | NtpPacket::V4(pkt) => (pkt.mode, pkt.ref_id), |
96 | | }; |
97 | 44.1k | if mode == NtpMode::SymmetricActive || mode == NtpMode::Client { |
98 | 39.2k | let mut tx = self.new_tx(direction); |
99 | 39.2k | // use the reference id as identifier |
100 | 39.2k | tx.xid = ref_id; |
101 | 39.2k | self.transactions.push(tx); |
102 | 39.2k | } |
103 | 44.1k | 0 |
104 | | }, |
105 | | Err(Err::Incomplete(_)) => { |
106 | | SCLogDebug!("Insufficient data while parsing NTP data"); |
107 | 252 | self.set_event(NTPEvent::MalformedData); |
108 | 252 | -1 |
109 | | }, |
110 | | Err(_) => { |
111 | | SCLogDebug!("Error while parsing NTP data"); |
112 | 153 | self.set_event(NTPEvent::MalformedData); |
113 | 153 | -1 |
114 | | }, |
115 | | } |
116 | 44.5k | } |
117 | | |
118 | 1.62k | fn free(&mut self) { |
119 | | // All transactions are freed when the `transactions` object is freed. |
120 | | // But let's be explicit |
121 | 1.62k | self.transactions.clear(); |
122 | 1.62k | } |
123 | | |
124 | 39.2k | fn new_tx(&mut self, direction: Direction) -> NTPTransaction { |
125 | 39.2k | self.tx_id += 1; |
126 | 39.2k | NTPTransaction::new(direction, self.tx_id) |
127 | 39.2k | } |
128 | | |
129 | 0 | pub fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&NTPTransaction> { |
130 | 0 | self.transactions.iter().find(|&tx| tx.id == tx_id + 1) |
131 | 0 | } |
132 | | |
133 | 38.6k | fn free_tx(&mut self, tx_id: u64) { |
134 | 38.6k | let tx = self.transactions.iter().position(|tx| tx.id == tx_id + 1); |
135 | 38.6k | debug_assert!(tx.is_some()); |
136 | 38.6k | if let Some(idx) = tx { |
137 | 38.6k | let _ = self.transactions.remove(idx); |
138 | 38.6k | } |
139 | 38.6k | } |
140 | | |
141 | | /// Set an event. The event is set on the most recent transaction. |
142 | 405 | pub fn set_event(&mut self, event: NTPEvent) { |
143 | 405 | if let Some(tx) = self.transactions.last_mut() { |
144 | 0 | tx.tx_data.set_event(event as u8); |
145 | 0 | self.events += 1; |
146 | 405 | } |
147 | 405 | } |
148 | | } |
149 | | |
150 | | impl NTPTransaction { |
151 | 39.2k | pub fn new(direction: Direction, id: u64) -> NTPTransaction { |
152 | 39.2k | NTPTransaction { |
153 | 39.2k | xid: 0, |
154 | 39.2k | id, |
155 | 39.2k | tx_data: applayer::AppLayerTxData::for_direction(direction), |
156 | 39.2k | } |
157 | 39.2k | } |
158 | | } |
159 | | |
160 | | /// Returns *mut NTPState |
161 | | #[no_mangle] |
162 | 1.62k | pub extern "C" fn rs_ntp_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { |
163 | 1.62k | let state = NTPState::new(); |
164 | 1.62k | let boxed = Box::new(state); |
165 | 1.62k | return Box::into_raw(boxed) as *mut _; |
166 | 1.62k | } |
167 | | |
168 | | /// Params: |
169 | | /// - state: *mut NTPState as void pointer |
170 | | #[no_mangle] |
171 | 1.62k | pub extern "C" fn rs_ntp_state_free(state: *mut std::os::raw::c_void) { |
172 | 1.62k | let mut ntp_state = unsafe{ Box::from_raw(state as *mut NTPState) }; |
173 | 1.62k | ntp_state.free(); |
174 | 1.62k | } |
175 | | |
176 | | #[no_mangle] |
177 | 22.9k | pub unsafe extern "C" fn rs_ntp_parse_request(_flow: *const core::Flow, |
178 | 22.9k | state: *mut std::os::raw::c_void, |
179 | 22.9k | _pstate: *mut std::os::raw::c_void, |
180 | 22.9k | stream_slice: StreamSlice, |
181 | 22.9k | _data: *const std::os::raw::c_void, |
182 | 22.9k | ) -> AppLayerResult { |
183 | 22.9k | let state = cast_pointer!(state,NTPState); |
184 | 22.9k | if state.parse(stream_slice.as_slice(), Direction::ToServer) < 0 { |
185 | 259 | return AppLayerResult::err(); |
186 | 22.6k | } |
187 | 22.6k | AppLayerResult::ok() |
188 | 22.9k | } |
189 | | |
190 | | #[no_mangle] |
191 | 21.5k | pub unsafe extern "C" fn rs_ntp_parse_response(_flow: *const core::Flow, |
192 | 21.5k | state: *mut std::os::raw::c_void, |
193 | 21.5k | _pstate: *mut std::os::raw::c_void, |
194 | 21.5k | stream_slice: StreamSlice, |
195 | 21.5k | _data: *const std::os::raw::c_void, |
196 | 21.5k | ) -> AppLayerResult { |
197 | 21.5k | let state = cast_pointer!(state,NTPState); |
198 | 21.5k | if state.parse(stream_slice.as_slice(), Direction::ToClient) < 0 { |
199 | 146 | return AppLayerResult::err(); |
200 | 21.4k | } |
201 | 21.4k | AppLayerResult::ok() |
202 | 21.5k | } |
203 | | |
204 | | #[no_mangle] |
205 | 0 | pub unsafe extern "C" fn rs_ntp_state_get_tx(state: *mut std::os::raw::c_void, |
206 | 0 | tx_id: u64) |
207 | 0 | -> *mut std::os::raw::c_void |
208 | | { |
209 | 0 | let state = cast_pointer!(state,NTPState); |
210 | 0 | match state.get_tx_by_id(tx_id) { |
211 | 0 | Some(tx) => tx as *const _ as *mut _, |
212 | 0 | None => std::ptr::null_mut(), |
213 | | } |
214 | 0 | } |
215 | | |
216 | | #[no_mangle] |
217 | 131k | pub unsafe extern "C" fn rs_ntp_state_get_tx_count(state: *mut std::os::raw::c_void) |
218 | 131k | -> u64 |
219 | | { |
220 | 131k | let state = cast_pointer!(state,NTPState); |
221 | 131k | state.tx_id |
222 | 131k | } |
223 | | |
224 | | #[no_mangle] |
225 | 38.6k | pub unsafe extern "C" fn rs_ntp_state_tx_free(state: *mut std::os::raw::c_void, |
226 | 38.6k | tx_id: u64) |
227 | | { |
228 | 38.6k | let state = cast_pointer!(state,NTPState); |
229 | 38.6k | state.free_tx(tx_id); |
230 | 38.6k | } |
231 | | |
232 | | #[no_mangle] |
233 | 77.3k | pub extern "C" fn rs_ntp_tx_get_alstate_progress(_tx: *mut std::os::raw::c_void, |
234 | 77.3k | _direction: u8) |
235 | 77.3k | -> std::os::raw::c_int |
236 | | { |
237 | 77.3k | 1 |
238 | 77.3k | } |
239 | | |
240 | | static mut ALPROTO_NTP : AppProto = ALPROTO_UNKNOWN; |
241 | | |
242 | | #[no_mangle] |
243 | 631 | pub extern "C" fn ntp_probing_parser(_flow: *const Flow, |
244 | 631 | _direction: u8, |
245 | 631 | input:*const u8, input_len: u32, |
246 | 631 | _rdir: *mut u8) -> AppProto |
247 | | { |
248 | 631 | if input.is_null() { |
249 | 0 | return ALPROTO_UNKNOWN; |
250 | 631 | } |
251 | 631 | let slice: &[u8] = unsafe { std::slice::from_raw_parts(input as *mut u8, input_len as usize) }; |
252 | 631 | let alproto = unsafe{ ALPROTO_NTP }; |
253 | 631 | match parse_ntp(slice) { |
254 | | Ok((_, _)) => { |
255 | | // parse_ntp already checks for supported version (3 or 4) |
256 | 462 | return alproto; |
257 | | }, |
258 | | Err(Err::Incomplete(_)) => { |
259 | 88 | return ALPROTO_UNKNOWN; |
260 | | }, |
261 | | Err(_) => { |
262 | 81 | return unsafe{ALPROTO_FAILED}; |
263 | | }, |
264 | | } |
265 | 631 | } |
266 | | |
267 | | export_tx_data_get!(rs_ntp_get_tx_data, NTPTransaction); |
268 | | export_state_data_get!(rs_ntp_get_state_data, NTPState); |
269 | | |
270 | | const PARSER_NAME : &[u8] = b"ntp\0"; |
271 | | |
272 | | #[no_mangle] |
273 | 34 | pub unsafe extern "C" fn rs_register_ntp_parser() { |
274 | 34 | let default_port = CString::new("123").unwrap(); |
275 | 34 | let parser = RustParser { |
276 | 34 | name : PARSER_NAME.as_ptr() as *const std::os::raw::c_char, |
277 | 34 | default_port : default_port.as_ptr(), |
278 | 34 | ipproto : core::IPPROTO_UDP, |
279 | 34 | probe_ts : Some(ntp_probing_parser), |
280 | 34 | probe_tc : Some(ntp_probing_parser), |
281 | 34 | min_depth : 0, |
282 | 34 | max_depth : 16, |
283 | 34 | state_new : rs_ntp_state_new, |
284 | 34 | state_free : rs_ntp_state_free, |
285 | 34 | tx_free : rs_ntp_state_tx_free, |
286 | 34 | parse_ts : rs_ntp_parse_request, |
287 | 34 | parse_tc : rs_ntp_parse_response, |
288 | 34 | get_tx_count : rs_ntp_state_get_tx_count, |
289 | 34 | get_tx : rs_ntp_state_get_tx, |
290 | 34 | tx_comp_st_ts : 1, |
291 | 34 | tx_comp_st_tc : 1, |
292 | 34 | tx_get_progress : rs_ntp_tx_get_alstate_progress, |
293 | 34 | get_eventinfo : Some(NTPEvent::get_event_info), |
294 | 34 | get_eventinfo_byid : Some(NTPEvent::get_event_info_by_id), |
295 | 34 | localstorage_new : None, |
296 | 34 | localstorage_free : None, |
297 | 34 | get_tx_files : None, |
298 | 34 | get_tx_iterator : Some(applayer::state_get_tx_iterator::<NTPState, NTPTransaction>), |
299 | 34 | get_tx_data : rs_ntp_get_tx_data, |
300 | 34 | get_state_data : rs_ntp_get_state_data, |
301 | 34 | apply_tx_config : None, |
302 | 34 | flags : 0, |
303 | 34 | truncate : None, |
304 | 34 | get_frame_id_by_name: None, |
305 | 34 | get_frame_name_by_id: None, |
306 | 34 | }; |
307 | | |
308 | 34 | let ip_proto_str = CString::new("udp").unwrap(); |
309 | 34 | if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { |
310 | 34 | let alproto = AppLayerRegisterProtocolDetection(&parser, 1); |
311 | | // store the allocated ID for the probe function |
312 | 34 | ALPROTO_NTP = alproto; |
313 | 34 | if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { |
314 | 34 | let _ = AppLayerRegisterParser(&parser, alproto); |
315 | 34 | } |
316 | 0 | } else { |
317 | 0 | SCLogDebug!("Protocol detector and parser disabled for NTP."); |
318 | 0 | } |
319 | 34 | } |
320 | | |
321 | | |
322 | | #[cfg(test)] |
323 | | mod tests { |
324 | | use super::*; |
325 | | |
326 | | #[test] |
327 | | fn test_ntp_parse_request_valid() { |
328 | | // A UDP NTP v4 request, in client mode |
329 | | const REQ : &[u8] = &[ |
330 | | 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
331 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
332 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
333 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
334 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
335 | | 0x18, 0x57, 0xab, 0xc3, 0x4a, 0x5f, 0x2c, 0xfe |
336 | | ]; |
337 | | |
338 | | let mut state = NTPState::new(); |
339 | | assert_eq!(0, state.parse(REQ, Direction::ToServer)); |
340 | | } |
341 | | } |