trust-dns-client 0.22.0

Trust-DNS is a safe and secure DNS library. This is the Client library with DNSec support. DNSSec with NSEC validation for negative records, is complete. The client supports dynamic DNS with SIG0 authenticated requests, implementing easy to use high level funtions. Trust-DNS is based on the Tokio and Futures libraries, which means it should be easily integrated into other software that also use those libraries.
Documentation
/*
 * Copyright (C) 2015-2019 Benjamin Fry <benjaminfry@me.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! record data enum variants

use crate::error::*;
#[cfg(feature = "dnssec")]
use crate::proto::rr::dnssec::rdata::DNSSECRData;
use crate::rr::{Name, RData, RecordType};
use crate::serialize::txt::rdata_parsers::*;

use crate::serialize::txt::zone_lex::Lexer;

use super::Token;

/// Extension on RData for text parsing
pub trait RDataParser: Sized {
    /// Attempts to parse a streem of tokenized strs into the RData of the specified record type
    fn parse<'i, I: Iterator<Item = &'i str>>(
        record_type: RecordType,
        tokens: I,
        origin: Option<&Name>,
    ) -> ParseResult<Self>;

    /// Parse RData from a string
    fn try_from_str(record_type: RecordType, s: &str) -> ParseResult<Self> {
        let mut lexer = Lexer::new(s);
        let mut rdata = Vec::new();

        while let Some(token) = lexer.next_token()? {
            match token {
                Token::List(list) => rdata.extend(list),
                Token::CharData(s) => rdata.push(s),
                Token::EOL | Token::Blank => (),
                _ => {
                    return Err(ParseError::from(format!(
                        "unexpected token in record data: {:?}",
                        token
                    )))
                }
            }
        }

        Self::parse(record_type, rdata.iter().map(AsRef::as_ref), None)
    }
}

#[warn(clippy::wildcard_enum_match_arm)] // make sure all cases are handled
impl RDataParser for RData {
    /// Parse the RData from a set of Tokens
    fn parse<'i, I: Iterator<Item = &'i str>>(
        record_type: RecordType,
        tokens: I,
        origin: Option<&Name>,
    ) -> ParseResult<Self> {
        let rdata = match record_type {
            RecordType::A => Self::A(a::parse(tokens)?),
            RecordType::AAAA => Self::AAAA(aaaa::parse(tokens)?),
            RecordType::ANAME => Self::ANAME(name::parse(tokens, origin)?),
            RecordType::ANY => return Err(ParseError::from("parsing ANY doesn't make sense")),
            RecordType::AXFR => return Err(ParseError::from("parsing AXFR doesn't make sense")),
            RecordType::CAA => caa::parse(tokens).map(Self::CAA)?,
            RecordType::CNAME => Self::CNAME(name::parse(tokens, origin)?),
            RecordType::CSYNC => csync::parse(tokens).map(Self::CSYNC)?,
            RecordType::HINFO => Self::HINFO(hinfo::parse(tokens)?),
            RecordType::HTTPS => svcb::parse(tokens).map(Self::SVCB)?,
            RecordType::IXFR => return Err(ParseError::from("parsing IXFR doesn't make sense")),
            RecordType::MX => Self::MX(mx::parse(tokens, origin)?),
            RecordType::NAPTR => Self::NAPTR(naptr::parse(tokens, origin)?),
            RecordType::NULL => Self::NULL(null::parse(tokens)?),
            RecordType::NS => Self::NS(name::parse(tokens, origin)?),
            RecordType::OPENPGPKEY => Self::OPENPGPKEY(openpgpkey::parse(tokens)?),
            RecordType::OPT => return Err(ParseError::from("parsing OPT doesn't make sense")),
            RecordType::PTR => Self::PTR(name::parse(tokens, origin)?),
            RecordType::SOA => Self::SOA(soa::parse(tokens, origin)?),
            RecordType::SRV => Self::SRV(srv::parse(tokens, origin)?),
            RecordType::SSHFP => Self::SSHFP(sshfp::parse(tokens)?),
            RecordType::SVCB => svcb::parse(tokens).map(Self::SVCB)?,
            RecordType::TLSA => Self::TLSA(tlsa::parse(tokens)?),
            RecordType::TXT => Self::TXT(txt::parse(tokens)?),
            RecordType::SIG => return Err(ParseError::from("parsing SIG doesn't make sense")),
            RecordType::DNSKEY => {
                return Err(ParseError::from("DNSKEY should be dynamically generated"))
            }
            RecordType::CDNSKEY => {
                return Err(ParseError::from("CDNSKEY should be dynamically generated"))
            }
            RecordType::KEY => return Err(ParseError::from("KEY should be dynamically generated")),
            #[cfg(feature = "dnssec")]
            RecordType::DS => Self::DNSSEC(DNSSECRData::DS(ds::parse(tokens)?)),
            #[cfg(not(feature = "dnssec"))]
            RecordType::DS => return Err(ParseError::from("DS should be dynamically generated")),
            RecordType::CDS => return Err(ParseError::from("CDS should be dynamically generated")),
            RecordType::NSEC => {
                return Err(ParseError::from("NSEC should be dynamically generated"))
            }
            RecordType::NSEC3 => {
                return Err(ParseError::from("NSEC3 should be dynamically generated"))
            }
            RecordType::NSEC3PARAM => {
                return Err(ParseError::from(
                    "NSEC3PARAM should be dynamically generated",
                ))
            }
            RecordType::RRSIG => {
                return Err(ParseError::from("RRSIG should be dynamically generated"))
            }
            RecordType::TSIG => return Err(ParseError::from("TSIG is only used during AXFR")),
            #[allow(deprecated)]
            RecordType::ZERO => Self::ZERO,
            r @ RecordType::Unknown(..) | r => {
                // TODO: add a way to associate generic record types to the zone
                return Err(ParseError::from(ParseErrorKind::UnsupportedRecordType(r)));
            }
        };

        Ok(rdata)
    }
}

#[cfg(test)]
mod tests {
    #![allow(clippy::dbg_macro, clippy::print_stdout)]

    use super::*;
    use crate::rr::domain::Name;
    use crate::rr::rdata::*;
    use std::str::FromStr;

    #[test]
    fn test_a() {
        let tokens = vec!["192.168.0.1"];
        let name = Name::from_str("example.com.").unwrap();
        let record =
            RData::parse(RecordType::A, tokens.iter().map(AsRef::as_ref), Some(&name)).unwrap();

        assert_eq!(record, RData::A("192.168.0.1".parse().unwrap()));
    }

    #[test]
    fn test_a_parse() {
        let data = "192.168.0.1";
        let record = RData::try_from_str(RecordType::A, data).unwrap();

        assert_eq!(record, RData::A("192.168.0.1".parse().unwrap()));
    }

    #[test]
    fn test_aaaa() {
        let tokens = vec!["::1"];
        let name = Name::from_str("example.com.").unwrap();
        let record = RData::parse(
            RecordType::AAAA,
            tokens.iter().map(AsRef::as_ref),
            Some(&name),
        )
        .unwrap();

        assert_eq!(record, RData::AAAA("::1".parse().unwrap()));
    }

    #[test]
    fn test_aaaa_parse() {
        let data = "::1";
        let record = RData::try_from_str(RecordType::AAAA, data).unwrap();

        assert_eq!(record, RData::AAAA("::1".parse().unwrap()));
    }

    #[test]
    fn test_ns_parse() {
        let data = "ns.example.com";
        let record = RData::try_from_str(RecordType::NS, data).unwrap();

        assert_eq!(
            record,
            RData::NS(Name::from_str("ns.example.com.").unwrap())
        );
    }

    #[test]
    fn test_csync() {
        let tokens = vec!["123", "1", "A", "NS"];
        let name = Name::from_str("example.com.").unwrap();
        let record = RData::parse(
            RecordType::CSYNC,
            tokens.iter().map(AsRef::as_ref),
            Some(&name),
        )
        .unwrap();

        assert_eq!(
            record,
            RData::CSYNC(CSYNC::new(
                123,
                true,
                false,
                vec![RecordType::A, RecordType::NS]
            ))
        );
    }

    #[test]
    fn test_csync_parse() {
        let data = "123 1 A NS";
        let record = RData::try_from_str(RecordType::CSYNC, data).unwrap();

        assert_eq!(
            record,
            RData::CSYNC(CSYNC::new(
                123,
                true,
                false,
                vec![RecordType::A, RecordType::NS]
            ))
        );
    }

    #[cfg(feature = "dnssec")]
    #[test]
    #[allow(deprecated)]
    fn test_ds() {
        let tokens = [
            "60485",
            "5",
            "1",
            "2BB183AF5F22588179A53B0A",
            "98631FAD1A292118",
        ];
        let name = Name::from_str("dskey.example.com.").unwrap();
        let record = RData::parse(
            RecordType::DS,
            tokens.iter().map(AsRef::as_ref),
            Some(&name),
        )
        .unwrap();

        assert_eq!(
            record,
            RData::DNSSEC(DNSSECRData::DS(DS::new(
                60485,
                crate::proto::rr::dnssec::Algorithm::RSASHA1,
                crate::proto::rr::dnssec::DigestType::SHA1,
                vec![
                    0x2B, 0xB1, 0x83, 0xAF, 0x5F, 0x22, 0x58, 0x81, 0x79, 0xA5, 0x3B, 0x0A, 0x98,
                    0x63, 0x1F, 0xAD, 0x1A, 0x29, 0x21, 0x18
                ]
            )))
        );
    }

    #[test]
    fn test_any() {
        let tokens = vec!["test"];
        let name = Name::from_str("example.com.").unwrap();
        let result = RData::parse(
            RecordType::ANY,
            tokens.iter().map(AsRef::as_ref),
            Some(&name),
        );

        assert!(result.is_err());
    }

    #[test]
    fn test_dynamically_generated() {
        let dynamically_generated = vec![
            RecordType::DS,
            RecordType::CDS,
            RecordType::DNSKEY,
            RecordType::CDNSKEY,
            RecordType::KEY,
            RecordType::NSEC,
            RecordType::NSEC3,
            RecordType::NSEC3PARAM,
            RecordType::RRSIG,
        ];

        let tokens = vec!["test"];

        let name = Name::from_str("example.com.").unwrap();

        for record_type in dynamically_generated {
            let result = RData::parse(record_type, tokens.iter().map(AsRef::as_ref), Some(&name));
            assert!(result.is_err());
        }
    }
}