diff --git a/Cargo.lock b/Cargo.lock index 83df422af..b90217517 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -783,6 +783,56 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hickory-proto" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07698b8420e2f0d6447a436ba999ec85d8fbf2a398bbd737b82cac4a2e96e512" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.4.0", + "ipnet", + "once_cell", + "rand 0.8.5", + "rustls", + "rustls-pemfile", + "thiserror", + "tinyvec", + "tokio", + "tokio-rustls", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28757f23aa75c98f254cf0405e6d8c25b831b32921b050a66692427679b1f243" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand 0.8.5", + "resolv-conf", + "rustls", + "smallvec", + "thiserror", + "tokio", + "tokio-rustls", + "tracing", +] + [[package]] name = "hostname" version = "0.3.1" @@ -1430,6 +1480,7 @@ dependencies = [ "env_logger", "futures", "gcd", + "hickory-resolver", "itertools", "log", "rand 0.7.3", @@ -1440,7 +1491,6 @@ dependencies = [ "subprocess", "text_placeholder", "toml", - "trust-dns-resolver", "wait-timeout", ] @@ -1755,59 +1805,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "trust-dns-proto" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.4.0", - "ipnet", - "once_cell", - "rand 0.8.5", - "rustls", - "rustls-pemfile", - "rustls-webpki", - "smallvec", - "thiserror", - "tinyvec", - "tokio", - "tokio-rustls", - "tracing", - "url", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6" -dependencies = [ - "cfg-if", - "futures-util", - "ipconfig", - "lru-cache", - "once_cell", - "parking_lot", - "rand 0.8.5", - "resolv-conf", - "rustls", - "smallvec", - "thiserror", - "tokio", - "tokio-rustls", - "tracing", - "trust-dns-proto", - "webpki-roots", -] - [[package]] name = "unicode-bidi" version = "0.3.15" @@ -1985,12 +1982,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - [[package]] name = "widestring" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 090a0d7af..08dc6fcd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ serde = "1.0.124" serde_derive = "1.0.116" cidr-utils = "0.5.1" itertools = "0.9.0" -trust-dns-resolver = { version = "0.23.2", features = ["dns-over-rustls"] } +hickory-resolver = { version = "0.24.0", features = ["dns-over-rustls"] } anyhow = "1.0.40" subprocess = "0.2.6" text_placeholder = { version = "0.5", features = ["struct_context"] } diff --git a/src/address.rs b/src/address.rs index 550b80901..4bce3a4c1 100644 --- a/src/address.rs +++ b/src/address.rs @@ -1,15 +1,16 @@ //! Provides functions to parse input IP addresses, CIDRs or files. -use std::fs::File; +use std::fs::{self, File}; use std::io::{prelude::*, BufReader}; -use std::net::{IpAddr, ToSocketAddrs}; +use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::path::Path; +use std::str::FromStr; use cidr_utils::cidr::IpCidr; -use log::debug; -use trust_dns_resolver::{ - config::{ResolverConfig, ResolverOpts}, +use hickory_resolver::{ + config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts}, Resolver, }; +use log::debug; use crate::input::Opts; use crate::warning; @@ -29,8 +30,7 @@ use crate::warning; pub fn parse_addresses(input: &Opts) -> Vec { let mut ips: Vec = Vec::new(); let mut unresolved_addresses: Vec<&str> = Vec::new(); - let backup_resolver = - Resolver::new(ResolverConfig::cloudflare_tls(), ResolverOpts::default()).unwrap(); + let backup_resolver = get_resolver(&input.resolver); for address in &input.addresses { let parsed_ips = parse_address(address, &backup_resolver); @@ -72,11 +72,14 @@ pub fn parse_addresses(input: &Opts) -> Vec { /// Given a string, parse it as a host, IP address, or CIDR. /// /// This allows us to pass files as hosts or cidr or IPs easily -/// Call this every time you have a possible IP_or_host +/// Call this every time you have a possible IP-or-host. +/// +/// If the address is a domain, we can self-resolve the domain locally +/// or resolve it by dns resolver list. /// /// ```rust /// # use rustscan::address::parse_address; -/// # use trust_dns_resolver::Resolver; +/// # use hickory_resolver::Resolver; /// let ips = parse_address("127.0.0.1", &Resolver::default().unwrap()); /// ``` pub fn parse_address(address: &str, resolver: &Resolver) -> Vec { @@ -94,7 +97,7 @@ pub fn parse_address(address: &str, resolver: &Resolver) -> Vec { /// Uses DNS to get the IPS associated with host fn resolve_ips_from_host(source: &str, backup_resolver: &Resolver) -> Vec { - let mut ips: Vec = Vec::new(); + let mut ips: Vec = Vec::new(); if let Ok(addrs) = source.to_socket_addrs() { for ip in addrs { @@ -107,16 +110,64 @@ fn resolve_ips_from_host(source: &str, backup_resolver: &Resolver) -> Vec) -> Resolver { + match resolver { + Some(r) => { + let mut config = ResolverConfig::new(); + let resolver_ips = match read_resolver_from_file(r) { + Ok(ips) => ips, + Err(_) => r + .split(',') + .filter_map(|r| IpAddr::from_str(r).ok()) + .collect::>(), + }; + for ip in resolver_ips { + config.add_name_server(NameServerConfig::new( + SocketAddr::new(ip, 53), + Protocol::Udp, + )); + } + Resolver::new(config, ResolverOpts::default()).unwrap() + } + None => match Resolver::from_system_conf() { + Ok(resolver) => resolver, + Err(_) => { + Resolver::new(ResolverConfig::cloudflare_tls(), ResolverOpts::default()).unwrap() + } + }, + } +} + +/// Parses and input file of IPs for use in DNS resolution. +fn read_resolver_from_file(path: &str) -> Result, std::io::Error> { + let ips = fs::read_to_string(path)? + .lines() + .filter_map(|line| IpAddr::from_str(line.trim()).ok()) + .collect(); + + Ok(ips) +} + #[cfg(not(tarpaulin_include))] /// Parses an input file of IPs and uses those fn read_ips_from_file( ips: &std::path::Path, backup_resolver: &Resolver, -) -> Result, std::io::Error> { +) -> Result, std::io::Error> { let file = File::open(ips)?; let reader = BufReader::new(file); - let mut ips: Vec = Vec::new(); + let mut ips: Vec = Vec::new(); for address_line in reader.lines() { if let Ok(address) = address_line { @@ -131,7 +182,7 @@ fn read_ips_from_file( #[cfg(test)] mod tests { - use super::{parse_addresses, Opts}; + use super::{get_resolver, parse_addresses, Opts}; use std::net::Ipv4Addr; #[test] @@ -204,4 +255,27 @@ mod tests { let ips = parse_addresses(&opts); assert_eq!(ips.len(), 0); } + + #[test] + fn resolver_default_cloudflare() { + let opts = Opts::default(); + + let resolver = get_resolver(&opts.resolver); + let lookup = resolver.lookup_ip("www.example.com.").unwrap(); + + assert!(opts.resolver.is_none()); + assert!(lookup.iter().next().is_some()); + } + + #[test] + fn resolver_args_google_dns() { + let mut opts = Opts::default(); + // https://developers.google.com/speed/public-dns + opts.resolver = Some("8.8.8.8,8.8.4.4".to_owned()); + + let resolver = get_resolver(&opts.resolver); + let lookup = resolver.lookup_ip("www.example.com.").unwrap(); + + assert!(lookup.iter().next().is_some()); + } } diff --git a/src/input.rs b/src/input.rs index 8028f13f9..08fbed899 100644 --- a/src/input.rs +++ b/src/input.rs @@ -100,6 +100,10 @@ pub struct Opts { #[structopt(long)] pub accessible: bool, + /// A comma-delimited list or file of DNS resolvers. + #[structopt(long)] + pub resolver: Option, + /// The batch size for port scanning, it increases or slows the speed of /// scanning. Depends on the open file limit of your OS. If you do 65535 /// it will do every port at the same time. Although, your OS may not @@ -208,7 +212,7 @@ impl Opts { self.ports = Some(ports); } - merge_optional!(range, ulimit, exclude_ports); + merge_optional!(range, resolver, ulimit, exclude_ports); } } @@ -225,6 +229,7 @@ impl Default for Opts { ulimit: None, command: vec![], accessible: false, + resolver: None, scan_order: ScanOrder::Serial, no_config: true, top: false, @@ -250,6 +255,7 @@ pub struct Config { timeout: Option, tries: Option, ulimit: Option, + resolver: Option, scan_order: Option, command: Option>, scripts: Option, @@ -317,6 +323,7 @@ mod tests { ulimit: None, command: Some(vec!["-A".to_owned()]), accessible: Some(true), + resolver: None, scan_order: Some(ScanOrder::Random), scripts: None, exclude_ports: None, @@ -364,10 +371,12 @@ mod tests { end: 1_000, }); config.ulimit = Some(1_000); + config.resolver = Some("1.1.1.1".to_owned()); opts.merge_optional(&config); assert_eq!(opts.range, config.range); assert_eq!(opts.ulimit, config.ulimit); + assert_eq!(opts.resolver, config.resolver); } }