huginn_net_db/
db_parse.rs

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>()), // olen
322        tag(":"),
323        alt((tag("*").map(|_| None), map_res(digit1, |s: &str| s.parse::<u16>().map(Some)))), // mss
324        tag(":"),
325        parse_window_size,
326        tag(","),
327        alt((tag("*").map(|_| None), map_res(digit1, |s: &str| s.parse::<u8>().map(Some)))), // wscale
328        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}