4b0d196bf7848bda62c75560daa3f5336d5c7b14
2 * API Reference: https://api.gandi.net/docs/livedns/
4 * Some similar implementations:
5 * - https://github.com/rmarchant/gandi-ddns/blob/master/gandi_ddns.py
6 * - https://github.com/brianhp2/gandi-automatic-dns
10 #![cfg_attr(debug_assertions, allow(unused_variables, unused_imports, dead_code))]
12 use std
::{ net
::{ IpAddr
, Ipv4Addr
}, thread
, time
};
13 use serde_json
::{ Value
, json
};
18 use crate::error
::{ Result
, Error
};
19 use crate::config
::Config
;
21 const FILE_CONF
: &str = "config.ron";
23 fn main() -> Result
<()> {
24 println!("=== GANDI LiveDNS updater ===");
26 let config
= Config
::read(FILE_CONF
)?
;
28 println!("Configuration: {:?}", Config
{ api_key
: String
::from("*****"), ..config
.clone() });
31 let time_beginning_loop
= time
::Instant
::now();
33 if let Err(err
) = check_and_update_dns(&config
.api_key
, &config
.domains
, config
.ttl
) {
34 println!("!! {}", err
);
37 let elapsed
= time
::Instant
::now() - time_beginning_loop
;
39 if elapsed
< config
.delay_between_check
{
40 let to_wait
= config
.delay_between_check
- elapsed
;
41 thread
::sleep(to_wait
);
46 fn check_and_update_dns(api_key
: &str, domains
: &Vec
<(String
, String
)>, ttl
: i32) -> Result
<()> {
47 let real_ip
= get_real_ip()?
;
49 for (hostname
, domain
) in domains
{
50 let current_ip
= get_current_record_ip(api_key
, hostname
, domain
)?
;
52 if real_ip
!= current_ip
{
53 println!("IP addresses don't match for domain {}: real = {}, dns = {}. Renewing DNS...", domain
, real_ip
, current_ip
);
54 update_record_ip(api_key
, hostname
, domain
, real_ip
, ttl
)?
;
55 println!("Renewing of {}.{} successfully", hostname
, domain
);
62 fn get_real_ip() -> Result
<Ipv4Addr
> {
63 let url
= "https://api.ipify.org";
64 let client
= reqwest
::blocking
::Client
::new();
66 match client
.get(url
).send() {
68 if resp
.status().is_success() {
69 let content
= resp
.text().unwrap();
70 match content
.parse() {
71 Ok(IpAddr
::V4(ip_v4
)) => Ok(ip_v4
),
72 _
=> Err(Box
::new(Error
{ message
: String
::from("Can't parse IPv4 from ipify") }))
75 Err(Box
::new(Error
{ message
: format!("Request unsuccessful to {}: {:#?}", url
, resp
) }))
79 Err(Box
::new(Error
{ message
: format!("Error during request to {}: {:?}", url
, error
) }))
84 fn get_current_record_ip(api_key
: &str, name
: &str, fqdn
: &str) -> Result
<Ipv4Addr
> {
85 let json_value
= request_livedns_gandi(api_key
, &format!("domains/{}/records/{}/A", fqdn
, name
), Method
::Get
)?
;
87 match &json_value
["rrset_values"][0] {
88 Value
::String(ip_str
) =>
91 Result
::Err(Box
::new(Error
{ message
: format!("Unable to extract the IP from the JSON answer: {}", json_value
) }))
95 fn update_record_ip(api_key
: &str, name
: &str, fqdn
: &str, ip
: Ipv4Addr
, ttl
: i32) -> Result
<()> {
99 "rrset_values": [ format!("{}", ip
) ],
104 let json_value
= request_livedns_gandi(api_key
, &format!("domains/{}/records/{}/A", fqdn
, name
), Method
::Put(json_body
.to_string()))?
;
106 println!("Update response: {}", json_value
);
116 fn request_livedns_gandi(api_key
: &str, url_fragment
: &str, method
: Method
) -> Result
<Value
> {
117 let url
= format!("https://api.gandi.net/v5/livedns/{}", url_fragment
);
118 let client
= reqwest
::blocking
::Client
::new();
120 let request_builder
=
122 Method
::Put(body
) => client
.put(&url
).body(body
),
123 Method
::Get
=> client
.get(&url
)
126 match request_builder
.header("Authorization", format!("Apikey {}", api_key
)).send() {
128 if resp
.status().is_success() {
129 let content
= resp
.text().unwrap();
130 Ok(serde_json
::from_str(&content
).unwrap())
132 Err(Box
::new(Error
{ message
: format!("Request unsuccessful to {}: {:#?}", &url
, resp
) }))
135 Err(Box
::new(Error
{ message
: format!("Error during request to {}: {:?}", &url
, error
) }))