resolv_conf/
grammar.rs

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