resolv-conf-0.7.0/.cargo_vcs_info.json0000644000000001121375303065300133320ustar { "git": { "sha1": "2bedcfaa6ee349f6e9d3e5156c06981da0c07ef8" } } resolv-conf-0.7.0/.gitignore010066400037200003720000000000351375303062700141300ustar 00000000000000/Cargo.lock /.vagga /target resolv-conf-0.7.0/.rustfmt.toml010066400037200003720000000001401375303062700146140ustar 00000000000000use_try_shorthand = true error_on_line_overflow = false error_on_line_overflow_comments = false resolv-conf-0.7.0/.travis.yml010066400037200003720000000006731375303062700142610ustar 00000000000000sudo: false dist: trusty language: rust cache: - cargo before_cache: - rm -r $TRAVIS_BUILD_DIR/target/debug jobs: include: - os: linux rust: stable - os: linux rust: beta - os: linux rust: nightly # deploy - stage: publish os: linux rust: stable install: true script: true deploy: - provider: script script: 'cargo publish --verbose --token=$CARGO_TOKEN' on: tags: true resolv-conf-0.7.0/Cargo.lock0000644000000032531375303065300113160ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "hostname" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ "libc", "match_cfg", "winapi", ] [[package]] name = "libc" version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" [[package]] name = "match_cfg" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "resolv-conf" version = "0.7.0" dependencies = [ "hostname", "quick-error", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" resolv-conf-0.7.0/Cargo.toml0000644000000021511375303065300113350ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "resolv-conf" version = "0.7.0" authors = ["paul@colomiets.name"] description = " The resolv.conf file parser\n" homepage = "http://github.com/tailhook/resolv-conf" documentation = "https://docs.rs/resolv-conf/" readme = "README.md" keywords = ["dns", "unix", "conf", "resolv"] categories = ["parser-implementations"] license = "MIT/Apache-2.0" repository = "http://github.com/tailhook/resolv-conf" [lib] name = "resolv_conf" path = "src/lib.rs" [dependencies.hostname] version = "^0.3" optional = true [dependencies.quick-error] version = "1.0.0" [features] system = ["hostname"] resolv-conf-0.7.0/Cargo.toml.orig010066400037200003720000000011201375303062700150230ustar 00000000000000[package] name = "resolv-conf" description = """ The resolv.conf file parser """ license = "MIT/Apache-2.0" readme = "README.md" keywords = ["dns", "unix", "conf", "resolv"] categories = ["parser-implementations"] homepage = "http://github.com/tailhook/resolv-conf" documentation = "https://docs.rs/resolv-conf/" repository = "http://github.com/tailhook/resolv-conf" version = "0.7.0" authors = ["paul@colomiets.name"] [dependencies] quick-error = "1.0.0" hostname = { version = "^0.3", optional = true } [features] system = ["hostname"] [lib] name = "resolv_conf" path = "src/lib.rs" resolv-conf-0.7.0/LICENSE-APACHE010066400037200003720000000261361375303062700140760ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. resolv-conf-0.7.0/LICENSE-MIT010066400037200003720000000020631375303062700135770ustar 00000000000000Copyright (c) 2015-2016 The resolv-conf Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. resolv-conf-0.7.0/README.md010066400037200003720000000013421375303062700134210ustar 00000000000000Resolv-conf =========== **Status: Beta** A `/etc/resolv.conf` parser crate for rust. Why? ==== 1. There is no bare file parser in the crates.io at the moment 2. I needed one to make dns resolver for [rotor] [rotor]: http://github.com/tailhook/rotor License ======= Licensed under either of * Apache License, Version 2.0, (./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) * MIT license (./LICENSE-MIT or http://opensource.org/licenses/MIT) at your option. Contribution ------------ Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. resolv-conf-0.7.0/bulk.yaml010066400037200003720000000002051375303062700137600ustar 00000000000000minimum-bulk: v0.4.5 versions: - file: Cargo.toml block-start: ^\[package\] block-end: ^\[.*\] regex: ^version\s*=\s*"(\S+)" resolv-conf-0.7.0/examples/parse.rs010066400037200003720000000005061375303062700154410ustar 00000000000000use std::io::Read; use std::fs::File; extern crate resolv_conf; fn main() { let mut buf = Vec::with_capacity(4096); let mut f = File::open("/etc/resolv.conf").unwrap(); f.read_to_end(&mut buf).unwrap(); let cfg = resolv_conf::Config::parse(&buf).unwrap(); println!("---- Config -----\n{:#?}\n", cfg); } resolv-conf-0.7.0/src/config.rs010066400037200003720000000351051375303062700145500ustar 00000000000000use 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, /// 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, /// List of suffixes to append to name when it doesn't contain ndots search: Option>, /// List of preferred addresses pub sortlist: Vec, /// 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, /// The order in which internet protocol families should be prefered /// **(openbsd-only)** pub family: Vec, } 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>(buf: T) -> Result { 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::>(); /// 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::>(); /// 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> { 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) { 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 { 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 { 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.0.next() } } #[derive(Debug, Clone)] enum DomainIterInternal<'a> { Search(Option>), Domain(Option<&'a String>), None, } impl<'a> Iterator for DomainIterInternal<'a> { type Item = &'a String; fn next(&mut self) -> Option { 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, } resolv-conf-0.7.0/src/grammar.rs010066400037200003720000000216231375303062700147310ustar 00000000000000use std::net::{Ipv4Addr, Ipv6Addr}; use std::str::{Utf8Error, from_utf8}; use {AddrParseError, Config, Network, Lookup, Family}; quick_error!{ /// Error while parsing resolv.conf file #[derive(Debug)] pub enum ParseError { /// Error that may be returned when the string to parse contains invalid UTF-8 sequences InvalidUtf8(line: usize, err: Utf8Error) { display("bad unicode at line {}: {}", line, err) cause(err) } /// Error returned a value for a given directive is invalid. /// This can also happen when the value is missing, if the directive requires a value. InvalidValue(line: usize) { display("directive at line {} is improperly formatted \ or contains invalid value", line) } /// Error returned when a value for a given option is invalid. /// This can also happen when the value is missing, if the option requires a value. InvalidOptionValue(line: usize) { display("directive options at line {} contains invalid \ value of some option", line) } /// Error returned when a invalid option is found. InvalidOption(line: usize) { display("option at line {} is not recognized", line) } /// Error returned when a invalid directive is found. InvalidDirective(line: usize) { display("directive at line {} is not recognized", line) } /// Error returned when a value cannot be parsed an an IP address. InvalidIp(line: usize, err: AddrParseError) { display("directive at line {} contains invalid IP: {}", line, err) } /// Error returned when there is extra data at the end of a line. ExtraData(line: usize) { display("extra data at the end of the line {}", line) } } } fn ip_v4_netw(val: &str) -> Result { let mut pair = val.splitn(2, '/'); let ip: Ipv4Addr = pair.next().unwrap().parse()?; if ip.is_unspecified() { return Err(AddrParseError); } if let Some(mask) = pair.next() { let mask = mask.parse()?; // make sure this is a valid mask let value: u32 = ip.octets().iter().fold(0, |acc, &x| acc + u32::from(x)); if value == 0 || (value & !value != 0) { Err(AddrParseError) } else { Ok(Network::V4(ip, mask)) } } else { // We have to "guess" the mask. // // FIXME(@little-dude) right now, we look at the number or bytes that are 0, but maybe we // should use the number of bits that are 0. // // In other words, with this implementation, the mask of `128.192.0.0` will be // `255.255.0.0` (a.k.a `/16`). But we could also consider that the mask is `/10` (a.k.a // `255.63.0.0`). // // My only source on topic is the "DNS and Bind" book which suggests using bytes, not bits. let octets = ip.octets(); let mask = if octets[3] == 0 { if octets[2] == 0 { if octets[1] == 0 { Ipv4Addr::new(255, 0, 0, 0) } else { Ipv4Addr::new(255, 255, 0, 0) } } else { Ipv4Addr::new(255, 255, 255, 0) } } else { Ipv4Addr::new(255, 255, 255, 255) }; Ok(Network::V4(ip, mask)) } } fn ip_v6_netw(val: &str) -> Result { let mut pair = val.splitn(2, '/'); let ip = pair.next().unwrap().parse()?; if let Some(msk) = pair.next() { // FIXME: validate the mask Ok(Network::V6(ip, msk.parse()?)) } else { // FIXME: "guess" an appropriate mask for the IP Ok(Network::V6( ip, Ipv6Addr::new( 65_535, 65_535, 65_535, 65_535, 65_535, 65_535, 65_535, 65_535, ), )) } } pub(crate) fn parse(bytes: &[u8]) -> Result { use self::ParseError::*; let mut cfg = Config::new(); 'lines: for (lineno, line) in bytes.split(|&x| x == b'\n').enumerate() { for &c in line.iter() { if c != b'\t' && c != b' ' { if c == b';' || c == b'#' { continue 'lines; } else { break; } } } // All that dances above to allow invalid utf-8 inside the comments let mut words = from_utf8(line) .map_err(|e| InvalidUtf8(lineno, e))? // ignore everything after ';' or '#' .split(|c| c == ';' || c == '#') .next() .ok_or_else(|| InvalidValue(lineno))? .split_whitespace(); let keyword = match words.next() { Some(x) => x, None => continue, }; match keyword { "nameserver" => { let srv = words .next() .ok_or_else(|| InvalidValue(lineno)) .map(|addr| addr.parse().map_err(|e| InvalidIp(lineno, e)))??; cfg.nameservers.push(srv); if words.next().is_some() { return Err(ExtraData(lineno)); } } "domain" => { let dom = words .next() .and_then(|x| x.parse().ok()) .ok_or_else(|| InvalidValue(lineno))?; cfg.set_domain(dom); if words.next().is_some() { return Err(ExtraData(lineno)); } } "search" => { cfg.set_search(words.map(|x| x.to_string()).collect()); } "sortlist" => { cfg.sortlist.clear(); for pair in words { let netw = ip_v4_netw(pair) .or_else(|_| ip_v6_netw(pair)) .map_err(|e| InvalidIp(lineno, e))?; cfg.sortlist.push(netw); } } "options" => { for pair in words { let mut iter = pair.splitn(2, ':'); let key = iter.next().unwrap(); let value = iter.next(); if iter.next().is_some() { return Err(ExtraData(lineno)); } match (key, value) { // TODO(tailhook) ensure that values are None? ("debug", _) => cfg.debug = true, ("ndots", Some(x)) => { cfg.ndots = x.parse().map_err(|_| InvalidOptionValue(lineno))? } ("timeout", Some(x)) => { cfg.timeout = x.parse().map_err(|_| InvalidOptionValue(lineno))? } ("attempts", Some(x)) => { cfg.attempts = x.parse().map_err(|_| InvalidOptionValue(lineno))? } ("rotate", _) => cfg.rotate = true, ("no-check-names", _) => cfg.no_check_names = true, ("inet6", _) => cfg.inet6 = true, ("ip6-bytestring", _) => cfg.ip6_bytestring = true, ("ip6-dotint", _) => cfg.ip6_dotint = true, ("no-ip6-dotint", _) => cfg.ip6_dotint = false, ("edns0", _) => cfg.edns0 = true, ("single-request", _) => cfg.single_request = true, ("single-request-reopen", _) => cfg.single_request_reopen = true, ("no-reload", _) => cfg.no_reload = true, ("trust-ad", _) => cfg.trust_ad = true, ("no-tld-query", _) => cfg.no_tld_query = true, ("use-vc", _) => cfg.use_vc = true, _ => return Err(InvalidOption(lineno)), } } } "lookup" => { for word in words { match word { "file" => cfg.lookup.push(Lookup::File), "bind" => cfg.lookup.push(Lookup::Bind), extra => cfg.lookup.push(Lookup::Extra(extra.to_string())), } } } "family" => { for word in words { match word { "inet4" => cfg.family.push(Family::Inet4), "inet6" => cfg.family.push(Family::Inet6), _ => return Err(InvalidValue(lineno)), } } } _ => return Err(InvalidDirective(lineno)), } } Ok(cfg) } resolv-conf-0.7.0/src/ip.rs010066400037200003720000000074471375303062700137230ustar 00000000000000use std::error::Error; use std::fmt; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::str::FromStr; /// A network, that is an IP address and a mask #[derive(Clone, Debug, PartialEq, Eq)] pub enum Network { /// Represent an IPv4 network address V4(Ipv4Addr, Ipv4Addr), /// Represent an IPv6 network address V6(Ipv6Addr, Ipv6Addr), } impl fmt::Display for Network { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match *self { Network::V4(address, mask) => write!(fmt, "{}/{}", address, mask), Network::V6(address, mask) => write!(fmt, "{}/{}", address, mask), } } } /// Represent an IP address. This type is similar to `std::net::IpAddr` but it supports IPv6 scope /// identifiers. #[derive(Debug, Clone, PartialEq, Eq)] pub enum ScopedIp { /// Represent an IPv4 address V4(Ipv4Addr), /// Represent an IPv6 and its scope identifier, if any V6(Ipv6Addr, Option), } impl Into for ScopedIp { fn into(self) -> IpAddr { match self { ScopedIp::V4(ip) => IpAddr::from(ip), ScopedIp::V6(ip, _) => IpAddr::from(ip), } } } impl<'a> Into for &'a ScopedIp { fn into(self) -> IpAddr { match *self { ScopedIp::V4(ref ip) => IpAddr::from(*ip), ScopedIp::V6(ref ip, _) => IpAddr::from(*ip), } } } impl From for ScopedIp { fn from(value: Ipv6Addr) -> Self { ScopedIp::V6(value, None) } } impl From for ScopedIp { fn from(value: Ipv4Addr) -> Self { ScopedIp::V4(value) } } impl From for ScopedIp { fn from(value: IpAddr) -> Self { match value { IpAddr::V4(ip) => ScopedIp::from(ip), IpAddr::V6(ip) => ScopedIp::from(ip), } } } impl FromStr for ScopedIp { type Err = AddrParseError; /// Parse a string representing an IP address. fn from_str(s: &str) -> Result { let mut parts = s.split('%'); let addr = parts.next().unwrap(); match IpAddr::from_str(addr) { Ok(IpAddr::V4(ip)) => { if parts.next().is_some() { // It's not a valid IPv4 address if it contains a '%' Err(AddrParseError) } else { Ok(ScopedIp::from(ip)) } } Ok(IpAddr::V6(ip)) => if let Some(scope_id) = parts.next() { if scope_id.is_empty() { return Err(AddrParseError); } for c in scope_id.chars() { if !c.is_alphanumeric() { return Err(AddrParseError); } } Ok(ScopedIp::V6(ip, Some(scope_id.to_string()))) } else { Ok(ScopedIp::V6(ip, None)) }, Err(e) => Err(e.into()), } } } impl fmt::Display for ScopedIp { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match *self { ScopedIp::V4(ref address) => address.fmt(fmt), ScopedIp::V6(ref address, None) => address.fmt(fmt), ScopedIp::V6(ref address, Some(ref scope)) => write!(fmt, "{}%{}", address, scope), } } } /// An error which can be returned when parsing an IP address. #[derive(Debug, Clone, PartialEq, Eq)] pub struct AddrParseError; impl fmt::Display for AddrParseError { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.write_str(self.description()) } } impl Error for AddrParseError { fn description(&self) -> &str { "invalid IP address syntax" } } impl From<::std::net::AddrParseError> for AddrParseError { fn from(_: ::std::net::AddrParseError) -> Self { AddrParseError } } resolv-conf-0.7.0/src/lib.rs010066400037200003720000000063421375303062700140520ustar 00000000000000//! The crate simply parses `/etc/resolv.conf` file and creates a config object //! //! # Examples //! //! ## Parsing a config from a string //! ```rust //! extern crate resolv_conf; //! //! use std::net::{Ipv4Addr, Ipv6Addr}; //! use resolv_conf::{ScopedIp, Config, Network}; //! //! fn main() { //! let config_str = " //! options ndots:8 timeout:8 attempts:8 //! //! domain example.com //! search example.com sub.example.com //! //! nameserver 2001:4860:4860::8888 //! nameserver 2001:4860:4860::8844 //! nameserver 8.8.8.8 //! nameserver 8.8.4.4 //! //! options rotate //! options inet6 no-tld-query //! //! sortlist 130.155.160.0/255.255.240.0 130.155.0.0"; //! //! // Parse the config. //! let parsed_config = Config::parse(&config_str).expect("Failed to parse config"); //! //! // We can build configs manually as well, either directly or with Config::new() //! let mut expected_config = Config::new(); //! expected_config.nameservers = vec![ //! ScopedIp::V6(Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888), None), //! ScopedIp::V6(Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8844), None), //! ScopedIp::V4(Ipv4Addr::new(8, 8, 8, 8)), //! ScopedIp::V4(Ipv4Addr::new(8, 8, 4, 4)), //! ]; //! expected_config.sortlist = vec![ //! Network::V4(Ipv4Addr::new(130, 155, 160, 0), Ipv4Addr::new(255, 255, 240, 0)), //! Network::V4(Ipv4Addr::new(130, 155, 0, 0), Ipv4Addr::new(255, 255, 0, 0)), //! ]; //! expected_config.debug = false; //! expected_config.ndots = 8; //! expected_config.timeout = 8; //! expected_config.attempts = 8; //! expected_config.rotate = true; //! expected_config.no_check_names = false; //! expected_config.inet6 = true; //! expected_config.ip6_bytestring = false; //! expected_config.ip6_dotint = false; //! expected_config.edns0 = false; //! expected_config.single_request = false; //! expected_config.single_request_reopen = false; //! expected_config.no_tld_query = true; //! expected_config.use_vc = false; //! expected_config.set_domain(String::from("example.com")); //! expected_config.set_search(vec![ //! String::from("example.com"), //! String::from("sub.example.com") //! ]); //! //! // We can compare configurations, since resolv_conf::Config implements Eq //! assert_eq!(parsed_config, expected_config); //! } //! ``` //! //! ## Parsing a file //! //! ```rust //! use std::io::Read; //! use std::fs::File; //! //! extern crate resolv_conf; //! //! fn main() { //! // Read the file //! let mut buf = Vec::with_capacity(4096); //! let mut f = File::open("/etc/resolv.conf").unwrap(); //! f.read_to_end(&mut buf).unwrap(); //! //! // Parse the buffer //! let cfg = resolv_conf::Config::parse(&buf).unwrap(); //! //! // Print the config //! println!("---- Parsed /etc/resolv.conf -----\n{:#?}\n", cfg); //! } //! ``` #![warn(missing_debug_implementations)] #![warn(missing_docs)] #[macro_use] extern crate quick_error; #[cfg(feature = "system")] extern crate hostname; mod grammar; mod ip; mod config; pub use grammar::ParseError; pub use ip::{AddrParseError, Network, ScopedIp}; pub use config::{Config, DomainIter, Lookup, Family}; resolv-conf-0.7.0/tests/hosts010066400037200003720000000005071375303062700143710ustar 00000000000000## # Host Database # # localhost is used to configure the loopback interface # when the system is booting. Do not change this entry. ## 127.0.0.1 localhost 255.255.255.255 broadcasthost ::1 localhost fe80::1%lo0 localhost 10.0.1.102 example.com 10.0.1.111 a.example.com b.example.com 10.1.0.104resolv-conf-0.7.0/tests/lib.rs010066400037200003720000000310411375303062700144170ustar 00000000000000extern crate resolv_conf; use resolv_conf::{Network, ScopedIp, Lookup, Family}; use std::path::Path; use std::io::Read; use std::fs::File; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; #[test] fn test_comment() { resolv_conf::Config::parse("#").unwrap(); resolv_conf::Config::parse(";").unwrap(); resolv_conf::Config::parse("#junk").unwrap(); resolv_conf::Config::parse("# junk").unwrap(); resolv_conf::Config::parse(";junk").unwrap(); resolv_conf::Config::parse("; junk").unwrap(); } fn ip(s: &str) -> ScopedIp { s.parse().unwrap() } fn parse_str(s: &str) -> resolv_conf::Config { resolv_conf::Config::parse(s).unwrap() } #[test] fn test_basic_options() { assert_eq!( parse_str("nameserver 127.0.0.1").nameservers, vec![ip("127.0.0.1")] ); assert_eq!( parse_str("search localnet.*").get_search(), Some(vec!["localnet.*".to_string()]).as_ref() ); assert_eq!( parse_str("domain example.com.").get_domain(), Some(String::from("example.com.")).as_ref() ); } #[test] fn test_extra_whitespace() { assert_eq!( parse_str("domain example.com.").get_domain(), Some(String::from("example.com.")).as_ref() ); assert_eq!( parse_str("domain example.com. ").get_domain(), Some(String::from("example.com.")).as_ref() ); // hard tabs assert_eq!( parse_str(" domain example.com. ").get_domain(), Some(String::from("example.com.")).as_ref() ); // hard tabs + spaces assert_eq!( parse_str(" domain example.com. ").get_domain(), Some(String::from("example.com.")).as_ref() ); } #[test] fn test_invalid_lines() { assert!(resolv_conf::Config::parse("nameserver 10.0.0.1%1").is_err()); assert!(resolv_conf::Config::parse("nameserver 10.0.0.1.0").is_err()); assert!(resolv_conf::Config::parse("Nameserver 10.0.0.1").is_err()); assert!(resolv_conf::Config::parse("nameserver 10.0.0.1 domain foo.com").is_err()); assert!(resolv_conf::Config::parse("invalid foo.com").is_err()); assert!(resolv_conf::Config::parse("options ndots:1 foo:1").is_err()); } #[test] fn test_empty_line() { assert_eq!(parse_str(""), resolv_conf::Config::new()); } #[test] fn test_multiple_options_on_one_line() { let config = parse_str("options ndots:8 attempts:8 rotate inet6 no-tld-query timeout:8"); assert_eq!(config.ndots, 8); assert_eq!(config.timeout, 8); assert_eq!(config.attempts, 8); assert_eq!(config.rotate, true); assert_eq!(config.inet6, true); assert_eq!(config.no_tld_query, true); } #[test] fn test_ip() { let parsed = ip("FE80::C001:1DFF:FEE0:0%eth0"); let address = Ipv6Addr::new(0xfe80, 0, 0, 0, 0xc001, 0x1dff, 0xfee0, 0); let scope = "eth0".to_string(); assert_eq!(parsed, ScopedIp::V6(address, Some(scope))); let parsed = ip("FE80::C001:1DFF:FEE0:0%1"); let address = Ipv6Addr::new(0xfe80, 0, 0, 0, 0xc001, 0x1dff, 0xfee0, 0); let scope = "1".to_string(); assert_eq!(parsed, ScopedIp::V6(address, Some(scope))); let parsed = ip("FE80::C001:1DFF:FEE0:0"); let address = Ipv6Addr::new(0xfe80, 0, 0, 0, 0xc001, 0x1dff, 0xfee0, 0); assert_eq!(parsed, ScopedIp::V6(address, None)); assert!("10.0.0.1%1".parse::().is_err()); assert!("10.0.0.1%eth0".parse::().is_err()); assert!("FE80::C001:1DFF:FEE0:0%".parse::().is_err()); assert!("FE80::C001:1DFF:FEE0:0% ".parse::().is_err()); let parsed = ip("192.168.10.1"); let address = Ipv4Addr::new(192, 168, 10, 1); assert_eq!(parsed, ScopedIp::V4(address)); } #[test] fn test_nameserver() { assert_eq!( parse_str("nameserver 127.0.0.1").nameservers[0], ip("127.0.0.1") ); assert_eq!( parse_str("nameserver 127.0.0.1#comment").nameservers[0], ip("127.0.0.1") ); assert_eq!( parse_str("nameserver 127.0.0.1;comment").nameservers[0], ip("127.0.0.1") ); assert_eq!( parse_str("nameserver 127.0.0.1 # another comment").nameservers[0], ip("127.0.0.1") ); assert_eq!( parse_str("nameserver 127.0.0.1 ; ").nameservers[0], ip("127.0.0.1") ); assert_eq!(parse_str("nameserver ::1").nameservers[0], ip("::1")); assert_eq!( parse_str("nameserver 2001:db8:85a3:8d3:1319:8a2e:370:7348").nameservers[0], ip("2001:db8:85a3:8d3:1319:8a2e:370:7348") ); assert_eq!( parse_str("nameserver ::ffff:192.0.2.128").nameservers[0], ip("::ffff:192.0.2.128") ); } fn parse_file>(path: P) -> resolv_conf::Config { let mut data = String::new(); let mut file = File::open(path).unwrap(); file.read_to_string(&mut data).unwrap(); resolv_conf::Config::parse(&data).unwrap() } #[test] fn test_parse_simple_conf() { let mut config = resolv_conf::Config::new(); config .nameservers .push(ScopedIp::V4(Ipv4Addr::new(8, 8, 8, 8))); config .nameservers .push(ScopedIp::V4(Ipv4Addr::new(8, 8, 4, 4))); assert_eq!(config, parse_file("tests/resolv.conf-simple")); } #[test] fn test_parse_linux_conf() { let mut config = resolv_conf::Config::new(); config.set_domain(String::from("example.com")); config.set_search(vec!["example.com".into(), "sub.example.com".into()]); config.nameservers = vec![ ScopedIp::V6( Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888), None, ), ScopedIp::V6( Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8844), None, ), ScopedIp::V4(Ipv4Addr::new(8, 8, 8, 8)), ScopedIp::V4(Ipv4Addr::new(8, 8, 4, 4)), ]; config.ndots = 8; config.timeout = 8; config.attempts = 8; config.rotate = true; config.inet6 = true; config.no_tld_query = true; config.sortlist = vec![ Network::V4( Ipv4Addr::new(130, 155, 160, 0), Ipv4Addr::new(255, 255, 240, 0), ), // This fails currently Network::V4(Ipv4Addr::new(130, 155, 0, 0), Ipv4Addr::new(255, 255, 0, 0)), ]; assert_eq!(config, parse_file("tests/resolv.conf-linux")); } #[test] fn test_parse_macos_conf() { let mut config = resolv_conf::Config::new(); config.set_domain(String::from("example.com.")); config.set_search(vec!["example.com.".into(), "sub.example.com.".into()]); config.nameservers = vec![ ScopedIp::V6( Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888), None, ), ScopedIp::V6( Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8844), None, ), ScopedIp::V4(Ipv4Addr::new(8, 8, 8, 8)), ScopedIp::V4(Ipv4Addr::new(8, 8, 4, 4)), ]; config.ndots = 8; config.timeout = 8; config.attempts = 8; assert_eq!(config, parse_file("tests/resolv.conf-macos")); } #[test] fn test_openbsd_conf() { let mut config = resolv_conf::Config::new(); config.nameservers = vec![ ScopedIp::V4(Ipv4Addr::new(8, 8, 8, 8)), ScopedIp::V4(Ipv4Addr::new(8, 8, 4, 4)), ]; config.lookup = vec![Lookup::File, Lookup::Bind]; assert_eq!(config, parse_file("tests/resolv.conf-openbsd")); } #[test] fn test_openbsd_grammar() { let mut config = resolv_conf::Config::new(); config.lookup = vec![Lookup::File, Lookup::Bind]; assert_eq!(resolv_conf::Config::parse("lookup file bind").unwrap(), config); let mut config = resolv_conf::Config::new(); config.lookup = vec![Lookup::Bind]; assert_eq!(resolv_conf::Config::parse("lookup bind").unwrap(), config); let mut config = resolv_conf::Config::new(); config.lookup = vec![Lookup::Extra(String::from("unexpected"))]; assert_eq!(resolv_conf::Config::parse("lookup unexpected").unwrap(), config); let mut config = resolv_conf::Config::new(); config.family = vec![Family::Inet4, Family::Inet6]; assert_eq!(resolv_conf::Config::parse("family inet4 inet6").unwrap(), config); let mut config = resolv_conf::Config::new(); config.family = vec![Family::Inet4]; assert_eq!(resolv_conf::Config::parse("family inet4").unwrap(), config); let mut config = resolv_conf::Config::new(); config.family = vec![Family::Inet6]; assert_eq!(resolv_conf::Config::parse("family inet6").unwrap(), config); assert!(resolv_conf::Config::parse("family invalid").is_err()); } #[test] fn test_glibc_normalize() { let mut config = resolv_conf::Config::new(); config.nameservers = vec![ ScopedIp::V6( Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888), None, ), ScopedIp::V6( Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8844), None, ), ScopedIp::V4(Ipv4Addr::new(8, 8, 8, 8)), ScopedIp::V4(Ipv4Addr::new(8, 8, 4, 4)), ]; config.set_search(vec![ "a.example.com".into(), "b.example.com".into(), "c.example.com".into(), "d.example.com".into(), "e.example.com".into(), "f.example.com".into(), "g.example.com".into(), "h.example.com".into(), ]); config.glibc_normalize(); assert_eq!( vec![ ScopedIp::V6( Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888), None, ), ScopedIp::V6( Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8844), None, ), ScopedIp::V4(Ipv4Addr::new(8, 8, 8, 8)), ], config.nameservers ); assert_eq!( Some(&vec![ "a.example.com".into(), "b.example.com".into(), "c.example.com".into(), "d.example.com".into(), "e.example.com".into(), "f.example.com".into() ]), config.get_search() ); } #[test] fn test_get_nameservers_or_local() { let config = resolv_conf::Config::new(); assert_eq!( 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))), ], config.get_nameservers_or_local() ); } #[test] #[cfg(feature = "system")] #[ignore] fn test_get_system_domain() { let config = resolv_conf::Config::new(); assert_eq!(Some("lan".into()), config.get_system_domain()); } #[test] fn test_default_display() { let original_config = resolv_conf::Config::new(); let output = original_config.to_string(); let restored_config = resolv_conf::Config::parse(&output).unwrap(); assert_eq!(original_config, restored_config); } #[test] fn test_non_default_display() { let mut original_config = resolv_conf::Config::new(); original_config.nameservers = vec![ ip("192.168.0.94"), ip("fe80::0123:4567:89ab:cdef"), ip("fe80::0123:4567:89ab:cdef%zone"), ]; original_config.sortlist = vec![ Network::V4( "192.168.1.94".parse().unwrap(), "255.255.252.0".parse().unwrap(), ), Network::V6("fe80::0123".parse().unwrap(), "fe80::cdef".parse().unwrap()), ]; original_config.set_domain("my.domain".to_owned()); original_config.set_search( vec!["my.domain", "alt.domain"] .into_iter() .map(str::to_owned) .collect(), ); original_config.debug = true; original_config.ndots = 4; original_config.timeout = 20; original_config.attempts = 5; original_config.rotate = true; original_config.no_check_names = true; original_config.inet6 = true; original_config.ip6_bytestring = true; original_config.ip6_dotint = true; original_config.edns0 = true; original_config.single_request = true; original_config.single_request_reopen = true; original_config.no_tld_query = true; original_config.use_vc = true; let output = original_config.to_string(); println!("Output:\n\n{}", output); let restored_config = resolv_conf::Config::parse(&output).unwrap(); assert_eq!(original_config, restored_config); } #[test] fn test_display_preservers_last_search() { let mut original_config = resolv_conf::Config::new(); original_config.set_search( vec!["my.domain", "alt.domain"] .into_iter() .map(str::to_owned) .collect(), ); original_config.set_domain("my.domain".to_owned()); let output = original_config.to_string(); println!("Output:\n\n{}", output); let restored_config = resolv_conf::Config::parse(&output).unwrap(); assert_eq!(original_config, restored_config); } resolv-conf-0.7.0/tests/resolv.conf-linux010066400037200003720000000007111375303062700166210ustar 00000000000000# Not all of these are supported by TRust-DNS # They are testing that they don't break parsing options ndots:8 timeout:8 attempts:8 domain example.com search example.com sub.example.com nameserver 2001:4860:4860::8888 nameserver 2001:4860:4860::8844 nameserver 8.8.8.8 nameserver 8.8.4.4 # some options not supported by TRust-DNS options rotate options inet6 no-tld-query # A basic option not supported sortlist 130.155.160.0/255.255.240.0 130.155.0.0 resolv-conf-0.7.0/tests/resolv.conf-macos010066400037200003720000000006361375303062700165720ustar 00000000000000# # Mac OS X Notice # # This file is not used by the host name and address resolution # or the DNS query routing mechanisms used by most processes on # this Mac OS X system. # # This file is automatically generated. # options ndots:8 timeout:8 attempts:8 domain example.com. search example.com. sub.example.com. nameserver 2001:4860:4860::8888 nameserver 2001:4860:4860::8844 nameserver 8.8.8.8 nameserver 8.8.4.4 resolv-conf-0.7.0/tests/resolv.conf-openbsd010066400037200003720000000001231375303062700171110ustar 00000000000000# Generated by em0 dhclient nameserver 8.8.8.8 nameserver 8.8.4.4 lookup file bind resolv-conf-0.7.0/tests/resolv.conf-simple010066400037200003720000000000451375303062700167530ustar 00000000000000nameserver 8.8.8.8 nameserver 8.8.4.4resolv-conf-0.7.0/vagga.yaml010066400037200003720000000020071375303062700141120ustar 00000000000000commands: make: !Command description: Build the library container: ubuntu run: [cargo, build] test: !Command description: Run unit tests container: ubuntu run: [cargo, test] cargo: !Command description: Run any cargo command symlink-name: cargo container: ubuntu run: [cargo] _bulk: !Command description: Run `bulk` command (for version bookkeeping) container: ubuntu run: [bulk] containers: ubuntu: setup: - !Ubuntu xenial - !Install [ca-certificates, git, build-essential, vim] - !TarInstall url: "https://static.rust-lang.org/dist/rust-1.31.0-x86_64-unknown-linux-gnu.tar.gz" script: "./install.sh --prefix=/usr \ --components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo" - &bulk !Tar url: "https://github.com/tailhook/bulk/releases/download/v0.4.12/bulk-v0.4.12.tar.gz" sha256: 7deeb4895b3909afea46194ef01bafdeb30ff89fc4a7b6497172ba117734040e path: / environ: HOME: /work/target