resolv_conf/
config.rs

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