diff --git a/.gitignore b/.gitignore index aab3793..71cead5 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ Cargo.lock *.json !*.example.json *.env + +.DS_Store diff --git a/Cargo.toml b/Cargo.toml index 281ad06..f5432ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,20 @@ [package] name = "update_dns_records" -version = "0.1.0" +version = "0.2.0" edition = "2021" [dependencies] -serde_json = "1" -bytes = "1" +serde_json = "1.0" +bytes = "1.11" serde = { version = "1.0", features = ["derive"] } -tokio = { version = "1", features = ["full"] } -tokio-native-tls = { version = "0.3.1" } +tokio = { version = "1.51", features = ["full"] } +tokio-native-tls = { version = "0.3" } hyper-util = { version = "0.1", features = ["full"] } native-tls = "0.2" -hyper = { version = "1", features = ["full"] } +hyper = { version = "1.9", features = ["full"] } http-body-util = "0.1" -rand = "0.8.5" -base64 = { version = "0.21" } +rand = "0.8" +base64 = { version = "0.22" } [features] default = ["cloudflare", "dyndns2"] diff --git a/LICENSE b/LICENSE index 5376ba3..3f3cce2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2024, W-lfpup +Copyright (c) 2024, Taylor Vann Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/toolkit/config.rs b/src/config.rs similarity index 92% rename from src/toolkit/config.rs rename to src/config.rs index f6ab0f8..4facf16 100644 --- a/src/toolkit/config.rs +++ b/src/config.rs @@ -3,8 +3,8 @@ use serde_json; use std::path; use tokio::fs; -use crate::toolkit::domain_services::DomainServices; -use crate::toolkit::ip_services::IpServices; +use crate::domain_services::DomainServices; +use crate::ip_services::IpServices; #[derive(Clone, Serialize, Deserialize, Debug)] pub struct Config { diff --git a/src/domain_services/cloudflare/mod.rs b/src/domain_services/cloudflare.rs similarity index 85% rename from src/domain_services/cloudflare/mod.rs rename to src/domain_services/cloudflare.rs index 8f52b3e..e966128 100644 --- a/src/domain_services/cloudflare/mod.rs +++ b/src/domain_services/cloudflare.rs @@ -11,10 +11,23 @@ use hyper::Request; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::toolkit::config::Config; -use crate::toolkit::domain_services::cloudflare::Cloudflare; -use crate::toolkit::requests::{request_http1_tls_response, ResponseJson}; -use crate::toolkit::results::{DomainResult, UpdateIpResults}; +use crate::domain_services::DomainServices; +use crate::requests::{request_http1_tls_response, ResponseDetails}; +use crate::results::{DomainResult, UpdateIpResults}; + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct Cloudflare { + pub api_token: String, + pub dns_record_id: String, + pub email: String, + pub name: String, + pub r#type: String, + pub zone_id: String, + pub comment: Option, + pub proxied: Option, + pub tags: Option>, + pub ttl: Option, +} #[derive(Clone, Serialize, Debug)] pub struct CloudflareRequestBody { @@ -37,12 +50,12 @@ pub struct CloudflareMinimalResponseBody { } pub async fn update_domains( - config: &Config, + domain_services: &DomainServices, prev_results: &Result, domain_results: &mut HashMap, ip_address: &str, ) { - let domains = match &config.domain_services.cloudflare { + let domains = match &domain_services.cloudflare { Some(domains) => domains, _ => return, }; @@ -65,9 +78,7 @@ pub async fn update_domains( } } - // build domain result domain_result = build_domain_result(&domain, ip_address).await; - // write over previous entry domain_results.insert(hostname, domain_result); } } @@ -99,7 +110,7 @@ async fn build_domain_result(domain: &Cloudflare, ip_address: &str) -> DomainRes domain_result } -fn verify_resposne(res: &ResponseJson) -> bool { +fn verify_resposne(res: &ResponseDetails) -> bool { if res.status_code != 200 { return false; } diff --git a/src/domain_services/cloudflare/README.md b/src/domain_services/cloudflare/README.md deleted file mode 100644 index 4c32409..0000000 --- a/src/domain_services/cloudflare/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Cloudflare - -## DNS Records - -List DNS records at cloudflare. -```sh -curl --request GET \ - --url https://api.cloudflare.com/client/v4/zones//dns_records \ - --header 'Content-Type: application/json' \ - --header 'X-Auth-Email: ' \ - --header 'Authorization: Bearer ' -``` - diff --git a/src/domain_services/dyndns2/mod.rs b/src/domain_services/dyndns2.rs similarity index 86% rename from src/domain_services/dyndns2/mod.rs rename to src/domain_services/dyndns2.rs index 358c214..209663e 100644 --- a/src/domain_services/dyndns2/mod.rs +++ b/src/domain_services/dyndns2.rs @@ -11,23 +11,30 @@ use base64::{engine::general_purpose, Engine as _}; use bytes::Bytes; use http_body_util::Full; use hyper::Request; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::toolkit::config::Config; -use crate::toolkit::domain_services::dyndns2::Dyndns2; -use crate::toolkit::requests::{request_http1_tls_response, ResponseJson}; -use crate::toolkit::results::{DomainResult, UpdateIpResults}; +use crate::domain_services::DomainServices; +use crate::requests::{request_http1_tls_response, ResponseDetails}; +use crate::results::{DomainResult, UpdateIpResults}; + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct Dyndns2 { + pub hostname: String, + pub password: String, + pub service_uri: String, + pub username: String, +} const CLIENT_HEADER_VALUE: &str = "hyper/1.0 rust-client"; -// must return results pub async fn update_domains( - config: &Config, + domain_services: &DomainServices, prev_results: &Result, domain_results: &mut HashMap, ip_address: &str, ) { - let domains = match &config.domain_services.dyndns2 { + let domains = match &domain_services.dyndns2 { Some(domains) => domains, _ => return, }; @@ -82,7 +89,7 @@ async fn build_domain_result(domain: &Dyndns2, ip_address: &str) -> DomainResult domain_result } -fn verify_resposne(res: &ResponseJson) -> bool { +fn verify_resposne(res: &ResponseDetails) -> bool { if res.status_code < 200 || res.status_code >= 300 { return false; } diff --git a/src/domain_services/mod.rs b/src/domain_services/mod.rs index 43a03d2..d0a8087 100644 --- a/src/domain_services/mod.rs +++ b/src/domain_services/mod.rs @@ -1,15 +1,23 @@ +use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::toolkit::config::Config; -use crate::toolkit::results::{DomainResult, IpServiceResult, UpdateIpResults}; +use crate::results::{DomainResult, IpServiceResult, UpdateIpResults}; #[cfg(feature = "cloudflare")] mod cloudflare; #[cfg(feature = "dyndns2")] mod dyndns2; +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct DomainServices { + #[cfg(feature = "cloudflare")] + pub cloudflare: Option>, + #[cfg(feature = "dyndns2")] + pub dyndns2: Option>, +} + pub async fn update_domains( - config: &Config, + domain_services: &DomainServices, prev_results: &Result, ip_service_result: &IpServiceResult, ) -> Result, String> { @@ -21,10 +29,22 @@ pub async fn update_domains( let mut domain_results = HashMap::::new(); #[cfg(feature = "dyndns2")] - dyndns2::update_domains(config, prev_results, &mut domain_results, &ip_address).await; + dyndns2::update_domains( + domain_services, + prev_results, + &mut domain_results, + &ip_address, + ) + .await; #[cfg(feature = "cloudflare")] - cloudflare::update_domains(config, prev_results, &mut domain_results, &ip_address).await; + cloudflare::update_domains( + domain_services, + prev_results, + &mut domain_results, + &ip_address, + ) + .await; Ok(domain_results) } diff --git a/src/ip_services/address_as_body.rs b/src/ip_services/address_as_body.rs index 1f90f2e..1ddcdac 100644 --- a/src/ip_services/address_as_body.rs +++ b/src/ip_services/address_as_body.rs @@ -1,11 +1,10 @@ -use bytes::Bytes; -use http_body_util::Full; -use hyper::Request; use std::net; -use crate::toolkit::requests::{get_host_and_authority, request_http1_tls_response, ResponseJson}; +use crate::requests::{ + create_request_with_empty_body, request_http1_tls_response, ResponseDetails, +}; -pub async fn request_address_as_body(service: &str) -> Result { +pub async fn request_address_as_body(service: &str) -> Result { let request = match create_request_with_empty_body(service) { Ok(req) => req, Err(e) => return Err(e), @@ -17,7 +16,7 @@ pub async fn request_address_as_body(service: &str) -> Result Result { +pub async fn get_ip_address_from_body(response_json: &ResponseDetails) -> Result { if response_json.status_code != 200 { return Err("response_json was not okay".to_string()); } @@ -30,26 +29,3 @@ pub async fn get_ip_address_from_body(response_json: &ResponseJson) -> Result Result>, String> { - let uri = match hyper::Uri::try_from(url_string) { - Ok(u) => u, - Err(e) => return Err(e.to_string()), - }; - - let (_, authority) = match get_host_and_authority(&uri) { - Some(u) => u.clone(), - _ => return Err("authority not found in url".to_string()), - }; - - let req = match Request::builder() - .uri(uri) - .header(hyper::header::HOST, authority.as_str()) - .body(Full::new(bytes::Bytes::new())) - { - Ok(r) => r, - Err(e) => return Err(e.to_string()), - }; - - Ok(req) -} diff --git a/src/ip_services/mod.rs b/src/ip_services/mod.rs index 7c6ccd2..1c0df14 100644 --- a/src/ip_services/mod.rs +++ b/src/ip_services/mod.rs @@ -1,13 +1,13 @@ use rand::{thread_rng, Rng}; -use crate::toolkit::config::Config; -use crate::toolkit::ip_services::IpServices; -use crate::toolkit::results::{IpServiceResult, UpdateIpResults}; +use crate::results::{IpServiceResult, UpdateIpResults}; mod address_as_body; +pub type IpServices = Vec<(String, String)>; + pub async fn fetch_service_results( - config: &Config, + ip_services: &IpServices, prev_results: &Result, ) -> Result { let service = match prev_results { @@ -15,7 +15,7 @@ pub async fn fetch_service_results( _ => "previous-results-do-not-exist", }; - let (ip_service, response_type) = match get_random_ip_service(&config.ip_services, service) { + let (ip_service, response_type) = match get_random_ip_service(ip_services, service) { Some(r) => r, _ => return Err("failed to find ip service".to_string()), }; diff --git a/src/main.rs b/src/main.rs index 5c09ac7..003fd2e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ use std::{env, path}; +mod config; mod domain_services; mod ip_services; -mod toolkit; - -use crate::toolkit::{config, results}; +mod requests; +mod results; #[tokio::main] async fn main() -> Result<(), String> { @@ -20,15 +20,22 @@ async fn main() -> Result<(), String> { Err(e) => return Err(e), }; - let prev_results = results::read_results_from_disk(&config).await; + let prev_results = results::read_results_from_disk(&config.results_filepath).await; - let ip_service_result = match ip_services::fetch_service_results(&config, &prev_results).await { - Ok(ip_result) => ip_result, - Err(e) => return Err(e), - }; + let ip_service_result = + match ip_services::fetch_service_results(&config.ip_services, &prev_results).await { + Ok(ip_result) => ip_result, + Err(e) => return Err(e), + }; let domain_service_results = - domain_services::update_domains(&config, &prev_results, &ip_service_result).await; - - results::write_results_to_disk(&config, ip_service_result, domain_service_results).await + domain_services::update_domains(&config.domain_services, &prev_results, &ip_service_result) + .await; + + results::write_results_to_disk( + &config.results_filepath, + ip_service_result, + domain_service_results, + ) + .await } diff --git a/src/toolkit/requests.rs b/src/requests.rs similarity index 75% rename from src/toolkit/requests.rs rename to src/requests.rs index df40c39..d559858 100644 --- a/src/toolkit/requests.rs +++ b/src/requests.rs @@ -10,12 +10,16 @@ use std::io; use tokio::net::TcpStream; #[derive(Clone, Serialize, Deserialize, Debug)] -pub struct ResponseJson { +pub struct ResponseDetails { pub status_code: u16, + // content type + // content encoding pub body: String, } -pub async fn request_http1_tls_response(req: Request>) -> Result { +pub async fn request_http1_tls_response( + req: Request>, +) -> Result { let (host, authority) = match get_host_and_authority(&req.uri()) { Some(stream) => stream, _ => return Err("failed to get authority from uri".to_string()), @@ -65,8 +69,6 @@ pub fn get_host_and_authority(uri: &Uri) -> Option<(&str, String)> { Some((host, authority)) } -// this has multiple "types" of errors -// signal that it is an inappropriate grouping? async fn create_tls_stream( host: &str, addr: &str, @@ -91,7 +93,9 @@ async fn create_tls_stream( Ok(tls_stream) } -async fn convert_response_to_json_struct(res: Response) -> Result { +async fn convert_response_to_json_struct( + res: Response, +) -> Result { let status = res.status().as_u16(); let body_str = match response_body_to_string(res).await { @@ -99,7 +103,7 @@ async fn convert_response_to_json_struct(res: Response) -> Result return Err(e), }; - Ok(ResponseJson { + Ok(ResponseDetails { status_code: status, body: body_str, }) @@ -119,3 +123,26 @@ async fn response_body_to_string(response: Response) -> Result Result>, String> { + let uri = match hyper::Uri::try_from(url_string) { + Ok(u) => u, + Err(e) => return Err(e.to_string()), + }; + + let (_, authority) = match get_host_and_authority(&uri) { + Some(u) => u.clone(), + _ => return Err("authority not found in url".to_string()), + }; + + let req = match Request::builder() + .uri(uri) + .header(hyper::header::HOST, authority.as_str()) + .body(Full::new(bytes::Bytes::new())) + { + Ok(r) => r, + Err(e) => return Err(e.to_string()), + }; + + Ok(req) +} diff --git a/src/toolkit/results.rs b/src/results.rs similarity index 84% rename from src/toolkit/results.rs rename to src/results.rs index df7073d..e20d380 100644 --- a/src/toolkit/results.rs +++ b/src/results.rs @@ -1,15 +1,15 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use std::path::PathBuf; use tokio::fs; -use crate::toolkit::config::Config; -use crate::toolkit::requests::ResponseJson; +use crate::requests::ResponseDetails; #[derive(Clone, Serialize, Deserialize, Debug)] pub struct IpServiceResult { pub service: String, pub ip_address: Option, - pub response: Option, + pub response: Option, } impl IpServiceResult { @@ -26,7 +26,7 @@ impl IpServiceResult { pub struct DomainResult { pub hostname: String, pub ip_address: Option, - pub response: Option, + pub response: Option, pub errors: Vec, } @@ -63,8 +63,8 @@ impl UpdateIpResults { } } -pub async fn read_results_from_disk(config: &Config) -> Result { - let json_as_str = match fs::read_to_string(&config.results_filepath).await { +pub async fn read_results_from_disk(results_filepath: &PathBuf) -> Result { + let json_as_str = match fs::read_to_string(results_filepath).await { Ok(json_str) => json_str, Err(e) => return Err(e.to_string()), }; @@ -76,7 +76,7 @@ pub async fn read_results_from_disk(config: &Config) -> Result, String>, ) -> Result<(), String> { @@ -90,7 +90,7 @@ pub async fn write_results_to_disk( Err(e) => return Err(e.to_string()), }; - if let Err(e) = fs::write(&config.results_filepath, json_str).await { + if let Err(e) = fs::write(results_filepath, json_str).await { return Err(e.to_string()); }; diff --git a/src/toolkit/domain_services/cloudflare.rs b/src/toolkit/domain_services/cloudflare.rs deleted file mode 100644 index 6974b8c..0000000 --- a/src/toolkit/domain_services/cloudflare.rs +++ /dev/null @@ -1,17 +0,0 @@ -use serde::{Deserialize, Serialize}; - -// following type is based on: -// https://developers.cloudflare.com/api/operations/dns-records-for-a-zone-update-dns-record -#[derive(Clone, Serialize, Deserialize, Debug)] -pub struct Cloudflare { - pub api_token: String, - pub comment: Option, - pub dns_record_id: String, - pub email: String, - pub name: String, - pub proxied: Option, - pub r#type: String, - pub tags: Option>, - pub ttl: Option, - pub zone_id: String, -} diff --git a/src/toolkit/domain_services/dyndns2.rs b/src/toolkit/domain_services/dyndns2.rs deleted file mode 100644 index 3795593..0000000 --- a/src/toolkit/domain_services/dyndns2.rs +++ /dev/null @@ -1,9 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Serialize, Deserialize, Debug)] -pub struct Dyndns2 { - pub hostname: String, - pub password: String, - pub service_uri: String, - pub username: String, -} diff --git a/src/toolkit/domain_services/mod.rs b/src/toolkit/domain_services/mod.rs deleted file mode 100644 index c59c3d5..0000000 --- a/src/toolkit/domain_services/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -use serde::{Deserialize, Serialize}; - -// add domain services here -// beware of hydra - -#[cfg(feature = "cloudflare")] -pub mod cloudflare; -#[cfg(feature = "dyndns2")] -pub mod dyndns2; - -#[cfg(feature = "cloudflare")] -use cloudflare::Cloudflare; -#[cfg(feature = "dyndns2")] -use dyndns2::Dyndns2; - -#[derive(Clone, Serialize, Deserialize, Debug)] -pub struct DomainServices { - #[cfg(feature = "cloudflare")] - pub cloudflare: Option>, - #[cfg(feature = "dyndns2")] - pub dyndns2: Option>, -} diff --git a/src/toolkit/ip_services.rs b/src/toolkit/ip_services.rs deleted file mode 100644 index 92a8968..0000000 --- a/src/toolkit/ip_services.rs +++ /dev/null @@ -1 +0,0 @@ -pub type IpServices = Vec<(String, String)>; diff --git a/src/toolkit/mod.rs b/src/toolkit/mod.rs deleted file mode 100644 index f9d9637..0000000 --- a/src/toolkit/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod config; -pub mod domain_services; -pub mod ip_services; -pub mod requests; -pub mod results;