Skip to content

Commit

Permalink
Separate ldns parsing from clap parsing (#19)
Browse files Browse the repository at this point in the history
* Separate ldns parsing from clap parsing

Co-authored-by: Jannik Peters <[email protected]>

* nsec3-hash: sort ldns argument parsing alphabetically

* abort ldns parsing early on malfored argv[0]

* add 'Error' prefix to ldns parsing errors

* use clap default exit code of 2 for dnst versions of commands

* remove args_overide_self from nsec3hash

* move parse_os and parse_os_with out of LdnsCommand

---------

Co-authored-by: Jannik Peters <[email protected]>
  • Loading branch information
tertsdiepraam and mozzieongit authored Nov 11, 2024
1 parent fd099ce commit bae3b00
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 5 deletions.
15 changes: 11 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2021"
[dependencies]
clap = { version = "4", features = ["derive"] }
domain = "0.10.1"
lexopt = "0.3.0"

# for implementation of nsec3 hash until domain has it stabilized
octseq = { version = "0.5.1", features = ["std"] }
Expand Down
6 changes: 6 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ impl Args {
self.command.execute()
}
}

impl From<Command> for Args {
fn from(value: Command) -> Self {
Args { command: value }
}
}
55 changes: 55 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
pub mod help;
pub mod nsec3hash;

use std::ffi::OsStr;
use std::str::FromStr;

use nsec3hash::Nsec3Hash;

use crate::Args;

use super::error::Error;

#[derive(Clone, Debug, clap::Subcommand)]
Expand All @@ -23,3 +30,51 @@ impl Command {
}
}
}

/// A command that can be invoked in an LDNS compatibility mode
///
/// These commands do their own argument parsing, because clap cannot always
/// (easily) parse arguments in the same way that the ldns tools do.
///
/// The `LdnsCommand::parse_ldns` function should parse arguments obtained
/// with [`std::env::args`] or [`std::env::args_os`] and return an error in
/// case of invalid arguments. The help string provided as
/// [`LdnsCommand::HELP`] is automatically appended to returned errors.
pub trait LdnsCommand: Into<Command> {
const HELP: &'static str;

fn parse_ldns() -> Result<Self, Error>;

fn parse_ldns_args() -> Result<Args, Error> {
match Self::parse_ldns() {
Ok(c) => Ok(Args::from(c.into())),
Err(e) => Err(format!("Error: {e}\n\n{}", Self::HELP).into()),
}
}
}

impl From<Nsec3Hash> for Command {
fn from(val: Nsec3Hash) -> Self {
Command::Nsec3Hash(val)
}
}

/// Utility function to parse an [`OsStr`] with a custom function
fn parse_os_with<T, E>(opt: &str, val: &OsStr, f: impl Fn(&str) -> Result<T, E>) -> Result<T, Error>
where
E: std::fmt::Display,
{
let Some(s) = val.to_str() else {
return Err(format!("Invalid value for {opt}: {val:?} is not valid unicode",).into());
};

f(s).map_err(|e| format!("Invalid value {val:?} for {opt}: {e}").into())
}

/// Utility function to parse an [`OsStr`] into a value via [`FromStr`]
fn parse_os<T: FromStr>(opt: &str, val: &OsStr) -> Result<T, Error>
where
T::Err: std::fmt::Display,
{
parse_os_with(opt, val, T::from_str)
}
65 changes: 65 additions & 0 deletions src/commands/nsec3hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ use domain::base::iana::nsec3::Nsec3HashAlg;
use domain::base::name::Name;
use domain::base::ToName;
use domain::rdata::nsec3::{Nsec3Salt, OwnerHash};
use lexopt::Arg;
// use domain::validator::nsec::nsec3_hash;
use octseq::OctetsBuilder;
use ring::digest;
use std::str::FromStr;

use super::{parse_os, parse_os_with, LdnsCommand};

#[derive(Clone, Debug, clap::Args)]
pub struct Nsec3Hash {
/// The hashing algorithm to use
Expand Down Expand Up @@ -40,6 +43,68 @@ pub struct Nsec3Hash {
name: Name<Vec<u8>>,
}

const LDNS_HELP: &str = "\
ldns-nsec3-hash [OPTIONS] <domain name>
prints the NSEC3 hash of the given domain name
-a <algorithm> hashing algorithm number
-t <number> iterations
-s <string> salt in hex\
";

impl LdnsCommand for Nsec3Hash {
const HELP: &'static str = LDNS_HELP;

fn parse_ldns() -> Result<Self, Error> {
let mut algorithm = Nsec3HashAlg::SHA1;
let mut iterations = 1;
let mut salt = Nsec3Salt::empty();
let mut name = None;

let mut parser = lexopt::Parser::from_env();

while let Some(arg) = parser.next()? {
match arg {
Arg::Short('a') => {
let val = parser.value()?;
algorithm = parse_os_with("algorithm (-a)", &val, Nsec3Hash::parse_nsec_alg)?;
}
Arg::Short('s') => {
let val = parser.value()?;
salt = parse_os("salt (-s)", &val)?;
}
Arg::Short('t') => {
let val = parser.value()?;
iterations = parse_os("iterations (-t)", &val)?;
}
Arg::Value(val) => {
// Strange ldns compatibility case: only the first
// domain name is used.
if name.is_some() {
continue;
}
name = Some(parse_os("domain name", &val)?);
}
Arg::Short(x) => return Err(format!("Invalid short option: -{x}").into()),
Arg::Long(x) => {
return Err(format!("Long options are not supported, but `--{x}` given").into())
}
}
}

let Some(name) = name else {
return Err("Missing domain name argument".into());
};

Ok(Self {
algorithm,
iterations,
salt,
name,
})
}
}

impl Nsec3Hash {
pub fn parse_name(arg: &str) -> Result<Name<Vec<u8>>, Error> {
Name::from_str(&arg.to_lowercase()).map_err(|e| Error::from(e.to_string()))
Expand Down
6 changes: 6 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ impl From<io::Error> for Error {
}
}

impl From<lexopt::Error> for Error {
fn from(value: lexopt::Error) -> Self {
value.to_string().into()
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.message, f)
Expand Down
28 changes: 27 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
use std::path::Path;

use clap::Parser;
use dnst::commands::{nsec3hash::Nsec3Hash, LdnsCommand};

fn main() {
if let Err(err) = dnst::Args::parse().execute() {
// If none of the ldns-* tools matched, then we continue with clap
// argument parsing.
let args = try_ldns_compatibility().unwrap_or_else(dnst::Args::parse);

if let Err(err) = args.execute() {
eprintln!("{}", err);
}
}

fn try_ldns_compatibility() -> Option<dnst::Args> {
let binary_path = std::env::args_os().next()?;

let binary_name = Path::new(&binary_path).file_name()?.to_str()?;

let res = match binary_name {
"ldns-nsec3-hash" => Nsec3Hash::parse_ldns_args(),
_ => return None,
};

match res {
Ok(args) => Some(args),
Err(e) => {
eprintln!("{e}");
std::process::exit(1)
}
}
}

0 comments on commit bae3b00

Please sign in to comment.