1use std::str::FromStr;
2
3use crate::db::{Database, FingerprintCollection, Label, Type};
4use crate::error::DatabaseError;
5use crate::{
6 http::{Header as HttpHeader, Signature as HttpSignature, Version as HttpVersion},
7 tcp::{IpVersion, PayloadSize, Quirk, Signature as TcpSignature, TcpOption, Ttl, WindowSize},
8};
9use nom::branch::alt;
10use nom::bytes::complete::{take_until, take_while};
11use nom::character::complete::{alpha1, char, digit1};
12use nom::combinator::{map, map_res, opt};
13use nom::multi::{separated_list0, separated_list1};
14use nom::sequence::{pair, separated_pair, terminated};
15use nom::*;
16use nom::{
17 bytes::complete::tag,
18 character::complete::{alphanumeric1, space0},
19 combinator::rest,
20 sequence::preceded,
21 IResult,
22};
23use tracing::{trace, warn};
24
25impl FromStr for Database {
26 type Err = DatabaseError;
27
28 fn from_str(s: &str) -> Result<Self, Self::Err> {
29 let mut classes = vec![];
30 let mut mtu_entries = vec![];
31 let mut ua_os_entries = vec![];
32
33 let mut temp_tcp_request_entries: Vec<(Label, Vec<TcpSignature>)> = vec![];
34 let mut temp_tcp_response_entries: Vec<(Label, Vec<TcpSignature>)> = vec![];
35 let mut temp_http_request_entries: Vec<(Label, Vec<HttpSignature>)> = vec![];
36 let mut temp_http_response_entries: Vec<(Label, Vec<HttpSignature>)> = vec![];
37
38 let mut cur_mod = None;
39
40 for line in s.lines() {
41 let line = line.trim();
42
43 if line.is_empty() || line.starts_with(';') {
44 continue;
45 }
46
47 if line.starts_with("classes") {
48 classes.append(
49 &mut parse_classes(line)
50 .map_err(|err| {
51 DatabaseError::Parse(format!("fail to parse `classes`: {line}, {err}"))
52 })?
53 .1,
54 );
55 } else if line.starts_with("ua_os") {
56 ua_os_entries.append(
57 &mut parse_ua_os(line)
58 .map_err(|err| {
59 DatabaseError::Parse(format!("fail to parse `ua_os`: {line}, {err}"))
60 })?
61 .1,
62 );
63 } else if line.starts_with('[') && line.ends_with(']') {
64 cur_mod = Some(
65 parse_module(line)
66 .map_err(|err| {
67 DatabaseError::Parse(format!("fail to parse `module`: {line}, {err}"))
68 })?
69 .1,
70 );
71 } else if let Some((module, direction)) = cur_mod.as_ref() {
72 let (_, (name, value)) = parse_named_value(line).map_err(|err| {
73 DatabaseError::Parse(format!("fail to parse named value: {line}, {err}"))
74 })?;
75
76 match name {
77 "label" if module == "mtu" => {
78 mtu_entries.push((value.to_string(), vec![]));
79 }
80 "sig" if module == "mtu" => {
81 if let Some((_label, values)) = mtu_entries.last_mut() {
82 let sig = value.parse::<u16>().map_err(|err| {
83 DatabaseError::Parse(format!(
84 "fail to parse `mtu` value: {value}, {err}"
85 ))
86 })?;
87 values.push(sig);
88 } else {
89 return Err(DatabaseError::Parse(format!(
90 "`mtu` value without `label`: {value}"
91 )));
92 }
93 }
94 "label" => {
95 let (_, label) = parse_label(value).map_err(|err| {
96 DatabaseError::Parse(format!("fail to parse `label`: {value}, {err}"))
97 })?;
98
99 match (module.as_str(), direction.as_ref().map(|s| s.as_ref())) {
100 ("tcp", Some("request")) => {
101 temp_tcp_request_entries.push((label, vec![]))
102 }
103 ("tcp", Some("response")) => {
104 temp_tcp_response_entries.push((label, vec![]))
105 }
106 ("http", Some("request")) => {
107 temp_http_request_entries.push((label, vec![]))
108 }
109 ("http", Some("response")) => {
110 temp_http_response_entries.push((label, vec![]))
111 }
112 _ => {
113 warn!("skip `label` in unknown module `{}`: {}", module, value);
114 }
115 }
116 }
117 "sig" => match (module.as_str(), direction.as_ref().map(|s| s.as_ref())) {
118 ("tcp", Some("request")) => {
119 if let Some((label, values)) = temp_tcp_request_entries.last_mut() {
120 let sig = value.parse()?;
121 trace!("sig for `{}` tcp request: {}", label, sig);
122 values.push(sig);
123 } else {
124 return Err(DatabaseError::Parse(format!(
125 "tcp signature without `label`: {value}"
126 )));
127 }
128 }
129 ("tcp", Some("response")) => {
130 if let Some((label, values)) = temp_tcp_response_entries.last_mut() {
131 let sig = value.parse()?;
132 trace!("sig for `{}` tcp response: {}", label, sig);
133 values.push(sig);
134 } else {
135 return Err(DatabaseError::Parse(format!(
136 "tcp signature without `label`: {value}"
137 )));
138 }
139 }
140 ("http", Some("request")) => {
141 if let Some((label, values)) = temp_http_request_entries.last_mut() {
142 let sig = value.parse()?;
143 trace!("sig for `{}` http request: {}", label, sig);
144 values.push(sig);
145 } else {
146 return Err(DatabaseError::Parse(format!(
147 "http signature without `label`: {value}"
148 )));
149 }
150 }
151 ("http", Some("response")) => {
152 if let Some((label, values)) = temp_http_response_entries.last_mut() {
153 let sig = value.parse()?;
154 trace!("sig for `{}` http response: {}", label, sig);
155 values.push(sig);
156 } else {
157 return Err(DatabaseError::Parse(format!(
158 "http signature without `label`: {value}"
159 )));
160 }
161 }
162 _ => {
163 warn!("skip `sig` in unknown module `{}`: {}", module, value);
164 }
165 },
166 "sys" if module != "mtu" => {}
167 _ => {
168 warn!("skip unknown named value: {} = {}", name, value);
169 }
170 }
171 } else {
172 return Err(DatabaseError::Parse(format!(
173 "unexpected line outside the module: {line}"
174 )));
175 }
176 }
177
178 Ok(Database {
179 classes,
180 mtu: mtu_entries,
181 ua_os: ua_os_entries,
182 tcp_request: FingerprintCollection::new(temp_tcp_request_entries),
183 tcp_response: FingerprintCollection::new(temp_tcp_response_entries),
184 http_request: FingerprintCollection::new(temp_http_request_entries),
185 http_response: FingerprintCollection::new(temp_http_response_entries),
186 })
187 }
188}
189
190macro_rules! impl_from_str {
191 ($ty:ty, $parse:ident) => {
192 impl FromStr for $ty {
193 type Err = DatabaseError;
194
195 fn from_str(s: &str) -> Result<Self, Self::Err> {
196 let (remaining, res) = $parse(s).map_err(|err| {
197 DatabaseError::Parse(format!(
198 "parse {} failed: {}, {}",
199 stringify!($ty),
200 s,
201 err
202 ))
203 })?;
204
205 if !remaining.is_empty() {
206 Err(DatabaseError::Parse(format!(
207 "parse {} failed, remaining: {}",
208 stringify!($ty),
209 remaining
210 )))
211 } else {
212 Ok(res)
213 }
214 }
215 }
216 };
217}
218
219impl_from_str!(Label, parse_label);
220impl_from_str!(Type, parse_type);
221impl_from_str!(TcpSignature, parse_tcp_signature);
222impl_from_str!(IpVersion, parse_ip_version);
223impl_from_str!(Ttl, parse_ttl);
224impl_from_str!(WindowSize, parse_window_size);
225impl_from_str!(TcpOption, parse_tcp_option);
226impl_from_str!(Quirk, parse_quirk);
227impl_from_str!(PayloadSize, parse_payload_size);
228impl_from_str!(HttpSignature, parse_http_signature);
229impl_from_str!(HttpHeader, parse_http_header);
230
231fn parse_named_value(input: &str) -> IResult<&str, (&str, &str)> {
232 let (input, (name, _, _, _, value)) =
233 (alphanumeric1, space0, tag("="), space0, rest).parse(input)?;
234 Ok((input, (name, value)))
235}
236
237fn parse_classes(input: &str) -> IResult<&str, Vec<String>> {
238 let (input, (_, _, _, _, classes)) = (
239 tag("classes"),
240 space0,
241 tag("="),
242 space0,
243 separated_list0(tag(","), alphanumeric1),
244 )
245 .parse(input)?;
246
247 let class_vec = classes.into_iter().map(|s| s.to_string()).collect();
248 Ok((input, class_vec))
249}
250
251fn parse_module(input: &str) -> IResult<&str, (String, Option<String>)> {
252 let (input, (_, module, direction, _)) =
253 (tag("["), alpha1, opt(preceded(tag(":"), alpha1)), tag("]")).parse(input)?;
254 let module_str = module.to_string();
255 let direction_str = direction.map(|s| s.to_string());
256
257 Ok((input, (module_str, direction_str)))
258}
259
260fn parse_ua_os(input: &str) -> IResult<&str, Vec<(String, Option<String>)>> {
261 let (input, (_, _, _, _, values)) = (
262 tag("ua_os"),
263 space0,
264 tag("="),
265 space0,
266 separated_list0(tag(","), parse_key_value),
267 )
268 .parse(input)?;
269
270 let result = values
271 .into_iter()
272 .map(|(name, value)| (name.to_string(), value.map(|s| s.to_string())))
273 .collect();
274
275 Ok((input, result))
276}
277
278fn parse_key_value(input: &str) -> IResult<&str, (&str, Option<&str>)> {
279 let (input, (name, _, value)) =
280 (alphanumeric1, space0, opt(preceded((space0, tag("="), space0), alphanumeric1)))
281 .parse(input)?;
282
283 Ok((input, (name, value)))
284}
285
286fn parse_label(input: &str) -> IResult<&str, Label> {
287 let (input, (ty, _, class, _, name, flavor)) = (
288 parse_type,
289 tag(":"),
290 alt((map(tag("!"), |_| None), map(take_until(":"), |s: &str| Some(s.to_string())))),
291 tag(":"),
292 take_until(":"),
293 opt(preceded(tag(":"), rest)),
294 )
295 .parse(input)?;
296
297 Ok((
298 input,
299 Label {
300 ty,
301 class,
302 name: name.to_string(),
303 flavor: flavor.filter(|f| !f.is_empty()).map(String::from),
304 },
305 ))
306}
307
308fn parse_type(input: &str) -> IResult<&str, Type> {
309 alt((tag("s").map(|_| Type::Specified), tag("g").map(|_| Type::Generic))).parse(input)
310}
311
312fn parse_tcp_signature(input: &str) -> IResult<&str, TcpSignature> {
313 let (
314 input,
315 (version, _, ittl, _, olen, _, mss, _, wsize, _, wscale, _, olayout, _, quirks, _, pclass),
316 ) = (
317 parse_ip_version,
318 tag(":"),
319 parse_ttl,
320 tag(":"),
321 map_res(digit1, |s: &str| s.parse::<u8>()), tag(":"),
323 alt((tag("*").map(|_| None), map_res(digit1, |s: &str| s.parse::<u16>().map(Some)))), tag(":"),
325 parse_window_size,
326 tag(","),
327 alt((tag("*").map(|_| None), map_res(digit1, |s: &str| s.parse::<u8>().map(Some)))), tag(":"),
329 separated_list1(tag(","), parse_tcp_option),
330 tag(":"),
331 separated_list0(tag(","), parse_quirk),
332 tag(":"),
333 parse_payload_size,
334 )
335 .parse(input)?;
336
337 Ok((
338 input,
339 TcpSignature { version, ittl, olen, mss, wsize, wscale, olayout, quirks, pclass },
340 ))
341}
342
343fn parse_ip_version(input: &str) -> IResult<&str, IpVersion> {
344 alt((
345 map(tag("4"), |_| IpVersion::V4),
346 map(tag("6"), |_| IpVersion::V6),
347 map(tag("*"), |_| IpVersion::Any),
348 ))
349 .parse(input)
350}
351
352fn parse_ttl(input: &str) -> IResult<&str, Ttl> {
353 alt((
354 map_res(terminated(digit1, tag("-")), |s: &str| s.parse::<u8>().map(Ttl::Bad)),
355 map_res(terminated(digit1, tag("+?")), |s: &str| s.parse::<u8>().map(Ttl::Guess)),
356 map_res(
357 separated_pair(digit1, tag("+"), digit1),
358 |(ttl_str, distance_str): (&str, &str)| match (
359 ttl_str.parse::<u8>(),
360 distance_str.parse::<u8>(),
361 ) {
362 (Ok(ttl), Ok(distance)) => Ok(Ttl::Distance(ttl, distance)),
363 (Err(_), _) => Err("Failed to parse ttl"),
364 (_, Err(_)) => Err("Failed to parse distance"),
365 },
366 ),
367 map_res(digit1, |s: &str| s.parse::<u8>().map(Ttl::Value)),
368 ))
369 .parse(input)
370}
371
372fn parse_window_size(input: &str) -> IResult<&str, WindowSize> {
373 alt((
374 map(tag("*"), |_| WindowSize::Any),
375 map_res(preceded(tag("mss*"), digit1), |s: &str| s.parse::<u8>().map(WindowSize::Mss)),
376 map_res(preceded(tag("mtu*"), digit1), |s: &str| s.parse::<u8>().map(WindowSize::Mtu)),
377 map_res(preceded(tag("%"), digit1), |s: &str| s.parse::<u16>().map(WindowSize::Mod)),
378 map_res(digit1, |s: &str| s.parse::<u16>().map(WindowSize::Value)),
379 ))
380 .parse(input)
381}
382
383fn parse_tcp_option(input: &str) -> IResult<&str, TcpOption> {
384 alt((
385 map_res(preceded(tag("eol+"), digit1), |s: &str| s.parse::<u8>().map(TcpOption::Eol)),
386 tag("nop").map(|_| TcpOption::Nop),
387 tag("mss").map(|_| TcpOption::Mss),
388 tag("ws").map(|_| TcpOption::Ws),
389 tag("sok").map(|_| TcpOption::Sok),
390 tag("sack").map(|_| TcpOption::Sack),
391 tag("ts").map(|_| TcpOption::TS),
392 preceded(tag("?"), map(digit1, |s: &str| s.parse::<u8>().unwrap_or(0)))
393 .map(TcpOption::Unknown),
394 ))
395 .parse(input)
396}
397
398fn parse_quirk(input: &str) -> IResult<&str, Quirk> {
399 alt((
400 map(tag("df"), |_| Quirk::Df),
401 map(tag("id+"), |_| Quirk::NonZeroID),
402 map(tag("id-"), |_| Quirk::ZeroID),
403 map(tag("ecn"), |_| Quirk::Ecn),
404 map(tag("0+"), |_| Quirk::MustBeZero),
405 map(tag("flow"), |_| Quirk::FlowID),
406 map(tag("seq-"), |_| Quirk::SeqNumZero),
407 map(tag("ack+"), |_| Quirk::AckNumNonZero),
408 map(tag("ack-"), |_| Quirk::AckNumZero),
409 map(tag("uptr+"), |_| Quirk::NonZeroURG),
410 map(tag("urgf+"), |_| Quirk::Urg),
411 map(tag("pushf+"), |_| Quirk::Push),
412 map(tag("ts1-"), |_| Quirk::OwnTimestampZero),
413 map(tag("ts2+"), |_| Quirk::PeerTimestampNonZero),
414 map(tag("opt+"), |_| Quirk::TrailinigNonZero),
415 map(tag("exws"), |_| Quirk::ExcessiveWindowScaling),
416 map(tag("bad"), |_| Quirk::OptBad),
417 ))
418 .parse(input)
419}
420
421fn parse_payload_size(input: &str) -> IResult<&str, PayloadSize> {
422 alt((
423 map(tag("0"), |_| PayloadSize::Zero),
424 map(tag("+"), |_| PayloadSize::NonZero),
425 map(tag("*"), |_| PayloadSize::Any),
426 ))
427 .parse(input)
428}
429
430fn parse_http_signature(input: &str) -> IResult<&str, HttpSignature> {
431 let (input, (version, _, horder, _, habsent, _, expsw)) = (
432 parse_http_version,
433 tag(":"),
434 separated_list1(tag(","), parse_http_header),
435 tag(":"),
436 opt(separated_list0(tag(","), parse_http_header)),
437 tag(":"),
438 rest,
439 )
440 .parse(input)?;
441
442 let habsent = habsent
443 .unwrap_or_default()
444 .into_iter()
445 .filter(|h| !h.name.is_empty())
446 .collect();
447
448 Ok((input, HttpSignature { version, horder, habsent, expsw: expsw.to_string() }))
449}
450
451fn parse_http_version(input: &str) -> IResult<&str, HttpVersion> {
452 alt((
453 map(tag("0"), |_| HttpVersion::V10),
454 map(tag("1"), |_| HttpVersion::V11),
455 map(tag("*"), |_| HttpVersion::Any),
456 ))
457 .parse(input)
458}
459
460fn parse_header_key_value(input: &str) -> IResult<&str, (&str, Option<&str>)> {
461 pair(
462 take_while(|c: char| (c.is_ascii_alphanumeric() || c == '-') && c != ':' && c != '='),
463 opt(preceded(tag("=["), terminated(take_until("]"), char(']')))),
464 )
465 .parse(input)
466}
467
468fn parse_http_header(input: &str) -> IResult<&str, HttpHeader> {
469 let (input, optional) = opt(char('?')).parse(input)?;
470 let (input, (name, value)) = parse_header_key_value(input)?;
471
472 Ok((
473 input,
474 HttpHeader {
475 optional: optional.is_some(),
476 name: name.to_string(),
477 value: value.map(|s| s.to_string()),
478 },
479 ))
480}