10
0
Fork 0
mirror of https://github.com/ZeusWPI/ZNS.git synced 2024-10-29 21:14:27 +01:00

Respond with fixed SOA of zones

This commit is contained in:
Xander Bil 2024-09-26 00:00:51 +02:00
parent 4939d2b3e1
commit c9767db879
No known key found for this signature in database
GPG key ID: EC9706B54A278598
13 changed files with 209 additions and 95 deletions

View file

@ -1,13 +1,14 @@
use std::{env, net::IpAddr, sync::OnceLock};
use dotenvy::dotenv;
use zns::labelstring::LabelString;
static CONFIG: OnceLock<Config> = OnceLock::new();
pub struct Config {
pub zauth_url: String,
pub db_uri: String,
pub authoritative_zone: Vec<String>,
pub authoritative_zone: LabelString,
pub port: u16,
pub address: IpAddr,
}
@ -25,11 +26,7 @@ impl Config {
Config {
db_uri: env::var("DATABASE_URL").expect("DATABASE_URL must be set"),
zauth_url: env::var("ZAUTH_URL").expect("ZAUTH_URL must be set"),
authoritative_zone: env::var("ZONE")
.expect("ZONE must be set")
.split('.')
.map(str::to_string)
.collect(),
authoritative_zone: LabelString::from(&env::var("ZONE").expect("ZONE must be set")),
port: env::var("ZNS_PORT")
.map(|v| v.parse::<u16>().expect("ZNS_PORT is invalid"))
.unwrap_or(5333),

View file

@ -2,6 +2,7 @@ use diesel::prelude::*;
use diesel::sql_types::Text;
use zns::{
errors::ZNSError,
labelstring::LabelString,
structs::{Class, Type, RR},
};
@ -103,7 +104,7 @@ pub fn insert_into_database(rr: &RR, connection: &mut PgConnection) -> Result<()
}
let record = Record {
name: rr.name.join("."),
name: rr.name.to_string(),
_type: rr._type.clone().into(),
class: rr.class.clone().into(),
ttl: rr.ttl,
@ -119,14 +120,14 @@ pub fn insert_into_database(rr: &RR, connection: &mut PgConnection) -> Result<()
}
pub fn get_from_database(
name: &[String],
name: &LabelString,
_type: Option<Type>,
class: Class,
connection: &mut PgConnection,
) -> Result<Vec<RR>, ZNSError> {
let records = Record::get(
connection,
name.join("."),
name.to_string(),
_type.map(|t| t.into()),
class.into(),
)
@ -137,7 +138,7 @@ pub fn get_from_database(
Ok(records
.into_iter()
.map(|record| RR {
name: record.name.split('.').map(str::to_string).collect(),
name: LabelString::from(&record.name),
_type: Type::from(record._type as u16),
class: Class::from(record.class as u16),
ttl: record.ttl,
@ -149,7 +150,7 @@ pub fn get_from_database(
//TODO: cleanup models
pub fn delete_from_database(
name: &[String],
name: &LabelString,
_type: Option<Type>,
class: Class,
rdata: Option<Vec<u8>>,
@ -157,7 +158,7 @@ pub fn delete_from_database(
) {
let _ = Record::delete(
connection,
name.join("."),
name.to_string(),
_type.map(|f| f.into()),
class.into(),
rdata,

View file

@ -2,7 +2,9 @@ use diesel::PgConnection;
use zns::{
errors::ZNSError,
structs::{Message, Question, RR},
labelstring::LabelString,
parser::ToBytes,
structs::{Class, Message, Question, RRClass, RRType, SoaRData, Type, RR},
};
use crate::{config::Config, db::models::get_from_database};
@ -35,12 +37,16 @@ impl ResponseHandler for QueryHandler {
if rrs.is_empty() {
rrs.extend(try_wildcard(question, connection)?);
if rrs.is_empty() {
if question.qtype == Type::Type(RRType::SOA) {
rrs.extend([get_soa(&question.qname)?])
} else {
return Err(ZNSError::NXDomain {
domain: question.qname.join("."),
domain: question.qname.to_string(),
qtype: question.qtype.clone(),
});
}
}
}
response.header.ancount += rrs.len() as u16;
response.answer.extend(rrs)
}
@ -59,13 +65,13 @@ impl ResponseHandler for QueryHandler {
fn try_wildcard(question: &Question, connection: &mut PgConnection) -> Result<Vec<RR>, ZNSError> {
let records = get_from_database(&question.qname, None, question.qclass.clone(), connection)?;
if !records.is_empty() || question.qname.is_empty() {
if !records.is_empty() || question.qname.as_slice().is_empty() {
Ok(vec![])
} else {
let mut qname = question.qname.clone();
qname[0] = String::from("*");
let qname = question.qname.clone().to_vec();
qname.to_vec()[0] = String::from("*");
Ok(get_from_database(
&qname,
&qname.into(),
Some(question.qtype.clone()),
question.qclass.clone(),
connection,
@ -79,6 +85,47 @@ fn try_wildcard(question: &Question, connection: &mut PgConnection) -> Result<Ve
}
}
fn get_soa(name: &LabelString) -> Result<RR, ZNSError> {
let auth_zone = Config::get().authoritative_zone.clone();
let rdata = if &Config::get().authoritative_zone == name {
// Recommended values taken from wikipedia: https://en.wikipedia.org/wiki/SOA_record
Ok(SoaRData {
mname: auth_zone,
rname: LabelString::from("admin.zeus.ugent.be"),
serial: 1,
refresh: 86400,
retry: 7200,
expire: 3600000,
minimum: 172800,
})
} else if name.len() > auth_zone.len() {
let zone: LabelString = name.as_slice()[name.len() - auth_zone.len() - 1..].into();
Ok(SoaRData {
mname: zone.clone(),
rname: LabelString::from(&format!("{}.zeus.ugent.be", zone.as_slice()[0])),
serial: 1,
refresh: 86400,
retry: 7200,
expire: 3600000,
minimum: 172800,
})
} else {
Err(ZNSError::NXDomain {
domain: name.to_string(),
qtype: Type::Type(RRType::SOA),
})
}?;
Ok(RR {
name: name.to_owned(),
_type: Type::Type(RRType::SOA),
class: Class::Class(RRClass::IN),
ttl: 11200,
rdlength: 0,
rdata: SoaRData::to_bytes(rdata),
})
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -5,9 +5,10 @@ use crate::{config::Config, db::models::get_from_database};
use zns::{
errors::ZNSError,
labelstring::LabelString,
parser::FromBytes,
reader::Reader,
structs::{Class, LabelString, RRClass, RRType, Type},
structs::{Class, RRClass, RRType, Type},
};
use super::{dnskey::DNSKeyRData, sig::Sig};
@ -17,8 +18,9 @@ pub async fn authenticate(
zone: &LabelString,
connection: &mut PgConnection,
) -> Result<bool, ZNSError> {
if zone.len() > Config::get().authoritative_zone.len() {
let username = &zone[zone.len() - Config::get().authoritative_zone.len() - 1];
if zone.as_slice().len() > Config::get().authoritative_zone.as_slice().len() {
let username = &zone.as_slice()
[zone.as_slice().len() - Config::get().authoritative_zone.as_slice().len() - 1];
let ssh_verified = validate_ssh(&username.to_lowercase(), sig)
.await
@ -62,7 +64,7 @@ async fn validate_ssh(username: &String, sig: &Sig) -> Result<bool, reqwest::Err
}
async fn validate_dnskey(
zone: &[String],
zone: &LabelString,
sig: &Sig,
connection: &mut PgConnection,
) -> Result<bool, ZNSError> {

View file

@ -5,8 +5,8 @@ use crate::{
db::models::{delete_from_database, insert_into_database},
};
use zns::errors::ZNSError;
use zns::structs::{Class, Message, RRClass, RRType, Type};
use zns::{errors::ZNSError, utils::labels_equal};
use self::sig::Sig;
@ -41,7 +41,7 @@ impl ResponseHandler for UpdateHandler {
// Check Prerequisite TODO: implement this
let zone = &message.question[0];
let zlen = zone.qname.len();
let zlen = zone.qname.as_slice().len();
//TODO: this code is ugly
let last = message.additional.last();
@ -61,10 +61,10 @@ impl ResponseHandler for UpdateHandler {
// Update Section Prescan
for rr in &message.authority {
let rlen = rr.name.len();
let rlen = rr.name.as_slice().len();
// Check if rr has same zone
if rlen < zlen || !(labels_equal(&zone.qname, &rr.name[rlen - zlen..].into())) {
if rlen < zlen || !(&zone.qname == &rr.name.as_slice()[rlen - zlen..].into()) {
return Err(ZNSError::Refused {
message: "RR has different zone from Question".to_string(),
});

View file

@ -4,10 +4,7 @@ use base64::prelude::*;
use int_enum::IntEnum;
use zns::{
errors::ZNSError,
parser::FromBytes,
reader::Reader,
structs::{LabelString, RR},
errors::ZNSError, labelstring::LabelString, parser::FromBytes, reader::Reader, structs::RR,
};
use super::{

85
zns/src/labelstring.rs Normal file
View file

@ -0,0 +1,85 @@
use std::fmt::Display;
#[derive(Debug, Clone)]
pub struct LabelString(Vec<String>);
pub fn labels_equal(vec1: &LabelString, vec2: &LabelString) -> bool {
if vec1.as_slice().len() != vec2.as_slice().len() {
return false;
}
for (elem1, elem2) in vec1.as_slice().iter().zip(vec2.as_slice().iter()) {
if elem1.to_lowercase() != elem2.to_lowercase() {
return false;
}
}
true
}
impl LabelString {
pub fn from(string: &str) -> Self {
LabelString(string.split('.').map(str::to_string).collect())
}
pub fn as_slice(&self) -> &[String] {
self.0.as_slice()
}
pub fn to_vec(self) -> Vec<String> {
self.0
}
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl PartialEq for LabelString {
fn eq(&self, other: &Self) -> bool {
labels_equal(self, other)
}
}
impl From<&[String]> for LabelString {
fn from(value: &[String]) -> Self {
LabelString(value.to_vec())
}
}
impl From<Vec<String>> for LabelString {
fn from(value: Vec<String>) -> Self {
LabelString(value)
}
}
impl Display for LabelString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.join("."))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_labels_equal() {
assert!(labels_equal(
&LabelString::from("one.two"),
&LabelString::from("oNE.two")
));
assert!(!labels_equal(
&LabelString::from("onne.two"),
&LabelString::from("oNEe.two")
));
}
}

View file

@ -1,8 +1,8 @@
pub mod errors;
pub mod labelstring;
pub mod message;
pub mod parser;
pub mod reader;
pub mod structs;
pub mod test_utils;
pub mod utils;

View file

@ -1,7 +1,7 @@
use crate::{
errors::ZNSError,
structs::{LabelString, Message, Opcode, RCODE},
utils::labels_equal,
labelstring::LabelString,
structs::{Message, Opcode, RCODE},
};
impl Message {
@ -23,10 +23,12 @@ impl Message {
for question in &self.question {
let zlen = question.qname.len();
if !(zlen >= auth_zone.len()
&& labels_equal(&question.qname[zlen - auth_zone.len()..].into(), auth_zone))
&& &Into::<LabelString>::into(
question.qname.as_slice()[zlen - auth_zone.len()..].to_vec(),
) == auth_zone)
{
return Err(ZNSError::Refused {
message: format!("Not authoritative for: {}", question.qname.join(".")),
message: format!("Not authoritative for: {}", question.qname),
});
}
}
@ -69,20 +71,16 @@ mod tests {
#[test]
fn test_authoritative() {
let name = vec![
String::from("not"),
String::from("good"),
String::from("zone"),
];
let name = LabelString::from("not.good.zone");
let message = get_message(Some(name));
assert!(message
.check_authoritative(&vec![String::from("good")])
.check_authoritative(&LabelString::from("good"))
.is_err_and(|x| x.rcode() == RCODE::REFUSED));
assert!(message
.check_authoritative(&vec![String::from("Zone")])
.check_authoritative(&LabelString::from("Zone"))
.is_ok())
}
}

View file

@ -2,8 +2,9 @@ use std::mem::size_of;
use crate::{
errors::ZNSError,
labelstring::LabelString,
reader::Reader,
structs::{Class, Header, LabelString, Message, Opcode, Question, RRClass, RRType, Type, RR},
structs::{Class, Header, Message, Opcode, Question, RRClass, RRType, SoaRData, Type, RR},
};
type Result<T> = std::result::Result<T, ZNSError>;
@ -143,17 +144,17 @@ impl FromBytes for LabelString {
if code & 0b11000000 != 0 {
let offset = (((code & 0b00111111) as u16) << 8) | reader.read_u8()? as u16;
let mut reader_past = reader.seek(offset as usize)?;
out.extend(LabelString::from_bytes(&mut reader_past)?);
out.extend(LabelString::from_bytes(&mut reader_past)?.to_vec());
}
Ok(out)
Ok(out.into())
}
}
impl ToBytes for LabelString {
fn to_bytes(name: Self) -> Vec<u8> {
let mut result: Vec<u8> = vec![];
for label in name {
for label in name.as_slice() {
result.push(label.len() as u8);
result.extend(label.as_bytes());
}
@ -289,6 +290,19 @@ impl ToBytes for Message {
}
}
impl ToBytes for SoaRData {
fn to_bytes(rdata: Self) -> Vec<u8> {
let mut result = LabelString::to_bytes(rdata.mname);
result.extend(LabelString::to_bytes(rdata.rname));
result.extend(u32::to_be_bytes(rdata.serial));
result.extend(i32::to_be_bytes(rdata.refresh));
result.extend(i32::to_be_bytes(rdata.retry));
result.extend(i32::to_be_bytes(rdata.expire));
result.extend(u32::to_be_bytes(rdata.minimum));
result
}
}
#[cfg(test)]
pub mod tests {
use crate::test_utils::{get_message, get_rr};
@ -315,7 +329,7 @@ pub mod tests {
#[test]
fn test_parse_question() {
let question = Question {
qname: vec![String::from("example"), String::from("org")],
qname: LabelString::from("example.org"),
qtype: Type::Type(RRType::A),
qclass: Class::Class(RRClass::IN),
};
@ -338,7 +352,7 @@ pub mod tests {
#[test]
fn test_labelstring() {
let labelstring = vec![String::from("example"), String::from("org")];
let labelstring: LabelString = vec![String::from("example"), String::from("org")].into();
let bytes = LabelString::to_bytes(labelstring.clone());
let parsed = LabelString::from_bytes(&mut Reader::new(&bytes));
@ -348,7 +362,7 @@ pub mod tests {
#[test]
fn test_labelstring_ptr() {
let labelstring = vec![String::from("example"), String::from("org")];
let labelstring: LabelString = vec![String::from("example"), String::from("org")].into();
let mut bytes = LabelString::to_bytes(labelstring.clone());
@ -370,7 +384,7 @@ pub mod tests {
#[test]
fn test_labelstring_invalid_ptr() {
let labelstring = vec![String::from("example"), String::from("org")];
let labelstring: LabelString = vec![String::from("example"), String::from("org")].into();
let mut bytes = LabelString::to_bytes(labelstring.clone());

View file

@ -1,5 +1,7 @@
use int_enum::IntEnum;
use crate::labelstring::LabelString;
#[derive(Debug, Clone, PartialEq)]
pub enum Type {
Type(RRType),
@ -89,4 +91,12 @@ pub struct RR {
pub rdata: Vec<u8>,
}
pub type LabelString = Vec<String>;
pub struct SoaRData {
pub mname: LabelString,
pub rname: LabelString,
pub serial: u32,
pub refresh: i32,
pub retry: i32,
pub expire: i32,
pub minimum: u32,
}

View file

@ -2,9 +2,10 @@
use crate::structs::*;
#[cfg(feature = "test-utils")]
use crate::labelstring::LabelString;
pub fn get_rr(name: Option<LabelString>) -> RR {
RR {
name: name.unwrap_or(vec![String::from("example"), String::from("org")]),
name: name.unwrap_or(LabelString::from("example.org")),
_type: Type::Type(RRType::A),
class: Class::Class(RRClass::IN),
ttl: 10,
@ -25,16 +26,12 @@ pub fn get_message(name: Option<LabelString>) -> Message {
},
question: vec![
Question {
qname: name
.clone()
.unwrap_or(vec![String::from("example"), String::from("org")]),
qname: name.clone().unwrap_or(LabelString::from("example.org")),
qtype: Type::Type(RRType::A),
qclass: Class::Class(RRClass::IN),
},
Question {
qname: name
.clone()
.unwrap_or(vec![String::from("example"), String::from("org")]),
qname: name.clone().unwrap_or(LabelString::from("example.org")),
qtype: Type::Type(RRType::A),
qclass: Class::Class(RRClass::IN),
},

View file

@ -1,34 +0,0 @@
use crate::structs::LabelString;
pub fn labels_equal(vec1: &LabelString, vec2: &LabelString) -> bool {
if vec1.len() != vec2.len() {
return false;
}
for (elem1, elem2) in vec1.iter().zip(vec2.iter()) {
if elem1.to_lowercase() != elem2.to_lowercase() {
return false;
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_labels_equal() {
assert!(labels_equal(
&vec![String::from("one"), String::from("two")],
&vec![String::from("oNE"), String::from("two")]
));
assert!(!labels_equal(
&vec![String::from("one"), String::from("two")],
&vec![String::from("oNEe"), String::from("two")]
));
}
}