From 71077db79be7831959e2014661a2fbeee6ada186 Mon Sep 17 00:00:00 2001 From: Eri Bastos Date: Mon, 27 Apr 2020 14:05:37 -0400 Subject: [PATCH 1/4] Add dreamhost handler --- go.mod | 1 + go.sum | 2 + handler/dreamhost/dreamhost_handler.go | 126 +++++++++++++++++++++++++ handler/handler.go | 4 + utils.go | 7 ++ 5 files changed, 140 insertions(+) create mode 100644 handler/dreamhost/dreamhost_handler.go diff --git a/go.mod b/go.mod index 0eaa091..2184793 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ require ( github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/bogdanovich/dns_resolver v0.0.0-20170211073258-a8e42bc6a5b6 github.com/fatih/color v1.7.0 + github.com/google/uuid v1.1.1 github.com/kr/pretty v0.1.0 // indirect github.com/mattn/go-colorable v0.0.9 // indirect github.com/mattn/go-isatty v0.0.4 // indirect diff --git a/go.sum b/go.sum index 35d85dd..ef880d2 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/bogdanovich/dns_resolver v0.0.0-20170211073258-a8e42bc6a5b6 h1:oV1V+u github.com/bogdanovich/dns_resolver v0.0.0-20170211073258-a8e42bc6a5b6/go.mod h1:txOV61Nn+21z77KUMkNsp8lTHoOFTtqotltQAFenS9I= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= diff --git a/handler/dreamhost/dreamhost_handler.go b/handler/dreamhost/dreamhost_handler.go new file mode 100644 index 0000000..587cbff --- /dev/null +++ b/handler/dreamhost/dreamhost_handler.go @@ -0,0 +1,126 @@ +package dreamhost + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "runtime/debug" + "strings" + "time" + + "github.com/TimothyYe/godns" + "github.com/google/uuid" +) + +var ( + // DreamhostURL the API address for he.net + DreamhostURL = "https://api.dreamhost.com" +) + +// Handler struct +type Handler struct { + Configuration *godns.Settings +} + +// SetConfiguration pass dns settings and store it to handler instance +func (handler *Handler) SetConfiguration(conf *godns.Settings) { + handler.Configuration = conf +} + +// DomainLoop the main logic loop +func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.Domain) { + defer func() { + if err := recover(); err != nil { + log.Printf("Recovered in %v: %v\n", err, debug.Stack()) + panicChan <- *domain + } + }() + + for { + currentIP, err := godns.GetCurrentIP(handler.Configuration) + + if err != nil { + log.Println("get_currentIP:", err) + continue + } + log.Println("currentIP is:", currentIP) + + //check against locally cached IP, if no change, skip update + + for _, subDomain := range domain.SubDomains { + hostname := subDomain + "." + domain.DomainName + lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver) + //check against currently known IP, if no change, skip update + if currentIP == lastIP { + log.Printf("IP is the same as cached one. Skip update.\n") + } else { + log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName) + handler.UpdateIP(hostname, currentIP, lastIP) + + // Send notification + if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil { + log.Println("Failed to send notification") + } + } + } + + // Sleep with interval + log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval) + time.Sleep(time.Second * time.Duration(handler.Configuration.Interval)) + } + +} + +// UpdateIP update subdomain with current IP +func (handler *Handler) UpdateIP(hostname, currentIP, lastIP string) { + + updateDNS(lastIP, currentIP, hostname, "remove") + updateDNS(lastIP, currentIP, hostname, "add") + +} + +// updateDNS can add or remove DNS records. +func (handler *Handler) updateDNS(dns, ip, hostname, action string) string { + // Generates UUID + uid, _ := uuid.NewRandom() + values := url.Values{} + values.Add("record", hostname) + values.Add("key", handler.Configuration.LoginToken) + values.Add("type", handler.Configuration.IPType) + values.Add("unique_id", uid.String()) + switch action { + case "remove": + // Build URL query (remove) + values.Add("cmd", "dns-remove_record") + values.Add("value", dns) + case "add": + // Build URL query (add) + values.Add("cmd", "dns-add_record") + values.Add("value", ip) + default: + log.Fatalf("Unknown action %s\n", action) + } + + client := godns.GetHttpClient(handler.Configuration) + req, _ := http.NewRequest("POST", DreamhostURL, strings.NewReader(values.Encode())) + req.SetBasicAuth(handler.Configuration.Email, handler.Configuration.Password) + + if h.Configuration.UserAgent != "" { + req.Header.Add("User-Agent", handler.Configuration.UserAgent) + } + + resp, err := client.Do(req) + if err != nil { + log.Println("Request error...") + log.Println("Err:", err.Error()) + } else { + body, _ := ioutil.ReadAll(resp.Body) + if resp.StatusCode == http.StatusOK { + log.Println("Update IP success:", string(body)) + } else { + log.Println("Update IP failed:", string(body)) + } + } +} diff --git a/handler/handler.go b/handler/handler.go index 0b15fae..a7c8d1a 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -5,9 +5,11 @@ import ( "github.com/TimothyYe/godns/handler/alidns" "github.com/TimothyYe/godns/handler/cloudflare" "github.com/TimothyYe/godns/handler/dnspod" + "github.com/TimothyYe/godns/handler/dreamhost" "github.com/TimothyYe/godns/handler/duck" "github.com/TimothyYe/godns/handler/google" "github.com/TimothyYe/godns/handler/he" + "github.com/TimothyYe/godns/handler/dreamhost" ) // IHandler is the interface for all DNS handlers @@ -25,6 +27,8 @@ func CreateHandler(provider string) IHandler { handler = IHandler(&cloudflare.Handler{}) case godns.DNSPOD: handler = IHandler(&dnspod.Handler{}) + case godns.DREAMHOST: + handler = IHandler(&dreamhost.Handler{}) case godns.HE: handler = IHandler(&he.Handler{}) case godns.ALIDNS: diff --git a/utils.go b/utils.go index c094694..9c95b7e 100644 --- a/utils.go +++ b/utils.go @@ -49,6 +49,8 @@ const ( GOOGLE = "Google" // DUCK for Duck DNS DUCK = "DuckDNS" + // DREAMHOST for Dreamhost + DREAMHOST = "Dreamhost" // IPV4 for IPV4 mode IPV4 = "IPV4" // IPV6 for IPV6 mode @@ -218,6 +220,11 @@ func CheckSettings(config *Settings) error { if config.Password == "" { return errors.New("password cannot be empty") } + case DREAMHOST: + if config.LoginToken == "" { + return errors.New("login token cannot be empty") + } + default: return errors.New("please provide supported DNS provider: DNSPod/HE/AliDNS/Cloudflare/GoogleDomain/DuckDNS") From f2e9c0ab00f1a36ca6f86ebc03158e4b72697778 Mon Sep 17 00:00:00 2001 From: Eri Bastos Date: Mon, 27 Apr 2020 14:05:37 -0400 Subject: [PATCH 2/4] Add dreamhost handler --- go.mod | 1 + go.sum | 2 + handler/dreamhost/dreamhost_handler.go | 124 +++++++++++++++++++++++++ handler/handler.go | 3 + utils.go | 7 ++ 5 files changed, 137 insertions(+) create mode 100644 handler/dreamhost/dreamhost_handler.go diff --git a/go.mod b/go.mod index 0eaa091..2184793 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ require ( github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/bogdanovich/dns_resolver v0.0.0-20170211073258-a8e42bc6a5b6 github.com/fatih/color v1.7.0 + github.com/google/uuid v1.1.1 github.com/kr/pretty v0.1.0 // indirect github.com/mattn/go-colorable v0.0.9 // indirect github.com/mattn/go-isatty v0.0.4 // indirect diff --git a/go.sum b/go.sum index 35d85dd..ef880d2 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/bogdanovich/dns_resolver v0.0.0-20170211073258-a8e42bc6a5b6 h1:oV1V+u github.com/bogdanovich/dns_resolver v0.0.0-20170211073258-a8e42bc6a5b6/go.mod h1:txOV61Nn+21z77KUMkNsp8lTHoOFTtqotltQAFenS9I= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= diff --git a/handler/dreamhost/dreamhost_handler.go b/handler/dreamhost/dreamhost_handler.go new file mode 100644 index 0000000..924f4a6 --- /dev/null +++ b/handler/dreamhost/dreamhost_handler.go @@ -0,0 +1,124 @@ +package dreamhost + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "runtime/debug" + "strings" + "time" + + "github.com/TimothyYe/godns" + "github.com/google/uuid" +) + +var ( + // DreamhostURL the API address for dreamhost.com + DreamhostURL = "https://api.dreamhost.com" +) + +// Handler struct +type Handler struct { + Configuration *godns.Settings +} + +// SetConfiguration pass dns settings and store it to handler instance +func (handler *Handler) SetConfiguration(conf *godns.Settings) { + handler.Configuration = conf +} + +// DomainLoop the main logic loop +func (handler *Handler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.Domain) { + defer func() { + if err := recover(); err != nil { + log.Printf("Recovered in %v: %v\n", err, debug.Stack()) + panicChan <- *domain + } + }() + + for { + currentIP, err := godns.GetCurrentIP(handler.Configuration) + + if err != nil { + log.Println("get_currentIP:", err) + continue + } + log.Println("currentIP is:", currentIP) + + for _, subDomain := range domain.SubDomains { + hostname := subDomain + "." + domain.DomainName + lastIP := godns.ResolveDNS(hostname, handler.Configuration.Resolver) + //check against currently known IP, if no change, skip update + if currentIP == lastIP { + log.Printf("IP is the same as cached one. Skip update.\n") + } else { + log.Printf("%s.%s Start to update record IP...\n", subDomain, domain.DomainName) + handler.UpdateIP(hostname, currentIP, lastIP) + + // Send notification + if err := godns.SendNotify(handler.Configuration, fmt.Sprintf("%s.%s", subDomain, domain.DomainName), currentIP); err != nil { + log.Println("Failed to send notification") + } + } + } + + // Sleep with interval + log.Printf("Going to sleep, will start next checking in %d seconds...\r\n", handler.Configuration.Interval) + time.Sleep(time.Second * time.Duration(handler.Configuration.Interval)) + } + +} + +// UpdateIP update subdomain with current IP +func (handler *Handler) UpdateIP(hostname, currentIP, lastIP string) { + + handler.updateDNS(lastIP, currentIP, hostname, "remove") + handler.updateDNS(lastIP, currentIP, hostname, "add") + +} + +// updateDNS can add or remove DNS records. +func (handler *Handler) updateDNS(dns, ip, hostname, action string) { + // Generates UUID + uid, _ := uuid.NewRandom() + values := url.Values{} + values.Add("record", hostname) + values.Add("key", handler.Configuration.LoginToken) + values.Add("type", "A") + values.Add("unique_id", uid.String()) + switch action { + case "remove": + // Build URL query (remove) + values.Add("cmd", "dns-remove_record") + values.Add("value", dns) + case "add": + // Build URL query (add) + values.Add("cmd", "dns-add_record") + values.Add("value", ip) + default: + log.Fatalf("Unknown action %s\n", action) + } + + client := godns.GetHttpClient(handler.Configuration) + req, _ := http.NewRequest("POST", DreamhostURL, strings.NewReader(values.Encode())) + req.SetBasicAuth(handler.Configuration.Email, handler.Configuration.Password) + + if handler.Configuration.UserAgent != "" { + req.Header.Add("User-Agent", handler.Configuration.UserAgent) + } + + resp, err := client.Do(req) + if err != nil { + log.Println("Request error...") + log.Println("Err:", err.Error()) + } else { + body, _ := ioutil.ReadAll(resp.Body) + if resp.StatusCode == http.StatusOK { + log.Println("Update IP success:", string(body)) + } else { + log.Println("Update IP failed:", string(body)) + } + } +} diff --git a/handler/handler.go b/handler/handler.go index 0b15fae..4455fd4 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -5,6 +5,7 @@ import ( "github.com/TimothyYe/godns/handler/alidns" "github.com/TimothyYe/godns/handler/cloudflare" "github.com/TimothyYe/godns/handler/dnspod" + "github.com/TimothyYe/godns/handler/dreamhost" "github.com/TimothyYe/godns/handler/duck" "github.com/TimothyYe/godns/handler/google" "github.com/TimothyYe/godns/handler/he" @@ -25,6 +26,8 @@ func CreateHandler(provider string) IHandler { handler = IHandler(&cloudflare.Handler{}) case godns.DNSPOD: handler = IHandler(&dnspod.Handler{}) + case godns.DREAMHOST: + handler = IHandler(&dreamhost.Handler{}) case godns.HE: handler = IHandler(&he.Handler{}) case godns.ALIDNS: diff --git a/utils.go b/utils.go index c094694..9c95b7e 100644 --- a/utils.go +++ b/utils.go @@ -49,6 +49,8 @@ const ( GOOGLE = "Google" // DUCK for Duck DNS DUCK = "DuckDNS" + // DREAMHOST for Dreamhost + DREAMHOST = "Dreamhost" // IPV4 for IPV4 mode IPV4 = "IPV4" // IPV6 for IPV6 mode @@ -218,6 +220,11 @@ func CheckSettings(config *Settings) error { if config.Password == "" { return errors.New("password cannot be empty") } + case DREAMHOST: + if config.LoginToken == "" { + return errors.New("login token cannot be empty") + } + default: return errors.New("please provide supported DNS provider: DNSPod/HE/AliDNS/Cloudflare/GoogleDomain/DuckDNS") From 690e138164ec7fdfcc4a2b2151c350a5cdd1eaa4 Mon Sep 17 00:00:00 2001 From: Eri Bastos Date: Mon, 27 Apr 2020 14:39:58 -0400 Subject: [PATCH 3/4] Updated README file to include Dreamhost --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 5d82e2f..7f49754 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Now I rewrite [DynDNS](https://github.com/TimothyYe/DynDNS) by Golang and call i * HE.net (Hurricane Electric) ([https://dns.he.net/](https://dns.he.net/)) * AliDNS ([https://help.aliyun.com/product/29697.html](https://help.aliyun.com/product/29697.html)) * DuckDNS ([https://www.duckdns.org](https://www.duckdns.org)) +* Dreamhost ([https://www.dreamhost.com](https://www.dreamhost.com)) ## Supported Platforms @@ -210,6 +211,30 @@ For DNSPod, you need to provide your API Token(you can create it [here](https:// } ``` +### Config example for Dreamhost + +For Dreamhost, you need to provide your API Token(you can create it [here](https://panel.dreamhost.com/?tree=home.api)), and config all the domains & subdomains. + +```json +{ + "provider": "Dreamhost", + "login_token": "your_api_key", + "domains": [{ + "domain_name": "example.com", + "sub_domains": ["www","test"] + },{ + "domain_name": "example2.com", + "sub_domains": ["www","test"] + } + ], + "ip_url": "https://myip.biturl.top", + "ip_type": "IPV4", + "interval": 300, + "resolver": "ns1.dreamhost.com", + "socks5_proxy": "" +} +``` + ### Config example for Google Domains For Google Domains, you need to provide email & password, and config all the domains & subdomains. From e8ce3434eed32a254bbbe00852aa41391bbc0fe5 Mon Sep 17 00:00:00 2001 From: Eri Bastos Date: Tue, 28 Apr 2020 18:40:04 -0400 Subject: [PATCH 4/4] Addressed code review plus missed updates --- README.md | 5 +++-- handler/dreamhost/dreamhost_handler.go | 7 ++++++- utils.go | 6 +++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7f49754..e91b5ec 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ Usage of ./godns: ## Config fields -* provider: The providers that GoDNS supports, available values are: `Cloudflare`, `Google`, `DNSPod`, `AliDNS`, `HE`, `DuckDNS`. +* provider: The providers that GoDNS supports, available values are: `Cloudflare`, `Google`, `DNSPod`, `AliDNS`, `HE`, `DuckDNS`, `Dreamhost`. * email: Email or account name of your DNS provider. * password: Password of your account. * login_token: API token of your account. @@ -117,10 +117,11 @@ Usage of ./godns: Supported provider(s): * Cloudflare -* HE.net * DNSPod +* Dreamhost * DuckDNS * Google Domains +* HE.net To enable the `IPv6` mode of GoDNS, you only need two steps: * Set the `ip_type` as `IPv6`, and make sure the `ipv6_url` is configured. diff --git a/handler/dreamhost/dreamhost_handler.go b/handler/dreamhost/dreamhost_handler.go index 924f4a6..2cd400f 100644 --- a/handler/dreamhost/dreamhost_handler.go +++ b/handler/dreamhost/dreamhost_handler.go @@ -81,12 +81,17 @@ func (handler *Handler) UpdateIP(hostname, currentIP, lastIP string) { // updateDNS can add or remove DNS records. func (handler *Handler) updateDNS(dns, ip, hostname, action string) { + ipType := "A" + if strings.ToUpper(handler.Configuration.IPType) == godns.IPV6 { + ipType = "AAAA" + } + // Generates UUID uid, _ := uuid.NewRandom() values := url.Values{} values.Add("record", hostname) values.Add("key", handler.Configuration.LoginToken) - values.Add("type", "A") + values.Add("type", ipType) values.Add("unique_id", uid.String()) switch action { case "remove": diff --git a/utils.go b/utils.go index 9c95b7e..19b1fb8 100644 --- a/utils.go +++ b/utils.go @@ -226,14 +226,14 @@ func CheckSettings(config *Settings) error { } default: - return errors.New("please provide supported DNS provider: DNSPod/HE/AliDNS/Cloudflare/GoogleDomain/DuckDNS") + return errors.New("please provide supported DNS provider: DNSPod/HE/AliDNS/Cloudflare/GoogleDomain/DuckDNS/Dreamhost") } return nil } -// SendNotify sends notify if IP is changed +// SendTelegramNotify sends notify if IP is changed func SendTelegramNotify(configuration *Settings, domain, currentIP string) error { if !configuration.Notify.Telegram.Enabled { return nil @@ -294,7 +294,7 @@ func SendTelegramNotify(configuration *Settings, domain, currentIP string) error return nil } -// SendNotify sends mail notify if IP is changed +// SendMailNotify sends mail notify if IP is changed func SendMailNotify(configuration *Settings, domain, currentIP string) error { if !configuration.Notify.Mail.Enabled { return nil