From e20d7b0d8699a03914a16cff7dbd748493cb2cb5 Mon Sep 17 00:00:00 2001 From: shad0wflame Date: Wed, 22 Dec 2021 16:19:59 +0100 Subject: [PATCH] Implemented request to update the records. --- src/go_daddy_ddns/mod.rs | 122 ++++++++++++++++++++++++++++++++++++--- src/ip_handler/mod.rs | 29 +++++----- 2 files changed, 127 insertions(+), 24 deletions(-) diff --git a/src/go_daddy_ddns/mod.rs b/src/go_daddy_ddns/mod.rs index 134d283..c1a9ec2 100644 --- a/src/go_daddy_ddns/mod.rs +++ b/src/go_daddy_ddns/mod.rs @@ -1,5 +1,52 @@ +use std::fs::read_to_string; +use std::path::Path; + +use serde::{Deserialize, Serialize}; + use crate::ip_handler::get_ip_to_publish; +const RECORDS_FILE_NAME: &'static str = "records.json"; + +#[derive(Debug, Deserialize)] +struct DNSRecordsHolder { + records: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +struct DNSRecord { + name: String, + record_type: String, + data: Option, + ttl: u32, +} + +#[derive(Debug, Serialize, Deserialize)] +struct DNSRecordCreateTypeName { + data: String, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + port: Option, // SRV Only. + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + priority: Option, // MX and SRV only. + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + protocol: Option, // SRV only. + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + service: Option, // SRV only. + + ttl: u32, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + weight: Option, // SRV only. +} + /// Updates the DNS records if the IP has changed and returns the result of the execution. /// /// # Arguments @@ -12,19 +59,78 @@ use crate::ip_handler::get_ip_to_publish; pub async fn exec(domain: &str, key: &str, secret: &str) -> Result<(), Box> { let new_ip = get_ip_to_publish().await; - /// There's no need to do anything here. So we stop the execution. + // There's no need to do anything here. So we stop the execution. if Option::is_none(&new_ip) { return Ok(()); } - // TODO: Create a struct representing the structure of - // a JSON file including the DNS Records we want to update. + let records = get_records(); - // TODO: Create that JSON file. - - // TODO: Read that JSON file and deserialize it with serde. - - // TODO: Update the DNS Records. + for record in records { + update_record(&record, &new_ip.clone().unwrap(), domain, key, secret).await; + } Ok(()) } + +/// Gets a vector of DNSRecord from RECORDS_FILE_NAME and returns it. +fn get_records() -> Vec { + let path = Path::new(RECORDS_FILE_NAME); + let content = read_to_string(path).unwrap(); + + let base: DNSRecordsHolder = + serde_json::from_str(&content).expect("Failed to deserialize JSON"); + + base.records +} + +/// Sends a put request to the GoDaddy API to update a DNS record. +/// +/// # Arguments +/// +/// * `record` - A &DNSRecord holding the record to update. +/// +/// * `value` - A &str holding the current WAN ip. +/// +/// * `domain` - A &str holding the domain to update. +/// +/// * `key` - A &str holding the GoDaddy developer key. +/// +/// * `secret` - A &str holding the GoDaddy developer secret. +async fn update_record(record: &DNSRecord, value: &str, domain: &str, key: &str, secret: &str) -> () { + + let url = format!( + "https://api.godaddy.com/v1/domains/{domain}/records/{record_type}/{name}", + domain = domain, + record_type = record.record_type, + name = record.name + ); + + let data = match &record.data { + Some(x) => String::from(x), + None => String::from(value), + }; + + let body = vec![DNSRecordCreateTypeName { + data, + port: None, + priority: None, + protocol: None, + service: None, + ttl: record.ttl, + weight: None, + }]; + + let header = format!("sso-key {}:{}", key, secret); + + let client = reqwest::Client::new(); + + let req = client + .put(url) + .json(&body) + .header("accept", "application/json") + .header("content-type", "application/json") + .header("authorization", &header); + + req.send().await.expect("Error updating record."); +} diff --git a/src/ip_handler/mod.rs b/src/ip_handler/mod.rs index 9674660..5f476d2 100644 --- a/src/ip_handler/mod.rs +++ b/src/ip_handler/mod.rs @@ -1,9 +1,9 @@ use serde::Deserialize; -use std::fs::File; -use std::io::{Read, Write}; +use std::fs::{read_to_string, File}; +use std::io::Write; use std::path::Path; -const FILE_NAME: &'static str = "ddns_ip"; +const IP_FILE_NAME: &'static str = "ddns_ip"; const WEBSITE_URL: &'static str = "https://httpbin.org/ip"; #[derive(Deserialize)] @@ -13,16 +13,14 @@ struct IP { /// Returns an Option holding the current IP address if the value has changed, otherwise returns None. pub async fn get_ip_to_publish() -> Option { - let current_ip = check_current_ip() - .await - .expect("Error getting the current IP."); - let previous_ip = match check_previous_ip() { Some(x) => x, None => String::new(), }; - println!("Current IP: {}, Previous IP: {}", current_ip, previous_ip); + let current_ip = check_current_ip() + .await + .expect("Error getting the current IP."); if current_ip.eq(&previous_ip) { return None; @@ -47,22 +45,21 @@ async fn check_current_ip() -> Result> { /// # Arguments /// /// * `current_ip` - A &str holding the current IP value. -fn record_current_ip(current_ip: &str) { - let mut file = File::create(FILE_NAME).expect("Error creating file."); +fn record_current_ip(current_ip: &str) -> () { + let mut file = File::create(IP_FILE_NAME).expect("Error creating file."); file.write_all(current_ip.as_ref()) .expect("Error writing file."); } /// Reads the current IP value from the FILE_NAME and returns it. fn check_previous_ip() -> Option { - if !Path::new(FILE_NAME).exists() { + let path = Path::new(IP_FILE_NAME); + + if !path.exists() { return None; } - let mut file = File::open(FILE_NAME).ok()?; - let mut contents = String::new(); - file.read_to_string(&mut contents) - .expect("Error reading file."); + let contents = read_to_string(path).expect("Error reading file."); - return Some(contents); + Some(contents) }