From e715a6246e22bac86989624b58c57d7755fcc9de Mon Sep 17 00:00:00 2001 From: Bodo Eggert <7eggert@gmx.de> Date: Fri, 15 Nov 2024 18:00:34 +0100 Subject: [PATCH] refactor after adding selfhost_de Both services do use a common result processing. --- ddclient.in | 220 +++++++++++++++++++--------------------------------- 1 file changed, 78 insertions(+), 142 deletions(-) diff --git a/ddclient.in b/ddclient.in index ecc8948..3ce14c5 100755 --- a/ddclient.in +++ b/ddclient.in @@ -3905,6 +3905,81 @@ EoEXAMPLE ###################################################################### ## nic_dyndns2_update ###################################################################### +sub nic_dyndns2_selfhost_de_process_reply(\@$$$\%) { + # Since both dyndns2 and selfhost_de use the same processing, + # it can be factored out. While dyndns2 uses one call for two machines, + # selfhost_de uses two separate ones. We call this for reach reply. + my ($hosts, $reply, $ipv4, $ipv6,$errors) = @_; + my @hosts = @$hosts; + # 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); + # 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. + # + # If there is only one result for multiple hosts, this function assumes the one result + # applies to all hosts. According to the documentation quoted above this should only + # happen if the result is a failure. In case there is a single successful result, this + # code applies the success to all hosts (with a warning) to maximize potential + # compatibility with all DynDNS-like services. If there are zero results, or two or more + # results, any host without a corresponding result line is treated as a failure. + # + # TODO: The DynDNS documentation does not mention 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 collection + # of addresses 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? + my @statuses = map({ (my $l = $_) =~ s/ .*$//; $l; } @reply); + if (@statuses < @hosts && @statuses == 1) { + warning("service returned one successful result for " . 1*@hosts . " hosts; " . + "assuming the one success is intended to apply to all hosts") + if $statuses[0] =~ qr/^(?:good|nochg)$/; + @statuses = ($statuses[0]) x @hosts; + } + for (my $i = 0; $i < @hosts; ++$i) { + my $h = $hosts[$i]; + local $_l = $_l->{parent}; $_l = pushlogctx($h); + my $status = $statuses[$i] // 'unknown'; + if ($status eq 'nochg') { + warning("$status: $errors->{$status}"); + $status = 'good'; + } + $recap{$h}{'status-ipv4'} = $status if $ipv4; + $recap{$h}{'status-ipv6'} = $status if $ipv6; + if ($status ne 'good') { + if (exists($errors->{$status})) { + failed("$status: $errors->{$status}"); + } elsif ($status eq 'unknown') { + failed('server did not return a success/fail result; assuming failure'); + } else { + # This case can only happen if there is a corresponding status line for this + # host or there was only one status line for all hosts. + failed("unexpected status: " . ($reply[$i] // $reply[0])); + } + next; + } + # 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). + $recap{$h}{'ipv4'} = $ipv4 if $ipv4; + $recap{$h}{'ipv6'} = $ipv6 if $ipv6; + $recap{$h}{'mtime'} = $now; + success("IPv4 address set to $ipv4") if $ipv4; + success("IPv6 address set to $ipv6") if $ipv6; + } + warning("unexpected extra lines after per-host update status lines:\n" . + join("\n", @reply[@hosts..$#reply])) + if (@reply > @hosts); +} + sub nic_dyndns2_update { my $self = shift; my %errors = ( @@ -3961,73 +4036,7 @@ sub nic_dyndns2_update { password => $groupcfg{'password'}, ); next if !header_ok($reply); - # 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); - # 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. - # - # If there is only one result for multiple hosts, this function assumes the one result - # applies to all hosts. According to the documentation quoted above this should only - # happen if the result is a failure. In case there is a single successful result, this - # code applies the success to all hosts (with a warning) to maximize potential - # compatibility with all DynDNS-like services. If there are zero results, or two or more - # results, any host without a corresponding result line is treated as a failure. - # - # TODO: The DynDNS documentation does not mention 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 collection - # of addresses 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? - my @statuses = map({ (my $l = $_) =~ s/ .*$//; $l; } @reply); - if (@statuses < @hosts && @statuses == 1) { - warning("service returned one successful result for multiple hosts; " . - "assuming the one success is intended to apply to all hosts") - if $statuses[0] =~ qr/^(?:good|nochg)$/; - @statuses = ($statuses[0]) x @hosts; - } - for (my $i = 0; $i < @hosts; ++$i) { - my $h = $hosts[$i]; - local $_l = $_l->{parent}; $_l = pushlogctx($h); - my $status = $statuses[$i] // 'unknown'; - if ($status eq 'nochg') { - warning("$status: $errors{$status}"); - $status = 'good'; - } - $recap{$h}{'status-ipv4'} = $status if $ipv4; - $recap{$h}{'status-ipv6'} = $status if $ipv6; - if ($status ne 'good') { - if (exists($errors{$status})) { - failed("$status: $errors{$status}"); - } elsif ($status eq 'unknown') { - failed('server did not return a success/fail result; assuming failure'); - } else { - # This case can only happen if there is a corresponding status line for this - # host or there was only one status line for all hosts. - failed("unexpected status: " . ($reply[$i] // $reply[0])); - } - next; - } - # 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). - $recap{$h}{'ipv4'} = $ipv4 if $ipv4; - $recap{$h}{'ipv6'} = $ipv6 if $ipv6; - $recap{$h}{'mtime'} = $now; - success("IPv4 address set to $ipv4") if $ipv4; - success("IPv6 address set to $ipv6") if $ipv6; - } - warning("unexpected extra lines after per-host update status lines:\n" . - join("\n", @reply[@hosts..$#reply])) - if (@reply > @hosts); + nic_dyndns2_selfhost_de_process_reply(@hosts, $reply, $ipv4, $ipv6, %errors); } } @@ -4101,79 +4110,6 @@ sub mkurl($@) { return $url; } -sub nic_selfhost_de_process_reply(\@$$$\%) { - my ($hosts, $reply, $ipv4, $ipv6,$errors) = @_; - my @hosts = @$hosts; - - # 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); - # 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. - # - # If there is only one result for multiple hosts, this function assumes the one result - # applies to all hosts. According to the documentation quoted above this should only - # happen if the result is a failure. In case there is a single successful result, this - # code applies the success to all hosts (with a warning) to maximize potential - # compatibility with all DynDNS-like services. If there are zero results, or two or more - # results, any host without a corresponding result line is treated as a failure. - # - # TODO: The DynDNS documentation does not mention 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 collection - # of addresses 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? - my @statuses = map({ (my $l = $_) =~ s/ .*$//; $l; } @reply); - if (@statuses < @hosts && @statuses == 1) { - warning("service returned one successful result for " . 1*@hosts . " hosts; " . - "assuming the one success is intended to apply to all hosts") - if $statuses[0] =~ qr/^(?:good|nochg)$/; - @statuses = ($statuses[0]) x @hosts; - } - for (my $i = 0; $i < @hosts; ++$i) { - my $h = $hosts[$i]; - local $_l = $_l->{parent}; $_l = pushlogctx($h); - my $status = $statuses[$i] // 'unknown'; - if ($status eq 'nochg') { - warning("$status: $errors->{$status}"); - $status = 'good'; - } - $recap{$h}{'status-ipv4'} = $status if $ipv4; - $recap{$h}{'status-ipv6'} = $status if $ipv6; - if ($status ne 'good') { - if (exists($errors->{$status})) { - failed("$status: $errors->{$status}"); - } elsif ($status eq 'unknown') { - failed('server did not return a success/fail result; assuming failure'); - } else { - # This case can only happen if there is a corresponding status line for this - # host or there was only one status line for all hosts. - failed("unexpected status: " . ($reply[$i] // $reply[0])); - } - next; - } - # 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). - $recap{$h}{'ipv4'} = $ipv4 if $ipv4; - $recap{$h}{'ipv6'} = $ipv6 if $ipv6; - $recap{$h}{'mtime'} = $now; - success("IPv4 address set to $ipv4") if $ipv4; - success("IPv6 address set to $ipv6") if $ipv6; - } - warning("unexpected extra lines after per-host update status lines:\n" . - join("\n", @reply[@hosts..$#reply])) - if (@reply > @hosts); -} - sub nic_selfhost_de_update { my $self = shift; my %errors = ( @@ -4257,8 +4193,8 @@ sub nic_selfhost_de_update { my $ok4 = ($reply_v4 && header_ok($reply_v4)); my $ok6 = ($reply_v6 && header_ok($reply_v6)); next if !$ok4 && !$ok6; - nic_selfhost_de_process_reply(@hosts, $reply_v4, $ipv4, undef, %errors) if $ipv4; - nic_selfhost_de_process_reply(@hosts, $reply_v6, undef, $ipv6, %errors) if $ipv6; + nic_dyndns2_selfhost_de_process_reply(@hosts, $reply_v4, $ipv4, undef, %errors) if $ipv4; + nic_dyndns2_selfhost_de_process_reply(@hosts, $reply_v6, undef, $ipv6, %errors) if $ipv6; } } ######################################################################