use std::env;
use std::fs::File;
use std::{process::exit, str::FromStr};
use bytes::Bytes;
use domain::base::iana::{Class, Rcode};
use domain::base::record::ComposeRecord;
use domain::base::{Name, ParsedName, Rtype};
use domain::base::{ParsedRecord, Record};
use domain::rdata::ZoneRecordData;
use domain::zonefile::inplace;
use domain::zonetree::{Answer, Rrset};
use domain::zonetree::{Zone, ZoneTree};
use octseq::Parser;
use tracing_subscriber::EnvFilter;
#[path = "common/serve-utils.rs"]
mod common;
#[derive(PartialEq, Eq)]
enum Verbosity {
Quiet,
Normal,
Verbose(u8),
}
fn main() {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.with_thread_ids(true)
.without_time()
.try_init()
.ok();
let mut args = env::args();
let prog_name = args.next().unwrap(); let usage = format!(
"Usage: {prog_name} [-q|--quiet|-v|--verbose] [+short] <zonefile_path> [<zonefile_path> ..] <qtype> <qname>",
);
let (verbosity, zone_files, qtype, qname, short) =
process_dig_style_args(args).unwrap_or_else(|err| {
eprintln!("{usage}");
eprintln!("{err}");
exit(2);
});
let mut zones = ZoneTree::new();
for (zone_file_path, mut zone_file) in zone_files {
if verbosity != Verbosity::Quiet {
println!("Reading zone file '{zone_file_path}'...");
}
let reader = inplace::Zonefile::load(&mut zone_file).unwrap();
if verbosity != Verbosity::Quiet {
println!("Constructing zone...");
}
let zone = Zone::try_from(reader).unwrap_or_else(|err| {
eprintln!("Error while constructing zone: {err}");
exit(1);
});
if verbosity != Verbosity::Quiet {
println!(
"Inserting zone for {} class {}...",
zone.apex_name(),
zone.class()
);
}
zones.insert_zone(zone).unwrap_or_else(|err| {
eprintln!("Error while inserting zone: {err}");
exit(1);
});
}
if let Verbosity::Verbose(level) = verbosity {
for zone in zones.iter_zones() {
println!(
"Dumping zone {} class {}...",
zone.apex_name(),
zone.class()
);
zone.read()
.walk(Box::new(move |owner, rrset, _at_zone_cut| {
dump_rrset(owner, rrset);
}));
println!("Dump complete.");
if level > 0 {
println!("Debug dumping zone...");
dbg!(zone);
}
}
}
let qclass = Class::IN;
if verbosity != Verbosity::Quiet {
println!("Finding zone for qname {qname} class {qclass}...");
}
let zone_answer = if let Some(zone) = zones.find_zone(&qname, qclass) {
if verbosity != Verbosity::Quiet {
println!("Querying zone {} class {} for qname {qname} with qtype {qtype}...", zone.apex_name(), zone.class());
}
zone.read().query(qname.clone(), qtype).unwrap()
} else {
Answer::new(Rcode::NXDOMAIN)
};
if verbosity != Verbosity::Quiet {
println!("Preparing dig style response...\n");
}
let wire_query = common::generate_wire_query(&qname, qtype);
let wire_response =
common::generate_wire_response(&wire_query, zone_answer);
common::print_dig_style_response(&wire_query, &wire_response, short);
}
#[allow(clippy::type_complexity)]
fn process_dig_style_args(
args: env::Args,
) -> Result<(Verbosity, Vec<(String, File)>, Rtype, Name<Bytes>, bool), String>
{
let mut abort_with_usage = false;
let mut verbosity = Verbosity::Normal;
let mut short = false;
let mut zone_files = vec![];
let args: Vec<_> = args
.filter(|arg| {
if arg.starts_with(['-', '+']) {
match arg.as_str() {
"-q" | "--quiet" => verbosity = Verbosity::Quiet,
"-v" | "--verbose" => {
if let Verbosity::Verbose(level) = verbosity {
verbosity = Verbosity::Verbose(level + 1)
} else {
verbosity = Verbosity::Verbose(0)
}
}
"+short" => {
short = true;
if verbosity == Verbosity::Normal {
verbosity = Verbosity::Quiet
}
}
_ => abort_with_usage = true,
}
false } else {
true }
})
.collect();
if args.len() >= 3 {
let mut i = 0;
while i < args.len() - 2 {
let zone_file = File::open(&args[i]).map_err(|err| {
format!("Cannot open zone file '{}': {err}", args[i])
})?;
zone_files.push((args[i].to_string(), zone_file));
i += 1;
}
let qtype = Rtype::from_str(&args[i])
.map_err(|err| format!("Cannot parse qtype: {err}"))?;
i += 1;
let qname = Name::<Bytes>::from_str(&args[i])
.map_err(|err| format!("Cannot parse qname: {err}"))?;
Ok((verbosity, zone_files, qtype, qname, short))
} else {
Err("Insufficient arguments".to_string())
}
}
fn dump_rrset(owner: Name<Bytes>, rrset: &Rrset) {
let mut target = Vec::<u8>::new();
for item in rrset.data() {
let record = Record::new(owner.clone(), Class::IN, rrset.ttl(), item);
if record.compose_record(&mut target).is_ok() {
let mut parser = Parser::from_ref(&target);
if let Ok(parsed_record) = ParsedRecord::parse(&mut parser) {
if let Ok(Some(record)) = parsed_record
.into_record::<ZoneRecordData<_, ParsedName<_>>>()
{
println!("> {record}");
}
}
}
}
}