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