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