Merge pull request #709 from rhansen/dyndns2

`dyndns2` readability improvements, take 2
This commit is contained in:
Richard Hansen 2024-07-19 02:25:08 -04:00 committed by GitHub
commit f6e13f8003
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 77 additions and 96 deletions

View file

@ -34,6 +34,13 @@ repository history](https://github.com/ddclient/ddclient/commits/master).
* Deprecated built-in web IP discovery services are not listed in the output * Deprecated built-in web IP discovery services are not listed in the output
of `--list-web-services`. of `--list-web-services`.
[#682](https://github.com/ddclient/ddclient/pull/682) [#682](https://github.com/ddclient/ddclient/pull/682)
* `dyndns2`: Support for "wait" response lines has been removed. The Dyn
documentation does not mention such responses, and the code to handle them,
untouched since at least 2006, is believed to be obsolete.
[#709](https://github.com/ddclient/ddclient/pull/709)
* `dyndns2`: The obsolete `static` and `custom` options have been removed.
Setting the options may produce a warning.
[#709](https://github.com/ddclient/ddclient/pull/709)
### New features ### New features

View file

@ -81,26 +81,6 @@ pid=@runstatedir@/ddclient.pid # record PID in file.
# protocol=dyndns2 \ # protocol=dyndns2 \
# your-dynamic-host.dyndns.org # your-dynamic-host.dyndns.org
##
## dyndns.org static addresses
##
## (supports variables: wildcard,mx,backupmx)
##
# static=yes, \
# server=members.dyndns.org, \
# protocol=dyndns2 \
# your-static-host.dyndns.org
##
## dyndns.org custom addresses
##
## (supports variables: wildcard,mx,backupmx)
##
# custom=yes, \
# server=members.dyndns.org, \
# protocol=dyndns2 \
# your-domain.top-level,your-other-domain.top-level
## ##
## ZoneEdit (zoneedit.com) ## ZoneEdit (zoneedit.com)
## ##

View file

@ -709,7 +709,6 @@ our %variables = (
'dyndns-common-defaults' => { 'dyndns-common-defaults' => {
'backupmx' => setv(T_BOOL, 0, 1, 0, undef), 'backupmx' => setv(T_BOOL, 0, 1, 0, undef),
'mx' => setv(T_OFQDN, 0, 1, undef, undef), 'mx' => setv(T_OFQDN, 0, 1, undef, undef),
'static' => setv(T_BOOL, 0, 1, 0, undef),
'wildcard' => setv(T_BOOL, 0, 1, 0, undef), 'wildcard' => setv(T_BOOL, 0, 1, 0, undef),
}, },
); );
@ -848,6 +847,7 @@ our %protocols = (
'variables' => { 'variables' => {
%{$variables{'protocol-common-defaults'}}, %{$variables{'protocol-common-defaults'}},
%{$variables{'dyndns-common-defaults'}}, %{$variables{'dyndns-common-defaults'}},
'static' => setv(T_BOOL, 0, 1, 0, undef),
}, },
}, },
'dyndns2' => { 'dyndns2' => {
@ -857,7 +857,6 @@ our %protocols = (
'variables' => { 'variables' => {
%{$variables{'protocol-common-defaults'}}, %{$variables{'protocol-common-defaults'}},
%{$variables{'dyndns-common-defaults'}}, %{$variables{'dyndns-common-defaults'}},
'custom' => setv(T_BOOL, 0, 1, 0, undef),
'script' => setv(T_STRING, 0, 1, '/nic/update', undef), 'script' => setv(T_STRING, 0, 1, '/nic/update', undef),
}, },
}, },
@ -4015,8 +4014,6 @@ Configuration variables applicable to the 'dyndns2' protocol are:
server=fqdn.of.service ## defaults to members.dyndns.org server=fqdn.of.service ## defaults to members.dyndns.org
script=/path/to/script ## defaults to /nic/update script=/path/to/script ## defaults to /nic/update
backupmx=no|yes ## indicates that this host is the primary MX for the domain. backupmx=no|yes ## indicates that this host is the primary MX for the domain.
static=no|yes ## indicates that this host has a static IP address.
custom=no|yes ## indicates that this host is a 'custom' top-level domain name.
mx=any.host.domain ## a host MX'ing for this host definition. mx=any.host.domain ## a host MX'ing for this host definition.
wildcard=no|yes ## add a DNS wildcard CNAME record that points to <host> wildcard=no|yes ## add a DNS wildcard CNAME record that points to <host>
login=service-login ## login name and password registered with the service login=service-login ## login name and password registered with the service
@ -4050,7 +4047,6 @@ EoEXAMPLE
###################################################################### ######################################################################
sub nic_dyndns2_update { sub nic_dyndns2_update {
debug("\nnic_dyndns2_update -------------------"); debug("\nnic_dyndns2_update -------------------");
my @groups = group_hosts_by(\@_, qw(login password server script static custom wildcard mx backupmx wantipv4 wantipv6));
my %errors = ( my %errors = (
'badauth' => 'Bad authorization (username or password)', 'badauth' => 'Bad authorization (username or password)',
'badsys' => 'The system parameter given was not valid', 'badsys' => 'The system parameter given was not valid',
@ -4064,7 +4060,18 @@ sub nic_dyndns2_update {
'dnserr' => 'System error: DNS error encountered. Contact support@dyndns.org', 'dnserr' => 'System error: DNS error encountered. Contact support@dyndns.org',
'nochg' => 'No update required; unnecessary attempts to change to the current address are considered abusive', 'nochg' => 'No update required; unnecessary attempts to change to the current address are considered abusive',
); );
for my $group (@groups) { my @group_by_attrs = qw(
backupmx
login
mx
password
script
server
wantipv4
wantipv6
wildcard
);
for my $group (group_hosts_by(\@_, @group_by_attrs)) {
my @hosts = @{$group->{hosts}}; my @hosts = @{$group->{hosts}};
my %groupcfg = %{$group->{cfg}}; my %groupcfg = %{$group->{cfg}};
my $hosts = join(',', @hosts); my $hosts = join(',', @hosts);
@ -4072,21 +4079,9 @@ sub nic_dyndns2_update {
my $ipv6 = $groupcfg{'wantipv6'}; my $ipv6 = $groupcfg{'wantipv6'};
delete $config{$_}{'wantipv4'} for @hosts; delete $config{$_}{'wantipv4'} for @hosts;
delete $config{$_}{'wantipv6'} for @hosts; delete $config{$_}{'wantipv6'} for @hosts;
info("setting IPv4 address to %s for %s", $ipv4, $hosts) if $ipv4; info("$hosts: setting IPv4 address to $ipv4") if $ipv4;
info("setting IPv6 address to %s for %s", $ipv6, $hosts) if $ipv6; info("$hosts: setting IPv6 address to $ipv6") if $ipv6;
verbose("UPDATE:", "updating %s", $hosts); my $url = "$groupcfg{'server'}$groupcfg{'script'}?hostname=$hosts&myip=";
my $url = "$groupcfg{'server'}$groupcfg{'script'}?system=";
if ($groupcfg{'custom'}) {
warning("updating %s: 'custom' and 'static' may not be used together. ('static' ignored)", $hosts)
if $groupcfg{'static'};
$url .= 'custom';
} elsif ($groupcfg{'static'}) {
$url .= 'statdns';
} else {
$url .= 'dyndns';
}
$url .= "&hostname=$hosts";
$url .= "&myip=";
$url .= $ipv4 if $ipv4; $url .= $ipv4 if $ipv4;
if ($ipv6) { if ($ipv6) {
$url .= "," if $ipv4; $url .= "," if $ipv4;
@ -4105,65 +4100,64 @@ sub nic_dyndns2_update {
password => $groupcfg{'password'}, password => $groupcfg{'password'},
) // ''; ) // '';
if ($reply eq '') { if ($reply eq '') {
failed("updating %s: Could not connect to %s.", $hosts, $groupcfg{'server'}); failed("$hosts: Could not connect to $groupcfg{'server'}");
next; next;
} }
next if !header_ok($hosts, $reply); next if !header_ok($hosts, $reply);
my @reply = split /\n/, $reply; # Some services can return 200 OK even if there is an error (e.g., bad authentication,
my $state = 'header'; # updates too frequent) so the body of the response must also be checked.
for my $line (@reply) { (my $body = $reply) =~ s/^.*?\n\n//s;
if ($state eq 'header') { my @reply = split(qr/\n/, $body);
$state = 'body'; if (!@reply) {
} elsif ($state eq 'body') { failed("$hosts: Could not connect to $groupcfg{'server'}");
$state = 'results' if $line eq ''; next;
} elsif ($state =~ /^results/) { }
$state = 'results2'; # From <https://help.dyn.com/remote-access-api/return-codes/>:
# bug #10: some dyndns providers does not return the IP so #
# we can't use the returned IP # If updating multiple hostnames, hostname-specific return codes are given one per line,
my ($status, $returnedips) = split / /, lc $line; # in the same order as the hostnames were specified. Return codes indicating a failure
for my $h (@hosts) { # with the account or the system are given only once.
$config{$h}{'status-ipv4'} = $status if $ipv4; #
$config{$h}{'status-ipv6'} = $status if $ipv6; # 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
if ($status eq 'good') { # one error status line? An error status line and a success status line? Or is an update
for my $h (@hosts) { # considered to be all-or-nothing and the status applies to the operation as a whole? If
$config{$h}{'ipv4'} = $ipv4 if $ipv4; # the IPv4 address changes but not the IPv6 address does that result in a status of "good"
$config{$h}{'ipv6'} = $ipv6 if $ipv6; # because the set of addresses for a host changed even if a subset did not?
$config{$h}{'mtime'} = $now; #
} # TODO: The logic below applies the last line's status to all hosts. Change it to apply
success("updating %s: %s: IPv4 address set to %s", $hosts, $status, $ipv4) if $ipv4; # each status to its corresponding host.
success("updating %s: %s: IPv6 address set to %s", $hosts, $status, $ipv6) if $ipv6; for my $line (@reply) {
} elsif (exists $errors{$status}) { # The IP address normally comes after the status, but we ignore it. We could compare
if ($status eq 'nochg') { # it with the expected address and mark the update as failed if it differs, but (1)
warning("updating %s: %s: %s", $hosts, $status, $errors{$status}); # some services do not return the IP; and (2) comparison is brittle (e.g.,
for my $h (@hosts) { # 192.000.002.001 vs. 192.0.2.1) and false errors could cause high load on the service
$config{$h}{'ipv4'} = $ipv4 if $ipv4; # (an update attempt every min-error-interval instead of every max-interval).
$config{$h}{'ipv6'} = $ipv6 if $ipv6; (my $status = $line) =~ s/ .*$//;
$config{$h}{'mtime'} = $now; if ($status eq 'nochg') {
$config{$h}{'status-ipv4'} = 'good' if $ipv4; warning("$hosts: $status: $errors{$status}");
$config{$h}{'status-ipv6'} = 'good' if $ipv6; $status = 'good';
} }
} else { for my $h (@hosts) {
failed("updating %s: %s: %s", $hosts, $status, $errors{$status}); $config{$h}{'status-ipv4'} = $status if $ipv4;
} $config{$h}{'status-ipv6'} = $status if $ipv6;
} elsif ($status =~ /w(\d+)(.)/) { }
my ($wait, $units) = ($1, lc $2); if ($status ne 'good') {
my ($sec, $scale) = ($wait, 1); if (exists($errors{$status})) {
($scale, $units) = (1, 'seconds') if $units eq 's'; failed("$hosts: $status: $errors{$status}");
($scale, $units) = (60, 'minutes') if $units eq 'm'; } else {
($scale, $units) = (60*60, 'hours') if $units eq 'h'; failed("$hosts: unexpected status: $line");
$sec = $wait * $scale; }
for my $h (@hosts) { next;
$config{$h}{'wtime'} = $now + $sec; }
} for my $h (@hosts) {
warning("updating %s: %s: wait %s %s before further updates", $hosts, $status, $wait, $units); $config{$h}{'ipv4'} = $ipv4 if $ipv4;
} else { $config{$h}{'ipv6'} = $ipv6 if $ipv6;
failed("updating %s: unexpected status (%s)", $hosts, $line); $config{$h}{'mtime'} = $now;
} }
} success("$hosts: IPv4 address set to $ipv4") if $ipv4;
success("$hosts: IPv6 address set to $ipv6") if $ipv6;
} }
failed("updating %s: Could not connect to %s.", $hosts, $groupcfg{'server'})
if $state ne 'results2';
} }
} }