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}