resolv_conf/
config.rs

1use std::fmt;
2use std::iter::{IntoIterator, Iterator};
3use std::slice::Iter;
4use {grammar, Network, ParseError, ScopedIp};
5use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
6
7const NAMESERVER_LIMIT:usize = 3;
8const SEARCH_LIMIT:usize = 6;
9
10#[derive(Copy, Clone, PartialEq, Eq, Debug)]
11enum 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)]
53pub 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    /// Disable the automatic reloading of a changed configuration file
98    pub no_reload: bool,
99    /// Optionally send the AD (authenticated data) bit in queries
100    pub trust_ad: bool,
101    /// The order in which databases should be searched during a lookup
102    /// **(openbsd-only)**
103    pub lookup: Vec<Lookup>,
104    /// The order in which internet protocol families should be prefered
105    /// **(openbsd-only)**
106    pub family: Vec<Family>,
107}
108
109impl Config {
110    /// Create a new `Config` object with default values.
111    ///
112    /// ```rust
113    /// # extern crate resolv_conf;
114    /// use resolv_conf::Config;
115    /// # fn main() {
116    /// let config = Config::new();
117    /// assert_eq!(config.nameservers, vec![]);
118    /// assert!(config.get_domain().is_none());
119    /// assert!(config.get_search().is_none());
120    /// assert_eq!(config.sortlist, vec![]);
121    /// assert_eq!(config.debug, false);
122    /// assert_eq!(config.ndots, 1);
123    /// assert_eq!(config.timeout, 5);
124    /// assert_eq!(config.attempts, 2);
125    /// assert_eq!(config.rotate, false);
126    /// assert_eq!(config.no_check_names, false);
127    /// assert_eq!(config.inet6, false);
128    /// assert_eq!(config.ip6_bytestring, false);
129    /// assert_eq!(config.ip6_dotint, false);
130    /// assert_eq!(config.edns0, false);
131    /// assert_eq!(config.single_request, false);
132    /// assert_eq!(config.single_request_reopen, false);
133    /// assert_eq!(config.no_tld_query, false);
134    /// assert_eq!(config.use_vc, false);
135    /// # }
136    pub fn new() -> Config {
137        Config {
138            nameservers: Vec::new(),
139            domain: None,
140            search: None,
141            last_search: LastSearch::None,
142            sortlist: Vec::new(),
143            debug: false,
144            ndots: 1,
145            timeout: 5,
146            attempts: 2,
147            rotate: false,
148            no_check_names: false,
149            inet6: false,
150            ip6_bytestring: false,
151            ip6_dotint: false,
152            edns0: false,
153            single_request: false,
154            single_request_reopen: false,
155            no_tld_query: false,
156            use_vc: false,
157            no_reload: false,
158            trust_ad: false,
159            lookup: Vec::new(),
160            family: Vec::new(),
161        }
162    }
163
164    /// Parse a buffer and return the corresponding `Config` object.
165    ///
166    /// ```rust
167    /// # extern crate resolv_conf;
168    /// use resolv_conf::{ScopedIp, Config};
169    /// # fn main() {
170    /// let config_str = "# /etc/resolv.conf
171    /// nameserver  8.8.8.8
172    /// nameserver  8.8.4.4
173    /// search      example.com sub.example.com
174    /// options     ndots:8 attempts:8";
175    ///
176    /// // Parse the config
177    /// let parsed_config = Config::parse(&config_str).expect("Failed to parse config");
178    ///
179    /// // Print the config
180    /// println!("{:?}", parsed_config);
181    /// # }
182    /// ```
183    pub fn parse<T: AsRef<[u8]>>(buf: T) -> Result<Config, ParseError> {
184        grammar::parse(buf.as_ref())
185    }
186
187    /// Return the suffixes declared in the last "domain" or "search" directive.
188    ///
189    /// ```rust
190    /// # extern crate resolv_conf;
191    /// use resolv_conf::{ScopedIp, Config};
192    /// # fn main() {
193    /// let config_str = "search example.com sub.example.com\ndomain localdomain";
194    /// let parsed_config = Config::parse(&config_str).expect("Failed to parse config");
195    /// let domains = parsed_config.get_last_search_or_domain()
196    ///                            .map(|domain| domain.clone())
197    ///                            .collect::<Vec<String>>();
198    /// assert_eq!(domains, vec![String::from("localdomain")]);
199    ///
200    /// let config_str = "domain localdomain\nsearch example.com sub.example.com";
201    /// let parsed_config = Config::parse(&config_str).expect("Failed to parse config");
202    /// let domains = parsed_config.get_last_search_or_domain()
203    ///                            .map(|domain| domain.clone())
204    ///                            .collect::<Vec<String>>();
205    /// assert_eq!(domains, vec![String::from("example.com"), String::from("sub.example.com")]);
206    /// # }
207    pub fn get_last_search_or_domain<'a>(&'a self) -> DomainIter<'a> {
208        let domain_iter = match self.last_search {
209            LastSearch::Search => DomainIterInternal::Search(
210                self.get_search()
211                    .and_then(|domains| Some(domains.into_iter())),
212            ),
213            LastSearch::Domain => DomainIterInternal::Domain(self.get_domain()),
214            LastSearch::None => DomainIterInternal::None,
215        };
216        DomainIter(domain_iter)
217    }
218
219    /// Return the domain declared in the last "domain" directive.
220    pub fn get_domain(&self) -> Option<&String> {
221        self.domain.as_ref()
222    }
223
224    /// Return the domains declared in the last "search" directive.
225    pub fn get_search(&self) -> Option<&Vec<String>> {
226        self.search.as_ref()
227    }
228
229    /// Set the domain corresponding to the "domain" directive.
230    pub fn set_domain(&mut self, domain: String) {
231        self.domain = Some(domain);
232        self.last_search = LastSearch::Domain;
233    }
234
235    /// Set the domains corresponding the "search" directive.
236    pub fn set_search(&mut self, search: Vec<String>) {
237        self.search = Some(search);
238        self.last_search = LastSearch::Search;
239    }
240
241    /// Normalize config according to glibc rulees
242    ///
243    /// Currently this method does the following things:
244    ///
245    /// 1. Truncates list of nameservers to 3 at max
246    /// 2. Truncates search list to 6 at max
247    ///
248    /// Other normalizations may be added in future as long as they hold true
249    /// for a particular GNU libc implementation.
250    ///
251    /// Note: this method is not called after parsing, because we think it's
252    /// not forward-compatible to rely on such small and ugly limits. Still,
253    /// it's useful to keep implementation as close to glibc as possible.
254    pub fn glibc_normalize(&mut self) {
255        self.nameservers.truncate(NAMESERVER_LIMIT);
256        self.search = self.search.take().map(|mut s| {
257            s.truncate(SEARCH_LIMIT);
258            s
259        });
260    }
261
262    /// Get nameserver or on the local machine
263    pub fn get_nameservers_or_local(&self) -> Vec<ScopedIp> {
264        if self.nameservers.is_empty() {
265            vec![
266                ScopedIp::from(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
267                ScopedIp::from(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))),
268            ]
269        } else {
270            self.nameservers.to_vec()
271        }
272    }
273
274    /// Get domain from config or fallback to the suffix of a hostname
275    ///
276    /// This is how glibc finds out a hostname. This method requires
277    /// ``system`` feature enabled.
278    #[cfg(feature = "system")]
279    pub fn get_system_domain(&self) -> Option<String> {
280        if self.domain.is_some() {
281            return self.domain.clone();
282        }
283
284        let hostname = match ::hostname::get().ok() {
285            Some(name) => name.into_string().ok(),
286            None => return None,
287        };
288
289        hostname.and_then(|s| {
290            if let Some(pos) = s.find('.') {
291                let hn = s[pos + 1..].to_string();
292                if !hn.is_empty() {
293                    return Some(hn)
294                }
295            };
296            None
297        })
298    }
299}
300
301impl fmt::Display for Config {
302    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
303        for nameserver in self.nameservers.iter() {
304            writeln!(fmt, "nameserver {}", nameserver)?;
305        }
306
307        if self.last_search != LastSearch::Domain {
308            if let Some(ref domain) = self.domain {
309                writeln!(fmt, "domain {}", domain)?;
310            }
311        }
312
313        if let Some(ref search) = self.search {
314            if !search.is_empty() {
315                write!(fmt, "search")?;
316                for suffix in search.iter() {
317                    write!(fmt, " {}", suffix)?;
318                }
319                writeln!(fmt)?;
320            }
321        }
322
323        if self.last_search == LastSearch::Domain {
324            if let Some(ref domain) = self.domain {
325                writeln!(fmt, "domain {}", domain)?;
326            }
327        }
328
329        if !self.sortlist.is_empty() {
330            write!(fmt, "sortlist")?;
331            for network in self.sortlist.iter() {
332                write!(fmt, " {}", network)?;
333            }
334            writeln!(fmt)?;
335        }
336
337        if self.debug {
338            writeln!(fmt, "options debug")?;
339        }
340        if self.ndots != 1 {
341            writeln!(fmt, "options ndots:{}", self.ndots)?;
342        }
343        if self.timeout != 5 {
344            writeln!(fmt, "options timeout:{}", self.timeout)?;
345        }
346        if self.attempts != 2 {
347            writeln!(fmt, "options attempts:{}", self.attempts)?;
348        }
349        if self.rotate {
350            writeln!(fmt, "options rotate")?;
351        }
352        if self.no_check_names {
353            writeln!(fmt, "options no-check-names")?;
354        }
355        if self.inet6 {
356            writeln!(fmt, "options inet6")?;
357        }
358        if self.ip6_bytestring {
359            writeln!(fmt, "options ip6-bytestring")?;
360        }
361        if self.ip6_dotint {
362            writeln!(fmt, "options ip6-dotint")?;
363        }
364        if self.edns0 {
365            writeln!(fmt, "options edns0")?;
366        }
367        if self.single_request {
368            writeln!(fmt, "options single-request")?;
369        }
370        if self.single_request_reopen {
371            writeln!(fmt, "options single-request-reopen")?;
372        }
373        if self.no_tld_query {
374            writeln!(fmt, "options no-tld-query")?;
375        }
376        if self.use_vc {
377            writeln!(fmt, "options use-vc")?;
378        }
379        if self.no_reload {
380            writeln!(fmt, "options no-reload")?;
381        }
382        if self.trust_ad {
383            writeln!(fmt, "options trust-ad")?;
384        }
385
386        Ok(())
387    }
388}
389
390/// An iterator returned by [`Config.get_last_search_or_domain`](struct.Config.html#method.get_last_search_or_domain)
391#[derive(Debug, Clone)]
392pub struct DomainIter<'a>(DomainIterInternal<'a>);
393
394impl<'a> Iterator for DomainIter<'a> {
395    type Item = &'a String;
396
397    fn next(&mut self) -> Option<Self::Item> {
398        self.0.next()
399    }
400}
401
402#[derive(Debug, Clone)]
403enum DomainIterInternal<'a> {
404    Search(Option<Iter<'a, String>>),
405    Domain(Option<&'a String>),
406    None,
407}
408
409impl<'a> Iterator for DomainIterInternal<'a> {
410    type Item = &'a String;
411
412    fn next(&mut self) -> Option<Self::Item> {
413        match *self {
414            DomainIterInternal::Search(Some(ref mut domains)) => domains.next(),
415            DomainIterInternal::Domain(ref mut domain) => domain.take(),
416            _ => None,
417        }
418    }
419}
420
421/// The databases that should be searched during a lookup.
422/// This option is commonly found on openbsd.
423#[derive(Clone, Debug, PartialEq, Eq)]
424pub enum Lookup {
425    /// Search for entries in /etc/hosts
426    File,
427    /// Query a domain name server
428    Bind,
429    /// A database we don't know yet
430    Extra(String),
431}
432
433/// The internet protocol family that is prefered.
434/// This option is commonly found on openbsd.
435#[derive(Clone, Debug, PartialEq, Eq)]
436pub enum Family {
437    /// A A lookup for an ipv4 address
438    Inet4,
439    /// A AAAA lookup for an ipv6 address
440    Inet6,
441}