From 83ef1fa99a55b7065ad79ce77a6d417ef42f11e7 Mon Sep 17 00:00:00 2001 From: Starkstromkonsument Date: Sun, 23 Jun 2024 12:23:50 +0200 Subject: [PATCH] Add new protocol inwx Adoption of protocol dyndns2 to support their custom URL: 'https://dyndns.inwx.com/nic/update?myip=&myipv6=' --- ChangeLog.md | 2 + README.md | 1 + ddclient.conf.in | 8 +++ ddclient.in | 171 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index f703f3a..864f687 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -77,6 +77,8 @@ repository history](https://github.com/ddclient/ddclient/commits/master). [#703](https://github.com/ddclient/ddclient/pull/703) * `ddns.fm`: New `protocol` option for updating [DDNS.FM](https://ddns.fm/) records. [#695](https://github.com/ddclient/ddclient/pull/695) + * `inwx`: New `protocol` option for updating [INWX](https://www.inwx.com/) + records. [#690](https://github.com/ddclient/ddclient/pull/690) ### Bug fixes diff --git a/README.md b/README.md index 5b3275a..ddef13e 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Dynamic DNS services currently supported include: * [Google](https://domains.google) * [Hurricane Electric](https://dns.he.net) * [Infomaniak](https://faq.infomaniak.com/2376) +* [INWX](https://www.inwx.com/) * [Loopia](https://www.loopia.se) * [Mythic Beasts](https://www.mythic-beasts.com/support/api/dnsv2/dynamic-dns) * [NameCheap](https://www.namecheap.com) diff --git a/ddclient.conf.in b/ddclient.conf.in index 1c7ae35..81cc746 100644 --- a/ddclient.conf.in +++ b/ddclient.conf.in @@ -404,3 +404,11 @@ pid=@runstatedir@/ddclient.pid # record PID in file. # login=subdomain.domain.tld \ # password=your_password \ # subdomain.domain.tld + +## +## INWX +## +# protocol=inwx \ +# login=my-inwx-DynDNS-account-username \ +# password=my-inwx-DynDNS-account-password \ +# myhost.example.org diff --git a/ddclient.in b/ddclient.in index 332f086..ba2eeea 100755 --- a/ddclient.in +++ b/ddclient.in @@ -955,6 +955,16 @@ our %protocols = ( 'zone' => setv(T_FQDN, 1, 0, undef, undef), }, }, + 'inwx' => { + 'force_update' => undef, + 'update' => \&nic_inwx_update, + 'examples' => \&nic_inwx_examples, + 'variables' => { + %{$variables{'protocol-common-defaults'}}, + 'server' => setv(T_FQDNP, 0, 0, 'dyndns.inwx.com', undef), + 'script' => setv(T_STRING, 0, 0, '/nic/update', undef), + }, + }, 'mythicdyn' => { 'force_update' => undef, 'update' => \&nic_mythicdyn_update, @@ -6459,6 +6469,167 @@ sub nic_hetzner_update { } } +###################################################################### +## nic_inwx_examples +###################################################################### +sub nic_inwx_examples { + return <<"EoEXAMPLE"; +o 'inwx' + +The 'inwx' protocol is designed for DynDNS accounts at INWX +. It is similar to the 'dyndns2' protocol except IPv6 +addresses are passed in a separate 'myipv6' URL parameter (rather than included +in the 'myip' parameter): + + https://dyndns.inwx.com/nic/update?myip=&myipv6= + +The 'inwx' protocol was designed around INWX's behavior as of June 2024: + - Omitting the IPv4 address (either no 'myip' URL parameter or '' is + the empty string) will cause INWX to silently set the IPv4 address (A + record) to '127.0.0.1'. No error message is returned. + - Omitting the IPv6 address (either no 'myipv6' URL parameter or '' + is the empty string) will cause INWX to delete the IPv6 address (AAAA + record) if it exists. + - INWX will automatically create an IPv6 AAAA record for your hostname if + necessary. + - 'dyndns.inwx.com' is not reachable via IPv6 (there is no AAAA record). + - GET 'https://dyndns.inwx.com/nic/update' without further parameters will set + the IPv4 A record to the public IP of the requesting host and delete the + IPv6 AAAA record. + - You can ask INWX support to manually convert a DynDNS account into an + IPv6-only account. No A record will be created in that case. + +Configuration variables applicable to the 'inwx' protocol are: + protocol=inwx ## + server=fqdn.of.service ## defaults to dyndns.inwx.com + script=/path/to/script ## defaults to /nic/update + login=service-login ## login name and password registered with the service + password=service-password ## + fully.qualified.host ## the host registered with the service. + +Example ${program}.conf file entries: + ## single host update + protocol=inwx \\ + login=my-inwx-DynDNS-account-username \\ + password=my-inwx-DynDNS-account-password \\ + myhost.example.org +EoEXAMPLE +} + +###################################################################### +## nic_inwx_update +###################################################################### +sub nic_inwx_update { + debug("\nnic_inwx_update -------------------"); + my %errors = ( + 'badauth' => 'Bad authorization (username or password)', + 'badsys' => 'The system parameter given was not valid', + 'notfqdn' => 'A Fully-Qualified Domain Name was not provided', + 'nohost' => 'The hostname specified does not exist in the database', + '!yours' => 'The hostname specified exists, but not under the username currently being used', + '!donator' => 'The offline setting was set, when the user is not a donator', + '!active' => 'The hostname specified is in a Custom DNS domain which has not yet been activated.', + 'abuse' => 'The hostname specified is blocked for abuse; you should receive an email notification which provides an unblock request link.', + 'numhost' => 'System error: Too many or too few hosts found.', + 'dnserr' => 'System error: DNS error encountered.', + 'nochg' => 'No update required; unnecessary attempts to change to the current address are considered abusive', + ); + my @group_by_attrs = qw( + login + password + server + script + wantipv4 + wantipv6 + ); + for my $group (group_hosts_by(\@_, @group_by_attrs)) { + my @hosts = @{$group->{hosts}}; + my %groupcfg = %{$group->{cfg}}; + my $hosts = join(',', @hosts); + my $ipv4 = $groupcfg{'wantipv4'}; + my $ipv6 = $groupcfg{'wantipv6'}; + delete $config{$_}{'wantipv4'} for @hosts; + delete $config{$_}{'wantipv6'} for @hosts; + info("$hosts: setting IPv4 address to $ipv4") if $ipv4; + info("$hosts: setting IPv6 address to $ipv6") if $ipv6; + my $url = "$groupcfg{'server'}$groupcfg{'script'}?"; + $url .= "myip=$ipv4" if $ipv4; + if ($ipv6) { + if (!$ipv4 && opt('usev4', $hosts) ne 'disabled') { + warning("Skipping IPv6 AAAA record update because INWX requires the IPv4 A record to be updated at the same time but the IPv4 address is unknown."); + next; + } + $url .= "&" if $ipv4; + $url .= "myipv6=$ipv6"; + } + my $reply = geturl( + proxy => opt('proxy'), + url => $url, + login => $groupcfg{'login'}, + password => $groupcfg{'password'}, + ) // ''; + if ($reply eq '') { + failed("$hosts: Could not connect to $groupcfg{'server'}"); + next; + } + next if !header_ok($hosts, $reply); + # INWX can return 200 OK even if there is an error (e.g., bad authentication, + # updates too frequent) so the body of the response must also be checked. + (my $body = $reply) =~ s/^.*?\n\n//s; + my @reply = split(qr/\n/, $body); + if (!@reply) { + failed("$hosts: Could not connect to $groupcfg{'server'}"); + next; + } + # From : + # + # If updating multiple hostnames, hostname-specific return codes are given one per line, + # in the same order as the hostnames were specified. Return codes indicating a failure + # with the account or the system are given only once. + # + # TODO: There is no mention of what happens if multiple IP addresses are supplied (e.g., + # IPv4 and IPv6) for a host. If one address fails to update and the other doesn't, is that + # one error status line? An error status line and a success status line? Or is an update + # considered to be all-or-nothing and the status applies to the operation as a whole? If + # the IPv4 address changes but not the IPv6 address does that result in a status of "good" + # because the set of addresses for a host changed even if a subset did not? + # + # TODO: The logic below applies the last line's status to all hosts. Change it to apply + # each status to its corresponding host. + for my $line (@reply) { + # The IP address normally comes after the status, but we ignore it. We could compare + # it with the expected address and mark the update as failed if it differs, but (1) + # some services do not return the IP; and (2) comparison is brittle (e.g., + # 192.000.002.001 vs. 192.0.2.1) and false errors could cause high load on the service + # (an update attempt every min-error-interval instead of every max-interval). + (my $status = $line) =~ s/ .*$//; + if ($status eq 'nochg') { + warning("$hosts: $status: $errors{$status}"); + $status = 'good'; + } + for my $h (@hosts) { + $config{$h}{'status-ipv4'} = $status if $ipv4; + $config{$h}{'status-ipv6'} = $status if $ipv6; + } + if ($status ne 'good') { + if (exists($errors{$status})) { + failed("$hosts: $status: $errors{$status}"); + } else { + failed("$hosts: unexpected status: $line"); + } + next; + } + for my $h (@hosts) { + $config{$h}{'ipv4'} = $ipv4 if $ipv4; + $config{$h}{'ipv6'} = $ipv6 if $ipv6; + $config{$h}{'mtime'} = $now; + } + success("$hosts: IPv4 address set to $ipv4") if $ipv4; + success("$hosts: IPv6 address set to $ipv6") if $ipv6; + } + } +} + ###################################################################### ## nic_yandex_examples ######################################################################