diff --git a/README.md b/README.md index dde945c..b0c5a83 100644 --- a/README.md +++ b/README.md @@ -9,20 +9,19 @@ related to netcup GmbH. It is **heavily** inspired by project which might be also a good solution for your dynamic dns needs. - ## Table of Contents * [Features](#features) - * [Implemented](#implemented) - * [Missing](#missing) + * [Implemented](#implemented) + * [Missing](#missing) * [Installation](#installation) - * [Manual](#manual) - * [From source](#from-source) + * [Manual](#manual) + * [From source](#from-source) * [Usage](#usage) - * [Prequisites](#prequisites) - * [Run dyndns-netcup-go](#run-dyndns-netcup-go) - * [Commandline flags](#commandline-flags) + * [Prequisites](#prequisites) + * [Run dyndns-netcup-go](#run-dyndns-netcup-go) + * [Commandline flags](#commandline-flags) * [Contributing](#contributing) @@ -35,10 +34,15 @@ dynamic dns needs. * TTL update support * Creation of a DNS record if it doesn't already exists. * Multi host support (nice when you need to update both `@` and `*`) +* IPv6 support +* Verbose option (when you specify `-v` you get alot of information) ### Missing -* IPv6 support -* Quiet option (output is always really verbose) + +* MX entry support + +There are currently no plans to implement this features. If you need those (or additional features) please +open up an [Issue](https://github.com/Hentra/dyndns-netcup-go/issues). ## Installation @@ -51,16 +55,19 @@ dynamic dns needs. First, install [Go](https://golang.org/doc/install) as recommended. After that run following commands: - git clone https://github.com/Hentra/dyndns-netcup-go.git cd - dyndns-netcup-go + git clone https://github.com/Hentra/dyndns-netcup-go.git + cd dyndns-netcup-go go install -This will create a binary named `dyndns-netcup-go` and install it to your go binary home. +This will create a binary named `dyndns-netcup-go` and install it to your go +binary home. Make sure your `GOPATH` environment variable is set. + Refer to [Usage](#usage) for further information. ## Usage ### Prequisites + * You need to have a netcup account and a domain, obviously. * Then you need an apikey and apipassword. [Here](https://www.netcup-wiki.de/wiki/CCP_API#Authentifizierung) is a diff --git a/example.yml b/example.yml index 21f904a..91e1202 100644 --- a/example.yml +++ b/example.yml @@ -2,17 +2,21 @@ CUSTOMERNR: 12345 APIKEY: 'yourapikey' APIPASSWORD: 'yourapipassword' -DOMAINS: - - NAME: 'example.de' # Your domain name without any subdomains - IPV6: false # Not supported yet +DOMAINS: + - NAME: 'example.de' # Your domain name without any subdomains. + IPV6: true # Whether the 'AAAA' entries of this host should be + # updated with the IPv6 address or not. TTL: 300 # Time to live for this zone. Around 300 is good for dyndns. HOSTS: # Every host that should get your public ip - '@' - '*' - - 'cool.subdomain' + - 'cool.subdomain' # You could also specify subdomains longer than this. - NAME: 'example.com' IPV6: false TTL: 350 HOSTS: - '@' + + # You can add how many domains as you like. Keep in mind that more domains also + # means more request -> more time. diff --git a/ip.go b/ip.go index 48fdd57..6ab4c5b 100644 --- a/ip.go +++ b/ip.go @@ -5,9 +5,15 @@ import ( "net/http" ) -func getIP() (string, error) { - url := "https://api.ipify.org?format=text" +func getIPv4() (string, error) { + return do("https://api.ipify.org?format=text") +} + +func getIPv6() (string, error) { + return do("https://api6.ipify.org?format=text") +} +func do(url string) (string, error) { resp, err := http.Get(url) if err != nil { return "", err diff --git a/main.go b/main.go index 2cb214f..f9c1f2d 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,11 @@ import( var ( configFile string + config *Config + ipv4 string + ipv6 string verbose bool + client *netcup.Client ) const ( @@ -18,8 +22,16 @@ const ( verboseUsage = "Use verbose output" ) - func main() { + login() + + loadIPv4() + loadIPv6() + + configureDomains() +} + +func init() { flag.StringVar(&configFile, "config", defaultConfigFile, configUsage) flag.StringVar(&configFile, "c", defaultConfigFile, configUsage + " (shorthand)") @@ -30,26 +42,50 @@ func main() { netcup.SetVerbose(verbose) - config, err := LoadConfig(configFile) + var err error + config, err = LoadConfig(configFile) if err != nil { log.Fatal(err) } +} - client := netcup.NewClient(config.CustomerNumber, config.ApiKey, config.ApiPassword) +func login() { + client = netcup.NewClient(config.CustomerNumber, config.ApiKey, config.ApiPassword) + err := client.Login() + if err != nil { + log.Fatal(err) + } +} - err = client.Login() +func loadIPv4() { + logInfo("Loading public IPv4 address") + var err error + ipv4, err = getIPv4() if err != nil { log.Fatal(err) } + logInfo("Public IPv4 address is %s", ipv4) +} - logInfo("Loading public IP address") - ip, err := getIP() +func loadIPv6() { + logInfo("Loading public IPv6 address") + var err error + ipv6, err = getIPv6() if err != nil { log.Fatal(err) } - logInfo("Public IP address is %s", ip) + logInfo("Public IPv6 address is %s", ipv6) + +} +func configureDomains() { for _, domain := range config.Domains { + configureZone(domain) + configureRecords(domain) + } +} + +func configureZone(domain Domain) { logInfo("Loading DNS Zone info for domain %s", domain.Name) err, zone := client.InfoDnsZone(domain.Name) if err != nil { @@ -70,7 +106,9 @@ func main() { log.Fatal(err) } } +} +func configureRecords(domain Domain) { logInfo("Loading DNS Records for domain %s", domain.Name) err, records := client.InfoDnsRecords(domain.Name) if err != nil { @@ -79,27 +117,23 @@ func main() { var updateRecords []netcup.DNSRecord for _, host := range domain.Hosts { - if records.GetARecordOccurences(host) > 1 { + if records.GetRecordOccurences(host, "A") > 1 { logInfo("Too many A records for host '%s'. Please specify only Hosts with one corresponding A record", host) - continue + } else { + newRecord, needsUpdate := configureARecord(host, records) + if needsUpdate { + updateRecords = append(updateRecords, *newRecord) + } } - if record, exists := records.GetARecord(host); exists { - logInfo("Found one A record for host '%s'.", host) - if record.Destination != ip { - logInfo("IP address of host '%s' is %s but should be %s. Queue for update...", host, record.Destination, ip) - record.Destination = ip - updateRecords = append(updateRecords, *record) + if domain.IPv6 { + if records.GetRecordOccurences(host, "AAAA") > 1 { + logInfo("Too many AAAA records for host '%s'. Please specify only Hosts with one corresponding AAAA record", host) } else { - logInfo("Destination of host '%s' is already public ip %s", host, ip) + newRecord, needsUpdate := configureAAAARecord(host, records) + if needsUpdate { + updateRecords = append(updateRecords, *newRecord) + } } - } else { - logInfo("There is no A record for '%s'. Creating and queueing for update", host) - record := netcup.DNSRecord{ - Hostname: host, - Type: "A", - Destination: ip, - } - updateRecords = append(updateRecords, record) } } @@ -113,7 +147,46 @@ func main() { } else { logInfo("No updates queued.") } - } +} + +func configureARecord(host string, records *netcup.DNSRecordSet) (*netcup.DNSRecord, bool) { + var result *netcup.DNSRecord + if record, exists := records.GetRecord(host, "A"); exists { + logInfo("Found one A record for host '%s'.", host) + if record.Destination != ipv4 { + logInfo("IP address of host '%s' is %s but should be %s. Queue for update...", host, record.Destination, ipv4) + record.Destination = ipv4 + result = record + } else { + logInfo("Destination of host '%s' is already public IPv4 %s", host, ipv4) + return nil, false + } + } else { + logInfo("There is no A record for '%s'. Creating and queueing for update", host) + result = netcup.NewDNSRecord(host, "A", ipv4) + } + + return result, true +} + +func configureAAAARecord(host string, records *netcup.DNSRecordSet) (*netcup.DNSRecord, bool) { + var result *netcup.DNSRecord + if record, exists := records.GetRecord(host, "AAAA"); exists { + logInfo("Found one AAAA record for host '%s'.", host) + if record.Destination != ipv6 { + logInfo("IP address of host '%s' is %s but should be %s. Queue for update...", host, record.Destination, ipv6) + record.Destination = ipv6 + result = record + } else { + logInfo("Destination of host '%s' is already public IPv6 %s", host, ipv6) + return nil, false + } + } else { + logInfo("There is no AAAA record for '%s'. Creating and queueing for update", host) + result = netcup.NewDNSRecord(host, "AAAA", ipv6) + } + + return result, true } func logInfo(msg string, v ...interface{}) { diff --git a/netcup/response.go b/netcup/response.go index d120dc7..95d1fc7 100644 --- a/netcup/response.go +++ b/netcup/response.go @@ -49,25 +49,33 @@ func NewDNSRecordSet(records []DNSRecord) *DNSRecordSet { } } -func (r *DNSRecordSet) GetARecordOccurences(hostname string) int { +func (r *DNSRecordSet) GetRecordOccurences(hostname, dnstype string) int { result := 0 for _, record := range r.DNSRecords { - if record.Hostname == hostname && record.Type == "A" { + if record.Hostname == hostname && record.Type == dnstype { result++ } } return result } -func (r *DNSRecordSet) GetARecord(name string) (*DNSRecord, bool) { +func (r *DNSRecordSet) GetRecord(name, dnstype string) (*DNSRecord, bool) { for _, record := range r.DNSRecords { - if record.Hostname == name && record.Type == "A" { + if record.Hostname == name && record.Type == dnstype { return &record, true } } return nil, false } +func NewDNSRecord(hostname, dnstype, destination string) *DNSRecord { + return &DNSRecord{ + Hostname: hostname, + Type: dnstype, + Destination: destination, + } +} + func (r *Response) isError() bool { return r.Status == "error" }