This commit is contained in:
Stefan Bühler 2017-12-29 14:07:58 +01:00
parent 92a76cd57a
commit 503bc29fcf
7 changed files with 280 additions and 31 deletions

View File

@ -40,7 +40,7 @@ impl DnsName {
label.push(raw[pos+1]);
}
} else {
ensure!(!quoted::is_ascii_whitespace(raw[pos+1]), "whitespace must be encoded as \\{:03} in: {:?}", raw[pos+1], name);
ensure!(!quoted::is_ascii_whitespace(raw[pos]), "whitespace must be encoded as \\{:03} in: {:?}", raw[pos], name);
label.push(raw[pos]);
}
pos += 1;

View File

@ -296,9 +296,9 @@ pub enum KnownType {
/// Host Identity Protocol
HIP = 0x0037, // RFC 8005
/// NINFO
NINFO = 0x0038, // Jim Reid
NINFO = 0x0038, // Jim Reid: https://tools.ietf.org/html/draft-reid-dnsext-zs-01
/// RKEY
RKEY = 0x0039, // Jim Reid
RKEY = 0x0039, // Jim Reid: https://tools.ietf.org/html/draft-reid-dnsext-rkey-00
/// Trust Anchor LINK
TALINK = 0x003a, // Wouter Wijngaards
/// Child DS

View File

@ -87,18 +87,31 @@ fn check(q: Type, text_input: &'static str, canonic: Option<&'static str>, raw:
context.set_record_type(q);
context.set_last_ttl(3600);
let d_zone = text::parse_with(text_input, |data| {
let d_zone: Box<RRData> = text::parse_with(text_input, |data| {
registry::parse_rr_data(&context, data)
}).unwrap();
let d_wire_packet = deserialize_with(fake_packet(q, raw), DnsPacket::deserialize).unwrap();
let d_wire = &d_wire_packet.answer[0].data;
let d_zone_text = d_zone.text().unwrap();
// make sure we actually know the type and the text representation
// uses the known representation (and not the generic one)
assert_eq!(Some(d_zone_text.0.as_ref()), d_zone.rr_type().known_name());
// ... and the text representation matches the canonic format
assert_eq!(&d_zone_text.1, canonic);
// (pdns tests compares this to the input_text sometimes, because
// they often (TXT, DNS names) use the master file representation
// internally instead of normalizing it. This is bad for testing,
// because they do normalize some parts...)
// pdns tests deserialize `zone_as_wire` (from below) here, but we
// make sure it's the same anyway
let d_wire_packet = deserialize_with(fake_packet(q, raw), DnsPacket::deserialize).unwrap();
let d_wire: &Box<RRData> = &d_wire_packet.answer[0].data;
let d_wire_text = d_wire.text().unwrap();
// pdns tests compare d_wire_text and canonic, but d_zone_text
// already matches canonic
assert_eq!(d_zone_text, d_wire_text, "data parsed from zone doesn't match data from wire");
assert_eq!(&d_zone_text.1, canonic);
let zone_as_wire = serialized_answer(d_zone).unwrap();
assert_eq!(zone_as_wire, raw);
@ -786,3 +799,45 @@ fn test_TYPE65226() {
assert_eq!(d1, d2);
assert_eq!(d1.text().unwrap(), ("TYPE65226".into(), "\\# 3 414243".into()));
}
fn check_invalid_zone(q: Type, text_input: &str) {
let mut context = text::DnsTextContext::new();
context.set_zone_class(classes::IN);
context.set_origin(DnsName::new_root());
context.set_record_type(q);
context.set_last_ttl(3600);
text::parse_with(text_input, |data| {
registry::parse_rr_data(&context, data)
}).unwrap_err();
}
fn check_invalid_wire(q: Type, raw: &'static [u8]) {
deserialize_with(fake_packet(q, raw), DnsPacket::deserialize).unwrap_err();
}
#[test]
fn test_invalid_data_checks() {
check_invalid_zone(types::A, "932.521.256.42"); // hollywood IP
check_invalid_zone(types::A, "932.521"); // truncated hollywood IP
check_invalid_zone(types::A, "10.0"); // truncated IP
check_invalid_zone(types::A, "10.0.0.1."); // trailing dot
check_invalid_zone(types::A, "10.0.0."); // trailing dot
check_invalid_zone(types::A, ".0.0.1"); // empty octet
check_invalid_zone(types::A, "10..0.1"); // empty octet
check_invalid_wire(types::A, b"\xca\xec\x00"); // truncated wire value
check_invalid_zone(types::A, "127.0.0.1 evil data"); // trailing garbage
check_invalid_zone(types::AAAA, "23:00"); // time when this test was written
check_invalid_zone(types::AAAA, "23:00::15::43"); // double compression
check_invalid_zone(types::AAAA, "2a23:00::15::"); // ditto
check_invalid_wire(types::AAAA, b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff"); // truncated wire value
// empty label, must be broken
check_invalid_zone(types::CNAME, "name..example.com.");
// overly large label (64), must be broken
check_invalid_zone(types::CNAME, "1234567890123456789012345678901234567890123456789012345678901234.example.com.");
// local overly large name (256), must be broken
check_invalid_zone(types::CNAME, "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123.rec.test.");
// non-local overly large name (256), must be broken
check_invalid_zone(types::CNAME, "123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.123456789012345678901234567890123456789012345678901234567890123.12345678901234567890123456789012345678901234567890123456789012.");
check_invalid_zone(types::SOA, "ns.rec.test hostmaster.test.rec 20130512010 3600 3600 604800 120"); // too long serial
}

View File

@ -168,11 +168,11 @@ impl Registry {
r.register_known::<structs::TLSA>();
r.register_known::<structs::SMIMEA>();
r.register_unknown("HIP" , types::HIP);
r.register_unknown("NINFO" , types::NINFO);
r.register_unknown("RKEY" , types::RKEY);
r.register_known::<structs::NINFO>();
r.register_known::<structs::RKEY>();
r.register_unknown("TALINK" , types::TALINK);
r.register_unknown("CDS" , types::CDS);
r.register_unknown("CDNSKEY" , types::CDNSKEY);
r.register_known::<structs::CDS>();
r.register_known::<structs::CDNSKEY>();
r.register_known::<structs::OPENPGPKEY>();
r.register_unknown("CSYNC" , types::CSYNC);
r.register_known::<structs::SPF>();

View File

@ -399,25 +399,43 @@ pub struct SMIMEA {
// #[RRClass(?)]
// pub struct HIP;
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// #[RRClass(?)]
// pub struct NINFO;
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
#[RRClass(ANY)]
pub struct NINFO {
text: LongText,
}
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// #[RRClass(?)]
// pub struct RKEY;
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
#[RRClass(ANY)]
pub struct RKEY {
flags: u16,
protocol: u8,
algorithm: u8,
public_key: Base64RemainingBlob,
}
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// #[RRClass(?)]
// pub struct TALINK;
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// #[RRClass(?)]
// pub struct CDS;
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
#[RRClass(ANY)]
pub struct CDS {
key_tag: u16,
algorithm: u8,
digest_type: u8,
digest: HexRemainingBlob,
}
// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
// #[RRClass(?)]
// pub struct CDNSKEY;
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
#[RRClass(ANY)]
pub struct CDNSKEY {
flags: u16,
protocol: u8,
algorithm: u8,
public_key: Base64RemainingBlob,
}
#[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)]
#[RRClass(ANY)]

View File

@ -3,7 +3,7 @@ use errors::*;
use common_types::*;
use failure::ResultExt;
use ser::packet::{DnsPacketData, DnsPacketWriteContext, remaining_bytes};
use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext};
use ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext, next_field};
use std::fmt;
use std::io::Read;
use std::net::{Ipv4Addr, Ipv6Addr};
@ -53,13 +53,189 @@ impl DnsPacketData for LOC {
}
impl DnsTextData for LOC {
fn dns_parse(_context: &DnsTextContext, _data: &mut &str) -> Result<Self> {
unimplemented!()
fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> Result<Self> {
let degrees_latitude = next_field(data)?.parse::<u8>()?;
ensure!(degrees_latitude <= 90, "degrees latitude out of range: {}", degrees_latitude);
let mut minutes_latitude = 0;
let mut seconds_latitude = 0.0;
let mut field = next_field(data)?;
if field != "N" && field != "n" && field != "S" && field != "s" {
minutes_latitude = field.parse::<u8>()?;
ensure!(minutes_latitude < 60, "minutes latitude out of range: {}", minutes_latitude);
field = next_field(data)?;
if field != "N" && field != "n" && field != "S" && field != "s" {
seconds_latitude = field.parse::<f32>()?;
ensure!(seconds_latitude >= 0.0 && seconds_latitude < 60.0, "seconds latitude out of range: {}", seconds_latitude);
field = next_field(data)?;
}
}
let latitude_off = (3600_000 * degrees_latitude as u32) + (60_000 * minutes_latitude as u32) + (1_000.0 * seconds_latitude).round() as u32;
ensure!(latitude_off <= 3600_000 * 180, "latitude out of range");
let latitude = match field {
"N"|"n" => 0x8000_0000 + latitude_off,
"S"|"s" => 0x8000_0000 - latitude_off,
_ => bail!("invalid latitude orientation [NS]: {}", field),
};
let degrees_longitude = next_field(data)?.parse::<u8>()?;
ensure!(degrees_longitude <= 180, "degrees longitude out of range: {}", degrees_longitude);
let mut minutes_longitude = 0;
let mut seconds_longitude = 0.0;
let mut field = next_field(data)?;
if field != "E" && field != "e" && field != "W" && field != "w" {
minutes_longitude = field.parse::<u8>()?;
ensure!(minutes_longitude < 60, "minutes longitude out of range: {}", minutes_longitude);
field = next_field(data)?;
if field != "E" && field != "e" && field != "W" && field != "w" {
seconds_longitude = field.parse::<f32>()?;
ensure!(seconds_longitude >= 0.0 && seconds_longitude < 60.0, "seconds longitude out of range: {}", seconds_longitude);
field = next_field(data)?;
}
}
let longitude_off = (3600_000 * degrees_longitude as u32) + (60_000 * minutes_longitude as u32) + (1_000.0 * seconds_longitude).round() as u32;
ensure!(longitude_off <= 3600_000 * 180, "longitude out of range");
let longitude = match field {
"E"|"e" => 0x8000_0000 + longitude_off,
"W"|"w" => 0x8000_0000 - longitude_off,
_ => bail!("invalid longitude orientation [EW]: {}", field),
};
fn trim_unit_m(s: &str) -> &str {
if s.ends_with('m') { &s[..s.len()-1] } else { s }
}
fn parse_precision(s: &str) -> Result<u8> {
let s = trim_unit_m(s);
let mut m = 0;
let mut e = 0;
let mut dec_point = None;
for &b in s.as_bytes() {
if b == b'.' {
ensure!(dec_point.is_none(), "invalid precision (double decimal point): {:?}", s);
dec_point = Some(0);
continue;
}
ensure!(b >= b'0' && b <= b'9', "invalid precision (invalid character): {:?}", s);
if let Some(ref mut dp) = dec_point {
if *dp == 2 { continue; } // ignore following digits
*dp += 1;
}
let d = b - b'0';
if 0 == m {
m = d;
} else {
e += 1;
ensure!(e <= 9, "invalid precision (overflow): {:?}", s);
}
}
e += 2 - dec_point.unwrap_or(0);
ensure!(e <= 9, "invalid precision (overflow): {:?}", s);
Ok(m << 4 | e)
}
let altitude = match next_field(data) {
Ok(field) => {
let f_altitude = trim_unit_m(field).parse::<f64>()?;
let altitude = (f_altitude * 100.0 + 10000000.0).round() as i64;
ensure!(altitude > 0 && (altitude as u32) as i64 == altitude, "altitude out of range");
altitude as u32
},
// standard requires the field, but the example parser doesn't..
Err(_) => 10000000, // 0m
};
let size = match next_field(data) {
Ok(field) => parse_precision(field)?,
Err(_) => 0x12, // => 1e2 cm = 1m
};
let horizontal_precision = match next_field(data) {
Ok(field) => parse_precision(field)?,
Err(_) => 0x16, // 1e6 cm = 10km */
};
let vertical_precision = match next_field(data) {
Ok(field) => parse_precision(field)?,
Err(_) => 0x13, // 1e3 cm = 10m */
};
Ok(LOC::Version0(LOC0{
size,
horizontal_precision,
vertical_precision,
latitude,
longitude,
altitude,
}))
}
fn dns_format(&self, _f: &mut DnsTextFormatter) -> fmt::Result {
// always prefer binary representation
Err(fmt::Error)
fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result {
let this = match *self {
LOC::Version0(ref t) => t,
_ => return Err(fmt::Error),
};
const MAX_LAT_OFFSET: u32 = 3600_000 * 180;
const MAX_LON_OFFSET: u32 = 3600_000 * 180;
const LATLON_MID: u32 = 0x8000_0000;
if this.latitude < LATLON_MID - MAX_LAT_OFFSET || this.latitude > LATLON_MID + MAX_LAT_OFFSET {
return Err(fmt::Error);
}
if this.longitude < LATLON_MID - MAX_LON_OFFSET || this.longitude > LATLON_MID + MAX_LON_OFFSET {
return Err(fmt::Error);
}
fn is_invalid_prec(v: u8) -> bool {
// "leading-digit" << 4 | "exponent(base 10)"
// if the leading digit is 0, the exponent must be 0 too.
(v > 0x00 && v < 0x10) || (v >> 4) > 9 || (v & 0xf) > 9
}
if is_invalid_prec(this.size) || is_invalid_prec(this.horizontal_precision) || is_invalid_prec(this.vertical_precision) {
return Err(fmt::Error);
}
fn putlatlon2(v: u32, f: &mut DnsTextFormatter) -> fmt::Result {
let msecs = v % 1000;
let fullsecs = v / 1000;
let secs = fullsecs % 60;
let fullmins = fullsecs / 60;
let mins = fullmins % 60;
let deg = fullmins / 60;
write!(f, "{} {} {}.{:03}", deg, mins, secs, msecs)
}
fn putlatlon(v: u32, f: &mut DnsTextFormatter, pos: char, neg: char) -> fmt::Result {
if v >= LATLON_MID {
putlatlon2(v - LATLON_MID, f)?;
write!(f, "{}", pos)
} else {
putlatlon2(LATLON_MID - v, f)?;
write!(f, "{}", neg)
}
}
putlatlon(this.latitude, f, 'N', 'S')?;
putlatlon(this.longitude, f, 'E', 'W')?;
write!(f, "{:.2}m", (this.altitude as f64 - 10000000.0) / 100.0)?;
fn put_prec(v: u8, f: &mut DnsTextFormatter) -> fmt::Result {
let m = v >> 4;
debug_assert!(m < 10);
let e = v & 0xf;
if e >= 2 {
write!(f, "{:0<width$}.00m", m, width = e as usize - 1)
} else if e == 1 {
write!(f, ".{}0m", m)
} else { // e == 0
write!(f, ".0{}m", m)
}
}
put_prec(this.size, f)?;
put_prec(this.horizontal_precision, f)?;
put_prec(this.vertical_precision, f)?;
Ok(())
}
}

View File

@ -65,7 +65,7 @@ pub trait RRDataText {
let ur = UnknownRecord::new(self.rr_type(), raw.into());
// formatting UnknownRecord should not fail
buf.clear();
self.dns_format_rr_data(&mut DnsTextFormatter::new(&mut buf)).unwrap();
ur.dns_format_rr_data(&mut DnsTextFormatter::new(&mut buf)).expect("formatting UnknownRecord must not fail");
Ok((ur.rr_type_txt().into(), buf))
}
}