diff --git a/doc/dnsi-lookup.1 b/doc/dnsi-lookup.1 new file mode 100644 index 0000000..bf955fc --- /dev/null +++ b/doc/dnsi-lookup.1 @@ -0,0 +1,20 @@ +.TH "dnsi-lookup" "1" "NLnet Labs" + +.SH NAME +dnsi-lookup - Look up a host or address + +.SH SYNOPSIS +.B dnsi lookup +[\fIoptions\fR] +.I host_or_addr + +.SH DESCRIPTION +The +.B dnsi lookup +command looks up the given hosts and address, does a corresponding forward or +reverse lookup and prints the results. + +.SH OPTIONS +.TP +.BR -h ,\ --help +Print help information. diff --git a/doc/dnsi.1 b/doc/dnsi.1 index 3075885..9a77d5c 100644 --- a/doc/dnsi.1 +++ b/doc/dnsi.1 @@ -20,7 +20,14 @@ information. .SH IDNS COMMANDS +.PP +\fBdnsi-lookup\fR(1) +.RS 4 +Look up a host or address. +.RE + .PP \fBdnsi-query\fR(1) .RS 4 Send a query to a name server. +.RE diff --git a/src/commands/lookup.rs b/src/commands/lookup.rs new file mode 100644 index 0000000..03e6b39 --- /dev/null +++ b/src/commands/lookup.rs @@ -0,0 +1,125 @@ +//! The lookup command of _dnsi._ + +use crate::error::Error; +use domain::base::name::UncertainName; +use domain::resolv::stub::StubResolver; +use std::net::IpAddr; +use std::str::FromStr; + +//------------ Lookup -------------------------------------------------------- + +#[derive(Clone, Debug, clap::Args)] +pub struct Lookup { + /// The host or address to look up. + #[arg(value_name = "HOST_OR_ADDR")] + names: Vec, +} + +/// # Executing the command +/// +impl Lookup { + pub fn execute(self) -> Result<(), Error> { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(self.async_execute()) + } + + pub async fn async_execute(self) -> Result<(), Error> { + let resolver = StubResolver::new(); + + let mut res = Ok(()); + let mut names = self.names.iter(); + + if let Some(name) = names.next() { + res = res.and(self.execute_one_name(&resolver, name).await); + } + + for name in names { + println!(); + res = res.and(self.execute_one_name(&resolver, name).await); + } + + res.map_err(|_| "not all lookups have succeeded".into()) + } + + async fn execute_one_name(&self, resolver: &StubResolver, name: &ServerName) -> Result<(), ()> { + let res = match name { + ServerName::Name(host) => forward(resolver, host).await, + ServerName::Addr(addr) => reverse(resolver, *addr).await, + }; + + if let Err(err) = res { + eprintln!("{err}"); + return Err(()); + } + + Ok(()) + } +} + +async fn forward(resolver: &StubResolver, name: &UncertainName>) -> Result<(), Error> { + let answer = match name { + UncertainName::Absolute(ref name) => resolver.lookup_host(name).await?, + UncertainName::Relative(ref name) => resolver.search_host(name).await?, + }; + + print!("{name}"); + + let canon = answer.canonical_name(); + if canon != answer.qname() { + print!(" (alias for {canon})"); + } + + println!(); + + let addrs: Vec<_> = answer.iter().collect(); + if addrs.is_empty() { + println!(" "); + } else { + for addr in addrs { + println!(" {addr}"); + } + } + + Ok(()) +} + +async fn reverse(resolver: &StubResolver, addr: IpAddr) -> Result<(), Error> { + let answer = resolver.lookup_addr(addr).await?; + println!("{addr}"); + + let hosts: Vec<_> = answer.iter().collect(); + if hosts.is_empty() { + println!(" "); + } else { + for name in hosts { + println!(" {name}"); + } + } + + Ok(()) +} + +//------------ ServerName --------------------------------------------------- + +#[derive(Clone, Debug)] +enum ServerName { + Name(UncertainName>), + Addr(IpAddr), +} + +impl FromStr for ServerName { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + if let Ok(addr) = IpAddr::from_str(s) { + Ok(ServerName::Addr(addr)) + } else { + UncertainName::from_str(s) + .map(Self::Name) + .map_err(|_| "illegal host name or address") + } + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index da31f42..e396750 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -2,6 +2,7 @@ pub mod help; pub mod query; +pub mod lookup; use super::error::Error; @@ -12,6 +13,9 @@ pub enum Commands { /// Query the DNS. Query(self::query::Query), + /// Lookup a host or address. + Lookup(self::lookup::Lookup), + /// Show the manual pages. Man(self::help::Help), } @@ -20,6 +24,7 @@ impl Commands { pub fn execute(self) -> Result<(), Error> { match self { Self::Query(query) => query.execute(), + Self::Lookup(lookup) => lookup.execute(), Self::Man(help) => help.execute(), } }