diff --git a/src/dns/api.rs b/src/dns/api.rs index 83b2038..bf5cf1d 100644 --- a/src/dns/api.rs +++ b/src/dns/api.rs @@ -16,9 +16,12 @@ pub trait RecordApi { // Zone management api, todo // E.g.: Manage catalog zone, dynamically generate knot / bind / nsd config... +#[async_trait] pub trait ZoneApi { + type Error; // get_zones // add_zone // delete_zone - // exists + // zone_exists + async fn zone_exists(&mut self, zone: dns::Name, class: dns::DNSClass) -> Result<(), Self::Error>; } \ No newline at end of file diff --git a/src/dns/dns_api.rs b/src/dns/dns_api.rs index 38a8557..2e6dd1d 100644 --- a/src/dns/dns_api.rs +++ b/src/dns/dns_api.rs @@ -6,11 +6,11 @@ use trust_dns_client::proto::xfer::{DnsRequestOptions}; use super::{Name, Record, RData}; use super::client::{ClientResponse, DnsClient}; -use super::api::RecordApi; +use super::api::{RecordApi, ZoneApi}; #[derive(Debug)] -pub enum MessageError { +pub enum DnsApiError { RecordNotInZone { zone: Name, class: DNSClass, @@ -18,7 +18,10 @@ pub enum MessageError { mismatched_zone: Vec, }, ClientError(ClientError), - ResponceNotOk(ResponseCode) + ResponceNotOk { + code: ResponseCode, + zone: Name, + }, } pub struct DnsApiClient { @@ -36,18 +39,21 @@ impl DnsApiClient { #[async_trait] impl RecordApi for DnsApiClient { - type Error = MessageError; + type Error = DnsApiError; async fn get_records(&mut self, zone: Name, class: DNSClass) -> Result, Self::Error> { let response = { - let mut query = Query::query(zone, RecordType::AXFR); + let mut query = Query::query(zone.clone(), RecordType::AXFR); query.set_query_class(class); - ClientResponse(self.client.lookup(query, DnsRequestOptions::default())).await.map_err(|e| MessageError::ClientError(e))? + ClientResponse(self.client.lookup(query, DnsRequestOptions::default())).await.map_err(|e| DnsApiError::ClientError(e))? }; if response.response_code() != ResponseCode::NoError { - return Err(MessageError::ResponceNotOk(response.response_code())); + return Err(DnsApiError::ResponceNotOk { + code: response.response_code(), + zone: zone, + }); } let answers = response.answers(); @@ -75,7 +81,7 @@ impl RecordApi for DnsApiClient { } if mismatched_class.len() > 0 || mismatched_zone.len() > 0 { - return Err(MessageError::RecordNotInZone { + return Err(DnsApiError::RecordNotInZone { zone, class, mismatched_zone, @@ -104,12 +110,40 @@ impl RecordApi for DnsApiClient { edns.set_version(0); } - let response = ClientResponse(self.client.send(message)).await.map_err(|e| MessageError::ClientError(e))?; + let response = ClientResponse(self.client.send(message)).await.map_err(|e| DnsApiError::ClientError(e))?; if response.response_code() != ResponseCode::NoError { - return Err(MessageError::ResponceNotOk(response.response_code())); + return Err(DnsApiError::ResponceNotOk { + code: response.response_code(), + zone: zone, + }); } Ok(()) } +} + + +#[async_trait] +impl ZoneApi for DnsApiClient { + type Error = DnsApiError; + + async fn zone_exists(&mut self, zone: Name, class: DNSClass) -> Result<(), Self::Error> + { + let response = { + let mut query = Query::query(zone.clone(), RecordType::SOA); + query.set_query_class(class); + ClientResponse(self.client.lookup(query, DnsRequestOptions::default())).await.map_err(|e| DnsApiError::ClientError(e))? + }; + + if response.response_code() != ResponseCode::NoError { + return Err(DnsApiError::ResponceNotOk { + code: response.response_code(), + zone: zone, + }); + } + + Ok(()) + } + } \ No newline at end of file diff --git a/src/dns/mod.rs b/src/dns/mod.rs index fb43308..73158c4 100644 --- a/src/dns/mod.rs +++ b/src/dns/mod.rs @@ -12,4 +12,5 @@ pub use trust_dns_client::rr::{ pub use trust_dns_proto::rr::Name; // Reexport module types -pub use api::{RecordApi, ZoneApi}; \ No newline at end of file +pub use api::{RecordApi, ZoneApi}; +pub use dns_api::DnsApiClient; \ No newline at end of file diff --git a/src/models/errors.rs b/src/models/errors.rs index c7edd76..bfbcd96 100644 --- a/src/models/errors.rs +++ b/src/models/errors.rs @@ -6,7 +6,8 @@ use rocket_contrib::json::Json; use serde_json::Value; use djangohashers::{HasherError}; use diesel::result::Error as DieselError; - +use crate::dns::dns_api::DnsApiError; +use crate::models; #[derive(Debug)] pub enum UserError { @@ -98,6 +99,37 @@ impl From for ErrorResponse { } } +impl From for ErrorResponse { + fn from(e: DnsApiError) -> Self { + match e { + DnsApiError::RecordNotInZone { zone, class, mismatched_class, mismatched_zone} => { + ErrorResponse::new( + Status::BadRequest, + "Record list contains records that do not belong to the zone".into() + ).with_details( + json!({ + "zone_name": zone.to_utf8(), + "class": models::DNSClass::from(class), + "mismatched_class": mismatched_class.into_iter().map(|r| r.clone().into()).collect::>(), + "mismatched_zone": mismatched_zone.into_iter().map(|r| r.clone().into()).collect::>(), + }) + ) + }, + DnsApiError::ResponceNotOk { code, zone } => { + println!("Query for zone {} failed with code {}", zone, code); + + ErrorResponse::new( + Status::NotFound, + "Zone could not be found".into() + ).with_details(json!({ + "zone_name": zone.to_utf8() + })) + }, + DnsApiError::ClientError(e) => make_500(e) + } + } +} + impl From for Outcome { fn from(e: ErrorResponse) -> Self { diff --git a/src/routes/zones.rs b/src/routes/zones.rs index 1276b6d..91032dd 100644 --- a/src/routes/zones.rs +++ b/src/routes/zones.rs @@ -5,16 +5,10 @@ use rocket::http::Status; use rocket_contrib::json::Json; -use trust_dns_client::client::ClientHandle; -use trust_dns_client::op::ResponseCode; -use trust_dns_client::rr::{DNSClass, RecordType}; - use crate::DbConn; -use crate::dns; -use crate::dns::api::RecordApi; -use crate::dns::dns_api::DnsApiClient; -use crate::dns::dns_api::MessageError; use crate::models; +use crate::dns; +use crate::dns::{RecordApi, ZoneApi}; #[get("/zones//records")] @@ -36,22 +30,10 @@ pub async fn get_zone_records( } }).await?; - let mut dns_api = DnsApiClient::new(client); + let mut dns_api = dns::DnsApiClient::new(client); - let records: Vec<_> = match dns_api.get_records(zone.clone(), DNSClass::IN).await { - Ok(records) => records.into_iter().map(models::Record::from).collect(), - - Err(MessageError::ResponceNotOk(code)) => { - println!("Querrying AXFR of zone {} failed with code {}", *zone, code); - return models::ErrorResponse::new( - Status::NotFound, - "Zone could not be found".into() - ).with_details(json!({ - "zone_name": zone.to_utf8() - })).err(); - }, - Err(err) => { return models::make_500(err).err(); }, - }; + let dns_records = dns_api.get_records(zone.clone(), dns::DNSClass::IN).await?; + let records: Vec<_> = dns_records.into_iter().map(models::Record::from).collect(); Ok(Json(records)) } @@ -101,37 +83,10 @@ pub async fn create_zone_records( ).err(); } - let mut dns_api = DnsApiClient::new(client); + let mut dns_api = dns::DnsApiClient::new(client); + dns_api.add_records(zone.clone(), dns::DNSClass::IN, records).await?; - match dns_api.add_records(zone.clone(), DNSClass::IN, records).await { - Ok(_) => { - return Ok(Json(())); - //query.await.map_err(models::make_500)?; - } - Err(MessageError::RecordNotInZone { zone, class, mismatched_class, mismatched_zone}) => { - return models::ErrorResponse::new( - Status::BadRequest, - "Record list contains records that do not belong to the zone".into() - ).with_details( - json!({ - "zone_name": zone.to_utf8(), - "class": models::DNSClass::from(class), - "mismatched_class": mismatched_class.into_iter().map(|r| r.clone().into()).collect::>(), - "mismatched_zone": mismatched_zone.into_iter().map(|r| r.clone().into()).collect::>(), - }) - ).err(); - }, - Err(MessageError::ResponceNotOk(code)) => { - println!("Update of zone {} failed with code {}", *zone, code); - return models::ErrorResponse::new( - Status::NotFound, - "Update of zone failed".into() - ).with_details(json!({ - "zone_name": zone.to_utf8() - })).err(); - }, - Err(e) => return models::make_500(e).err() - }; + return Ok(Json(())); } #[get("/zones")] @@ -155,25 +110,14 @@ pub async fn get_zones( #[post("/zones", data = "")] pub async fn create_zone( conn: DbConn, - mut client: dns::client::DnsClient, + client: dns::client::DnsClient, user_info: Result, zone_request: Json, ) -> Result, models::ErrorResponse> { user_info?.check_admin()?; - // Check if the zone exists in the DNS server - let response = { - let query = client.query(zone_request.name.clone(), DNSClass::IN, RecordType::SOA); - query.await.map_err(models::make_500)? - }; - - if response.response_code() != ResponseCode::NoError { - println!("Querrying SOA of zone {} failed with code {}", *zone_request.name, response.response_code()); - return models::ErrorResponse::new( - Status::NotFound, - format!("Zone {} could not be found", *zone_request.name) - ).err() - } + let mut dns_api = dns::DnsApiClient::new(client); + dns_api.zone_exists(zone_request.name.clone(), dns::DNSClass::IN).await?; let zone = conn.run(move |c| { models::Zone::create_zone(c, zone_request.into_inner())