resolv_conf/
grammar.rs

1use std::net::{Ipv4Addr, Ipv6Addr};
2use std::str::{from_utf8, Utf8Error};
3
4use crate::{AddrParseError, Config, Family, Lookup, Network};
5
6/// Error while parsing resolv.conf file
7#[derive(Debug)]
8pub enum ParseError {
9    /// Error that may be returned when the string to parse contains invalid UTF-8 sequences
10    InvalidUtf8(usize, Utf8Error),
11    /// Error returned a value for a given directive is invalid.
12    /// This can also happen when the value is missing, if the directive requires a value.
13    InvalidValue(usize),
14    /// Error returned when a value for a given option is invalid.
15    /// This can also happen when the value is missing, if the option requires a value.
16    InvalidOptionValue(usize),
17    /// Error returned when a invalid option is found.
18    InvalidOption(usize),
19    /// Error returned when a invalid directive is found.
20    InvalidDirective(usize),
21    /// Error returned when a value cannot be parsed an an IP address.
22    InvalidIp(usize, AddrParseError),
23    /// Error returned when there is extra data at the end of a line.
24    ExtraData(usize),
25}
26
27impl std::fmt::Display for ParseError {
28    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
29        match self {
30            ParseError::InvalidUtf8(line, err) => write!(f, "bad unicode at line {line}: {err}"),
31            ParseError::InvalidValue(line) => write!(
32                f,
33                "directive at line {line} is improperly formatted or contains invalid value",
34            ),
35            ParseError::InvalidOptionValue(line) => write!(
36                f,
37                "directive options at line {line} contains invalid value of some option",
38            ),
39            ParseError::InvalidOption(line) => {
40                write!(f, "option at line {line} is not recognized")
41            }
42            ParseError::InvalidDirective(line) => {
43                write!(f, "directive at line {line} is not recognized")
44            }
45            ParseError::InvalidIp(line, err) => {
46                write!(f, "directive at line {line} contains invalid IP: {err}")
47            }
48            ParseError::ExtraData(line) => write!(f, "extra data at the end of line {line}"),
49        }
50    }
51}
52
53impl std::error::Error for ParseError {
54    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
55        match self {
56            ParseError::InvalidUtf8(_, err) => Some(err),
57            _ => None,
58        }
59    }
60}
61
62fn ip_v4_netw(val: &str) -> Result<Network, AddrParseError> {
63    let mut pair = val.splitn(2, '/');
64    let ip: Ipv4Addr = pair.next().unwrap().parse()?;
65    if ip.is_unspecified() {
66        return Err(AddrParseError);
67    }
68    if let Some(mask) = pair.next() {
69        let mask = mask.parse()?;
70        // make sure this is a valid mask
71        let value: u32 = ip.octets().iter().fold(0, |acc, &x| acc + u32::from(x));
72        if value == 0 || (value & !value != 0) {
73            Err(AddrParseError)
74        } else {
75            Ok(Network::V4(ip, mask))
76        }
77    } else {
78        // We have to "guess" the mask.
79        //
80        // FIXME(@little-dude) right now, we look at the number or bytes that are 0, but maybe we
81        // should use the number of bits that are 0.
82        //
83        // In other words, with this implementation, the mask of `128.192.0.0` will be
84        // `255.255.0.0` (a.k.a `/16`). But we could also consider that the mask is `/10` (a.k.a
85        // `255.63.0.0`).
86        //
87        // My only source on topic is the "DNS and Bind" book which suggests using bytes, not bits.
88        let octets = ip.octets();
89        let mask = if octets[3] == 0 {
90            if octets[2] == 0 {
91                if octets[1] == 0 {
92                    Ipv4Addr::new(255, 0, 0, 0)
93                } else {
94                    Ipv4Addr::new(255, 255, 0, 0)
95                }
96            } else {
97                Ipv4Addr::new(255, 255, 255, 0)
98            }
99        } else {
100            Ipv4Addr::new(255, 255, 255, 255)
101        };
102        Ok(Network::V4(ip, mask))
103    }
104}
105
106fn ip_v6_netw(val: &str) -> Result<Network, AddrParseError> {
107    let mut pair = val.splitn(2, '/');
108    let ip = pair.next().unwrap().parse()?;
109    if let Some(msk) = pair.next() {
110        // FIXME: validate the mask
111        Ok(Network::V6(ip, msk.parse()?))
112    } else {
113        // FIXME: "guess" an appropriate mask for the IP
114        Ok(Network::V6(
115            ip,
116            Ipv6Addr::new(
117                65_535, 65_535, 65_535, 65_535, 65_535, 65_535, 65_535, 65_535,
118            ),
119        ))
120    }
121}
122
123pub(crate) fn parse(bytes: &[u8]) -> Result<Config, ParseError> {
124    use self::ParseError::*;
125    let mut cfg = Config::new();
126    'lines: for (lineno, line) in bytes.split(|&x| x == b'\n').enumerate() {
127        for &c in line.iter() {
128            if c != b'\t' && c != b' ' {
129                if c == b';' || c == b'#' {
130                    continue 'lines;
131                } else {
132                    break;
133                }
134            }
135        }
136        // All that dances above to allow invalid utf-8 inside the comments
137        let mut words = from_utf8(line)
138            .map_err(|e| InvalidUtf8(lineno, e))?
139            // ignore everything after ';' or '#'
140            .split([';', '#'])
141            .next()
142            .ok_or(InvalidValue(lineno))?
143            .split_whitespace();
144        let keyword = match words.next() {
145            Some(x) => x,
146            None => continue,
147        };
148        match keyword {
149            "nameserver" => {
150                let srv = words
151                    .next()
152                    .ok_or(InvalidValue(lineno))
153                    .map(|addr| addr.parse().map_err(|e| InvalidIp(lineno, e)))??;
154                cfg.nameservers.push(srv);
155                if words.next().is_some() {
156                    return Err(ExtraData(lineno));
157                }
158            }
159            "domain" => {
160                let dom = words
161                    .next()
162                    .and_then(|x| x.parse().ok())
163                    .ok_or(InvalidValue(lineno))?;
164                cfg.set_domain(dom);
165                if words.next().is_some() {
166                    return Err(ExtraData(lineno));
167                }
168            }
169            "search" => {
170                cfg.set_search(words.map(|x| x.to_string()).collect());
171            }
172            "sortlist" => {
173                cfg.sortlist.clear();
174                for pair in words {
175                    let netw = ip_v4_netw(pair)
176                        .or_else(|_| ip_v6_netw(pair))
177                        .map_err(|e| InvalidIp(lineno, e))?;
178                    cfg.sortlist.push(netw);
179                }
180            }
181            "options" => {
182                for pair in words {
183                    let mut iter = pair.splitn(2, ':');
184                    let key = iter.next().unwrap();
185                    let value = iter.next();
186                    if iter.next().is_some() {
187                        return Err(ExtraData(lineno));
188                    }
189                    match (key, value) {
190                        // TODO(tailhook) ensure that values are None?
191                        ("debug", _) => cfg.debug = true,
192                        ("ndots", Some(x)) => {
193                            cfg.ndots = x.parse().map_err(|_| InvalidOptionValue(lineno))?
194                        }
195                        ("timeout", Some(x)) => {
196                            cfg.timeout = x.parse().map_err(|_| InvalidOptionValue(lineno))?
197                        }
198                        ("attempts", Some(x)) => {
199                            cfg.attempts = x.parse().map_err(|_| InvalidOptionValue(lineno))?
200                        }
201                        ("rotate", _) => cfg.rotate = true,
202                        ("no-check-names", _) => cfg.no_check_names = true,
203                        ("inet6", _) => cfg.inet6 = true,
204                        ("ip6-bytestring", _) => cfg.ip6_bytestring = true,
205                        ("ip6-dotint", _) => cfg.ip6_dotint = true,
206                        ("no-ip6-dotint", _) => cfg.ip6_dotint = false,
207                        ("edns0", _) => cfg.edns0 = true,
208                        ("single-request", _) => cfg.single_request = true,
209                        ("single-request-reopen", _) => cfg.single_request_reopen = true,
210                        ("no-reload", _) => cfg.no_reload = true,
211                        ("trust-ad", _) => cfg.trust_ad = true,
212                        ("no-tld-query", _) => cfg.no_tld_query = true,
213                        ("use-vc", _) => cfg.use_vc = true,
214                        _ => return Err(InvalidOption(lineno)),
215                    }
216                }
217            }
218            "lookup" => {
219                for word in words {
220                    match word {
221                        "file" => cfg.lookup.push(Lookup::File),
222                        "bind" => cfg.lookup.push(Lookup::Bind),
223                        extra => cfg.lookup.push(Lookup::Extra(extra.to_string())),
224                    }
225                }
226            }
227            "family" => {
228                for word in words {
229                    match word {
230                        "inet4" => cfg.family.push(Family::Inet4),
231                        "inet6" => cfg.family.push(Family::Inet6),
232                        _ => return Err(InvalidValue(lineno)),
233                    }
234                }
235            }
236            _ => return Err(InvalidDirective(lineno)),
237        }
238    }
239    Ok(cfg)
240}