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.
(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) { for my $line (@reply) {
if ($state eq 'header') { # The IP address normally comes after the status, but we ignore it. We could compare
$state = 'body'; # it with the expected address and mark the update as failed if it differs, but (1)
} elsif ($state eq 'body') { # some services do not return the IP; and (2) comparison is brittle (e.g.,
$state = 'results' if $line eq ''; # 192.000.002.001 vs. 192.0.2.1) and false errors could cause high load on the service
} elsif ($state =~ /^results/) { # (an update attempt every min-error-interval instead of every max-interval).
$state = 'results2'; (my $status = $line) =~ s/ .*$//;
# bug #10: some dyndns providers does not return the IP so if ($status eq 'nochg') {
# we can't use the returned IP warning("$hosts: $status: $errors{$status}");
my ($status, $returnedips) = split / /, lc $line; $status = 'good';
}
for my $h (@hosts) { for my $h (@hosts) {
$config{$h}{'status-ipv4'} = $status if $ipv4; $config{$h}{'status-ipv4'} = $status if $ipv4;
$config{$h}{'status-ipv6'} = $status if $ipv6; $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) { for my $h (@hosts) {
$config{$h}{'ipv4'} = $ipv4 if $ipv4; $config{$h}{'ipv4'} = $ipv4 if $ipv4;
$config{$h}{'ipv6'} = $ipv6 if $ipv6; $config{$h}{'ipv6'} = $ipv6 if $ipv6;
$config{$h}{'mtime'} = $now; $config{$h}{'mtime'} = $now;
} }
success("updating %s: %s: IPv4 address set to %s", $hosts, $status, $ipv4) if $ipv4; success("$hosts: IPv4 address set to $ipv4") if $ipv4;
success("updating %s: %s: IPv6 address set to %s", $hosts, $status, $ipv6) if $ipv6; success("$hosts: IPv6 address set to $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;
} }
} 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';
} }
} }