Google domain names support (#70)

This commit is contained in:
Quentin McGaw
2020-05-30 19:46:24 -04:00
committed by GitHub
parent 0a6c6b9bc7
commit 066bcdd3bf
12 changed files with 186 additions and 12 deletions

View File

@@ -22,6 +22,7 @@ assignees: qdm12
- [ ] Dreamhost
- [ ] DuckDNS
- [ ] GoDaddy
- [ ] Google
- [ ] Infomaniak
- [ ] Namecheap
- [ ] NoIP

View File

@@ -22,6 +22,7 @@ assignees:
- [ ] Dreamhost
- [ ] DuckDNS
- [ ] GoDaddy
- [ ] Google
- [ ] Infomaniak
- [ ] Namecheap
- [ ] NoIP

View File

@@ -32,7 +32,7 @@ LABEL \
org.opencontainers.image.documentation="https://github.com/qdm12/ddns-updater" \
org.opencontainers.image.source="https://github.com/qdm12/ddns-updater" \
org.opencontainers.image.title="ddns-updater" \
org.opencontainers.image.description="Universal DNS updater with WebUI. Works with Cloudflare, DDNSS.de, DNSPod, Dreamhost, DuckDNS, GoDaddy, Infomaniak, Namecheap and NoIP"
org.opencontainers.image.description="Universal DNS updater with WebUI. Works with Cloudflare, DDNSS.de, DNSPod, Dreamhost, DuckDNS, GoDaddy, Google, Infomaniak, Namecheap and NoIP"
COPY --from=alpine --chown=1000 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=alpine --chown=1000 /usr/share/zoneinfo /usr/share/zoneinfo
EXPOSE 8000

View File

@@ -1,6 +1,6 @@
# Lightweight universal DDNS Updater with Docker and web UI
*Light container updating DNS A records periodically for Cloudflare, DDNSS.de, DNSPod, Dreamhost, DuckDNS, GoDaddy, Infomaniak, Namecheap and NoIP*
*Light container updating DNS A records periodically for Cloudflare, DDNSS.de, DNSPod, Dreamhost, DuckDNS, GoDaddy, Google, Infomaniak, Namecheap and NoIP*
[![DDNS Updater by Quentin McGaw](https://github.com/qdm12/ddns-updater/raw/master/readme/title.png)](https://hub.docker.com/r/qmcgaw/ddns-updater)
@@ -17,7 +17,7 @@
## Features
- Updates periodically A records for different DNS providers: Cloudflare, DDNSS.de, DNSPod, Dreamhost, DuckDNS, GoDaddy, Infomaniak, Namecheap and NoIP ([create an issue](https://github.com/qdm12/ddns-updater/issues/new/choose) for more)
- Updates periodically A records for different DNS providers: Cloudflare, DDNSS.de, DNSPod, Dreamhost, DuckDNS, GoDaddy, Google, Infomaniak, Namecheap and NoIP ([create an issue](https://github.com/qdm12/ddns-updater/issues/new/choose) for more)
- Web User interface
![Web UI](https://raw.githubusercontent.com/qdm12/ddns-updater/master/readme/webui.png)
@@ -128,7 +128,7 @@ Start by having the following content in *config.json*, or in your `CONFIG` envi
The following parameters are to be added:
For all record update configuration, you have to specify the DNS provider with `"provider"` which can be `"cloudflare"`, `"ddnss"`, `"dnspod"`, `"dreamhost"`, `"duckdns"`, `"godaddy"`, `"infomaniak"`, `"namecheap"` or `"noip"`.
For all record update configuration, you have to specify the DNS provider with `"provider"` which can be `"cloudflare"`, `"ddnss"`, `"dnspod"`, `"dreamhost"`, `"duckdns"`, `"godaddy"`, `"google"`, `"infomaniak"`, `"namecheap"` or `"noip"`.
You can optionnally add the parameters:
- `"no_dns_lookup"` can be `true` or `false` and allows, if `true`, to prevent the periodic Docker healthcheck from running a DNS lookup on your domain.
@@ -201,6 +201,14 @@ DDNSS.de:
- `"password"`
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`
Google:
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
- `"username"`
- `"password"`
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`
### Additional notes
- You can specify multiple hosts for the same domain using a comma separated list. For example with `"host": "@,subdomain1,subdomain2",`.

View File

@@ -10,6 +10,7 @@ const (
DUCKDNS models.Provider = "duckdns"
DREAMHOST models.Provider = "dreamhost"
GODADDY models.Provider = "godaddy"
GOOGLE models.Provider = "google"
INFOMANIAK models.Provider = "infomaniak"
NAMECHEAP models.Provider = "namecheap"
NOIP models.Provider = "noip"
@@ -23,6 +24,7 @@ func ProviderChoices() []models.Provider {
DUCKDNS,
DREAMHOST,
GODADDY,
GOOGLE,
INFOMANIAK,
NAMECHEAP,
NOIP,

View File

@@ -121,6 +121,8 @@ func makeSettingsFromObject(common commonSettings, rawSettings json.RawMessage)
settingsConstructor = settings.NewDuckdns
case constants.GODADDY:
settingsConstructor = settings.NewGodaddy
case constants.GOOGLE:
settingsConstructor = settings.NewGoogle
case constants.INFOMANIAK:
settingsConstructor = settings.NewInfomaniak
case constants.NAMECHEAP:

View File

@@ -0,0 +1,7 @@
package settings
const (
badauth = "badauth"
success = "success"
nohost = "nohost"
)

View File

@@ -130,7 +130,7 @@ func (d *ddnss) Update(client network.Client, ip net.IP) (newIP net.IP, err erro
switch {
case strings.Contains(s, "badysys"):
return nil, fmt.Errorf("ddnss.de: invalid system parameter")
case strings.Contains(s, "badauth"):
case strings.Contains(s, badauth):
return nil, fmt.Errorf("ddnss.de: bad authentication")
case strings.Contains(s, "notfqdn"):
return nil, fmt.Errorf("ddnss.de: hostname %q does not exist", fqdn)

View File

@@ -87,8 +87,6 @@ func (d *dreamhost) HTML() models.HTMLRow {
}
}
const success = "success"
func (d *dreamhost) Update(client network.Client, ip net.IP) (newIP net.IP, err error) {
if ip == nil {
return nil, fmt.Errorf("IP address was not given to updater")

155
internal/settings/google.go Normal file
View File

@@ -0,0 +1,155 @@
package settings
import (
"encoding/json"
"fmt"
"net"
"net/http"
"net/url"
"strings"
"github.com/qdm12/ddns-updater/internal/models"
netlib "github.com/qdm12/golibs/network"
"github.com/qdm12/golibs/verification"
)
//nolint:maligned
type google struct {
domain string
host string
ipVersion models.IPVersion
dnsLookup bool
username string
password string
useProviderIP bool
}
func NewGoogle(data json.RawMessage, domain, host string, ipVersion models.IPVersion, noDNSLookup bool) (s Settings, err error) {
extraSettings := struct {
Username string `json:"username"`
Password string `json:"password"`
UseProviderIP bool `json:"provider_ip"`
}{}
if err := json.Unmarshal(data, &extraSettings); err != nil {
return nil, err
}
g := &google{
domain: domain,
host: host,
ipVersion: ipVersion,
dnsLookup: !noDNSLookup,
username: extraSettings.Username,
password: extraSettings.Password,
useProviderIP: extraSettings.UseProviderIP,
}
if err := g.isValid(); err != nil {
return nil, err
}
return g, nil
}
func (g *google) isValid() error {
switch {
case len(g.username) == 0:
return fmt.Errorf("username cannot be empty")
case len(g.password) == 0:
return fmt.Errorf("password cannot be empty")
}
return nil
}
func (g *google) String() string {
return fmt.Sprintf("[domain: %s | host: %s | provider: Google]", g.domain, g.host)
}
func (g *google) Domain() string {
return g.domain
}
func (g *google) Host() string {
return g.host
}
func (g *google) DNSLookup() bool {
return g.dnsLookup
}
func (g *google) IPVersion() models.IPVersion {
return g.ipVersion
}
func (g *google) BuildDomainName() string {
return buildDomainName(g.host, g.domain)
}
func (g *google) HTML() models.HTMLRow {
return models.HTMLRow{
Domain: models.HTML(fmt.Sprintf("<a href=\"http://%s\">%s</a>", g.BuildDomainName(), g.BuildDomainName())),
Host: models.HTML(g.Host()),
Provider: "<a href=\"https://domains.google.com/m/registrar\">Google</a>",
IPVersion: models.HTML(g.ipVersion),
}
}
func (g *google) Update(client netlib.Client, ip net.IP) (newIP net.IP, err error) {
u := url.URL{
Scheme: "https",
Host: "domains.google.com",
Path: "/nic/update",
User: url.UserPassword(g.username, g.password),
}
values := url.Values{}
fqdn := g.BuildDomainName()
values.Set("hostname", fqdn)
if !g.useProviderIP {
values.Set("myip", ip.String())
}
u.RawQuery = values.Encode()
r, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return nil, err
}
r.Header.Set("User-Agent", "DDNS-Updater quentig.mcgaw@gmail.com")
status, content, err := client.DoHTTPRequest(r)
if err != nil {
return nil, err
}
s := string(content)
switch s {
case "":
return nil, fmt.Errorf("HTTP status %d", status)
case nohost:
return nil, fmt.Errorf("hostname does not exist")
case badauth:
return nil, fmt.Errorf("invalid username password combination")
case "notfqdn":
return nil, fmt.Errorf("hostname %q is not a valid fully qualified domain name", fqdn)
case "badagent":
return nil, fmt.Errorf("user agent is banned")
case "abuse":
return nil, fmt.Errorf("username is banned due to abuse")
case "911":
return nil, fmt.Errorf("Google's internal server error 911")
case "conflict A":
return nil, fmt.Errorf("custom A record conflicts with the update")
case "conflict AAAA":
return nil, fmt.Errorf("custom AAAA record conflicts with the update")
}
if strings.Contains(s, "nochg") || strings.Contains(s, "good") {
ipsV4 := verification.NewVerifier().SearchIPv4(s)
ipsV6 := verification.NewVerifier().SearchIPv6(s)
ips := append(ipsV4, ipsV6...)
if ips == nil {
return nil, fmt.Errorf("no IP address in response")
}
newIP = net.ParseIP(ips[0])
if newIP == nil {
return nil, fmt.Errorf("IP address received %q is malformed", ips[0])
}
if !g.useProviderIP && !ip.Equal(newIP) {
return nil, fmt.Errorf("new IP address %s is not %s", newIP.String(), ip.String())
}
return newIP, nil
}
return nil, fmt.Errorf("invalid response %q", s)
}

View File

@@ -142,9 +142,9 @@ func (i *infomaniak) Update(client network.Client, ip net.IP) (newIP net.IP, err
}
case http.StatusBadRequest:
switch s {
case "nohost":
case nohost:
return nil, fmt.Errorf("infomaniak.com: host %q does not exist for domain %q", i.host, i.domain)
case "badauth":
case badauth:
return nil, fmt.Errorf("infomaniak.com: bad authentication")
default:
return nil, fmt.Errorf("infomaniak.com: bad request: %s", s)

View File

@@ -129,9 +129,9 @@ func (n *noip) Update(client netlib.Client, ip net.IP) (newIP net.IP, err error)
return nil, fmt.Errorf("user has not this extra feature")
case "badagent":
return nil, fmt.Errorf("user agent is banned")
case "badauth":
case badauth:
return nil, fmt.Errorf("invalid username password combination")
case "nohost":
case nohost:
return nil, fmt.Errorf("hostname does not exist")
}
if strings.Contains(s, "nochg") || strings.Contains(s, "good") {
@@ -143,7 +143,7 @@ func (n *noip) Update(client netlib.Client, ip net.IP) (newIP net.IP, err error)
if newIP == nil {
return nil, fmt.Errorf("IP address received %q is malformed", ips[0])
}
if ip != nil && !ip.Equal(newIP) {
if !n.useProviderIP && !ip.Equal(newIP) {
return nil, fmt.Errorf("new IP address %s is not %s", newIP.String(), ip.String())
}
return newIP, nil