domain 0.12.0

A DNS library for Rust.
Documentation
use core::str::FromStr;

use std::io::Read;
use std::string::ToString;
use std::vec::Vec;

use bytes::Bytes;

use crate::base::iana::{Class, SecurityAlgorithm};
use crate::base::name::{FlattenInto, Name};
use crate::base::{Record, Rtype, Serial, ToName, Ttl};
use crate::dnssec::common::nsec3_hash;
use crate::dnssec::sign::denial::nsec3::mk_hashed_nsec3_owner_name;
use crate::rdata::dnssec::{RtypeBitmap, Timestamp};
use crate::rdata::nsec3::OwnerHash;
use crate::rdata::{
    Aaaa, Dnskey, Ns, Nsec, Nsec3, Nsec3param, Rrsig, Soa, A,
};
use crate::utils::base32;
use crate::zonefile::inplace::{Entry, Zonefile};
use crate::zonetree::types::StoredRecordData;
use crate::zonetree::StoredName;

use super::denial::nsec3::GenerateNsec3Config;
use super::records::SortedRecords;

pub(crate) const TEST_TTL: Ttl = Ttl::from_secs(3600);

pub(crate) fn bytes_to_records(
    mut zonefile: impl Read,
) -> SortedRecords<StoredName, StoredRecordData> {
    let reader = Zonefile::load(&mut zonefile).unwrap();
    let mut records = SortedRecords::default();
    for entry in reader {
        let entry = entry.unwrap();
        if let Entry::Record(record) = entry {
            records.insert(record.flatten_into()).unwrap()
        }
    }
    records
}

pub(crate) fn mk_name(name: &str) -> StoredName {
    StoredName::from_str(name).unwrap()
}

pub(crate) fn mk_record<D>(owner: &str, data: D) -> Record<StoredName, D> {
    Record::new(mk_name(owner), Class::IN, TEST_TTL, data)
}

pub(crate) fn mk_a_rr<R>(owner: &str) -> Record<StoredName, R>
where
    R: From<A>,
{
    mk_record(owner, A::from_str("1.2.3.4").unwrap().into())
}

pub(crate) fn mk_aaaa_rr<R>(owner: &str) -> Record<StoredName, R>
where
    R: From<Aaaa>,
{
    mk_record(owner, Aaaa::from_str("2001:db8::0").unwrap().into())
}

pub(crate) fn mk_dnskey_rr<R>(
    owner: &str,
    flags: u16,
    algorithm: SecurityAlgorithm,
    public_key: &Bytes,
) -> Record<StoredName, R>
where
    R: From<Dnskey<Bytes>>,
{
    // https://datatracker.ietf.org/doc/html/rfc4034#section-2.1.2
    // 2.1.2.  The Protocol Field
    //   "The Protocol Field MUST have value 3, and the DNSKEY RR MUST be
    //    treated as invalid during signature verification if it is found to
    //    be some value other than 3."
    mk_record(
        owner,
        Dnskey::new(flags, 3, algorithm, public_key.clone())
            .unwrap()
            .into(),
    )
}

pub(crate) fn mk_ns_rr<R>(owner: &str, nsdname: &str) -> Record<StoredName, R>
where
    R: From<Ns<StoredName>>,
{
    let nsdname = mk_name(nsdname);
    mk_record(owner, Ns::new(nsdname).into())
}

pub(crate) fn mk_nsec_rr<R>(
    owner: &str,
    next_name: &str,
    types: &str,
) -> Record<StoredName, R>
where
    R: From<Nsec<Bytes, StoredName>>,
{
    let next_name = mk_name(next_name);
    let mut builder = RtypeBitmap::<Bytes>::builder();
    for rtype in types.split_whitespace() {
        builder.add(Rtype::from_str(rtype).unwrap()).unwrap();
    }
    let types = builder.finalize();
    mk_record(owner, Nsec::new(next_name, types).into())
}

pub(crate) fn mk_nsec3param_rr<R, Sort>(
    owner: &str,
    cfg: &GenerateNsec3Config<Bytes, Sort>,
) -> Record<StoredName, R>
where
    R: From<Nsec3param<Bytes>>,
{
    mk_record(owner, cfg.params.clone().into())
}

