diff --git a/.gitignore b/.gitignore index 7bd65eb..63340e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target **/*.rs.bk ns_tests/*.key +ns_tests/* diff --git a/Cargo.toml b/Cargo.toml index 5df6d36..bf5e96f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,15 +23,6 @@ serde = "1.0.125" dotenv = "0.15.0" tera = "1" - -[[bin]] -name = "client" -path = "src/client.rs" - -[[bin]] -name = "zone" -path = "src/generate_zone.rs" - [[bin]] name = "main" path = "src/main.rs" \ No newline at end of file diff --git a/src/client.rs b/src/client.rs deleted file mode 100644 index 28612d3..0000000 --- a/src/client.rs +++ /dev/null @@ -1,57 +0,0 @@ -use futures::try_join; -use std::io; -use tokio::task; - -use std::net::Ipv4Addr; -use std::str::FromStr; -use trust_dns_client::client::{Client, SyncClient}; -use trust_dns_client::op::update_message; -use trust_dns_client::op::DnsResponse; -use trust_dns_client::rr::{DNSClass, Name, RData, Record, RecordSet, RecordType}; -use trust_dns_client::tcp::TcpClientConnection; -use trust_dns_client::udp::UdpClientConnection; -use trust_dns_server::authority::{ - AuthLookup, Authority, LookupError, MessageRequest, UpdateResult, -}; - -pub fn check_domain_available_using_nslookup(domain: &str) -> bool { - let address = "167.99.136.83:53".parse().unwrap(); - let conn = UdpClientConnection::new(address).unwrap(); - let client = SyncClient::new(conn); - let name = Name::from_str(domain).unwrap(); - - println!("++ making query {:?}", domain); - let response: DnsResponse = client.query(&name, DNSClass::IN, RecordType::A).unwrap(); - println!("++ received response"); - let answers: &[Record] = response.answers(); - - if answers.len() > 0 { - println!("found: {:?}", answers[0].rdata()); - true - } else { - false - } -} - -fn update_test() { - let address = "167.99.136.83:53".parse().unwrap(); - let conn = UdpClientConnection::new(address).unwrap(); - let client = SyncClient::new(conn); - - // Specify the name, note the final '.' which specifies it's an FQDN - let name = Name::from_str("test.time.commoninternet.net").unwrap(); - - let record = Record::from_rdata(name.clone(), 8, RData::A(Ipv4Addr::new(127, 0, 0, 10))); - let rrset: RecordSet = record.clone().into(); - let zone_origin = Name::from_str("time.commoninternet.net").unwrap(); - - let response: DnsResponse = client - .create(rrset, zone_origin) - .expect("failed to create record"); - println!("response: {:?}", response); -} - -fn main() { - check_domain_available_using_nslookup("test"); - // update_test(); -} diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..f7b62f9 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,5 @@ + + +// this is the base domain which peach-dyndns creates subdomains under +// e.g. blue.dyn.peachcloud.org +pub const BASE_DOMAIN: &str = "dyn.commoninternet.net"; \ No newline at end of file diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..fdb3808 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,22 @@ +use std::string::FromUtf8Error; + + +#[derive(Debug)] +pub enum PeachDynError { + GenerateTsigIoError(std::io::Error), + GenerateTsigParseError(std::string::FromUtf8Error), + DomainAlreadyExistsError(String), + BindConfigurationError(String), +} + +impl From for PeachDynError { + fn from(err: std::io::Error) -> PeachDynError { + PeachDynError::GenerateTsigIoError(err) + } +} + +impl From for PeachDynError { + fn from(err: std::string::FromUtf8Error) -> PeachDynError { + PeachDynError::GenerateTsigParseError(err) + } +} \ No newline at end of file diff --git a/src/generate_zone.rs b/src/generate_zone.rs index 14aed36..c9436e6 100644 --- a/src/generate_zone.rs +++ b/src/generate_zone.rs @@ -8,33 +8,12 @@ use std::fs::File; use std::fs::OpenOptions; use std::io::Write; use std::process::Command; -use std::string::FromUtf8Error; use tera::{Tera, Context}; +use crate::errors::PeachDynError; +use crate::constants::BASE_DOMAIN; -const BASE_DOMAIN: &str = "dyn.commoninternet.net"; - -#[derive(Debug)] -pub enum PeachDynError { - GenerateTsigIoError(std::io::Error), - GenerateTsigParseError(std::string::FromUtf8Error), - DomainAlreadyExistsError(String), - BindConfigurationError(String), -} - -impl From for PeachDynError { - fn from(err: std::io::Error) -> PeachDynError { - PeachDynError::GenerateTsigIoError(err) - } -} - -impl From for PeachDynError { - fn from(err: std::string::FromUtf8Error) -> PeachDynError { - PeachDynError::GenerateTsigParseError(err) - } -} - -/// helper function to generate the text of a TSIG key file +/// function to generate the text of a TSIG key file pub fn generate_tsig_key(full_domain: &str) -> Result { let output = Command::new("/usr/sbin/tsig-keygen") .arg("-a") @@ -45,7 +24,8 @@ pub fn generate_tsig_key(full_domain: &str) -> Result { Ok(key_file_text) } -/// helper function which returns true if all three conditions are true +/// function which helps us guarantee that a given domain is not already being used by bind +/// it checks three places for the domain, and only returns true if it is not found in all three places /// - no already extant tsig key for the given domain /// - no zone file for the given domain in /var/lib/bind /// - no zone section for the given domain in named.conf.local @@ -64,6 +44,7 @@ pub fn check_domain_available(full_domain: &str) -> bool { // domain is only available if domain does not exist in either named.conf.local or dyn.commoninternet.netkeys // and a file with that name is not found in /var/lib/bind/ + // grep returns a status code of 1 if lines are not found, which is why we check that the codes equal 1 let domain_available = (code1 == 1) & (code2 == 1) & (!condition3); // return @@ -71,9 +52,9 @@ pub fn check_domain_available(full_domain: &str) -> bool { } -/// helper function which generates all necessary bind configuration to serve the given +/// function which generates all necessary bind configuration to serve the given /// subdomain using dynamic DNS authenticated via a new TSIG key which is unique to that subdomain -/// and thus only the possessor of that key can use nsupdate to modify the records +/// - thus only the possessor of that key can use nsupdate to modify the records /// for that subodmain pub fn generate_zone(full_domain: &str) -> Result { @@ -152,18 +133,3 @@ pub fn generate_zone(full_domain: &str) -> Result { // return success Ok(key_file_text) } - -// this main function is only used for the purpose of testing -fn main() { - - let result = generate_zone("purple"); - match result { - Ok(key_text) => { - println!("successfully created zone"); - println!("{}", key_text); - } - Err(err) => { - println!("error creating zone {:?}", err); - } - } -} diff --git a/src/http.rs b/src/http.rs deleted file mode 100644 index 4df4098..0000000 --- a/src/http.rs +++ /dev/null @@ -1,43 +0,0 @@ -/* -* -* /register-user (sends an email verification to create a new account) -* /verify (for clicking the link in the email) -* /register-domain (add a new domain and get back the secret for subsequent updating) -* /update-domain (update the IP for the domain, passing the associated secret) -* -*/ -use crate::generate_zone::{check_domain_available, generate_zone}; -use rocket_contrib::json::{Json, JsonValue}; -use serde::Deserialize; -use std::thread; - -#[get("/")] -pub fn index() -> &'static str { - "This is the peach-dyn-dns server." -} - -#[derive(Deserialize, Debug)] -pub struct RegisterDomainPost { - domain: String, -} - -#[post("/register-domain", data = "")] -pub async fn register_domain(data: Json) -> &'static str { - info!("++ post request to register new domain: {:?}", data); - // TODO: first confirm domain is in the right format ("*.dyn.peachcloud.org") - let is_domain_available = check_domain_available(&data.domain); - if !is_domain_available{ - "can't register domain that already exists" - } else { - let result = generate_zone(&data.domain); - match result { - Ok(key_file_text) => { - // TODO: figure out how to return key_file_text - "successfully created zone" - } - Err(err) => { - "there was an error registering the domain" - } - } - } -} diff --git a/src/main.rs b/src/main.rs index a41c2e8..26fab5c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,14 +3,15 @@ #[macro_use] extern crate rocket; -use crate::http::{index, register_domain}; +use crate::routes::{index, register_domain}; use futures::try_join; use std::io; use tokio::task; mod cli; -mod client; -mod http; +mod routes; +mod errors; +mod constants; mod generate_zone; #[tokio::main] diff --git a/src/routes.rs b/src/routes.rs new file mode 100644 index 0000000..5dd5bcb --- /dev/null +++ b/src/routes.rs @@ -0,0 +1,63 @@ +/* +* +* /register-user (sends an email verification to create a new account) NOT IMPLEMENTED +* /verify (for clicking the link in the email) NOT IMPLEMENTED +* /register-domain (add a new domain and get back the TSIG key for subsequent updating with nsupdate) +*/ +use crate::generate_zone::{check_domain_available, generate_zone}; +use rocket_contrib::json::{Json, JsonValue}; +use serde::{Deserialize, Serialize}; + +#[get("/")] +pub fn index() -> &'static str { + "This is the peach-dyndns server." +} + +#[derive(Serialize)] +pub struct JsonResponse { + pub status: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub data: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub msg: Option, +} + +// helper function to build a JsonResponse object +pub fn build_json_response( + status: String, + data: Option, + msg: Option, +) -> JsonResponse { + JsonResponse { status, data, msg } +} + +#[derive(Deserialize, Debug)] +pub struct RegisterDomainPost { + domain: String, +} + +#[post("/register-domain", data = "")] +pub async fn register_domain(data: Json) -> Json { + info!("++ post request to register new domain: {:?}", data); + // TODO: first confirm domain is in the right format ("*.dyn.peachcloud.org") + let is_domain_available = check_domain_available(&data.domain); + if !is_domain_available{ + let status = "error".to_string(); + let msg = "can't register a domain that is already registered".to_string(); + Json(build_json_response(status, None, Some(msg))) + } else { + let result = generate_zone(&data.domain); + match result { + Ok(key_file_text) => { + let status = "success".to_string(); + let msg = key_file_text.to_string(); + Json(build_json_response(status, None, Some(msg))) + } + Err(_err) => { + let status = "error".to_string(); + let msg = "there was an error creating the zone file".to_string(); + Json(build_json_response(status, None, Some(msg))) + } + } + } +}