├── .gitignore ├── libresolv-sys ├── resolv.lints ├── Cargo.toml ├── build.rs └── lib.d │ ├── lib_228.rs │ ├── lib_234.rs │ └── lib_231.rs ├── Cargo.toml ├── src ├── record │ ├── txt.rs │ ├── a.rs │ ├── aaaa.rs │ ├── class.rs │ ├── tlsa.rs │ ├── ns.rs │ ├── ptr.rs │ ├── cname.rs │ ├── mx.rs │ ├── srv.rs │ ├── soa.rs │ └── mod.rs ├── tests.rs ├── error.rs ├── response.rs └── lib.rs ├── LICENSE-MIT ├── README.md └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | /libresolv-sys/lib.rs 2 | /libresolv-sys/resolv.rs 3 | /libresolv-sys/target 4 | /target 5 | Cargo.lock 6 | -------------------------------------------------------------------------------- /libresolv-sys/resolv.lints: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | #![allow(non_upper_case_globals)] 5 | #![allow(unused_imports)] -------------------------------------------------------------------------------- /libresolv-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libresolv-sys" 3 | version = "0.4.0" 4 | authors = ["Mike Dilger "] 5 | links = "resolv" 6 | build = "build.rs" 7 | license = "MIT/Apache-2.0" 8 | repository = "https://github.com/mikedilger/resolv-rs" 9 | description = "Native bindings to the libresolv library" 10 | 11 | [lib] 12 | name = "libresolv_sys" 13 | path = "lib.rs" 14 | 15 | [dependencies] 16 | libc = "0.2" 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "resolv" 3 | version = "0.4.0" 4 | authors = ["Mike Dilger "] 5 | license = "MIT/Apache-2.0" 6 | keywords = ["resolv", "dns"] 7 | readme = "README.md" 8 | repository = "https://github.com/mikedilger/resolv-rs" 9 | homepage = "https://github.com/mikedilger/resolv-rs" 10 | documentation = "https://docs.rs/resolv-rs" 11 | description = """ 12 | DNS resolution via glibc 13 | """ 14 | edition = "2018" 15 | 16 | [dependencies] 17 | libc = "0.2" 18 | libresolv-sys = { path = "libresolv-sys", version = "0.4.0" } 19 | byteorder = "1" 20 | -------------------------------------------------------------------------------- /src/record/txt.rs: -------------------------------------------------------------------------------- 1 | use super::{RecordData, RecordType}; 2 | use crate::error::Error; 3 | use libresolv_sys::ns_msg as Message; 4 | use libresolv_sys::ns_rr as Rr; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct TXT { 8 | pub dname: String, 9 | } 10 | 11 | impl RecordData for TXT { 12 | fn get_record_type() -> RecordType { 13 | RecordType::TXT 14 | } 15 | 16 | fn extract(_msg: &mut Message, rr: &Rr) -> Result { 17 | if rr.type_ != Self::get_record_type() as u16 { 18 | return Err(Error::WrongRRType); 19 | } 20 | 21 | let slice: &[u8] = 22 | unsafe { ::std::slice::from_raw_parts(rr.rdata.offset(1), *rr.rdata as usize) }; 23 | 24 | Ok(TXT { 25 | dname: String::from_utf8_lossy(slice).into_owned(), 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/record/a.rs: -------------------------------------------------------------------------------- 1 | use super::{RecordData, RecordType}; 2 | use crate::error::Error; 3 | use libresolv_sys::ns_msg as Message; 4 | use libresolv_sys::ns_rr as Rr; 5 | 6 | use std::mem; 7 | use std::net::Ipv4Addr; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct A { 11 | pub address: Ipv4Addr, 12 | } 13 | 14 | impl RecordData for A { 15 | fn get_record_type() -> RecordType { 16 | RecordType::A 17 | } 18 | 19 | fn extract(_msg: &mut Message, rr: &Rr) -> Result { 20 | if rr.type_ != Self::get_record_type() as u16 { 21 | return Err(Error::WrongRRType); 22 | } 23 | Ok(A { 24 | address: Ipv4Addr::from(unsafe { 25 | let input: &[u8; 4] = mem::transmute(rr.rdata); 26 | *input 27 | }), 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/record/aaaa.rs: -------------------------------------------------------------------------------- 1 | use super::{RecordData, RecordType}; 2 | use crate::error::Error; 3 | use libresolv_sys::ns_msg as Message; 4 | use libresolv_sys::ns_rr as Rr; 5 | 6 | use std::mem; 7 | use std::net::Ipv6Addr; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct AAAA { 11 | pub address: Ipv6Addr, 12 | } 13 | 14 | impl RecordData for AAAA { 15 | fn get_record_type() -> RecordType { 16 | RecordType::AAAA 17 | } 18 | 19 | fn extract(_msg: &mut Message, rr: &Rr) -> Result { 20 | if rr.type_ != Self::get_record_type() as u16 { 21 | return Err(Error::WrongRRType); 22 | } 23 | 24 | Ok(AAAA { 25 | address: Ipv6Addr::from(unsafe { 26 | let input: &[u8; 16] = mem::transmute(rr.rdata); 27 | *input 28 | }), 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/record/class.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | 3 | /// DNS Class. 4 | #[derive(Debug, Clone, Copy, PartialEq)] 5 | #[repr(u16)] 6 | pub enum Class { 7 | /// Internet Class. By far the most common. 8 | IN = 1, 9 | /// CSNET class (obsoleted) 10 | CSNET = 2, 11 | /// CHAOS class (https://en.wikipedia.org/wiki/Chaosnet) 12 | CHAOS = 3, 13 | /// Hesoid (https://en.wikipedia.org/wiki/Hesiod_(name_service)) 14 | HS = 4, 15 | /// None. Used for "name is not in use" requests. 16 | NONE = 254, 17 | /// Any. Used for "name is in use" requests. 18 | ANY = 255, 19 | } 20 | 21 | impl Class { 22 | pub fn from_rr_class(rr_class: u16) -> Result { 23 | Ok(match rr_class { 24 | 1 => Class::IN, 25 | 2 => Class::CSNET, 26 | 3 => Class::CHAOS, 27 | 4 => Class::HS, 28 | 254 => Class::NONE, 29 | 255 => Class::ANY, 30 | other => return Err(Error::UnknownClass(other)), 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/record/tlsa.rs: -------------------------------------------------------------------------------- 1 | use super::{RecordData, RecordType}; 2 | use crate::error::Error; 3 | use byteorder::ReadBytesExt; 4 | use libresolv_sys::ns_msg as Message; 5 | use libresolv_sys::ns_rr as Rr; 6 | use std::io::Cursor; 7 | use std::slice; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct TLSA { 11 | pub usage: u8, 12 | pub selector: u8, 13 | pub matching_type: u8, 14 | pub data: Vec, 15 | } 16 | 17 | impl RecordData for TLSA { 18 | fn get_record_type() -> RecordType { 19 | RecordType::TLSA 20 | } 21 | 22 | fn extract(_msg: &mut Message, rr: &Rr) -> Result { 23 | if rr.type_ != Self::get_record_type() as u16 { 24 | return Err(Error::WrongRRType); 25 | } 26 | 27 | let mut reader = 28 | unsafe { Cursor::new(slice::from_raw_parts(rr.rdata, rr.rdlength as usize)) }; 29 | 30 | Ok(TLSA { 31 | usage: reader.read_u8().unwrap(), 32 | selector: reader.read_u8().unwrap(), 33 | matching_type: reader.read_u8().unwrap(), 34 | data: reader.into_inner()[3..].to_vec(), 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Michael Dilger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/record/ns.rs: -------------------------------------------------------------------------------- 1 | use super::{RecordData, RecordType}; 2 | use crate::error::Error; 3 | use libresolv_sys::ns_msg as Message; 4 | use libresolv_sys::ns_rr as Rr; 5 | use libresolv_sys::MAXDNAME; 6 | 7 | use std::ffi::CStr; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct NS { 11 | pub dname: String, 12 | } 13 | 14 | impl RecordData for NS { 15 | fn get_record_type() -> RecordType { 16 | RecordType::NS 17 | } 18 | 19 | fn extract(msg: &mut Message, rr: &Rr) -> Result { 20 | if rr.type_ != Self::get_record_type() as u16 { 21 | return Err(Error::WrongRRType); 22 | } 23 | 24 | let mut buffer: [u8; MAXDNAME as usize] = [0; MAXDNAME as usize]; 25 | 26 | let size = unsafe { 27 | ::libresolv_sys::ns_name_uncompress( 28 | msg._msg, 29 | msg._eom, 30 | rr.rdata, 31 | buffer.as_mut_ptr() as *mut i8, 32 | MAXDNAME as usize, 33 | ) 34 | }; 35 | if size < 0 { 36 | return Err(Error::UncompressError); 37 | } 38 | 39 | Ok(NS { 40 | dname: unsafe { 41 | CStr::from_ptr(buffer.as_ptr() as *const i8) 42 | .to_string_lossy() 43 | .into_owned() 44 | }, 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/record/ptr.rs: -------------------------------------------------------------------------------- 1 | use super::{RecordData, RecordType}; 2 | use crate::error::Error; 3 | use libresolv_sys::ns_msg as Message; 4 | use libresolv_sys::ns_rr as Rr; 5 | use libresolv_sys::MAXDNAME; 6 | 7 | use std::ffi::CStr; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct PTR { 11 | pub dname: String, 12 | } 13 | 14 | impl RecordData for PTR { 15 | fn get_record_type() -> RecordType { 16 | RecordType::PTR 17 | } 18 | 19 | fn extract(msg: &mut Message, rr: &Rr) -> Result { 20 | if rr.type_ != Self::get_record_type() as u16 { 21 | return Err(Error::WrongRRType); 22 | } 23 | 24 | let mut buffer: [u8; MAXDNAME as usize] = [0; MAXDNAME as usize]; 25 | 26 | let size = unsafe { 27 | ::libresolv_sys::ns_name_uncompress( 28 | msg._msg, 29 | msg._eom, 30 | rr.rdata, 31 | buffer.as_mut_ptr() as *mut i8, 32 | MAXDNAME as usize, 33 | ) 34 | }; 35 | if size < 0 { 36 | return Err(Error::UncompressError); 37 | } 38 | 39 | Ok(PTR { 40 | dname: unsafe { 41 | CStr::from_ptr(buffer.as_ptr() as *const i8) 42 | .to_string_lossy() 43 | .into_owned() 44 | }, 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/record/cname.rs: -------------------------------------------------------------------------------- 1 | use super::{RecordData, RecordType}; 2 | use crate::error::Error; 3 | use libresolv_sys::ns_msg as Message; 4 | use libresolv_sys::ns_rr as Rr; 5 | use libresolv_sys::MAXDNAME; 6 | 7 | use std::ffi::CStr; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct CNAME { 11 | pub cname: String, 12 | } 13 | 14 | impl RecordData for CNAME { 15 | fn get_record_type() -> RecordType { 16 | RecordType::CNAME 17 | } 18 | 19 | fn extract(msg: &mut Message, rr: &Rr) -> Result { 20 | if rr.type_ != Self::get_record_type() as u16 { 21 | return Err(Error::WrongRRType); 22 | } 23 | 24 | let mut buffer: [u8; MAXDNAME as usize] = [0; MAXDNAME as usize]; 25 | 26 | let size = unsafe { 27 | ::libresolv_sys::ns_name_uncompress( 28 | msg._msg, 29 | msg._eom, 30 | rr.rdata, 31 | buffer.as_mut_ptr() as *mut i8, 32 | MAXDNAME as usize, 33 | ) 34 | }; 35 | if size < 0 { 36 | return Err(Error::UncompressError); 37 | } 38 | 39 | Ok(CNAME { 40 | cname: unsafe { 41 | CStr::from_ptr(buffer.as_ptr() as *const i8) 42 | .to_string_lossy() 43 | .into_owned() 44 | }, 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/record/mx.rs: -------------------------------------------------------------------------------- 1 | use super::{RecordData, RecordType}; 2 | use crate::error::Error; 3 | use byteorder::{BigEndian, ByteOrder}; 4 | use libresolv_sys::ns_msg as Message; 5 | use libresolv_sys::ns_rr as Rr; 6 | use libresolv_sys::MAXDNAME; 7 | use std::ffi::CStr; 8 | use std::slice; 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct MX { 12 | pub preference: i16, 13 | pub exchange: String, 14 | } 15 | 16 | impl RecordData for MX { 17 | fn get_record_type() -> RecordType { 18 | RecordType::MX 19 | } 20 | 21 | fn extract(msg: &mut Message, rr: &Rr) -> Result { 22 | if rr.type_ != Self::get_record_type() as u16 { 23 | return Err(Error::WrongRRType); 24 | } 25 | 26 | let mut buffer: [u8; MAXDNAME as usize] = [0; MAXDNAME as usize]; 27 | 28 | let size = unsafe { 29 | ::libresolv_sys::ns_name_uncompress( 30 | msg._msg, 31 | msg._eom, 32 | rr.rdata.offset(2), 33 | buffer.as_mut_ptr() as *mut i8, 34 | MAXDNAME as usize, 35 | ) 36 | }; 37 | if size < 0 { 38 | return Err(Error::UncompressError); 39 | } 40 | 41 | Ok(MX { 42 | preference: unsafe { 43 | let slice: &[u8] = slice::from_raw_parts(rr.rdata, 2); 44 | BigEndian::read_i16(slice) 45 | }, 46 | exchange: unsafe { 47 | CStr::from_ptr(buffer.as_ptr() as *const i8) 48 | .to_string_lossy() 49 | .into_owned() 50 | }, 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/record/srv.rs: -------------------------------------------------------------------------------- 1 | use super::{RecordData, RecordType}; 2 | use crate::error::Error; 3 | use byteorder::{BigEndian, ByteOrder}; 4 | use libresolv_sys::ns_msg as Message; 5 | use libresolv_sys::ns_rr as Rr; 6 | use libresolv_sys::MAXHOSTNAMELEN; 7 | use std::ffi::CStr; 8 | use std::slice; 9 | 10 | #[derive(Debug, Clone, PartialEq, Eq)] 11 | pub struct SRV { 12 | pub priority: u16, 13 | pub weight: u16, 14 | pub port: u16, 15 | pub name: String, 16 | } 17 | 18 | impl RecordData for SRV { 19 | fn get_record_type() -> RecordType { 20 | RecordType::SRV 21 | } 22 | 23 | fn extract(msg: &mut Message, rr: &Rr) -> Result { 24 | if rr.type_ != Self::get_record_type() as u16 { 25 | return Err(Error::WrongRRType); 26 | } 27 | 28 | let mut buffer: [u8; MAXHOSTNAMELEN as usize] = [0; MAXHOSTNAMELEN as usize]; 29 | let size = unsafe { 30 | ::libresolv_sys::ns_name_uncompress( 31 | msg._msg, 32 | msg._eom, 33 | rr.rdata.offset(6), 34 | buffer.as_mut_ptr() as *mut i8, 35 | MAXHOSTNAMELEN as usize, 36 | ) 37 | }; 38 | if size < 0 { 39 | return Err(Error::UncompressError); 40 | } 41 | 42 | Ok(SRV { 43 | priority: unsafe { 44 | let slice: &[u8] = slice::from_raw_parts(rr.rdata.offset(0), 2); 45 | BigEndian::read_u16(slice) 46 | }, 47 | weight: unsafe { 48 | let slice: &[u8] = slice::from_raw_parts(rr.rdata.offset(2), 2); 49 | BigEndian::read_u16(slice) 50 | }, 51 | port: unsafe { 52 | let slice: &[u8] = slice::from_raw_parts(rr.rdata.offset(4), 2); 53 | BigEndian::read_u16(slice) 54 | }, 55 | name: unsafe { 56 | CStr::from_ptr(buffer.as_ptr() as *const i8) 57 | .to_string_lossy() 58 | .into_owned() 59 | }, 60 | }) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /libresolv-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fs::{self, File}; 3 | use std::io::Write; // File.write_all() 4 | use std::path::PathBuf; 5 | use std::process::Command; 6 | use std::str; 7 | 8 | fn main() -> Result<(), Box> { 9 | static BINDING: &str = "resolv.rs"; 10 | let header = format!("{}/resolv.h", std::env::var("GLIBC_INCLUDE").unwrap_or("/usr/include".into())); 11 | let mut cmd; 12 | let mut output; 13 | 14 | eprintln!("Generating binding {:?} from {:?} ...\n", BINDING, header); 15 | cmd = Command::new("bindgen"); 16 | cmd.args(["--with-derive-default", &header]); 17 | output = cmd.output()?; 18 | if !output.status.success() { 19 | let msg = str::from_utf8(output.stderr.as_slice())?; 20 | eprintln!("\"\"\"\n{}\"\"\"\n", msg); 21 | panic!("{:?}", cmd); 22 | } 23 | 24 | let lints = fs::read("resolv.lints")?; 25 | let mut f = File::create(BINDING)?; 26 | f.write_all(lints.as_slice())?; 27 | f.write_all(output.stdout.as_slice())?; 28 | 29 | eprintln!( 30 | "Checking available libresolv adapters to the generated binding {:?} ...\n", 31 | BINDING 32 | ); 33 | 34 | let mut paths: Vec = fs::read_dir("lib.d")?.map(|e| e.unwrap().path()).collect(); 35 | paths.sort_unstable(); 36 | for path in paths.iter().rev() { 37 | eprintln!("Trying {:?} ...\n", path); 38 | fs::copy(path, "lib.rs")?; 39 | cmd = Command::new("rustc"); 40 | cmd.args(["--emit", "dep-info=/dev/null", "lib.rs"]); 41 | output = cmd.output()?; 42 | if output.status.success() { 43 | eprintln!("Success\n"); 44 | break; 45 | } else { 46 | let msg = str::from_utf8(output.stderr.as_slice())?; 47 | eprintln!("\"\"\"\n{}\"\"\"\n", msg); 48 | fs::remove_file("lib.rs")?; 49 | } 50 | } 51 | 52 | if !output.status.success() { 53 | panic!("None of the available adapters compiled successfully!"); 54 | } 55 | 56 | println!("cargo:rustc-flags=-l resolv"); 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /libresolv-sys/lib.d/lib_228.rs: -------------------------------------------------------------------------------- 1 | mod resolv; 2 | 3 | pub use resolv::{ 4 | MAXDNAME, 5 | MAXHOSTNAMELEN, 6 | NS_PACKETSZ, 7 | RES_DEBUG, 8 | RES_DEFAULT, 9 | RES_DEFNAMES, 10 | RES_DNSRCH, 11 | RES_IGNTC, 12 | RES_INIT, 13 | RES_NOALIASES, 14 | RES_NORELOAD, 15 | RES_NOTLDQUERY, 16 | RES_RECURSE, 17 | RES_ROTATE, 18 | RES_SNGLKUP, 19 | RES_SNGLKUPREOP, 20 | RES_STAYOPEN, 21 | RES_USEVC, 22 | RES_USE_DNSSEC, 23 | RES_USE_EDNS0, 24 | __ns_sect_ns_s_an, 25 | __ns_sect_ns_s_ar, 26 | __ns_sect_ns_s_ns, 27 | __ns_sect_ns_s_qd, 28 | __res_ninit as res_ninit, 29 | __res_nquery as res_nquery, 30 | __res_nsearch as res_nsearch, 31 | __res_state, 32 | ns_initparse, 33 | ns_msg, 34 | ns_name_uncompress, 35 | ns_parserr, 36 | ns_rr, 37 | ns_sect, 38 | }; 39 | 40 | /// Options for the Resolver 41 | #[repr(u32)] 42 | #[derive(Debug, Clone, Copy, PartialEq)] 43 | pub enum ResolverOption { 44 | /// address initialized 45 | Init = RES_INIT, 46 | /// print debug messages 47 | Debug = RES_DEBUG, 48 | /// use virtual circuit 49 | UseVC = RES_USEVC, 50 | /// ignore truncation errors 51 | IgnTc = RES_IGNTC, 52 | /// recursion desired 53 | Recurse = RES_RECURSE, 54 | /// use default domain name 55 | DefNames = RES_DEFNAMES, 56 | /// Keep TCP socket open 57 | StayOpen = RES_STAYOPEN, 58 | /// search up local domain tree 59 | DNSrch = RES_DNSRCH, 60 | /// shuts off HOSTALIASES feature 61 | NoAliases = RES_NOALIASES, 62 | /// rotate ns list after each query 63 | Rotate = RES_ROTATE, 64 | /// Use EDNS0. 65 | UseEDNS0 = RES_USE_EDNS0, 66 | /// one outstanding request at a time 67 | SngLkup = RES_SNGLKUP, 68 | /// one outstanding request at a time, but open new socket for each request 69 | SngLkupReop = RES_SNGLKUPREOP, 70 | /// use DNSSEC using OK bit in OPT 71 | UseDNSSEC = RES_USE_DNSSEC, 72 | /// Do not look up unqualified name as a TLD. 73 | NoTLDQuery = RES_NOTLDQUERY, 74 | /// No automatic configuration reload (since glibc 2.26; invalid in prior versions) 75 | NoReload = RES_NORELOAD, 76 | /// Default values 77 | Default = RES_DEFAULT, 78 | } 79 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::record::MX; 2 | use crate::record::TLSA; 3 | use crate::{Class, RecordType, Resolver, Section}; 4 | 5 | #[test] 6 | fn basic_test_query() { 7 | let mut resolver = Resolver::new().unwrap(); 8 | let mut response = resolver 9 | .query(b"gmail.com", Class::IN, RecordType::MX) 10 | .unwrap(); 11 | 12 | // Verify that some of the default options came back to us 13 | let flags = response.get_flags(); 14 | assert_eq!(flags.question_response(), true); 15 | assert_eq!(flags.recursion_desired(), true); 16 | 17 | // Verify that the question section has something in it 18 | assert!(response.get_section_count(Section::Question) > 0); 19 | 20 | // Verify that the answer section has something in it 21 | assert!(response.get_section_count(Section::Answer) > 0); 22 | 23 | let mut count: usize = 0; 24 | for answer in response.answers::() { 25 | count += 1; 26 | println!("{:?}", answer); 27 | } 28 | 29 | // Verify that the iterator made it through all of the answers 30 | assert_eq!(response.get_section_count(Section::Answer), count); 31 | } 32 | 33 | // This DNS domain no longer responds. FIXME. 34 | #[test] 35 | fn test_tlsa() { 36 | let mut resolver = Resolver::new().unwrap(); 37 | let mut response = resolver 38 | .query( 39 | b"_443._tcp.fedoraproject.org", 40 | Class::IN, 41 | RecordType::TLSA, 42 | ) 43 | .unwrap(); 44 | 45 | // Verify that some of the default options came back to us 46 | let flags = response.get_flags(); 47 | assert_eq!(flags.question_response(), true); 48 | assert_eq!(flags.recursion_desired(), true); 49 | 50 | // Verify that the question section has something in it 51 | assert!(response.get_section_count(Section::Question) > 0); 52 | 53 | // Verify that the answer section has something in it 54 | assert!(response.get_section_count(Section::Answer) > 0); 55 | 56 | let mut count: usize = 0; 57 | for answer in response.answers::() { 58 | count += 1; 59 | println!("{:?}", answer); 60 | } 61 | 62 | // Verify that the iterator made it through all of the answers 63 | assert_eq!(response.get_section_count(Section::Answer), count); 64 | } 65 | -------------------------------------------------------------------------------- /libresolv-sys/lib.d/lib_234.rs: -------------------------------------------------------------------------------- 1 | mod resolv; 2 | 3 | pub use resolv::{ 4 | MAXDNAME, 5 | MAXHOSTNAMELEN, 6 | NS_PACKETSZ, 7 | RES_DEBUG, 8 | RES_DEFAULT, 9 | RES_DEFNAMES, 10 | RES_DNSRCH, 11 | RES_IGNTC, 12 | RES_INIT, 13 | RES_NOALIASES, 14 | RES_NORELOAD, 15 | RES_NOTLDQUERY, 16 | RES_RECURSE, 17 | RES_ROTATE, 18 | RES_SNGLKUP, 19 | RES_SNGLKUPREOP, 20 | RES_STAYOPEN, 21 | RES_TRUSTAD, 22 | RES_USEVC, 23 | RES_USE_DNSSEC, 24 | RES_USE_EDNS0, 25 | __ns_sect_ns_s_an, 26 | __ns_sect_ns_s_ar, 27 | __ns_sect_ns_s_ns, 28 | __ns_sect_ns_s_qd, 29 | __res_ninit as res_ninit, 30 | __res_state, 31 | ns_initparse, 32 | ns_msg, 33 | ns_name_uncompress, 34 | ns_parserr, 35 | ns_rr, 36 | ns_sect, 37 | res_nquery, 38 | res_nsearch, 39 | }; 40 | 41 | /// Options for the Resolver 42 | #[repr(u32)] 43 | #[derive(Debug, Clone, Copy, PartialEq)] 44 | pub enum ResolverOption { 45 | /// address initialized 46 | Init = RES_INIT, 47 | /// print debug messages 48 | Debug = RES_DEBUG, 49 | /// use virtual circuit 50 | UseVC = RES_USEVC, 51 | /// ignore truncation errors 52 | IgnTc = RES_IGNTC, 53 | /// recursion desired 54 | Recurse = RES_RECURSE, 55 | /// use default domain name 56 | DefNames = RES_DEFNAMES, 57 | /// Keep TCP socket open 58 | StayOpen = RES_STAYOPEN, 59 | /// search up local domain tree 60 | DNSrch = RES_DNSRCH, 61 | /// shuts off HOSTALIASES feature 62 | NoAliases = RES_NOALIASES, 63 | /// rotate ns list after each query 64 | Rotate = RES_ROTATE, 65 | /// Use EDNS0. 66 | UseEDNS0 = RES_USE_EDNS0, 67 | /// one outstanding request at a time 68 | SngLkup = RES_SNGLKUP, 69 | /// one outstanding request at a time, but open new socket for each request 70 | SngLkupReop = RES_SNGLKUPREOP, 71 | /// use DNSSEC using OK bit in OPT 72 | UseDNSSEC = RES_USE_DNSSEC, 73 | /// Do not look up unqualified name as a TLD. 74 | NoTLDQuery = RES_NOTLDQUERY, 75 | /// No automatic configuration reload (since glibc 2.26; invalid in prior versions) 76 | NoReload = RES_NORELOAD, 77 | /// Request AD bit, keep it in responses (since glibc 2.31; invalid in prior version) 78 | TrustAD = RES_TRUSTAD, 79 | /// Default values 80 | Default = RES_DEFAULT, 81 | } 82 | -------------------------------------------------------------------------------- /libresolv-sys/lib.d/lib_231.rs: -------------------------------------------------------------------------------- 1 | mod resolv; 2 | 3 | pub use resolv::{ 4 | MAXDNAME, 5 | MAXHOSTNAMELEN, 6 | NS_PACKETSZ, 7 | RES_DEBUG, 8 | RES_DEFAULT, 9 | RES_DEFNAMES, 10 | RES_DNSRCH, 11 | RES_IGNTC, 12 | RES_INIT, 13 | RES_NOALIASES, 14 | RES_NORELOAD, 15 | RES_NOTLDQUERY, 16 | RES_RECURSE, 17 | RES_ROTATE, 18 | RES_SNGLKUP, 19 | RES_SNGLKUPREOP, 20 | RES_STAYOPEN, 21 | RES_TRUSTAD, 22 | RES_USEVC, 23 | RES_USE_DNSSEC, 24 | RES_USE_EDNS0, 25 | __ns_sect_ns_s_an, 26 | __ns_sect_ns_s_ar, 27 | __ns_sect_ns_s_ns, 28 | __ns_sect_ns_s_qd, 29 | __res_ninit as res_ninit, 30 | __res_nquery as res_nquery, 31 | __res_nsearch as res_nsearch, 32 | __res_state, 33 | ns_initparse, 34 | ns_msg, 35 | ns_name_uncompress, 36 | ns_parserr, 37 | ns_rr, 38 | ns_sect, 39 | }; 40 | 41 | /// Options for the Resolver 42 | #[repr(u32)] 43 | #[derive(Debug, Clone, Copy, PartialEq)] 44 | pub enum ResolverOption { 45 | /// address initialized 46 | Init = RES_INIT, 47 | /// print debug messages 48 | Debug = RES_DEBUG, 49 | /// use virtual circuit 50 | UseVC = RES_USEVC, 51 | /// ignore truncation errors 52 | IgnTc = RES_IGNTC, 53 | /// recursion desired 54 | Recurse = RES_RECURSE, 55 | /// use default domain name 56 | DefNames = RES_DEFNAMES, 57 | /// Keep TCP socket open 58 | StayOpen = RES_STAYOPEN, 59 | /// search up local domain tree 60 | DNSrch = RES_DNSRCH, 61 | /// shuts off HOSTALIASES feature 62 | NoAliases = RES_NOALIASES, 63 | /// rotate ns list after each query 64 | Rotate = RES_ROTATE, 65 | /// Use EDNS0. 66 | UseEDNS0 = RES_USE_EDNS0, 67 | /// one outstanding request at a time 68 | SngLkup = RES_SNGLKUP, 69 | /// one outstanding request at a time, but open new socket for each request 70 | SngLkupReop = RES_SNGLKUPREOP, 71 | /// use DNSSEC using OK bit in OPT 72 | UseDNSSEC = RES_USE_DNSSEC, 73 | /// Do not look up unqualified name as a TLD. 74 | NoTLDQuery = RES_NOTLDQUERY, 75 | /// No automatic configuration reload (since glibc 2.26; invalid in prior versions) 76 | NoReload = RES_NORELOAD, 77 | /// Request AD bit, keep it in responses (since glibc 2.31; invalid in prior version) 78 | TrustAD = RES_TRUSTAD, 79 | /// Default values 80 | Default = RES_DEFAULT, 81 | } 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Resolv 2 | 3 | DNS resolution via glibc. 4 | 5 | Note: If you are only looking up IP addresses for DNS names and don't need other records 6 | (MX, TXT, etc), consider other more portable rust libraries like `dns-lookup`. 7 | 8 | This uses `libresolv.so` which is typically configured via `/etc/resolv.conf` to do DNS 9 | resolution. It allows you to look up DNS resource records of any type (e.g. A, AAAA, MX, TXT, 10 | etc), use recursion (if your system's DNS resolver permits it), and perform the DNS search 11 | algorithm to complete incomplete names and use your `/etc/hosts` file. 12 | 13 | ## Example 14 | 15 | ````rust 16 | extern crate resolv; 17 | 18 | use resolv::{Resolver, Class, RecordType}; 19 | use resolv::record::MX; 20 | 21 | fn main() { 22 | // You must create a mutable resolver object to hold the context. 23 | let mut resolver = Resolver::new().unwrap(); 24 | 25 | // .query() and .search() are the main interfaces to the resolver. 26 | let mut response = resolver.query(b"gmail.com", Class::IN, 27 | RecordType::MX).unwrap(); 28 | 29 | // You can iterate through answers as follows. You must specify the 30 | // type of record. A run-time error will occur if the records 31 | // decoded are of the wrong type. 32 | for answer in response.answers::() { 33 | println!("{:?}", answer); 34 | } 35 | } 36 | ```` 37 | 38 | ## Building 39 | 40 | You need to have bindgen version at least 0.62 installed, for example: 41 | 42 | ``` 43 | cargo install bindgen-cli 44 | ``` 45 | 46 | ## Limitations 47 | 48 | You cannot specify a DNS server separate from editing `/etc/resolv.conf` for the entire 49 | system. 50 | 51 | Not all NS record types are supported yet. 52 | 53 | The thread-safe interface is used, which may not be available on older systems. 54 | 55 | Depending on the version of glibc installed on the system, one of several 56 | adapter modules is chosen. It is likely that some systems will require making a 57 | new adapter module from one of the existing ones. These are in 58 | libresolv-sys/lib.d. Pull requests with additional adapters are welcome. 59 | 60 | ## License 61 | 62 | Licensed under either of 63 | 64 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 65 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 66 | 67 | at your option. 68 | 69 | ### Contribution 70 | 71 | Unless you explicitly state otherwise, any contribution intentionally submitted 72 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall 73 | be dual licensed as above, without any additional terms or conditions. 74 | -------------------------------------------------------------------------------- /src/record/soa.rs: -------------------------------------------------------------------------------- 1 | use super::{RecordData, RecordType}; 2 | use crate::error::Error; 3 | use byteorder::{BigEndian, ByteOrder}; 4 | use libresolv_sys::ns_msg as Message; 5 | use libresolv_sys::ns_rr as Rr; 6 | use libresolv_sys::MAXDNAME; 7 | use std::ffi::CStr; 8 | use std::slice; 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct SOA { 12 | pub mname: String, 13 | pub rname: String, 14 | pub serial: u32, 15 | pub refresh: u32, 16 | pub retry: u32, 17 | pub expire: u32, 18 | pub minimum: u32, 19 | } 20 | 21 | impl RecordData for SOA { 22 | fn get_record_type() -> RecordType { 23 | RecordType::SOA 24 | } 25 | 26 | fn extract(msg: &mut Message, rr: &Rr) -> Result { 27 | if rr.type_ != Self::get_record_type() as u16 { 28 | return Err(Error::WrongRRType); 29 | } 30 | 31 | let mut soa = SOA { 32 | mname: "".to_owned(), 33 | rname: "".to_owned(), 34 | serial: 0, 35 | refresh: 0, 36 | retry: 0, 37 | expire: 0, 38 | minimum: 0, 39 | }; 40 | 41 | let mut buffer: [u8; MAXDNAME as usize] = [0; MAXDNAME as usize]; 42 | 43 | let mut offset = 0; 44 | 45 | soa.mname = { 46 | let count = unsafe { 47 | ::libresolv_sys::ns_name_uncompress( 48 | msg._msg, 49 | msg._eom, 50 | rr.rdata.offset(offset), 51 | buffer.as_mut_ptr() as *mut i8, 52 | MAXDNAME as usize, 53 | ) 54 | }; 55 | if count < 0 { 56 | return Err(Error::UncompressError); 57 | } 58 | offset += count as isize; 59 | unsafe { 60 | CStr::from_ptr(buffer.as_ptr() as *const i8) 61 | .to_string_lossy() 62 | .into_owned() 63 | } 64 | }; 65 | 66 | soa.rname = { 67 | let count = unsafe { 68 | ::libresolv_sys::ns_name_uncompress( 69 | msg._msg, 70 | msg._eom, 71 | rr.rdata.offset(offset), 72 | buffer.as_mut_ptr() as *mut i8, 73 | MAXDNAME as usize, 74 | ) 75 | }; 76 | if count < 0 { 77 | return Err(Error::UncompressError); 78 | } 79 | offset += count as isize; 80 | unsafe { 81 | CStr::from_ptr(buffer.as_ptr() as *const i8) 82 | .to_string_lossy() 83 | .into_owned() 84 | } 85 | }; 86 | 87 | soa.serial = unsafe { 88 | let slice: &[u8] = slice::from_raw_parts(rr.rdata.offset(offset), 4); 89 | BigEndian::read_u32(slice) 90 | }; 91 | offset += 4; 92 | 93 | soa.refresh = unsafe { 94 | let slice: &[u8] = slice::from_raw_parts(rr.rdata.offset(offset), 4); 95 | BigEndian::read_u32(slice) 96 | }; 97 | offset += 4; 98 | 99 | soa.retry = unsafe { 100 | let slice: &[u8] = slice::from_raw_parts(rr.rdata.offset(offset), 4); 101 | BigEndian::read_u32(slice) 102 | }; 103 | offset += 4; 104 | 105 | soa.expire = unsafe { 106 | let slice: &[u8] = slice::from_raw_parts(rr.rdata.offset(offset), 4); 107 | BigEndian::read_u32(slice) 108 | }; 109 | offset += 4; 110 | 111 | soa.minimum = unsafe { 112 | let slice: &[u8] = slice::from_raw_parts(rr.rdata.offset(offset), 4); 113 | BigEndian::read_u32(slice) 114 | }; 115 | 116 | Ok(soa) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::Section; 2 | use std::convert::From; 3 | use std::ffi::{FromBytesWithNulError, NulError}; 4 | use std::fmt; 5 | use std::str::Utf8Error; 6 | 7 | #[derive(Clone, PartialEq, Eq)] 8 | // Taken in part from glibc-2.23/resolv/herror.c h_errlist 9 | #[repr(i32)] 10 | pub enum ResolutionError { 11 | /// Success 12 | Success = 0, 13 | /// Authoritative Answer "Host not found" 14 | HostNotFound = 1, 15 | /// Non-Authoritative "Host not found" or SERVERFAIL. 16 | TryAgain = 2, 17 | /// Non recoverable errors, FORMERR, REFUSED, NOTIMP. 18 | NoRecovery = 3, 19 | /// Valid name, no data record of requested type. 20 | NoData = 4, 21 | } 22 | impl fmt::Debug for ResolutionError { 23 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 24 | match *self { 25 | ResolutionError::Success => write!(f, "Resolver Error 0 (no error)"), 26 | ResolutionError::HostNotFound => write!(f, "Unknown host"), 27 | ResolutionError::TryAgain => write!(f, "Host name lookup failure"), 28 | ResolutionError::NoRecovery => write!(f, "Unknown server error"), 29 | ResolutionError::NoData => write!(f, "No address associated with name"), 30 | } 31 | } 32 | } 33 | 34 | #[derive(Clone, PartialEq, Eq)] 35 | pub enum Error { 36 | /// Name Resolution failed 37 | Resolver(ResolutionError), 38 | /// String contains null bytes 39 | CString(NulError), 40 | /// Stirng contains null bytes 41 | CStr(FromBytesWithNulError), 42 | /// Name service response does not parse 43 | ParseError, 44 | /// Section/Index is out of bounds 45 | NoSuchSectionIndex(Section, usize), 46 | /// Uncompress Error 47 | UncompressError, 48 | /// Result from dn_expand was not null terminated 49 | Unterminated, 50 | /// Wrong Resource record type 51 | WrongRRType, 52 | /// String is not valid UTF-8 53 | Utf8(Utf8Error), 54 | /// Unknown class 55 | UnknownClass(u16), 56 | } 57 | impl fmt::Debug for Error { 58 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 59 | match *self { 60 | Error::Resolver(ref e) => write!(f, "{}: {:?}", self.description(), e), 61 | Error::CString(ref e) => write!( 62 | f, 63 | "Name supplied contains a null byte at \ 64 | position {}", 65 | e.nul_position() 66 | ), 67 | Error::CStr(ref e) => write!(f, "{}: {:?}", self.description(), e), 68 | Error::NoSuchSectionIndex(s, i) => write!( 69 | f, 70 | "No such section index \ 71 | (section={:?}, index={})", 72 | s, i 73 | ), 74 | Error::Utf8(ref e) => write!(f, "{}: {:?}", self.description(), e), 75 | Error::UnknownClass(u) => write!(f, "{}: {}", self.description(), u), 76 | _ => write!(f, "{}", self.description()), 77 | } 78 | } 79 | } 80 | impl Error { 81 | fn description(&self) -> &str { 82 | match *self { 83 | Error::Resolver(_) => "Name Resolution failed", 84 | Error::CString(_) => "Name supplied contains a null byte", 85 | Error::CStr(_) => "CStr failed", 86 | Error::ParseError => "Name service response does not parse", 87 | Error::NoSuchSectionIndex(_, _) => "No such section index", 88 | Error::UncompressError => "Error uncompressing domain name", 89 | Error::Unterminated => "Result from dn_expand was not null terminated", 90 | Error::WrongRRType => "Wrong Resource Record type", 91 | Error::Utf8(_) => "UTF-8 error", 92 | Error::UnknownClass(_) => "Unknown class", 93 | } 94 | } 95 | } 96 | impl ::std::error::Error for Error { 97 | fn cause(&self) -> Option<&dyn (::std::error::Error)> { 98 | match *self { 99 | Error::CString(ref e) => Some(e), 100 | Error::Utf8(ref e) => Some(e), 101 | _ => None, 102 | } 103 | } 104 | } 105 | 106 | impl fmt::Display for Error { 107 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 108 | match *self { 109 | Error::Resolver(ref e) => write!(f, "{}: {:?}", self.description(), e), 110 | Error::CString(ref e) => write!( 111 | f, 112 | "Name supplied contains a null byte at \ 113 | position {}", 114 | e.nul_position() 115 | ), 116 | Error::CStr(ref e) => write!(f, "{}: {:?}", self.description(), e), 117 | Error::NoSuchSectionIndex(s, i) => write!( 118 | f, 119 | "No such section index \ 120 | (section={:?}, index={})", 121 | s, i 122 | ), 123 | Error::Utf8(ref e) => write!(f, "{}: {}", self.description(), e), 124 | Error::UnknownClass(u) => write!(f, "{}: {}", self.description(), u), 125 | _ => write!(f, "{}", self.description()), 126 | } 127 | } 128 | } 129 | 130 | impl From for Error { 131 | fn from(err: ResolutionError) -> Error { 132 | Error::Resolver(err) 133 | } 134 | } 135 | impl From for Error { 136 | fn from(err: Utf8Error) -> Error { 137 | Error::Utf8(err) 138 | } 139 | } 140 | impl From for Error { 141 | fn from(err: FromBytesWithNulError) -> Error { 142 | Error::CStr(err) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/record/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use libresolv_sys::ns_msg as Message; 3 | use libresolv_sys::ns_rr as Rr; 4 | use std::ffi::CStr; 5 | 6 | mod class; 7 | pub use self::class::Class; 8 | 9 | /// For internal use. 10 | pub trait RecordData: Sized { 11 | /// Get type of record 12 | fn get_record_type() -> RecordType; 13 | 14 | /// Convert from low level resource record. For internal use. 15 | fn extract(msg: &mut Message, rr: &Rr) -> Result; 16 | } 17 | 18 | /// A DNS response record of a particular type 19 | #[derive(Debug, Clone)] 20 | pub struct Record { 21 | pub name: String, 22 | pub class: Class, 23 | pub ttl: u32, 24 | pub data: T, 25 | } 26 | 27 | impl Record { 28 | /// For internal use. 29 | pub fn extract(msg: &mut Message, rr: &Rr) -> Result, Error> { 30 | Ok(Record { 31 | name: unsafe { 32 | CStr::from_ptr(rr.name.as_ptr()) 33 | .to_string_lossy() 34 | .into_owned() 35 | }, 36 | class: Class::from_rr_class(rr.rr_class)?, 37 | ttl: rr.ttl, 38 | data: ::extract(msg, rr)?, 39 | }) 40 | } 41 | } 42 | 43 | /// This is a simple u16 value indicating the type of a resource record, and is equal in 44 | /// value to `::libresolv_sys::__ns_type`, but we have extended it with record types not 45 | /// present or supported by the underlying library 46 | #[derive(Debug, Clone, Copy, PartialEq)] 47 | #[allow(non_camel_case_types)] 48 | #[repr(u16)] 49 | pub enum RecordType { 50 | /// RFC 1035 - Host Address 51 | A = 1, 52 | /// RFC 1035 - Authoritative Name Server 53 | NS = 2, 54 | /// RFC 1035 - Mail Destination (Obsolete, use MX) 55 | MD = 3, 56 | /// RFC 1035 - Mail Forwarder (Obsolete, use MX) 57 | MF = 4, 58 | /// RFC 1035, 4035, 6604 - Canonical Name for an Alias 59 | CNAME = 5, 60 | /// RFC 1035, 1982, 2181, 2308 - Start of a Zone of Authority 61 | SOA = 6, 62 | /// RFC 1035 - Mailbox Domain Name (EXPERIMENTAL) 63 | MB = 7, 64 | /// RFC 1035 - Mail Group Member (EXPERIMENTAL) 65 | MG = 8, 66 | /// RFC 1035 - Mail Rename Domain Name (EXPERIMENTAL) 67 | MR = 9, 68 | /// RFC 1035 - A null resource record (EXPERIMENTAL) 69 | NULL = 10, 70 | /// RFC 1035 - A well known service description 71 | WKS = 11, 72 | /// RFC 1035 - A domain name pointer 73 | PTR = 12, 74 | /// RFC 1035 - Host information 75 | HINFO = 13, 76 | /// RFC 1035 - Mailbox or mail list information 77 | MINFO = 14, 78 | /// RFC 1035 - Mail exchange 79 | MX = 15, 80 | /// RFC 1035 - Text strings 81 | TXT = 16, 82 | /// RFC 1183 - Responsible Person 83 | RP = 17, 84 | /// RFC 1183, 6895 - AFS Database Location (Deprecated by RFC5864) 85 | AFSDB = 18, 86 | /// RFC 1183 - X.25 Addresses (EXPERIMENTAL) 87 | X25 = 19, 88 | /// RFC 1183 - ISDN Addresses (EXPERIMENTAL) 89 | ISDN = 20, 90 | /// RFC 1183 - Route Through (EXPERIMENTAL) 91 | RT = 21, 92 | /// RFC 1706 - Network Service Access Protocol 93 | NSAP = 22, 94 | /// RFC 1706 - Network Service Access Protocol PTR 95 | NSAP_PTR = 23, 96 | /// RFC 2535 (Obsolete), 2931 - Signautre 97 | SIG = 24, 98 | /// RFC 2535 (Obsolete) - Key 99 | KEY = 25, 100 | /// RFC 2163, 3597 - Pointer to X.400/RFC822 mapping information 101 | PX = 26, 102 | /// RFC 1712 - Geographical location 103 | GPOS = 27, 104 | /// RFC 3596 - IPv6 Address 105 | AAAA = 28, 106 | /// RFC 1876 - Location Information 107 | LOC = 29, 108 | /// RFC 2535 (Obsolete) - Non-existant Names and Types 109 | NXT = 30, 110 | /// https://tools.ietf.org/html/draft-ietf-nimrod-dns-00 - Endpoint Identifier 111 | EID = 31, 112 | /// https://tools.ietf.org/html/draft-ietf-nimrod-dns-00 - Nimrod Locator 113 | NIMLOC = 32, 114 | /// RFC 2782, 6335 - Service Location 115 | SRV = 33, 116 | ATMA = 34, 117 | NAPTR = 35, 118 | KX = 36, 119 | CERT = 37, 120 | A6 = 38, 121 | /// RFC 2672, 6604 - Delegation Name 122 | DNAME = 39, 123 | SINK = 40, 124 | /// RFC 6891, 6895 - Option 125 | OPT = 41, 126 | APL = 42, 127 | /// RFC 4033, 4034, 4035, 4509 - Delegation signer 128 | DS = 43, 129 | /// RFC 4255 -SSH Public Key Fingerprint 130 | SSHFP = 44, 131 | /// IPsec Key 132 | IPSECKEY = 45, 133 | /// RFC 4033, 4034, 4035, 5702 - DNSSEC signature 134 | RRSIG = 46, 135 | /// RFC 4033, 4034, 4035, 4470 - Next Secure record 136 | NSEC = 47, 137 | /// RFC 4033, 4034, 4035, 5702 - DNS Key 138 | DNSKEY = 48, 139 | /// RFC 4701 - DHCP identifier 140 | DHCID = 49, 141 | /// RFC 5155 - Next Secure record version 3 142 | NSEC3 = 50, 143 | /// RFC 5155 - NSEC3 parameters 144 | NSEC3PARAM = 51, 145 | /// RFC 6698 - DNS-Based Authentication for TLS 146 | TLSA = 52, 147 | /// RFC 5205 - Host Identity Protocol 148 | HIP = 55, 149 | /// Child DS - RFC 7344 150 | CDS = 59, 151 | /// Child DNSKEY - RFC 7344 152 | CDNSKEY = 60, 153 | /// RFC 2930 - Transtion Key record 154 | TKEY = 249, 155 | /// RFC 2845, 3645, 4635, 6895 - Transaction Signature 156 | TSIG = 250, 157 | /// RFC 1995 - Incremental Zone Transfer 158 | IXFR = 251, 159 | /// RFC 1035, 5936 - A request for a transfer of an entire zone 160 | AXFR = 252, 161 | /// RFC 1035 - A request for mailbox-related records (MB, MG or MR) 162 | MAILB = 253, 163 | /// RFC 1035 - A request for mail agent RRs (Obsolete - see MX) 164 | MAILA = 254, 165 | /// RFC 1035 - A request for all records 166 | ANY = 255, 167 | /// ALSO URI=256, see RFC 7553 168 | ZXFR = 256, 169 | /// RFC 6844 - Certification Authority Authorization 170 | CAA = 257, 171 | /// DNSSEC Trust Authorities 172 | TA = 32768, 173 | /// RFC 4431 - DNSSEC Lookaside Validation record 174 | DLV = 32769, 175 | } 176 | 177 | // FIXME: Add the other record types 178 | pub use self::a::A; 179 | pub use self::aaaa::AAAA; 180 | pub use self::cname::CNAME; 181 | pub use self::mx::MX; 182 | pub use self::ns::NS; 183 | pub use self::ptr::PTR; 184 | pub use self::soa::SOA; 185 | pub use self::srv::SRV; 186 | pub use self::tlsa::TLSA; 187 | pub use self::txt::TXT; 188 | 189 | // FIXME: Add the other record types 190 | mod a; 191 | mod aaaa; 192 | mod cname; 193 | mod mx; 194 | mod ns; 195 | mod ptr; 196 | mod soa; 197 | mod srv; 198 | mod tlsa; 199 | mod txt; 200 | -------------------------------------------------------------------------------- /src/response.rs: -------------------------------------------------------------------------------- 1 | use libresolv_sys::ns_msg as Message; 2 | use libresolv_sys::ns_rr as Rr; 3 | use libresolv_sys::ns_sect as NSSection; 4 | 5 | use crate::error::Error; 6 | use crate::record::{Record, RecordData}; 7 | 8 | pub struct Flags(pub u16); 9 | 10 | impl Flags { 11 | #[inline] 12 | pub fn question_response(&self) -> bool { 13 | ((self.0 & 0x8000) >> 15) > 0 14 | } 15 | #[inline] 16 | pub fn operation_code(&self) -> u16 { 17 | (self.0 & 0x7800) >> 11 18 | } 19 | #[inline] 20 | pub fn authoritative_answer(&self) -> bool { 21 | ((self.0 & 0x0400) >> 10) > 0 22 | } 23 | #[inline] 24 | pub fn truncation_occurred(&self) -> bool { 25 | ((self.0 & 0x0200) >> 9) > 0 26 | } 27 | #[inline] 28 | pub fn recursion_desired(&self) -> bool { 29 | ((self.0 & 0x0100) >> 8) > 0 30 | } 31 | #[inline] 32 | pub fn recursion_available(&self) -> bool { 33 | ((self.0 & 0x0080) >> 7) > 0 34 | } 35 | #[inline] 36 | pub fn must_be_zero(&self) -> bool { 37 | ((self.0 & 0x0040) >> 6) > 0 38 | } 39 | #[inline] 40 | pub fn authentic_data(&self) -> bool { 41 | ((self.0 & 0x0020) >> 5) > 0 42 | } 43 | #[inline] 44 | pub fn checking_disabled(&self) -> bool { 45 | ((self.0 & 0x0010) >> 4) > 0 46 | } 47 | #[inline] 48 | pub fn response_code(&self) -> u16 { 49 | self.0 & 0x000f 50 | } 51 | } 52 | 53 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 54 | pub enum Section { 55 | Question, 56 | Answer, 57 | Authority, 58 | Additional, 59 | } 60 | impl Section { 61 | fn ns_sect(&self) -> NSSection { 62 | use ::libresolv_sys::{ 63 | __ns_sect_ns_s_an, __ns_sect_ns_s_ar, __ns_sect_ns_s_ns, __ns_sect_ns_s_qd, 64 | }; 65 | 66 | match *self { 67 | Section::Question => __ns_sect_ns_s_qd, 68 | Section::Answer => __ns_sect_ns_s_an, 69 | Section::Authority => __ns_sect_ns_s_ns, 70 | Section::Additional => __ns_sect_ns_s_ar, 71 | } 72 | } 73 | } 74 | 75 | pub struct Response { 76 | msg: Message, 77 | #[allow(dead_code)] // buffer is where the real data is; keep it until Response drops. 78 | buffer: Box>, 79 | } 80 | 81 | impl Response { 82 | /// This is for internal use. 83 | pub fn new(msg: Message, buffer: Box>) -> Response { 84 | Response { 85 | msg, 86 | buffer, 87 | } 88 | } 89 | 90 | /// Gets the ID field of the Name Server response 91 | pub fn get_id(&self) -> u16 { 92 | self.msg._id 93 | } 94 | 95 | /// Gets flags (and opcodes) in the header of the Name Server response 96 | pub fn get_flags(&self) -> Flags { 97 | Flags(self.msg._flags) 98 | } 99 | 100 | /// Returns a count of how many records exist in the given section 101 | pub fn get_section_count(&self, section: Section) -> usize { 102 | self.msg._counts[section.ns_sect() as usize] as usize 103 | } 104 | 105 | /// Gets a record from a section. Returns an error if index is out of bounds 106 | /// (use get_section_count()). Also returns an error (at run-time) if assigned into 107 | /// a Record of the wrong type. 108 | pub fn get_record(&mut self, section: Section, index: usize) -> Result, Error> 109 | where 110 | T: RecordData, 111 | { 112 | if index >= self.get_section_count(section) { 113 | return Err(Error::NoSuchSectionIndex(section, index)); 114 | } 115 | let mut rr: Rr = Rr::default(); 116 | unsafe { 117 | if ::libresolv_sys::ns_parserr(&mut self.msg, section.ns_sect(), index as i32, &mut rr) 118 | < 0 119 | { 120 | return Err(Error::ParseError); 121 | } 122 | } 123 | 124 | Record::extract(&mut self.msg, &rr) 125 | } 126 | 127 | pub fn questions(&mut self) -> RecordItems 128 | where 129 | T: RecordData, 130 | { 131 | RecordItems { 132 | msg: &mut self.msg, 133 | section: Section::Question, 134 | index: 0, 135 | _marker: ::std::marker::PhantomData, 136 | } 137 | } 138 | 139 | pub fn answers(&mut self) -> RecordItems 140 | where 141 | T: RecordData, 142 | { 143 | RecordItems { 144 | msg: &mut self.msg, 145 | section: Section::Answer, 146 | index: 0, 147 | _marker: ::std::marker::PhantomData, 148 | } 149 | } 150 | 151 | pub fn authorities(&mut self) -> RecordItems 152 | where 153 | T: RecordData, 154 | { 155 | RecordItems { 156 | msg: &mut self.msg, 157 | section: Section::Authority, 158 | index: 0, 159 | _marker: ::std::marker::PhantomData, 160 | } 161 | } 162 | 163 | pub fn additional_records(&mut self) -> RecordItems 164 | where 165 | T: RecordData, 166 | { 167 | RecordItems { 168 | msg: &mut self.msg, 169 | section: Section::Additional, 170 | index: 0, 171 | _marker: ::std::marker::PhantomData, 172 | } 173 | } 174 | } 175 | 176 | /// An iterator to iterate through DNS records 177 | pub struct RecordItems<'a, T: RecordData> { 178 | msg: &'a mut Message, 179 | section: Section, 180 | index: usize, 181 | _marker: ::std::marker::PhantomData, 182 | } 183 | 184 | impl<'a, T: RecordData> Iterator for RecordItems<'a, T> { 185 | type Item = Record; 186 | 187 | fn next(&mut self) -> Option> { 188 | let len = self.msg._counts[self.section.ns_sect() as usize] as usize; 189 | 190 | loop { 191 | if self.index >= len { 192 | return None; 193 | } 194 | 195 | let mut rr: Rr = Rr::default(); 196 | unsafe { 197 | if ::libresolv_sys::ns_parserr( 198 | self.msg, 199 | self.section.ns_sect(), 200 | self.index as i32, 201 | &mut rr, 202 | ) < 0 203 | { 204 | self.index = len; 205 | return None; 206 | } 207 | } 208 | self.index += 1; 209 | 210 | match Record::extract(self.msg, &rr) { 211 | Ok(record) => return Some(record), 212 | Err(_e) => {} // skip the record by looping around 213 | } 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This library consists of a high-level interface to Gnu libc's (glibc) `libresolv` DNS 2 | //! resolver. It allows you to look up DNS resource records of any type (e.g. A, AAAA, MX, TXT, 3 | //! etc), use recursion (if your system's DNS resolver permits it), and perform the DNS search 4 | //! algorithm to complete incomplete names, all via your operating system (glibc, not the kernel). 5 | //! 6 | //! The lower level library, libresolv-sys, was generated from glibc version 2.23 on linux, 7 | //! using the newer thread-safe function calls. It may not work on older version of glibc, or 8 | //! on other operating systems. Pull-requests which improve portability are appreciated. 9 | //! 10 | //! # Example 11 | //! 12 | //! ```` 13 | //! extern crate resolv; 14 | //! 15 | //! use resolv::{Resolver, Class, RecordType, Section, Record}; 16 | //! use resolv::record::MX; 17 | //! 18 | //! fn main() { 19 | //! // You must create a mutable resolver object to hold the context. 20 | //! let mut resolver = Resolver::new().unwrap(); 21 | //! 22 | //! // .query() and .search() are the main interfaces to the resolver. 23 | //! let mut response = resolver.query(b"gmail.com", Class::IN, 24 | //! RecordType::MX).unwrap(); 25 | //! 26 | //! // .get_section_count() returns the number of records in that 27 | //! // section of the response. There are four sections, but we 28 | //! // usually care about `Section::Answer` 29 | //! for i in 0..response.get_section_count(Section::Answer) { 30 | //! 31 | //! // As records are strongly typed, you must know what you are 32 | //! // looking for. If you assign into the wrong type, a run-time 33 | //! // error `WrongRRType` will be returned. 34 | //! let mx: Record = response.get_record(Section::Answer, i) 35 | //! .unwrap(); 36 | //! 37 | //! println!("{:?}", mx); 38 | //! } 39 | //! } 40 | //! ```` 41 | 42 | extern crate byteorder; 43 | extern crate libresolv_sys; 44 | 45 | pub mod error; 46 | use error::{Error, ResolutionError}; 47 | 48 | mod response; 49 | pub use response::{Flags, RecordItems, Response, Section}; 50 | 51 | pub mod record; 52 | pub use record::{Class, Record, RecordType}; 53 | 54 | #[cfg(test)] 55 | mod tests; 56 | 57 | use std::ffi::CString; 58 | use std::ops::{Deref, DerefMut}; 59 | 60 | type Context = libresolv_sys::__res_state; 61 | 62 | pub use libresolv_sys::ResolverOption; 63 | 64 | pub struct Resolver { 65 | context: Context, 66 | } 67 | 68 | impl Resolver { 69 | pub fn new() -> Option { 70 | let mut resolver = Resolver { 71 | context: libresolv_sys::__res_state::default(), 72 | }; 73 | 74 | if unsafe { libresolv_sys::res_ninit(&mut resolver.context) } != 0 { 75 | return None; 76 | } 77 | 78 | resolver.option(ResolverOption::Default, true); 79 | 80 | Some(resolver) 81 | } 82 | 83 | /// Set or unset an option 84 | pub fn option(&mut self, option: ResolverOption, value: bool) { 85 | if value { 86 | self.context.options = self.context.options | (option as u64); 87 | } else { 88 | self.context.options = self.context.options & !(option as u64); 89 | } 90 | } 91 | 92 | /// Lookup the record. Applies the search algorithm to the domain name given 93 | /// (if not fully qualified, it completes it using rules specified in `resolv.conf` 94 | /// search entries). In addition, this also searches your hosts file. Applies 95 | /// recursion if available and not turned off (it is on by default). 96 | /// 97 | /// This is the highest level resolver routine, and is the one called by 98 | /// gethostbyname. 99 | pub fn search( 100 | &mut self, 101 | name: &[u8], 102 | class: Class, 103 | typ: RecordType, 104 | ) -> Result { 105 | let name = match CString::new(name) { 106 | Ok(c) => c, 107 | Err(n) => return Err(Error::CString(n)), 108 | }; 109 | let buflen: usize = libresolv_sys::NS_PACKETSZ as usize; 110 | let mut buffer: Box> = Box::new(Vec::with_capacity(buflen)); 111 | 112 | let rlen: i32 = unsafe { 113 | libresolv_sys::res_nsearch( 114 | &mut self.context, 115 | name.as_ptr(), 116 | class as i32, 117 | typ as i32, 118 | buffer.deref_mut().as_mut_ptr(), 119 | buflen as i32, 120 | ) 121 | }; 122 | if rlen == -1 { 123 | return Err(From::from(self.get_error())); 124 | } 125 | 126 | let mut msg: libresolv_sys::ns_msg = libresolv_sys::ns_msg::default(); 127 | unsafe { 128 | if libresolv_sys::ns_initparse(buffer.deref().as_ptr(), rlen, &mut msg) < 0 { 129 | return Err(Error::ParseError); 130 | } 131 | } 132 | 133 | Ok(Response::new(msg, buffer)) 134 | } 135 | 136 | /// Lookup the record. Does not apply the search algorithm, so `dname` must be a complete 137 | /// domain name, and only DNS will be consulted (not your hosts file). Applies recursion 138 | /// if available and not turned off (it is on by default). 139 | pub fn query( 140 | &mut self, 141 | dname: &[u8], 142 | class: Class, 143 | typ: RecordType, 144 | ) -> Result { 145 | let name = match CString::new(dname) { 146 | Ok(c) => c, 147 | Err(n) => return Err(Error::CString(n)), 148 | }; 149 | let buflen: usize = libresolv_sys::NS_PACKETSZ as usize; 150 | let mut buffer: Box> = Box::new(Vec::with_capacity(buflen)); 151 | 152 | let rlen: i32 = unsafe { 153 | libresolv_sys::res_nquery( 154 | &mut self.context, 155 | name.as_ptr(), 156 | class as i32, 157 | typ as i32, 158 | buffer.deref_mut().as_mut_ptr(), 159 | buflen as i32, 160 | ) 161 | }; 162 | if rlen == -1 { 163 | return Err(From::from(self.get_error())); 164 | } 165 | 166 | let mut msg: libresolv_sys::ns_msg = libresolv_sys::ns_msg::default(); 167 | unsafe { 168 | if libresolv_sys::ns_initparse(buffer.deref().as_ptr(), rlen, &mut msg) < 0 { 169 | return Err(Error::ParseError); 170 | } 171 | } 172 | 173 | Ok(Response::new(msg, buffer)) 174 | } 175 | 176 | fn get_error(&self) -> ResolutionError { 177 | match self.context.res_h_errno { 178 | 0 => ResolutionError::Success, 179 | 1 => ResolutionError::HostNotFound, 180 | 2 => ResolutionError::TryAgain, 181 | 3 => ResolutionError::NoRecovery, 182 | 4 => ResolutionError::NoData, 183 | _ => ResolutionError::HostNotFound, // fallback 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------