~asimpson/hover-dns

ref: 395e6866c3141b17d7febc93b135b29b5c493f93 hover-dns/src/main.rs -rw-r--r-- 3.6 KiB
395e6866 — Adam Simpson refactor: only import what I need from std::net 1 year, 2 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
use std::env;
use std::net::{IpAddr, Ipv4Addr};

use anyhow::{anyhow, Result};
use argh::FromArgs;
use serde::Deserialize;
use trust_dns_resolver::config::{NameServerConfigGroup, ResolverConfig, ResolverOpts};
use trust_dns_resolver::Resolver;
use ureq::{json, Response};

#[derive(FromArgs)]
#[argh(description = "Set a domain hosted on hover.com to your public IP address.")]
struct Args {
    #[argh(option, short = 'd', description = "the domain to modify")]
    domain: String,
    #[argh(
        option,
        short = 's',
        description = "the subdomain to modify",
        default = "String::from(\"@\")"
    )]
    subdomain: String,
}

#[derive(Deserialize, Debug)]
struct Overview {
    domains: Vec<Domain>,
}

#[derive(Deserialize, Debug)]
struct Domain {
    domain_name: String,
    entries: Vec<Entry>,
    id: String,
}

#[derive(Deserialize, Debug)]
struct Entry {
    content: String,
    id: String,
    name: String,
    r#type: String,
}

impl Overview {
    fn compare(&self, state: &State) {
        for entry in self.domains[0].entries.iter() {
            if entry.name == state.subdomain && entry.r#type == "A" && state.ip != entry.content {
                self.update(&entry.id, state);
            }
        }
    }

    fn update(&self, id: &str, state: &State) {
        let put_req = ureq::put("https://www.hover.com/api/control_panel/dns")
            .set("Cookie", &state.cookie)
            .send_json(json!({
              "domain": {
                "dns_records": [{
                  "id": id,
                }],
                "id": format!("domain-{}", state.domain)
              },
              "fields": {
                "content": state.ip
              }
            }));

        println!("{:?}: {:?}", put_req.status(), put_req.into_json());
    }
}

#[derive(Debug)]
struct State {
    ip: String,
    domain: String,
    subdomain: String,
    cookie: String,
}

fn lookup_ip() -> Result<String> {
    let opendns = NameServerConfigGroup::from_ips_clear(
        &[
            IpAddr::V4(Ipv4Addr::new(208, 67, 222, 222)),
            IpAddr::V4(Ipv4Addr::new(208, 67, 220, 220)),
        ],
        53,
    );
    let resolver_config = ResolverConfig::from_parts(None, vec![], opendns);
    let resolver = Resolver::new(resolver_config, ResolverOpts::default())?;
    let resolver_result = resolver.lookup_ip("myip.opendns.com")?;
    let ipv4 = resolver_result.iter().next();

    match ipv4 {
        Some(x) => Ok(x.to_string()),
        None => Err(anyhow!("Public IP lookup failed.")),
    }
}

fn parse_cookie(response: &Response) -> Result<String> {
    match response.header("Set-Cookie") {
        Some(cookie) => {
            let auth_cookie: Vec<&str> = cookie.split(';').collect();
            Ok(auth_cookie[0].to_string())
        }
        None => Err(anyhow!("Failed to get cookies from hover.")),
    }
}

fn main() -> Result<()> {
    let user = env::var("HOVER_USERNAME")?;
    let pass = env::var("HOVER_PASSWORD")?;
    let args: Args = argh::from_env();

    let resp = ureq::post("https://www.hover.com/api/login")
        .set("Accept", "application/json")
        .send_json(json!({
          "username": user,
          "password": pass
        }));

    let state = State {
        ip: lookup_ip()?,
        cookie: parse_cookie(&resp)?,
        domain: args.domain,
        subdomain: args.subdomain,
    };

    let url = format!("https://www.hover.com/api/domains/{}/dns", state.domain);
    let domains: Overview = ureq::get(&url)
        .set("Cookie", &state.cookie)
        .call()
        .into_json_deserialize()?;

    domains.compare(&state);

    Ok(())
}