blob: b6b64609f08f624026550097c332647050485cba [file] [log] [blame]
use std::fmt;
use std::iter::{IntoIterator, Iterator};
use std::slice::Iter;
use {grammar, Network, ParseError, ScopedIp};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
const NAMESERVER_LIMIT:usize = 3;
const SEARCH_LIMIT:usize = 6;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum LastSearch {
None,
Domain,
Search,
}
/// Represent a resolver configuration, as described in `man 5 resolv.conf`.
/// The options and defaults match those in the linux `man` page.
///
/// Note: while most fields in the structure are public the `search` and
/// `domain` fields must be accessed via methods. This is because there are
/// few different ways to treat `domain` field. In GNU libc `search` and
/// `domain` replace each other ([`get_last_search_or_domain`]).
/// In MacOS `/etc/resolve/*` files `domain` is treated in entirely different
/// way.
///
/// Also consider using [`glibc_normalize`] and [`get_system_domain`] to match
/// behavior of GNU libc. (latter requires ``system`` feature enabled)
///
/// ```rust
/// extern crate resolv_conf;
///
/// use std::net::Ipv4Addr;
/// use resolv_conf::{Config, ScopedIp};
///
/// fn main() {
/// // Create a new config
/// let mut config = Config::new();
/// config.nameservers.push(ScopedIp::V4(Ipv4Addr::new(8, 8, 8, 8)));
/// config.set_search(vec!["example.com".into()]);
///
/// // Parse a config
/// let parsed = Config::parse("nameserver 8.8.8.8\nsearch example.com").unwrap();
/// assert_eq!(parsed, config);
/// }
/// ```
///
/// [`glibc_normalize`]: #method.glibc_normalize
/// [`get_last_search_or_domain`]: #method.get_last_search_or_domain
/// [`get_system_domain`]: #method.get_system_domain
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Config {
/// List of nameservers
pub nameservers: Vec<ScopedIp>,
/// Indicated whether the last line that has been parsed is a "domain" directive or a "search"
/// directive. This is important for compatibility with glibc, since in glibc's implementation,
/// "search" and "domain" are mutually exclusive, and only the last directive is taken into
/// consideration.
last_search: LastSearch,
/// Domain to append to name when it doesn't contain ndots
domain: Option<String>,
/// List of suffixes to append to name when it doesn't contain ndots
search: Option<Vec<String>>,
/// List of preferred addresses
pub sortlist: Vec<Network>,
/// Enable DNS resolve debugging
pub debug: bool,
/// Number of dots in name to try absolute resolving first (default 1)
pub ndots: u32,
/// Dns query timeout (default 5 [sec])
pub timeout: u32,
/// Number of attempts to resolve name if server is inaccesible (default 2)
pub attempts: u32,
/// Round-robin selection of servers (default false)
pub rotate: bool,
/// Don't check names for validity (default false)
pub no_check_names: bool,
/// Try AAAA query before A
pub inet6: bool,
/// Use reverse lookup of ipv6 using bit-label format described instead
/// of nibble format
pub ip6_bytestring: bool,
/// Do ipv6 reverse lookups in ip6.int zone instead of ip6.arpa
/// (default false)
pub ip6_dotint: bool,
/// Enable dns extensions described in RFC 2671
pub edns0: bool,
/// Don't make ipv4 and ipv6 requests simultaneously
pub single_request: bool,
/// Use same socket for the A and AAAA requests
pub single_request_reopen: bool,
/// Don't resolve unqualified name as top level domain
pub no_tld_query: bool,
/// Force using TCP for DNS resolution
pub use_vc: bool,
/// Disable the automatic reloading of a changed configuration file
pub no_reload: bool,
/// Optionally send the AD (authenticated data) bit in queries
pub trust_ad: bool,
/// The order in which databases should be searched during a lookup
/// **(openbsd-only)**
pub lookup: Vec<Lookup>,
/// The order in which internet protocol families should be prefered
/// **(openbsd-only)**
pub family: Vec<Family>,
}
impl Config {
/// Create a new `Config` object with default values.
///
/// ```rust
/// # extern crate resolv_conf;
/// use resolv_conf::Config;
/// # fn main() {
/// let config = Config::new();
/// assert_eq!(config.nameservers, vec![]);
/// assert!(config.get_domain().is_none());
/// assert!(config.get_search().is_none());
/// assert_eq!(config.sortlist, vec![]);
/// assert_eq!(config.debug, false);
/// assert_eq!(config.ndots, 1);
/// assert_eq!(config.timeout, 5);
/// assert_eq!(config.attempts, 2);
/// assert_eq!(config.rotate, false);
/// assert_eq!(config.no_check_names, false);
/// assert_eq!(config.inet6, false);
/// assert_eq!(config.ip6_bytestring, false);
/// assert_eq!(config.ip6_dotint, false);
/// assert_eq!(config.edns0, false);
/// assert_eq!(config.single_request, false);
/// assert_eq!(config.single_request_reopen, false);
/// assert_eq!(config.no_tld_query, false);
/// assert_eq!(config.use_vc, false);
/// # }
pub fn new() -> Config {
Config {
nameservers: Vec::new(),
domain: None,
search: None,
last_search: LastSearch::None,
sortlist: Vec::new(),
debug: false,
ndots: 1,
timeout: 5,
attempts: 2,
rotate: false,
no_check_names: false,
inet6: false,
ip6_bytestring: false,
ip6_dotint: false,
edns0: false,
single_request: false,
single_request_reopen: false,
no_tld_query: false,
use_vc: false,
no_reload: false,
trust_ad: false,
lookup: Vec::new(),
family: Vec::new(),
}
}
/// Parse a buffer and return the corresponding `Config` object.
///
/// ```rust
/// # extern crate resolv_conf;
/// use resolv_conf::{ScopedIp, Config};
/// # fn main() {
/// let config_str = "# /etc/resolv.conf
/// nameserver 8.8.8.8
/// nameserver 8.8.4.4
/// search example.com sub.example.com
/// options ndots:8 attempts:8";
///
/// // Parse the config
/// let parsed_config = Config::parse(&config_str).expect("Failed to parse config");
///
/// // Print the config
/// println!("{:?}", parsed_config);
/// # }
/// ```
pub fn parse<T: AsRef<[u8]>>(buf: T) -> Result<Config, ParseError> {
grammar::parse(buf.as_ref())
}
/// Return the suffixes declared in the last "domain" or "search" directive.
///
/// ```rust
/// # extern crate resolv_conf;
/// use resolv_conf::{ScopedIp, Config};
/// # fn main() {
/// let config_str = "search example.com sub.example.com\ndomain localdomain";
/// let parsed_config = Config::parse(&config_str).expect("Failed to parse config");
/// let domains = parsed_config.get_last_search_or_domain()
/// .map(|domain| domain.clone())
/// .collect::<Vec<String>>();
/// assert_eq!(domains, vec![String::from("localdomain")]);
///
/// let config_str = "domain localdomain\nsearch example.com sub.example.com";
/// let parsed_config = Config::parse(&config_str).expect("Failed to parse config");
/// let domains = parsed_config.get_last_search_or_domain()
/// .map(|domain| domain.clone())
/// .collect::<Vec<String>>();
/// assert_eq!(domains, vec![String::from("example.com"), String::from("sub.example.com")]);
/// # }
pub fn get_last_search_or_domain<'a>(&'a self) -> DomainIter<'a> {
let domain_iter = match self.last_search {
LastSearch::Search => DomainIterInternal::Search(
self.get_search()
.and_then(|domains| Some(domains.into_iter())),
),
LastSearch::Domain => DomainIterInternal::Domain(self.get_domain()),
LastSearch::None => DomainIterInternal::None,
};
DomainIter(domain_iter)
}
/// Return the domain declared in the last "domain" directive.
pub fn get_domain(&self) -> Option<&String> {
self.domain.as_ref()
}
/// Return the domains declared in the last "search" directive.
pub fn get_search(&self) -> Option<&Vec<String>> {
self.search.as_ref()
}
/// Set the domain corresponding to the "domain" directive.
pub fn set_domain(&mut self, domain: String) {
self.domain = Some(domain);
self.last_search = LastSearch::Domain;
}
/// Set the domains corresponding the "search" directive.
pub fn set_search(&mut self, search: Vec<String>) {
self.search = Some(search);
self.last_search = LastSearch::Search;
}
/// Normalize config according to glibc rulees
///
/// Currently this method does the following things:
///
/// 1. Truncates list of nameservers to 3 at max
/// 2. Truncates search list to 6 at max
///
/// Other normalizations may be added in future as long as they hold true
/// for a particular GNU libc implementation.
///
/// Note: this method is not called after parsing, because we think it's
/// not forward-compatible to rely on such small and ugly limits. Still,
/// it's useful to keep implementation as close to glibc as possible.
pub fn glibc_normalize(&mut self) {
self.nameservers.truncate(NAMESERVER_LIMIT);
self.search = self.search.take().map(|mut s| {
s.truncate(SEARCH_LIMIT);
s
});
}
/// Get nameserver or on the local machine
pub fn get_nameservers_or_local(&self) -> Vec<ScopedIp> {
if self.nameservers.is_empty() {
vec![
ScopedIp::from(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
ScopedIp::from(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))),
]
} else {
self.nameservers.to_vec()
}
}
/// Get domain from config or fallback to the suffix of a hostname
///
/// This is how glibc finds out a hostname. This method requires
/// ``system`` feature enabled.
#[cfg(feature = "system")]
pub fn get_system_domain(&self) -> Option<String> {
if self.domain.is_some() {
return self.domain.clone();
}
let hostname = match ::hostname::get().ok() {
Some(name) => name.into_string().ok(),
None => return None,
};
hostname.and_then(|s| {
if let Some(pos) = s.find('.') {
let hn = s[pos + 1..].to_string();
if !hn.is_empty() {
return Some(hn)
}
};
None
})
}
}
impl fmt::Display for Config {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
for nameserver in self.nameservers.iter() {
writeln!(fmt, "nameserver {}", nameserver)?;
}
if self.last_search != LastSearch::Domain {
if let Some(ref domain) = self.domain {
writeln!(fmt, "domain {}", domain)?;
}
}
if let Some(ref search) = self.search {
if !search.is_empty() {
write!(fmt, "search")?;
for suffix in search.iter() {
write!(fmt, " {}", suffix)?;
}
writeln!(fmt)?;
}
}
if self.last_search == LastSearch::Domain {
if let Some(ref domain) = self.domain {
writeln!(fmt, "domain {}", domain)?;
}
}
if !self.sortlist.is_empty() {
write!(fmt, "sortlist")?;
for network in self.sortlist.iter() {
write!(fmt, " {}", network)?;
}
writeln!(fmt)?;
}
if self.debug {
writeln!(fmt, "options debug")?;
}
if self.ndots != 1 {
writeln!(fmt, "options ndots:{}", self.ndots)?;
}
if self.timeout != 5 {
writeln!(fmt, "options timeout:{}", self.timeout)?;
}
if self.attempts != 2 {
writeln!(fmt, "options attempts:{}", self.attempts)?;
}
if self.rotate {
writeln!(fmt, "options rotate")?;
}
if self.no_check_names {
writeln!(fmt, "options no-check-names")?;
}
if self.inet6 {
writeln!(fmt, "options inet6")?;
}
if self.ip6_bytestring {
writeln!(fmt, "options ip6-bytestring")?;
}
if self.ip6_dotint {
writeln!(fmt, "options ip6-dotint")?;
}
if self.edns0 {
writeln!(fmt, "options edns0")?;
}
if self.single_request {
writeln!(fmt, "options single-request")?;
}
if self.single_request_reopen {
writeln!(fmt, "options single-request-reopen")?;
}
if self.no_tld_query {
writeln!(fmt, "options no-tld-query")?;
}
if self.use_vc {
writeln!(fmt, "options use-vc")?;
}
if self.no_reload {
writeln!(fmt, "options no-reload")?;
}
if self.trust_ad {
writeln!(fmt, "options trust-ad")?;
}
Ok(())
}
}
/// An iterator returned by [`Config.get_last_search_or_domain`](struct.Config.html#method.get_last_search_or_domain)
#[derive(Debug, Clone)]
pub struct DomainIter<'a>(DomainIterInternal<'a>);
impl<'a> Iterator for DomainIter<'a> {
type Item = &'a String;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
#[derive(Debug, Clone)]
enum DomainIterInternal<'a> {
Search(Option<Iter<'a, String>>),
Domain(Option<&'a String>),
None,
}
impl<'a> Iterator for DomainIterInternal<'a> {
type Item = &'a String;
fn next(&mut self) -> Option<Self::Item> {
match *self {
DomainIterInternal::Search(Some(ref mut domains)) => domains.next(),
DomainIterInternal::Domain(ref mut domain) => domain.take(),
_ => None,
}
}
}
/// The databases that should be searched during a lookup.
/// This option is commonly found on openbsd.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Lookup {
/// Search for entries in /etc/hosts
File,
/// Query a domain name server
Bind,
/// A database we don't know yet
Extra(String),
}
/// The internet protocol family that is prefered.
/// This option is commonly found on openbsd.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Family {
/// A A lookup for an ipv4 address
Inet4,
/// A AAAA lookup for an ipv6 address
Inet6,
}