use std::convert::TryInto; use serde_json::json; use rocket::Response; 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::{dns::{self, trust_dns_types}, DbConn}; use crate::models::errors::{ErrorResponse, make_500}; use crate::models::user::{LocalUser, UserInfo}; use crate::models::zone::{Zone, AddZoneMemberRequest, CreateZoneRequest}; use crate::dns::message::DnsMessage; use crate::dns::message::MessageError; #[get("/zones//records")] pub async fn get_zone_records( mut client: dns::client::DnsClient, conn: DbConn, user_info: Result, zone: dns::name::AbsoluteName ) -> Result>, ErrorResponse> { let user_info = user_info?; let zone_name = zone.to_string(); conn.run(move |c| { if user_info.is_admin() { Zone::get_by_name(c, &zone_name) } else { user_info.get_zone(c, &zone_name) } }).await?; let records: Vec<_> = match client.get_records(zone.clone(), DNSClass::IN).await { Ok(records) => records.into_iter().map(dns::record::Record::from).collect(), Err(MessageError::ResponceNotOk(code)) => { println!("Querrying AXFR of zone {} failed with code {}", *zone, code); return ErrorResponse::new( Status::NotFound, "Zone could not be found".into() ).with_details(json!({ "zone_name": zone.to_utf8() })).err(); }, Err(err) => { return make_500(err).err(); }, }; Ok(Json(records)) } #[post("/zones//records", data = "")] pub async fn create_zone_records( mut client: dns::client::DnsClient, conn: DbConn, user_info: Result, zone: dns::name::AbsoluteName, new_records: Json> ) -> Result, ErrorResponse> { let user_info = user_info?; let zone_name = zone.to_utf8(); conn.run(move |c| { if user_info.is_admin() { Zone::get_by_name(c, &zone_name) } else { user_info.get_zone(c, &zone_name) } }).await?; // TODO: What about relative names (also in cnames and stuff) let mut bad_records = Vec::new(); let mut records: Vec = Vec::new(); for record in new_records.into_inner().into_iter() { let this_record = record.clone(); if let Ok(record) = record.try_into() { records.push(record); } else { bad_records.push(this_record.clone()); } } if !bad_records.is_empty() { return ErrorResponse::new( Status::BadRequest, "Record list contains records that could not been parsed into DNS records".into() ).with_details( json!({ "zone_name": zone.to_utf8(), "records": bad_records }) ).err(); } let response = match client.add_records(zone.clone(), DNSClass::IN, records) { Ok(query) => query.await.map_err(make_500)?, Err(MessageError::RecordNotInZone { zone, class, mismatched_class, mismatched_zone}) => { return 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": dns::class::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(e) => return make_500(e).err() }; // TODO: better error handling if response.response_code() != ResponseCode::NoError { println!("Update of zone {} failed with code {}", *zone, response.response_code()); return ErrorResponse::new( Status::NotFound, "Update of zone failed".into() ).with_details(json!({ "zone_name": zone.to_utf8() })).err(); } Ok(Json(())) } #[get("/zones")] pub async fn get_zones( conn: DbConn, user_info: Result, ) -> Result>, ErrorResponse> { let user_info = user_info?; let zones = conn.run(move |c| { if user_info.is_admin() { Zone::get_all(c) } else { user_info.get_zones(c) } }).await?; Ok(Json(zones)) } #[post("/zones", data = "")] pub async fn create_zone( conn: DbConn, mut client: dns::client::DnsClient, user_info: Result, zone_request: Json, ) -> Result, 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(make_500)? }; if response.response_code() != ResponseCode::NoError { println!("Querrying SOA of zone {} failed with code {}", *zone_request.name, response.response_code()); return ErrorResponse::new( Status::NotFound, format!("Zone {} could not be found", *zone_request.name) ).err() } let zone = conn.run(move |c| { Zone::create_zone(c, zone_request.into_inner()) }).await?; Ok(Json(zone)) } #[post("/zones//members", data = "")] pub async fn add_member_to_zone<'r>( conn: DbConn, zone: dns::name::AbsoluteName, user_info: Result, zone_member_request: Json ) -> Result, ErrorResponse> { let user_info = user_info?; let zone_name = zone.to_utf8(); conn.run(move |c| { let zone = if user_info.is_admin() { Zone::get_by_name(c, &zone_name) } else { user_info.get_zone(c, &zone_name) }?; let new_member = LocalUser::get_user_by_uuid(c, &zone_member_request.id)?; zone.add_member(&c, &new_member) }).await?; Response::build() .status(Status::Created) // TODO: change this? .ok() }