Working json response with keyfile
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
/target
|
/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
ns_tests/*.key
|
ns_tests/*.key
|
||||||
|
ns_tests/*
|
||||||
|
@ -23,15 +23,6 @@ serde = "1.0.125"
|
|||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
tera = "1"
|
tera = "1"
|
||||||
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "client"
|
|
||||||
path = "src/client.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "zone"
|
|
||||||
path = "src/generate_zone.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "main"
|
name = "main"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
@ -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();
|
|
||||||
}
|
|
5
src/constants.rs
Normal file
5
src/constants.rs
Normal file
@ -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";
|
22
src/errors.rs
Normal file
22
src/errors.rs
Normal file
@ -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<std::io::Error> for PeachDynError {
|
||||||
|
fn from(err: std::io::Error) -> PeachDynError {
|
||||||
|
PeachDynError::GenerateTsigIoError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FromUtf8Error> for PeachDynError {
|
||||||
|
fn from(err: std::string::FromUtf8Error) -> PeachDynError {
|
||||||
|
PeachDynError::GenerateTsigParseError(err)
|
||||||
|
}
|
||||||
|
}
|
@ -8,33 +8,12 @@ use std::fs::File;
|
|||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::string::FromUtf8Error;
|
|
||||||
use tera::{Tera, Context};
|
use tera::{Tera, Context};
|
||||||
|
use crate::errors::PeachDynError;
|
||||||
|
use crate::constants::BASE_DOMAIN;
|
||||||
|
|
||||||
|
|
||||||
const BASE_DOMAIN: &str = "dyn.commoninternet.net";
|
/// function to generate the text of a TSIG key file
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum PeachDynError {
|
|
||||||
GenerateTsigIoError(std::io::Error),
|
|
||||||
GenerateTsigParseError(std::string::FromUtf8Error),
|
|
||||||
DomainAlreadyExistsError(String),
|
|
||||||
BindConfigurationError(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::io::Error> for PeachDynError {
|
|
||||||
fn from(err: std::io::Error) -> PeachDynError {
|
|
||||||
PeachDynError::GenerateTsigIoError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<FromUtf8Error> for PeachDynError {
|
|
||||||
fn from(err: std::string::FromUtf8Error) -> PeachDynError {
|
|
||||||
PeachDynError::GenerateTsigParseError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// helper function to generate the text of a TSIG key file
|
|
||||||
pub fn generate_tsig_key(full_domain: &str) -> Result<String, PeachDynError> {
|
pub fn generate_tsig_key(full_domain: &str) -> Result<String, PeachDynError> {
|
||||||
let output = Command::new("/usr/sbin/tsig-keygen")
|
let output = Command::new("/usr/sbin/tsig-keygen")
|
||||||
.arg("-a")
|
.arg("-a")
|
||||||
@ -45,7 +24,8 @@ pub fn generate_tsig_key(full_domain: &str) -> Result<String, PeachDynError> {
|
|||||||
Ok(key_file_text)
|
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 already extant tsig key for the given domain
|
||||||
/// - no zone file for the given domain in /var/lib/bind
|
/// - no zone file for the given domain in /var/lib/bind
|
||||||
/// - no zone section for the given domain in named.conf.local
|
/// - 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
|
// 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/
|
// 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);
|
let domain_available = (code1 == 1) & (code2 == 1) & (!condition3);
|
||||||
|
|
||||||
// return
|
// 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
|
/// 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
|
/// for that subodmain
|
||||||
pub fn generate_zone(full_domain: &str) -> Result<String, PeachDynError> {
|
pub fn generate_zone(full_domain: &str) -> Result<String, PeachDynError> {
|
||||||
|
|
||||||
@ -152,18 +133,3 @@ pub fn generate_zone(full_domain: &str) -> Result<String, PeachDynError> {
|
|||||||
// return success
|
// return success
|
||||||
Ok(key_file_text)
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
43
src/http.rs
43
src/http.rs
@ -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 = "<data>")]
|
|
||||||
pub async fn register_domain(data: Json<RegisterDomainPost>) -> &'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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,14 +3,15 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
use crate::http::{index, register_domain};
|
use crate::routes::{index, register_domain};
|
||||||
use futures::try_join;
|
use futures::try_join;
|
||||||
use std::io;
|
use std::io;
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod client;
|
mod routes;
|
||||||
mod http;
|
mod errors;
|
||||||
|
mod constants;
|
||||||
mod generate_zone;
|
mod generate_zone;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
63
src/routes.rs
Normal file
63
src/routes.rs
Normal file
@ -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<JsonValue>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub msg: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to build a JsonResponse object
|
||||||
|
pub fn build_json_response(
|
||||||
|
status: String,
|
||||||
|
data: Option<JsonValue>,
|
||||||
|
msg: Option<String>,
|
||||||
|
) -> JsonResponse {
|
||||||
|
JsonResponse { status, data, msg }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct RegisterDomainPost {
|
||||||
|
domain: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/register-domain", data = "<data>")]
|
||||||
|
pub async fn register_domain(data: Json<RegisterDomainPost>) -> Json<JsonResponse> {
|
||||||
|
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)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user