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
of `--list-web-services`.
[#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

View file

@ -81,26 +81,6 @@ pid=@runstatedir@/ddclient.pid # record PID in file.
# protocol=dyndns2 \
# 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)
##

View file

@ -709,7 +709,6 @@ our %variables = (
'dyndns-common-defaults' => {
'backupmx' => setv(T_BOOL, 0, 1, 0, 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),
},
);
@ -848,6 +847,7 @@ our %protocols = (
'variables' => {
%{$variables{'protocol-common-defaults'}},
%{$variables{'dyndns-common-defaults'}},
'static' => setv(T_BOOL, 0, 1, 0, undef),
},
},
'dyndns2' => {
@ -857,7 +857,6 @@ our %protocols = (
'variables' => {
%{$variables{'protocol-common-defaults'}},
%{$variables{'dyndns-common-defaults'}},
'custom' => setv(T_BOOL, 0, 1, 0, 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
script=/path/to/script ## defaults to /nic/update
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.
wildcard=no|yes ## add a DNS wildcard CNAME record that points to <host>
login=service-login ## login name and password registered with the service
@ -4050,7 +4047,6 @@ EoEXAMPLE
######################################################################
sub nic_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 = (
'badauth' => 'Bad authorization (username or password)',
'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',
'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 %groupcfg = %{$group->{cfg}};
my $hosts = join(',', @hosts);
@ -4072,21 +4079,9 @@ sub nic_dyndns2_update {
my $ipv6 = $groupcfg{'wantipv6'};
delete $config{$_}{'wantipv4'} for @hosts;
delete $config{$_}{'wantipv6'} for @hosts;
info("setting IPv4 address to %s for %s", $ipv4, $hosts) if $ipv4;
info("setting IPv6 address to %s for %s", $ipv6, $hosts) if $ipv6;
verbose("UPDATE:", "updating %s", $hosts);
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=";
info("$hosts: setting IPv4 address to $ipv4") if $ipv4;
info("$hosts: setting IPv6 address to $ipv6") if $ipv6;
my $url = "$groupcfg{'server'}$groupcfg{'script'}?hostname=$hosts&myip=";
$url .= $ipv4 if $ipv4;
if ($ipv6) {
$url .= "," if $ipv4;
@ -4105,65 +4100,64 @@ sub nic_dyndns2_update {
password => $groupcfg{'password'},
) // '';
if ($reply eq '') {
failed("updating %s: Could not connect to %s.", $hosts, $groupcfg{'server'});
failed("$hosts: Could not connect to $groupcfg{'server'}");
next;
}
next if !header_ok($hosts, $reply);
my @reply = split /\n/, $reply;
my $state = 'header';
# Some services 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 <https://help.dyn.com/remote-access-api/return-codes/>:
#
# 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) {
if ($state eq 'header') {
$state = 'body';
} elsif ($state eq 'body') {
$state = 'results' if $line eq '';
} elsif ($state =~ /^results/) {
$state = 'results2';
# bug #10: some dyndns providers does not return the IP so
# we can't use the returned IP
my ($status, $returnedips) = split / /, lc $line;
# 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 eq 'good') {
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("updating %s: %s: IPv4 address set to %s", $hosts, $status, $ipv4) if $ipv4;
success("updating %s: %s: IPv6 address set to %s", $hosts, $status, $ipv6) if $ipv6;
} elsif (exists $errors{$status}) {
if ($status eq 'nochg') {
warning("updating %s: %s: %s", $hosts, $status, $errors{$status});
for my $h (@hosts) {
$config{$h}{'ipv4'} = $ipv4 if $ipv4;
$config{$h}{'ipv6'} = $ipv6 if $ipv6;
$config{$h}{'mtime'} = $now;
$config{$h}{'status-ipv4'} = 'good' if $ipv4;
$config{$h}{'status-ipv6'} = 'good' if $ipv6;
success("$hosts: IPv4 address set to $ipv4") if $ipv4;
success("$hosts: IPv6 address set to $ipv6") if $ipv6;
}
} else {
failed("updating %s: %s: %s", $hosts, $status, $errors{$status});
}
} elsif ($status =~ /w(\d+)(.)/) {
my ($wait, $units) = ($1, lc $2);
my ($sec, $scale) = ($wait, 1);
($scale, $units) = (1, 'seconds') if $units eq 's';
($scale, $units) = (60, 'minutes') if $units eq 'm';
($scale, $units) = (60*60, 'hours') if $units eq 'h';
$sec = $wait * $scale;
for my $h (@hosts) {
$config{$h}{'wtime'} = $now + $sec;
}
warning("updating %s: %s: wait %s %s before further updates", $hosts, $status, $wait, $units);
} else {
failed("updating %s: unexpected status (%s)", $hosts, $line);
}
}
}
failed("updating %s: Could not connect to %s.", $hosts, $groupcfg{'server'})
if $state ne 'results2';
}
}