1 use std::fmt;
2 use std::iter::{IntoIterator, Iterator};
3 use std::slice::Iter;
4 use {grammar, Network, ParseError, ScopedIp};
5 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
6 
7 const NAMESERVER_LIMIT:usize = 3;
8 const SEARCH_LIMIT:usize = 6;
9 
10 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
11 enum LastSearch {
12     None,
13     Domain,
14     Search,
15 }
16 
17 
18 /// Represent a resolver configuration, as described in `man 5 resolv.conf`.
19 /// The options and defaults match those in the linux `man` page.
20 ///
21 /// Note: while most fields in the structure are public the `search` and
22 /// `domain` fields must be accessed via methods. This is because there are
23 /// few different ways to treat `domain` field. In GNU libc `search` and
24 /// `domain` replace each other ([`get_last_search_or_domain`]).
25 /// In MacOS `/etc/resolve/*` files `domain` is treated in entirely different
26 /// way.
27 ///
28 /// Also consider using [`glibc_normalize`] and [`get_system_domain`] to match
29 /// behavior of GNU libc. (latter requires ``system`` feature enabled)
30 ///
31 /// ```rust
32 /// extern crate resolv_conf;
33 ///
34 /// use std::net::Ipv4Addr;
35 /// use resolv_conf::{Config, ScopedIp};
36 ///
37 /// fn main() {
38 ///     // Create a new config
39 ///     let mut config = Config::new();
40 ///     config.nameservers.push(ScopedIp::V4(Ipv4Addr::new(8, 8, 8, 8)));
41 ///     config.set_search(vec!["example.com".into()]);
42 ///
43 ///     // Parse a config
44 ///     let parsed = Config::parse("nameserver 8.8.8.8\nsearch example.com").unwrap();
45 ///     assert_eq!(parsed, config);
46 /// }
47 /// ```
48 ///
49 /// [`glibc_normalize`]: #method.glibc_normalize
50 /// [`get_last_search_or_domain`]: #method.get_last_search_or_domain
51 /// [`get_system_domain`]: #method.get_system_domain
52 #[derive(Clone, Debug, PartialEq, Eq)]
53 pub struct Config {
54     /// List of nameservers
55     pub nameservers: Vec<ScopedIp>,
56     /// Indicated whether the last line that has been parsed is a "domain" directive or a "search"
57     /// directive. This is important for compatibility with glibc, since in glibc's implementation,
58     /// "search" and "domain" are mutually exclusive, and only the last directive is taken into
59     /// consideration.
60     last_search: LastSearch,
61     /// Domain to append to name when it doesn't contain ndots
62     domain: Option<String>,
63     /// List of suffixes to append to name when it doesn't contain ndots
64     search: Option<Vec<String>>,
65     /// List of preferred addresses
66     pub sortlist: Vec<Network>,
67     /// Enable DNS resolve debugging
68     pub debug: bool,
69     /// Number of dots in name to try absolute resolving first (default 1)
70     pub ndots: u32,
71     /// Dns query timeout (default 5 [sec])
72     pub timeout: u32,
73     /// Number of attempts to resolve name if server is inaccesible (default 2)
74     pub attempts: u32,
75     /// Round-robin selection of servers (default false)
76     pub rotate: bool,
77     /// Don't check names for validity (default false)
78     pub no_check_names: bool,
79     /// Try AAAA query before A
80     pub inet6: bool,
81     /// Use reverse lookup of ipv6 using bit-label format described instead
82     /// of nibble format
83     pub ip6_bytestring: bool,
84     /// Do ipv6 reverse lookups in ip6.int zone instead of ip6.arpa
85     /// (default false)
86     pub ip6_dotint: bool,
87     /// Enable dns extensions described in RFC 2671
88     pub edns0: bool,
89     /// Don't make ipv4 and ipv6 requests simultaneously
90     pub single_request: bool,
91     /// Use same socket for the A and AAAA requests
92     pub single_request_reopen: bool,
93     /// Don't resolve unqualified name as top level domain
94     pub no_tld_query: bool,
95     /// Force using TCP for DNS resolution
96     pub use_vc: bool,
97     /// The order in which databases should be searched during a lookup
98     /// **(openbsd-only)**
99     pub lookup: Vec<Lookup>,
100     /// The order in which internet protocol families should be prefered
101     /// **(openbsd-only)**
102     pub family: Vec<Family>,
103 }
104 
105 impl Config {
106     /// Create a new `Config` object with default values.
107     ///
108     /// ```rust
109     /// # extern crate resolv_conf;
110     /// use resolv_conf::Config;
111     /// # fn main() {
112     /// let config = Config::new();
113     /// assert_eq!(config.nameservers, vec![]);
114     /// assert!(config.get_domain().is_none());
115     /// assert!(config.get_search().is_none());
116     /// assert_eq!(config.sortlist, vec![]);
117     /// assert_eq!(config.debug, false);
118     /// assert_eq!(config.ndots, 1);
119     /// assert_eq!(config.timeout, 5);
120     /// assert_eq!(config.attempts, 2);
121     /// assert_eq!(config.rotate, false);
122     /// assert_eq!(config.no_check_names, false);
123     /// assert_eq!(config.inet6, false);
124     /// assert_eq!(config.ip6_bytestring, false);
125     /// assert_eq!(config.ip6_dotint, false);
126     /// assert_eq!(config.edns0, false);
127     /// assert_eq!(config.single_request, false);
128     /// assert_eq!(config.single_request_reopen, false);
129     /// assert_eq!(config.no_tld_query, false);
130     /// assert_eq!(config.use_vc, false);
131     /// # }
new() -> Config132     pub fn new() -> Config {
133         Config {
134             nameservers: Vec::new(),
135             domain: None,
136             search: None,
137             last_search: LastSearch::None,
138             sortlist: Vec::new(),
139             debug: false,
140             ndots: 1,
141             timeout: 5,
142             attempts: 2,
143             rotate: false,
144             no_check_names: false,
145             inet6: false,
146             ip6_bytestring: false,
147             ip6_dotint: false,
148             edns0: false,
149             single_request: false,
150             single_request_reopen: false,
151             no_tld_query: false,
152             use_vc: false,
153             lookup: Vec::new(),
154             family: Vec::new(),
155         }
156     }
157 
158     /// Parse a buffer and return the corresponding `Config` object.
159     ///
160     /// ```rust
161     /// # extern crate resolv_conf;
162     /// use resolv_conf::{ScopedIp, Config};
163     /// # fn main() {
164     /// let config_str = "# /etc/resolv.conf
165     /// nameserver  8.8.8.8
166     /// nameserver  8.8.4.4
167     /// search      example.com sub.example.com
168     /// options     ndots:8 attempts:8";
169     ///
170     /// // Parse the config
171     /// let parsed_config = Config::parse(&config_str).expect("Failed to parse config");
172     ///
173     /// // Print the config
174     /// println!("{:?}", parsed_config);
175     /// # }
176     /// ```
parse<T: AsRef<[u8]>>(buf: T) -> Result<Config, ParseError>177     pub fn parse<T: AsRef<[u8]>>(buf: T) -> Result<Config, ParseError> {
178         grammar::parse(buf.as_ref())
179     }
180 
181     /// Return the suffixes declared in the last "domain" or "search" directive.
182     ///
183     /// ```rust
184     /// # extern crate resolv_conf;
185     /// use resolv_conf::{ScopedIp, Config};
186     /// # fn main() {
187     /// let config_str = "search example.com sub.example.com\ndomain localdomain";
188     /// let parsed_config = Config::parse(&config_str).expect("Failed to parse config");
189     /// let domains = parsed_config.get_last_search_or_domain()
190     ///                            .map(|domain| domain.clone())
191     ///                            .collect::<Vec<String>>();
192     /// assert_eq!(domains, vec![String::from("localdomain")]);
193     ///
194     /// let config_str = "domain localdomain\nsearch example.com sub.example.com";
195     /// let parsed_config = Config::parse(&config_str).expect("Failed to parse config");
196     /// let domains = parsed_config.get_last_search_or_domain()
197     ///                            .map(|domain| domain.clone())
198     ///                            .collect::<Vec<String>>();
199     /// assert_eq!(domains, vec![String::from("example.com"), String::from("sub.example.com")]);
200     /// # }
get_last_search_or_domain<'a>(&'a self) -> DomainIter<'a>201     pub fn get_last_search_or_domain<'a>(&'a self) -> DomainIter<'a> {
202         let domain_iter = match self.last_search {
203             LastSearch::Search => DomainIterInternal::Search(
204                 self.get_search()
205                     .and_then(|domains| Some(domains.into_iter())),
206             ),
207             LastSearch::Domain => DomainIterInternal::Domain(self.get_domain()),
208             LastSearch::None => DomainIterInternal::None,
209         };
210         DomainIter(domain_iter)
211     }
212 
213     /// Return the domain declared in the last "domain" directive.
get_domain(&self) -> Option<&String>214     pub fn get_domain(&self) -> Option<&String> {
215         self.domain.as_ref()
216     }
217 
218     /// Return the domains declared in the last "search" directive.
get_search(&self) -> Option<&Vec<String>>219     pub fn get_search(&self) -> Option<&Vec<String>> {
220         self.search.as_ref()
221     }
222 
223     /// Set the domain corresponding to the "domain" directive.
set_domain(&mut self, domain: String)224     pub fn set_domain(&mut self, domain: String) {
225         self.domain = Some(domain);
226         self.last_search = LastSearch::Domain;
227     }
228 
229     /// Set the domains corresponding the "search" directive.
set_search(&mut self, search: Vec<String>)230     pub fn set_search(&mut self, search: Vec<String>) {
231         self.search = Some(search);
232         self.last_search = LastSearch::Search;
233     }
234 
235     /// Normalize config according to glibc rulees
236     ///
237     /// Currently this method does the following things:
238     ///
239     /// 1. Truncates list of nameservers to 3 at max
240     /// 2. Truncates search list to 6 at max
241     ///
242     /// Other normalizations may be added in future as long as they hold true
243     /// for a particular GNU libc implementation.
244     ///
245     /// Note: this method is not called after parsing, because we think it's
246     /// not forward-compatible to rely on such small and ugly limits. Still,
247     /// it's useful to keep implementation as close to glibc as possible.
glibc_normalize(&mut self)248     pub fn glibc_normalize(&mut self) {
249         self.nameservers.truncate(NAMESERVER_LIMIT);
250         self.search = self.search.take().map(|mut s| {
251             s.truncate(SEARCH_LIMIT);
252             s
253         });
254     }
255 
256     /// Get nameserver or on the local machine
get_nameservers_or_local(&self) -> Vec<ScopedIp>257     pub fn get_nameservers_or_local(&self) -> Vec<ScopedIp> {
258         if self.nameservers.is_empty() {
259             vec![
260                 ScopedIp::from(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
261                 ScopedIp::from(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))),
262             ]
263         } else {
264             self.nameservers.to_vec()
265         }
266     }
267 
268     /// Get domain from config or fallback to the suffix of a hostname
269     ///
270     /// This is how glibc finds out a hostname. This method requires
271     /// ``system`` feature enabled.
272     #[cfg(feature = "system")]
get_system_domain(&self) -> Option<String>273     pub fn get_system_domain(&self) -> Option<String> {
274         if self.domain.is_some() {
275             return self.domain.clone();
276         }
277 
278         let hostname = match ::hostname::get().ok() {
279             Some(name) => name.into_string().ok(),
280             None => return None,
281         };
282 
283         hostname.and_then(|s| {
284             if let Some(pos) = s.find('.') {
285                 let hn = s[pos + 1..].to_string();
286                 if !hn.is_empty() {
287                     return Some(hn)
288                 }
289             };
290             None
291         })
292     }
293 }
294 
295 impl fmt::Display for Config {
fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result296     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
297         for nameserver in self.nameservers.iter() {
298             writeln!(fmt, "nameserver {}", nameserver)?;
299         }
300 
301         if self.last_search != LastSearch::Domain {
302             if let Some(ref domain) = self.domain {
303                 writeln!(fmt, "domain {}", domain)?;
304             }
305         }
306 
307         if let Some(ref search) = self.search {
308             if !search.is_empty() {
309                 write!(fmt, "search")?;
310                 for suffix in search.iter() {
311                     write!(fmt, " {}", suffix)?;
312                 }
313                 writeln!(fmt)?;
314             }
315         }
316 
317         if self.last_search == LastSearch::Domain {
318             if let Some(ref domain) = self.domain {
319                 writeln!(fmt, "domain {}", domain)?;
320             }
321         }
322 
323         if !self.sortlist.is_empty() {
324             write!(fmt, "sortlist")?;
325             for network in self.sortlist.iter() {
326                 write!(fmt, " {}", network)?;
327             }
328             writeln!(fmt)?;
329         }
330 
331         if self.debug {
332             writeln!(fmt, "options debug")?;
333         }
334         if self.ndots != 1 {
335             writeln!(fmt, "options ndots:{}", self.ndots)?;
336         }
337         if self.timeout != 5 {
338             writeln!(fmt, "options timeout:{}", self.timeout)?;
339         }
340         if self.attempts != 2 {
341             writeln!(fmt, "options attempts:{}", self.attempts)?;
342         }
343         if self.rotate {
344             writeln!(fmt, "options rotate")?;
345         }
346         if self.no_check_names {
347             writeln!(fmt, "options no-check-names")?;
348         }
349         if self.inet6 {
350             writeln!(fmt, "options inet6")?;
351         }
352         if self.ip6_bytestring {
353             writeln!(fmt, "options ip6-bytestring")?;
354         }
355         if self.ip6_dotint {
356             writeln!(fmt, "options ip6-dotint")?;
357         }
358         if self.edns0 {
359             writeln!(fmt, "options edns0")?;
360         }
361         if self.single_request {
362             writeln!(fmt, "options single-request")?;
363         }
364         if self.single_request_reopen {
365             writeln!(fmt, "options single-request-reopen")?;
366         }
367         if self.no_tld_query {
368             writeln!(fmt, "options no-tld-query")?;
369         }
370         if self.use_vc {
371             writeln!(fmt, "options use-vc")?;
372         }
373 
374         Ok(())
375     }
376 }
377 
378 /// An iterator returned by [`Config.get_last_search_or_domain`](struct.Config.html#method.get_last_search_or_domain)
379 #[derive(Debug, Clone)]
380 pub struct DomainIter<'a>(DomainIterInternal<'a>);
381 
382 impl<'a> Iterator for DomainIter<'a> {
383     type Item = &'a String;
384 
next(&mut self) -> Option<Self::Item>385     fn next(&mut self) -> Option<Self::Item> {
386         self.0.next()
387     }
388 }
389 
390 #[derive(Debug, Clone)]
391 enum DomainIterInternal<'a> {
392     Search(Option<Iter<'a, String>>),
393     Domain(Option<&'a String>),
394     None,
395 }
396 
397 impl<'a> Iterator for DomainIterInternal<'a> {
398     type Item = &'a String;
399 
next(&mut self) -> Option<Self::Item>400     fn next(&mut self) -> Option<Self::Item> {
401         match *self {
402             DomainIterInternal::Search(Some(ref mut domains)) => domains.next(),
403             DomainIterInternal::Domain(ref mut domain) => domain.take(),
404             _ => None,
405         }
406     }
407 }
408 
409 /// The databases that should be searched during a lookup.
410 /// This option is commonly found on openbsd.
411 #[derive(Clone, Debug, PartialEq, Eq)]
412 pub enum Lookup {
413     /// Search for entries in /etc/hosts
414     File,
415     /// Query a domain name server
416     Bind,
417     /// A database we don't know yet
418     Extra(String),
419 }
420 
421 /// The internet protocol family that is prefered.
422 /// This option is commonly found on openbsd.
423 #[derive(Clone, Debug, PartialEq, Eq)]
424 pub enum Family {
425     /// A A lookup for an ipv4 address
426     Inet4,
427     /// A AAAA lookup for an ipv6 address
428     Inet6,
429 }
430