first implementation

This commit is contained in:
Tim Wundenberg
2023-04-21 18:10:58 +02:00
parent 90562b3834
commit 3607826c39
6 changed files with 1310 additions and 1 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
/target
.env

1169
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -6,3 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
reqwest = { version = "0.11.16", features = ["blocking"] }
dotenv_codegen = "0.15.0"
serde_json = "1.0.96"
regex = "1.7.3"
log = "0.4.17"

11
src/errors.rs Normal file
View File

@@ -0,0 +1,11 @@
pub struct Error {
pub message: String,
}
impl Error {
pub fn new(message: &str) -> Self {
Self {
message: String::from(message),
}
}
}

View File

@@ -1,3 +1,88 @@
mod errors;
mod public_ip;
#[macro_use]
extern crate dotenv_codegen;
use log::{error, info};
use public_ip::get_public_ip_address;
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE};
use serde_json::Value;
fn main() {
println!("Hello, world!");
let token = dotenv!("AUTH_BEARER");
let zone_id = dotenv!("ZONE_ID");
let domain = dotenv!("DOMAIN");
let (id, content) = get_id_and_content_of_dns(token, zone_id, domain);
match get_public_ip_address() {
Ok(current_ip_address) => {
if content != current_ip_address {
update_ip_address(token, zone_id, &current_ip_address, domain, &id);
} else {
info!("Not updating IP Address");
}
}
Err(err) => error!("{}", err.message),
}
}
fn get_id_and_content_of_dns(token: &str, zone_id: &str, domain: &str) -> (String, String) {
let client = reqwest::blocking::Client::new();
let resp = client
.get(format!(
"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records"
))
.header(AUTHORIZATION, format!("Bearer {token}",))
.send()
.unwrap();
let content = resp.text().unwrap();
let json: Value = serde_json::from_str(&content).unwrap();
let array = json["result"].clone();
let array = array.as_array().unwrap();
for value in array {
if value["name"] == domain {
return (
String::from(value["id"].as_str().unwrap()),
String::from(value["content"].as_str().unwrap()),
);
}
}
panic!();
}
fn update_ip_address(
api_token: &str,
zone_id: &str,
current_ip_address: &str,
domain: &str,
dns_id: &str,
) {
let client = reqwest::blocking::Client::new();
let resp = client
.put(format!(
"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{dns_id}"
))
.header(AUTHORIZATION, format!("Bearer {api_token}",))
.header(CONTENT_TYPE, "application/json")
.body(format!(
"{{
\"content\": \"{current_ip_address}\",
\"name\": \"{domain}\",
\"type\": \"A\"
}}"
))
.send()
.unwrap();
println!("{}", resp.text().unwrap());
// if (resp.status()) != 200 {
// panic!()
// }
}

38
src/public_ip.rs Normal file
View File

@@ -0,0 +1,38 @@
use regex::Regex;
use reqwest::blocking::Client;
use crate::errors::Error;
pub fn get_public_ip_address() -> Result<String, Error> {
//see https://wiki.ubuntuusers.de/FritzBox/Skripte/
let resp = Client::new()
.post("http://fritz.box:49000/igdupnp/control/WANIPConn1")
.header("Content-Type", "text/xml; charset=utf-8")
.header(
"SoapAction",
"urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress",
)
.body("<?xml version='1.0' encoding='utf-8'?> <s:Envelope s:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'> <s:Body> <u:GetExternalIPAddress xmlns:u='urn:schemas-upnp-org:service:WANIPConnection:1' /> </s:Body> </s:Envelope>")
.send()
.map_err(|_| Error::new("Error fetching result via Http"))?;
if resp.status() != 200 {
return Err(Error::new(&format!("Status: {}", resp.status())));
}
let xml = resp.text().map_err(|_| Error::new("No data"))?;
let regex = Regex::new(r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}")
.map_err(|_| Error::new("Regex parsing error"))?;
let text = regex
.captures(&xml)
.map(|cap| cap.get(0))
.flatten()
.map(|cap| cap.as_str())
.map(|str| String::from(str))
.ok_or(Error::new("Regex no match"));
text
}