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}