pub(crate) fn mk_nsec3_rr<R, Sort>(
    apex_owner: &str,
    owner: &str,
    next_owner: &str,
    types: &str,
    cfg: &GenerateNsec3Config<Bytes, Sort>,
) -> Record<StoredName, R>
where
    R: From<Nsec3<Bytes>>,
{
    let hashed_owner_name =
        mk_hashed_nsec3_owner_name::<Name<Bytes>, Bytes, Bytes>(
            &Name::from_str(owner).unwrap(),
            cfg.params.hash_algorithm(),
            cfg.params.iterations(),
            cfg.params.salt(),
            &Name::from_str(apex_owner).unwrap(),
        )
        .unwrap()
        .to_name::<Bytes>()
        .to_string();

    let next_owner_hash_octets: Vec<u8> = nsec3_hash::<Name<Vec<u8>>, _, _>(
        Name::from_str(next_owner).unwrap(),
        cfg.params.hash_algorithm(),
        cfg.params.iterations(),
        cfg.params.salt(),
    )
    .unwrap()
    .into_octets();
    let next_owner_hash = base32::encode_string_hex(&next_owner_hash_octets)
        .to_ascii_lowercase();

    let mut builder = RtypeBitmap::<Bytes>::builder();
    for rtype in types.split_whitespace() {
        builder.add(Rtype::from_str(rtype).unwrap()).unwrap();
    }
    let types = builder.finalize();

    mk_record(
        &hashed_owner_name,
        Nsec3::new(
            cfg.params.hash_algorithm(),
            cfg.params.flags(),
            cfg.params.iterations(),
            cfg.params.salt().clone(),
            OwnerHash::from_str(&next_owner_hash).unwrap(),
            types,
        )
        .into(),
    )
}

pub(crate) fn mk_precalculated_nsec3_rr<R, Sort>(
    owner: &str,
    next_owner: &str,
    types: &str,
    cfg: &GenerateNsec3Config<Bytes, Sort>,
) -> Record<StoredName, R>
where
    R: From<Nsec3<Bytes>>,
{
    let mut builder = RtypeBitmap::<Bytes>::builder();
    for rtype in types.split_whitespace() {
        builder.add(Rtype::from_str(rtype).unwrap()).unwrap();
    }
    let types = builder.finalize();

    mk_record(
        owner,
        Nsec3::new(
            cfg.params.hash_algorithm(),
            cfg.params.flags(),
            cfg.params.iterations(),
            cfg.params.salt().clone(),
            OwnerHash::from_str(next_owner).unwrap(),
            types,
        )
        .into(),
    )
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn mk_rrsig_rr<R>(
    owner: &str,
    covered_rtype: Rtype,
    algorithm: &SecurityAlgorithm,
    labels: u8,
    expiration: u32,
    inception: u32,
    key_tag: u16,
    signer_name: &str,
    signature: Bytes,
) -> Record<StoredName, R>
where
    R: From<Rrsig<Bytes, StoredName>>,
{
    let signer_name = mk_name(signer_name);
    let expiration = Timestamp::from(expiration);
    let inception = Timestamp::from(inception);
    mk_record(
        owner,
        Rrsig::new(
            covered_rtype,
            *algorithm,
            labels,
            TEST_TTL,
            expiration,
            inception,
            key_tag,
            signer_name,
            signature,
        )
        .unwrap()
        .into(),
    )
}

pub(crate) fn mk_soa_rr<R>(
    owner: &str,
    mname: &str,
    rname: &str,
) -> Record<StoredName, R>
where
    R: From<Soa<StoredName>>,
{
    let soa = Soa::new(
        mk_name(mname),
        mk_name(rname),
        Serial::now(),
        TEST_TTL,
        TEST_TTL,
        TEST_TTL,
        TEST_TTL,
    );
    mk_record(owner, soa.into())
}

#[allow(clippy::type_complexity)]
pub(crate) fn contains_owner<R>(
    recs: &[Record<StoredName, R>],
    name: &str,
) -> bool {
    let name = mk_name(name);
    recs.iter().any(|rr| rr.owner() == &name)
}