]>
Commit | Line | Data |
---|---|---|
2f5834cd | 1 | /* Copyright (C) 2018-2020 Open Information Security Foundation |
9210d874 JI |
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 | ||
2f5834cd | 18 | use crate::applayer::{self, *}; |
42e5065a JI |
19 | use crate::core; |
20 | use crate::core::{ALPROTO_UNKNOWN, AppProto, Flow, IPPROTO_UDP}; | |
42e5065a | 21 | use crate::dhcp::parser::*; |
9210d874 | 22 | use std; |
9c3f06d9 | 23 | use std::ffi::CString; |
9210d874 JI |
24 | |
25 | static mut ALPROTO_DHCP: AppProto = ALPROTO_UNKNOWN; | |
26 | ||
27 | static DHCP_MIN_FRAME_LEN: u32 = 232; | |
28 | ||
29 | pub const BOOTP_REQUEST: u8 = 1; | |
30 | pub const BOOTP_REPLY: u8 = 2; | |
31 | ||
32 | // DHCP option types. Names based on IANA naming: | |
33 | // https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml | |
34 | pub const DHCP_OPT_SUBNET_MASK: u8 = 1; | |
35 | pub const DHCP_OPT_ROUTERS: u8 = 3; | |
36 | pub const DHCP_OPT_DNS_SERVER: u8 = 6; | |
37 | pub const DHCP_OPT_HOSTNAME: u8 = 12; | |
38 | pub const DHCP_OPT_REQUESTED_IP: u8 = 50; | |
39 | pub const DHCP_OPT_ADDRESS_TIME: u8 = 51; | |
40 | pub const DHCP_OPT_TYPE: u8 = 53; | |
41 | pub const DHCP_OPT_SERVER_ID: u8 = 54; | |
42 | pub const DHCP_OPT_PARAMETER_LIST: u8 = 55; | |
43 | pub const DHCP_OPT_RENEWAL_TIME: u8 = 58; | |
44 | pub const DHCP_OPT_REBINDING_TIME: u8 = 59; | |
45 | pub const DHCP_OPT_CLIENT_ID: u8 = 61; | |
46 | pub const DHCP_OPT_END: u8 = 255; | |
47 | ||
48 | /// DHCP message types. | |
49 | pub const DHCP_TYPE_DISCOVER: u8 = 1; | |
50 | pub const DHCP_TYPE_OFFER: u8 = 2; | |
51 | pub const DHCP_TYPE_REQUEST: u8 = 3; | |
52 | pub const DHCP_TYPE_DECLINE: u8 = 4; | |
53 | pub const DHCP_TYPE_ACK: u8 = 5; | |
54 | pub const DHCP_TYPE_NAK: u8 = 6; | |
55 | pub const DHCP_TYPE_RELEASE: u8 = 7; | |
56 | pub const DHCP_TYPE_INFORM: u8 = 8; | |
57 | ||
eb6cc629 JI |
58 | // DHCP parameter types. |
59 | // https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.txt | |
9210d874 JI |
60 | pub const DHCP_PARAM_SUBNET_MASK: u8 = 1; |
61 | pub const DHCP_PARAM_ROUTER: u8 = 3; | |
62 | pub const DHCP_PARAM_DNS_SERVER: u8 = 6; | |
63 | pub const DHCP_PARAM_DOMAIN: u8 = 15; | |
64 | pub const DHCP_PARAM_ARP_TIMEOUT: u8 = 35; | |
65 | pub const DHCP_PARAM_NTP_SERVER: u8 = 42; | |
66 | pub const DHCP_PARAM_TFTP_SERVER_NAME: u8 = 66; | |
67 | pub const DHCP_PARAM_TFTP_SERVER_IP: u8 = 150; | |
68 | ||
9c3f06d9 | 69 | #[derive(AppLayerEvent)] |
9210d874 | 70 | pub enum DHCPEvent { |
9c3f06d9 | 71 | TruncatedOptions, |
9210d874 JI |
72 | MalformedOptions, |
73 | } | |
74 | ||
75 | /// The concept of a transaction is more to satisfy the Suricata | |
76 | /// app-layer. This DHCP parser is actually stateless where each | |
77 | /// message is its own transaction. | |
78 | pub struct DHCPTransaction { | |
79 | tx_id: u64, | |
80 | pub message: DHCPMessage, | |
a484bbbe | 81 | tx_data: applayer::AppLayerTxData, |
9210d874 JI |
82 | } |
83 | ||
84 | impl DHCPTransaction { | |
85 | pub fn new(id: u64, message: DHCPMessage) -> DHCPTransaction { | |
86 | DHCPTransaction { | |
87 | tx_id: id, | |
88 | message: message, | |
a484bbbe | 89 | tx_data: applayer::AppLayerTxData::new(), |
9210d874 JI |
90 | } |
91 | } | |
a337908c VJ |
92 | } |
93 | ||
ac4c5ada JI |
94 | impl Transaction for DHCPTransaction { |
95 | fn id(&self) -> u64 { | |
96 | self.tx_id | |
97 | } | |
98 | } | |
99 | ||
aafb0a60 | 100 | #[derive(Default)] |
9210d874 JI |
101 | pub struct DHCPState { |
102 | // Internal transaction ID. | |
103 | tx_id: u64, | |
104 | ||
105 | // List of transactions. | |
106 | transactions: Vec<DHCPTransaction>, | |
107 | ||
108 | events: u16, | |
109 | } | |
110 | ||
ac4c5ada JI |
111 | impl State<DHCPTransaction> for DHCPState { |
112 | fn get_transactions(&self) -> &[DHCPTransaction] { | |
113 | &self.transactions | |
114 | } | |
115 | } | |
116 | ||
9210d874 | 117 | impl DHCPState { |
aafb0a60 JL |
118 | pub fn new() -> Self { |
119 | Default::default() | |
9210d874 JI |
120 | } |
121 | ||
122 | pub fn parse(&mut self, input: &[u8]) -> bool { | |
123 | match dhcp_parse(input) { | |
13b73997 | 124 | Ok((_, message)) => { |
9210d874 JI |
125 | let malformed_options = message.malformed_options; |
126 | let truncated_options = message.truncated_options; | |
127 | self.tx_id += 1; | |
128 | let transaction = DHCPTransaction::new(self.tx_id, message); | |
129 | self.transactions.push(transaction); | |
130 | if malformed_options { | |
131 | self.set_event(DHCPEvent::MalformedOptions); | |
132 | } | |
133 | if truncated_options { | |
134 | self.set_event(DHCPEvent::TruncatedOptions); | |
135 | } | |
136 | return true; | |
137 | } | |
138 | _ => { | |
139 | return false; | |
140 | } | |
141 | } | |
142 | } | |
143 | ||
144 | pub fn get_tx(&mut self, tx_id: u64) -> Option<&DHCPTransaction> { | |
145 | for tx in &mut self.transactions { | |
146 | if tx.tx_id == tx_id + 1 { | |
147 | return Some(tx); | |
148 | } | |
149 | } | |
150 | return None; | |
151 | } | |
152 | ||
153 | fn free_tx(&mut self, tx_id: u64) { | |
154 | let len = self.transactions.len(); | |
155 | let mut found = false; | |
156 | let mut index = 0; | |
157 | for i in 0..len { | |
158 | let tx = &self.transactions[i]; | |
159 | if tx.tx_id == tx_id + 1 { | |
160 | found = true; | |
161 | index = i; | |
162 | break; | |
163 | } | |
164 | } | |
165 | if found { | |
166 | self.transactions.remove(index); | |
167 | } | |
168 | } | |
169 | ||
170 | fn set_event(&mut self, event: DHCPEvent) { | |
171 | if let Some(tx) = self.transactions.last_mut() { | |
7732efbe | 172 | tx.tx_data.set_event(event as u8); |
9210d874 JI |
173 | self.events += 1; |
174 | } | |
175 | } | |
9210d874 JI |
176 | } |
177 | ||
178 | #[no_mangle] | |
363b5f99 | 179 | pub unsafe extern "C" fn rs_dhcp_probing_parser(_flow: *const Flow, |
422e4892 | 180 | _direction: u8, |
bf1bd407 | 181 | input: *const u8, |
422e4892 VJ |
182 | input_len: u32, |
183 | _rdir: *mut u8) -> AppProto | |
184 | { | |
9210d874 JI |
185 | if input_len < DHCP_MIN_FRAME_LEN { |
186 | return ALPROTO_UNKNOWN; | |
187 | } | |
188 | ||
189 | let slice = build_slice!(input, input_len as usize); | |
190 | match parse_header(slice) { | |
13b73997 | 191 | Ok((_, _)) => { |
363b5f99 | 192 | return ALPROTO_DHCP; |
9210d874 JI |
193 | } |
194 | _ => { | |
195 | return ALPROTO_UNKNOWN; | |
196 | } | |
197 | } | |
198 | } | |
199 | ||
200 | #[no_mangle] | |
3f6624bf VJ |
201 | pub extern "C" fn rs_dhcp_tx_get_alstate_progress(_tx: *mut std::os::raw::c_void, |
202 | _direction: u8) -> std::os::raw::c_int { | |
9210d874 JI |
203 | // As this is a stateless parser, simply use 1. |
204 | return 1; | |
205 | } | |
206 | ||
9210d874 | 207 | #[no_mangle] |
363b5f99 | 208 | pub unsafe extern "C" fn rs_dhcp_state_get_tx(state: *mut std::os::raw::c_void, |
3f6624bf | 209 | tx_id: u64) -> *mut std::os::raw::c_void { |
9210d874 JI |
210 | let state = cast_pointer!(state, DHCPState); |
211 | match state.get_tx(tx_id) { | |
212 | Some(tx) => { | |
53413f2d | 213 | return tx as *const _ as *mut _; |
9210d874 JI |
214 | } |
215 | None => { | |
216 | return std::ptr::null_mut(); | |
217 | } | |
218 | } | |
219 | } | |
220 | ||
221 | #[no_mangle] | |
363b5f99 | 222 | pub unsafe extern "C" fn rs_dhcp_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { |
9210d874 JI |
223 | let state = cast_pointer!(state, DHCPState); |
224 | return state.tx_id; | |
225 | } | |
226 | ||
227 | #[no_mangle] | |
363b5f99 | 228 | pub unsafe extern "C" fn rs_dhcp_parse(_flow: *const core::Flow, |
3f6624bf VJ |
229 | state: *mut std::os::raw::c_void, |
230 | _pstate: *mut std::os::raw::c_void, | |
bf1bd407 | 231 | input: *const u8, |
9210d874 | 232 | input_len: u32, |
3f6624bf | 233 | _data: *const std::os::raw::c_void, |
44d3f264 | 234 | _flags: u8) -> AppLayerResult { |
9210d874 JI |
235 | let state = cast_pointer!(state, DHCPState); |
236 | let buf = build_slice!(input, input_len as usize); | |
237 | if state.parse(buf) { | |
44d3f264 | 238 | return AppLayerResult::ok(); |
9210d874 | 239 | } |
44d3f264 | 240 | return AppLayerResult::err(); |
9210d874 JI |
241 | } |
242 | ||
243 | #[no_mangle] | |
363b5f99 | 244 | pub unsafe extern "C" fn rs_dhcp_state_tx_free( |
3f6624bf | 245 | state: *mut std::os::raw::c_void, |
bf1bd407 | 246 | tx_id: u64) |
9210d874 JI |
247 | { |
248 | let state = cast_pointer!(state, DHCPState); | |
249 | state.free_tx(tx_id); | |
250 | } | |
251 | ||
252 | #[no_mangle] | |
547d6c2d | 253 | pub extern "C" fn rs_dhcp_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { |
9210d874 JI |
254 | let state = DHCPState::new(); |
255 | let boxed = Box::new(state); | |
53413f2d | 256 | return Box::into_raw(boxed) as *mut _; |
9210d874 JI |
257 | } |
258 | ||
259 | #[no_mangle] | |
363b5f99 JI |
260 | pub unsafe extern "C" fn rs_dhcp_state_free(state: *mut std::os::raw::c_void) { |
261 | std::mem::drop(Box::from_raw(state as *mut DHCPState)); | |
9210d874 JI |
262 | } |
263 | ||
a484bbbe VJ |
264 | export_tx_data_get!(rs_dhcp_get_tx_data, DHCPTransaction); |
265 | ||
9210d874 JI |
266 | const PARSER_NAME: &'static [u8] = b"dhcp\0"; |
267 | ||
268 | #[no_mangle] | |
269 | pub unsafe extern "C" fn rs_dhcp_register_parser() { | |
b5bc5098 | 270 | SCLogDebug!("Registering DHCP parser."); |
9210d874 JI |
271 | let ports = CString::new("[67,68]").unwrap(); |
272 | let parser = RustParser { | |
3f6624bf | 273 | name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, |
e3ca6b43 JL |
274 | default_port : ports.as_ptr(), |
275 | ipproto : IPPROTO_UDP, | |
66632465 PA |
276 | probe_ts : Some(rs_dhcp_probing_parser), |
277 | probe_tc : Some(rs_dhcp_probing_parser), | |
e3ca6b43 JL |
278 | min_depth : 0, |
279 | max_depth : 16, | |
280 | state_new : rs_dhcp_state_new, | |
281 | state_free : rs_dhcp_state_free, | |
282 | tx_free : rs_dhcp_state_tx_free, | |
283 | parse_ts : rs_dhcp_parse, | |
284 | parse_tc : rs_dhcp_parse, | |
285 | get_tx_count : rs_dhcp_state_get_tx_count, | |
286 | get_tx : rs_dhcp_state_get_tx, | |
efc9a7a3 VJ |
287 | tx_comp_st_ts : 1, |
288 | tx_comp_st_tc : 1, | |
e3ca6b43 | 289 | tx_get_progress : rs_dhcp_tx_get_alstate_progress, |
9c3f06d9 JI |
290 | get_eventinfo : Some(DHCPEvent::get_event_info), |
291 | get_eventinfo_byid : Some(DHCPEvent::get_event_info_by_id), | |
e3ca6b43 JL |
292 | localstorage_new : None, |
293 | localstorage_free : None, | |
e3ca6b43 | 294 | get_files : None, |
ac4c5ada | 295 | get_tx_iterator : Some(applayer::state_get_tx_iterator::<DHCPState, DHCPTransaction>), |
c94a5e63 | 296 | get_tx_data : rs_dhcp_get_tx_data, |
5665fc83 | 297 | apply_tx_config : None, |
0529a00f | 298 | flags : APP_LAYER_PARSER_OPT_UNIDIR_TXS, |
4da0d9bd | 299 | truncate : None, |
9210d874 JI |
300 | }; |
301 | ||
302 | let ip_proto_str = CString::new("udp").unwrap(); | |
303 | ||
304 | if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { | |
305 | let alproto = AppLayerRegisterProtocolDetection(&parser, 1); | |
306 | ALPROTO_DHCP = alproto; | |
307 | if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { | |
308 | let _ = AppLayerRegisterParser(&parser, alproto); | |
309 | } | |
310 | } else { | |
311 | SCLogDebug!("Protocol detector and parser disabled for DHCP."); | |
312 | } | |
313 | } |