Working json response with keyfile

This commit is contained in:
notplants 2021-05-18 09:25:18 +02:00
parent 7c626c5ef7
commit 4d14a58ff7
9 changed files with 103 additions and 154 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/target
**/*.rs.bk
ns_tests/*.key
ns_tests/*

View File

@ -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"

View File

@ -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
View 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
View 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)
}
}

View File

@ -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<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
/// function to generate the text of a TSIG key file
pub fn generate_tsig_key(full_domain: &str) -> Result<String, PeachDynError> {
let output = Command::new("/usr/sbin/tsig-keygen")
.arg("-a")
@ -45,7 +24,8 @@ pub fn generate_tsig_key(full_domain: &str) -> Result<String, PeachDynError> {
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<String, PeachDynError> {
@ -152,18 +133,3 @@ pub fn generate_zone(full_domain: &str) -> Result<String, PeachDynError> {
// 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);
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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]

63
src/routes.rs Normal file
View 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)))
}
}
}
}