From d8a9d9d089787773855070841389bc3215322091 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Mon, 20 May 2024 00:55:11 -0400 Subject: [PATCH 001/150] Add support for infinite duration --- Makefile.am | 1 + ddclient.in | 8 +++++-- t/interval_expired.pl | 51 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 t/interval_expired.pl diff --git a/Makefile.am b/Makefile.am index 9503c04..459931e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -65,6 +65,7 @@ handwritten_tests = \ t/builtinfw_query.pl \ t/get_ip_from_if.pl \ t/geturl_connectivity.pl \ + t/interval_expired.pl \ t/is-and-extract-ipv4.pl \ t/is-and-extract-ipv6.pl \ t/is-and-extract-ipv6-global.pl \ diff --git a/ddclient.in b/ddclient.in index f6bf5ae..c0fa54e 100755 --- a/ddclient.in +++ b/ddclient.in @@ -104,7 +104,7 @@ my $programd = $0; $programd =~ s%^.*/%%; my $program = $programd; $program =~ s/d$//; -my $now = time; +our $now = time; my $hostname = hostname(); # subst_var(subst, default) returns subst unless it looks like @foo@ in which case it returns @@ -140,7 +140,8 @@ $ENV{'PATH'} = (exists($ENV{PATH}) ? "$ENV{PATH}:" : "") . "/sbin:/usr/sbin:/bin our %globals; our %config; -my ($result, %cache); +our %cache; +my $result; my $saved_cache; my %saved_opt; my $daemon; @@ -2465,6 +2466,8 @@ sub interval { $value = $1 * 60*60; } elsif ($value =~ /^(\d+)(days|d)/i) { $value = $1 * 60*60*24; + } elsif ($value =~ qr/^(?:inf(?:init[ye])?|indefinite(?:ly)?|never|forever|always)$/i) { + $value = 'inf'; } elsif ($value !~ /^\d+$/) { $value = undef; } @@ -2473,6 +2476,7 @@ sub interval { sub interval_expired { my ($host, $time, $interval) = @_; + return 0 if ($config{$host}{$interval} // 0) == 'inf'; return 1 if !exists $cache{$host}; return 1 if !exists $cache{$host}{$time} || !$cache{$host}{$time}; return 1 if !exists $config{$host}{$interval} || !$config{$host}{$interval}; diff --git a/t/interval_expired.pl b/t/interval_expired.pl new file mode 100644 index 0000000..1043dea --- /dev/null +++ b/t/interval_expired.pl @@ -0,0 +1,51 @@ +use Test::More; +SKIP: { eval { require Test::Warnings; } or skip($@, 1); } +eval { require 'ddclient'; } or BAIL_OUT($@); + +my $h = 't/interval_expired.pl'; + +my $default_now = 1000000000; + +my @test_cases = ( + { + interval => 'inf', + want => 0, + }, + { + now => 'inf', + interval => 'inf', + want => 0, + }, + { + cache => '-inf', + interval => 'inf', + want => 0, + }, + { + cache => undef, # Falsy cache value. + interval => 'inf', + want => 0, + }, + { + now => 0, + cache => 0, # Different kind of falsy cache value. + interval => 'inf', + want => 0, + }, +); + +for my $tc (@test_cases) { + $tc->{now} //= $default_now; + # For convenience, $tc->{cache} is an offset from $tc->{now}, not an absolute time.. + my $cachetime = $tc->{now} + $tc->{cache} if defined($tc->{cache}); + $ddclient::config{$h} = {'interval' => $tc->{interval}}; + %ddclient::config if 0; # suppress spurious warning "Name used only once: possible typo" + $ddclient::cache{$h} = {'cached-time' => $cachetime} if defined($cachetime); + %ddclient::cache if 0; # suppress spurious warning "Name used only once: possible typo" + $ddclient::now = $tc->{now}; + $ddclient::now if 0; # suppress spurious warning "Name used only once: possible typo" + my $desc = "now=$tc->{now}, cache=${\($cachetime // 'undef')}, interval=$tc->{interval}"; + is(ddclient::interval_expired($h, 'cached-time', 'interval'), $tc->{want}, $desc); +} + +done_testing(); From 61b979c49eed96b5f69bd0ffbbbf92555c6923ee Mon Sep 17 00:00:00 2001 From: Joel Croteau Date: Sat, 20 Apr 2024 20:13:14 -0700 Subject: [PATCH 002/150] New 'emailonly' protocol that simply sends an email on IP change This adds a protocol to email IP address changes without needing a dynamic DNS service. This is useful if you don't use a DDNS service but want to be notified when the IP of a machine changes. --- ChangeLog.md | 2 ++ ddclient.conf.in | 6 +++++ ddclient.in | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index d0ca95c..eea6429 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -41,6 +41,8 @@ repository history](https://github.com/ddclient/ddclient/commits/master). * The second and subsequent lines in a multi-line log message are now prefixed with a `|` character. [#676](https://github.com/ddclient/ddclient/pull/676) + * `emailonly`: New `protocol` option that simply emails you when your IP + address changes. [#654](https://github.com/ddclient/ddclient/pull/654) ### Bug fixes diff --git a/ddclient.conf.in b/ddclient.conf.in index 8652635..e916958 100644 --- a/ddclient.conf.in +++ b/ddclient.conf.in @@ -391,3 +391,9 @@ ssl=yes # use ssl-support. Works with # password=ddns_password # redirect=2 # example.com + +## +## Email Only +## +# protocol=emailonly +# host.example.com diff --git a/ddclient.in b/ddclient.in index c0fa54e..9ce0d1d 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1067,6 +1067,18 @@ my %services = ( %{$variables{'service-common-defaults'}}, }, }, + 'emailonly' => { + 'updateable' => undef, + 'update' => \&nic_emailonly_update, + 'examples' => \&nic_emailonly_examples, + 'variables' => { + %{$variables{'service-common-defaults'}}, + 'login' => undef, + 'password' => undef, + # Change default to never re-notify if IP address has not changed. + 'max-interval' => setv(T_DELAY, 0, 0, 'inf', 0), + }, + }, ); # Delete undefined variables to make it easier to cancel previously defined variables. for my $svc (values(%services)) { @@ -8080,6 +8092,55 @@ sub nic_infomaniak_update { } } +###################################################################### +## nic_emailonly_update +## +## Written by Joel Croteau +## +## Do not update Dynamic DNS, only send status emails. Use if you do +## not have a DDNS host, but still want to get emails when your IP +## address changes. Note that you must set the "mail" config option +## and configure sendmail for this to have an effect. At least one +## host must be specified; the host names are mentioned in the email. +###################################################################### +sub nic_emailonly_update { + debug("\nnic_emailonly_update -------------------"); + # Note: This is logged after $config{$_}{'max-interval'] even if the IP address hasn't changed, + # so it is best to avoid phrasing like, "IP address has changed." + logmsg(email => 1, join("\n", 'Host IP addresses:', map({ + my $ipv4 = delete($config{$_}{'wantipv4'}); + my $ipv6 = delete($config{$_}{'wantipv6'}); + $config{$_}{'status-ipv4'} = 'good' if $ipv4; + $config{$_}{'status-ipv6'} = 'good' if $ipv6; + $config{$_}{'ipv4'} = $ipv4 if $ipv4; + $config{$_}{'ipv6'} = $ipv6 if $ipv6; + $config{$_}{'mtime'} = $now; + sprintf('%30s %s', $_, join(' ', grep(defined($_), $ipv4, $ipv6))); + } @_))); +} + +###################################################################### +## nic_emailonly_examples +###################################################################### +sub nic_emailonly_examples { + return <<"EoEXAMPLE"; +o 'emailonly' + +The 'emailonly' protocol is a dummy protocol that will send status emails but +not actually issue any dynamic DNS updates. You can use this if you don\'t +have a DDNS host, but still want to get emails when your IP address changes. +For this to have an effect, you must set the 'mail' config option, have +sendmail properly configured on your machine, and specify at least one dummy +hostname. + +Example ${program}.conf file entries: + ## single host update + mail=me\@example.com + protocol=emailonly + host.example.com +EoEXAMPLE +} + # Execute main() if this file is run as a script or run via PAR (https://metacpan.org/pod/PAR), # otherwise do nothing. This "modulino" pattern makes it possible to import this file as a module # and test its functions directly; there's no need for test-only command-line arguments or stdout From a91ca7a19949511cf2899b92130620ba298df98f Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 29 May 2024 16:48:00 -0400 Subject: [PATCH 003/150] s/foreach/for/g for consistency --- ddclient.in | 228 ++++++++++++++++++++++++++-------------------------- 1 file changed, 114 insertions(+), 114 deletions(-) diff --git a/ddclient.in b/ddclient.in index 9ce0d1d..1313ba7 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1343,12 +1343,12 @@ sub update_nics { my %ipv4list = (); my %ipv6list = (); - foreach my $s (sort keys %services) { + for my $s (sort keys %services) { my (@hosts, %ipsv4, %ipsv6) = (); my $updateable = $services{$s}{'updateable'}; my $update = $services{$s}{'update'}; - foreach my $h (sort keys %config) { + for my $h (sort keys %config) { next if $config{$h}{'protocol'} ne lc($s); $examined{$h} = 1; # we only do this once per 'use' and argument combination @@ -1464,7 +1464,7 @@ sub update_nics { # The new '--usev*' parameters set 'wantipv*' and the new providers set 'ipv*' and 'status-ipv*'. # To allow gradual transition, we make sure both the old 'status' and 'ip' are being set # accordingly to what new providers returned in the new 'status-ipv*' and 'ipv*' fields respectively. - foreach my $h (@hosts) { + for my $h (@hosts) { $config{$h}{'status'} //= $config{$h}{'status-ipv4'} // $config{$h}{'status-ipv6'}; $config{$h}{'ip'} //= $config{$h}{'ipv4'} // $config{$h}{'ipv6'}; } @@ -1472,7 +1472,7 @@ sub update_nics { runpostscript(join ' ', keys %ipsv4, keys %ipsv6); } } - foreach my $h (sort keys %config) { + for my $h (sort keys %config) { if (!exists $examined{$h}) { failed("%s was not updated because protocol %s is not supported.", $h, $config{$h}{'protocol'} // ''); @@ -1515,7 +1515,7 @@ sub write_cache { my ($file) = @_; ## merge the updated host entries into the cache. - foreach my $h (keys %config) { + for my $h (keys %config) { if (!exists $cache{$h} || $config{$h}{'update'}) { map { defined($config{$h}{$_}) ? ($cache{$h}{$_} = $config{$h}{$_}) : () } @{$config{$h}{'cacheable'}}; } else { @@ -1525,7 +1525,7 @@ sub write_cache { ## construct the cache file. my $cache = ""; - foreach my $h (sort keys %cache) { + for my $h (sort keys %cache) { my $opt = join(',', map { "$_=" . ($cache{$h}{$_} // '') } sort keys %{$cache{$h}}); $cache .= sprintf "%s%s%s\n", $opt, ($opt ? ' ' : ''), $h; @@ -1573,9 +1573,9 @@ sub read_cache { $saved_cache = _read_config($config, $globals, "##\\s*$program-$version\\s*", $file); %opt = %saved; - foreach my $h (keys %cache) { + for my $h (keys %cache) { if (exists $config->{$h}) { - foreach (qw(atime mtime wtime ip status)) { + for (qw(atime mtime wtime ip status)) { $config->{$h}{$_} = $cache{$h}{$_} if exists $cache{$h}{$_}; } } @@ -1757,7 +1757,7 @@ sub _read_config { my @args = split; ## verify that keywords are valid...and check the value - foreach my $k (keys %locals) { + for my $k (keys %locals) { # Handle '_env' keyword suffix if ($k =~ /(.*)_env$/) { @@ -1810,7 +1810,7 @@ sub _read_config { $locals{'password'} = $password if defined $password; ## allow {host} to be a comma separated list of hosts - foreach my $h (split_by_comma($host)) { + for my $h (split_by_comma($host)) { if ($config{$h}) { ## host already defined, merging configs $config{$h} = {%locals, %{$config{$h}}}; @@ -1878,7 +1878,7 @@ sub init_config { if (exists $opt{'options'} && defined $opt{'options'}) { ## collect cmdline configuration options. my %options = (); - foreach my $opt (split_by_comma($opt{'options'})) { + for my $opt (split_by_comma($opt{'options'})) { my ($name, $var) = split /\s*=\s*/, $opt; if ($name eq 'fw-banlocal' || $name eq 'if-skip') { warning("'$name' is deprecated and does nothing"); @@ -1889,20 +1889,20 @@ sub init_config { ## determine hosts specified with --host my @hosts = (); if (exists $opt{'host'}) { - foreach my $h (split_by_comma($opt{'host'})) { + for my $h (split_by_comma($opt{'host'})) { push @hosts, $h; } } ## and those in --options=... if (exists $options{'host'}) { - foreach my $h (split_by_comma($options{'host'})) { + for my $h (split_by_comma($options{'host'})) { push @hosts, $h; } delete $options{'host'}; } ## merge options into host definitions or globals if (@hosts) { - foreach my $h (@hosts) { + for my $h (@hosts) { $config{$h} = {%{$config{$h}}, %options}; } $opt{'host'} = join(',', @hosts); @@ -1912,7 +1912,7 @@ sub init_config { } ## override global options with those on the command-line. - foreach my $o (keys %opt) { + for my $o (keys %opt) { if (defined $opt{$o} && exists $variables{'global-defaults'}{$o}) { $globals{$o} = $opt{$o}; } @@ -1939,9 +1939,9 @@ sub init_config { map { delete $config{$_} unless exists $hosts{$_} } keys %config; ## collect the cacheable variables. - foreach my $proto (keys %services) { + for my $proto (keys %services) { my @cacheable = (); - foreach my $k (keys %{$services{$proto}{'variables'}}) { + for my $k (keys %{$services{$proto}{'variables'}}) { push @cacheable, $k if $services{$proto}{'variables'}{$k}{'cache'}; } $services{$proto}{'cacheable'} = [ @cacheable ]; @@ -1950,7 +1950,7 @@ sub init_config { ## sanity check.. ## make sure config entries have all defaults and they meet minimums ## first the globals... - foreach my $k (keys %globals) { + for my $k (keys %globals) { # Make sure any _env suffixed variables look at their original entry $k = $1 if $k =~ /^(.*)_env$/; @@ -1966,7 +1966,7 @@ sub init_config { ## now the host definitions... HOST: - foreach my $h (keys %config) { + for my $h (keys %config) { my $proto; $proto = $config{$h}{'protocol'}; $proto = opt('protocol') if !defined($proto); @@ -1982,7 +1982,7 @@ sub init_config { my $svars = $services{$proto}{'variables'}; my $conf = { 'protocol' => $proto }; - foreach my $k (keys %$svars) { + for my $k (keys %$svars) { # Make sure any _env suffixed variables look at their original entry $k = $1 if $k =~ /^(.*)_env$/; @@ -2010,7 +2010,7 @@ sub process_args { my @spec = (); my $usage = ""; - foreach (@_) { + for (@_) { if (ref $_) { my ($key, $specifier, $arg_usage) = @$_; my $value = default($key); @@ -2068,14 +2068,14 @@ sub test_possible_ip { `command -v ifconfig >/dev/null && ifconfig -a`) if $? || !@ifs; @ifs = () if $?; warning("failed to get list of interfaces") if !@ifs; - foreach my $if (@ifs) { + for my $if (@ifs) { local $opt{'if'} = $if; printf "use=if, if=%s address is %s\n", opt('if'), get_ip('if') // 'NOT FOUND'; } } if (opt('fw')) { if (opt('fw') !~ m%/%) { - foreach my $fw (sort keys %builtinfw) { + for my $fw (sort keys %builtinfw) { local $opt{'use'} = $fw; printf "use=%s address is %s\n", $fw, get_ip($fw) // 'NOT FOUND'; } @@ -2087,7 +2087,7 @@ sub test_possible_ip { } { local $opt{'use'} = 'web'; - foreach my $web (sort keys %builtinweb) { + for my $web (sort keys %builtinweb) { local $opt{'web'} = $web; printf "use=web, web=%s address is %s\n", $web, get_ip('web') // 'NOT FOUND'; } @@ -2113,14 +2113,14 @@ sub test_possible_ip { `command -v ifconfig >/dev/null && ifconfig -a`) if $? || !@ifs; @ifs = () if $?; warning("failed to get list of interfaces") if !@ifs; - foreach my $if (@ifs) { + for my $if (@ifs) { local $opt{'ifv4'} = $if; printf "use=ifv4, ifv4=%s address is %s\n", opt('ifv4'), get_ipv4('ifv4') // 'NOT FOUND'; } } { local $opt{'usev4'} = 'webv4'; - foreach my $web (sort keys %builtinweb) { + for my $web (sort keys %builtinweb) { local $opt{'webv4'} = $web; printf "use=webv4, webv4=$web address is %s\n", get_ipv4('webv4') // 'NOT FOUND' if ($web !~ "6") ## Don't bother if web site only supports IPv6; @@ -2147,14 +2147,14 @@ sub test_possible_ip { `command -v ifconfig >/dev/null && ifconfig -a`) if $? || !@ifs; @ifs = () if $?; warning("failed to get list of interfaces") if !@ifs; - foreach my $if (@ifs) { + for my $if (@ifs) { local $opt{'ifv6'} = $if; printf "use=ifv6, ifv6=%s address is %s\n", opt('ifv6'), get_ipv6('ifv6') // 'NOT FOUND'; } } { local $opt{'usev6'} = 'webv6'; - foreach my $web (sort keys %builtinweb) { + for my $web (sort keys %builtinweb) { local $opt{'webv6'} = $web; printf "use=webv6, webv6=$web address is %s\n", get_ipv6('webv6') // 'NOT FOUND' if ($web !~ "4"); ## Don't bother if web site only supports IPv4 @@ -2247,7 +2247,7 @@ sub _print_hash { if (!defined($ptr)) { $value = ""; } elsif (ref $ptr eq 'HASH') { - foreach my $key (sort keys %$ptr) { + for my $key (sort keys %$ptr) { if (($key eq "login") || ($key eq "password")) { $value = ""; } else { @@ -2366,14 +2366,14 @@ sub opt { } sub min { my $min = shift; - foreach my $arg (@_) { + for my $arg (@_) { $min = $arg if $arg < $min; } return $min; } sub max { my $max = shift; - foreach my $arg (@_) { + for my $arg (@_) { $max = $arg if $arg > $max; } return $max; @@ -2386,10 +2386,10 @@ sub ynu { return $no if !($value // ''); return $yes if $value eq '1'; - foreach (qw(yes true)) { + for (qw(yes true)) { return $yes if $_ =~ /^$value/i; } - foreach (qw(no false)) { + for (qw(no false)) { return $no if $_ =~ /^$value/i; } return $undef; @@ -2765,7 +2765,7 @@ sub geturl { # Each header line is added individually @header_lines = split('\n', $headers); - $_ = "header=\"".escape_curl_param($_).'"' foreach (@header_lines); + $_ = "header=\"".escape_curl_param($_).'"' for (@header_lines); push(@curlopt, @header_lines); # Add in the data if any was provided (for POST/PATCH) @@ -2780,7 +2780,7 @@ sub geturl { # don't include ${url} as that might expose login credentials $0 = sprintf("%s - Curl system cmd sending to %s", $program, "${protocol}://${server}"); verbose("SENDING:", "Curl system cmd to %s", "${protocol}://${server}"); - verbose("SENDING:", "%s", $_) foreach (@curlopt); + verbose("SENDING:", "%s", $_) for (@curlopt); $reply = curl_cmd(@curlopt); verbose("RECEIVE:", "%s", $reply // ""); @@ -3079,7 +3079,7 @@ sub get_default_interface { debug("Default routes found for IPv%s :\n%s", $ipver, join("\n",@list)); # now check each interface to make sure it is global (not loopback). - foreach my $line (@list) { + for my $line (@list) { ## Interface will be after "dev" or the last word in the line. Must accept blank spaces ## at the end. Interface name may not have any whitespace or forward slash. $line =~ /\bdev\b\s*\K[^\s\/]+|\b[^\s\/]+(?=[\s\/]*$)/; @@ -3426,7 +3426,7 @@ sub group_hosts_by { my %attrs = (map({ ($_ => 1) } @$attributes), 'wantip' => 1); my @attrs = sort(keys(%attrs)); my %groups = (); - foreach my $h (@$hosts) { + for my $h (@$hosts) { my $sig = join(',', map({ sprintf("%s=%s", $_, $config{$h}{$_} // '') } @attrs)); push @{$groups{$sig}}, $h; } @@ -3442,7 +3442,7 @@ sub encode_www_form_urlencoded { my $must_encode = qr'[<>"#%{}|\\^~\[\]`;/?:=&+]'; my $encoded; my $i = 0; - foreach my $k (keys %$formdata) { + for my $k (keys %$formdata) { my $kenc = $k; my $venc = $formdata->{$k}; @@ -3468,7 +3468,7 @@ sub encode_www_form_urlencoded { sub nic_examples { my $examples = ""; my $separator = ""; - foreach my $s (sort keys %services) { + for my $s (sort keys %services) { my $subr = $services{$s}{'examples'}; my $example; @@ -3852,7 +3852,7 @@ EoEXAMPLE sub nic_dyndns1_update { debug("\nnic_dyndns1_update -------------------"); ## update each configured host - foreach my $h (@_) { + for my $h (@_) { my $ip = delete $config{$h}{'wantip'}; info("setting IP address to %s for %s", $ip, $h); verbose("UPDATE:", "updating %s", $h); @@ -3883,7 +3883,7 @@ sub nic_dyndns1_update { my @reply = split /\n/, $reply; my ($title, $return_code, $error_code) = ('', '', ''); - foreach my $line (@reply) { + for my $line (@reply) { $title = $1 if $line =~ m%\s*(.*)\s*%i; $return_code = $1 if $line =~ m%^return\s+code\s*:\s*(.*)\s*$%i; $error_code = $1 if $line =~ m%^error\s+code\s*:\s*(.*)\s*$%i; @@ -4002,14 +4002,14 @@ sub nic_dyndns2_update { ); ## update each set of hosts that had similar configurations - foreach my $sig (keys %groups) { + for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); my $h = $hosts[0]; my $ipv4 = $config{$h}{'wantipv4'}; my $ipv6 = $config{$h}{'wantipv6'}; - delete $config{$_}{'wantipv4'} foreach @hosts; - delete $config{$_}{'wantipv6'} foreach @hosts; + 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; @@ -4059,7 +4059,7 @@ sub nic_dyndns2_update { my @reply = split /\n/, $reply; my $state = 'header'; - foreach my $line (@reply) { + for my $line (@reply) { if ($state eq 'header') { $state = 'body'; @@ -4073,13 +4073,13 @@ sub nic_dyndns2_update { # we can't use the returned IP my ($status, $returnedips) = split / /, lc $line; - foreach my $h (@hosts) { + for my $h (@hosts) { $config{$h}{'status-ipv4'} = $status if $ipv4; $config{$h}{'status-ipv6'} = $status if $ipv6; } if ($status eq 'good') { - foreach my $h (@hosts) { + for my $h (@hosts) { $config{$h}{'ipv4'} = $ipv4 if $ipv4; $config{$h}{'ipv6'} = $ipv6 if $ipv6; $config{$h}{'mtime'} = $now; @@ -4092,7 +4092,7 @@ sub nic_dyndns2_update { if ($status eq 'nochg') { warning("updating %s: %s: %s", $hosts, $status, $errors{$status}); - foreach my $h (@hosts) { + for my $h (@hosts) { $config{$h}{'ipv4'} = $ipv4 if $ipv4; $config{$h}{'ipv6'} = $ipv6 if $ipv6; $config{$h}{'mtime'} = $now; @@ -4113,7 +4113,7 @@ sub nic_dyndns2_update { ($scale, $units) = (60*60, 'hours') if $units eq 'h'; $sec = $wait * $scale; - foreach my $h (@hosts) { + for my $h (@hosts) { $config{$h}{'wtime'} = $now + $sec; } @@ -4173,7 +4173,7 @@ sub nic_dnsexit2_update { debug("\nnic_dnsexit2_update -------------------"); ## Update each configured host (hosts cannot be grouped on this API) - foreach my $h (@_) { + for my $h (@_) { # All the known status my %status = ( '0' => [ 'good', 'Success! Actions got executed successfully.' ], @@ -4191,7 +4191,7 @@ sub nic_dnsexit2_update { # Updates for ipv4 and ipv6 need to be combined in a single API call, create Hash of Arrays for tracking my %total_payload; - foreach my $ip ($ipv4, $ipv6){ + for my $ip ($ipv4, $ipv6){ next if (!$ip); my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A'; @@ -4293,7 +4293,7 @@ sub nic_dnsexit2_update { if ($status eq 'good') { $config{$h}{'mtime'} = $now; my $tracked_ipv; - foreach $tracked_ipv ( keys %total_payload ){ + for $tracked_ipv ( keys %total_payload ){ $config{$h}{"ipv$tracked_ipv"} = $total_payload{$tracked_ipv}{content}; $config{$h}{"status-ipv$tracked_ipv"} = 'good'; success("%s", $message); @@ -4340,14 +4340,14 @@ sub nic_noip_update { ); ## update each set of hosts that had similar configurations - foreach my $sig (keys %groups) { + for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); my $h = $hosts[0]; my $ipv4 = $config{$h}{'wantipv4'}; my $ipv6 = $config{$h}{'wantipv6'}; - delete $config{$_}{'wantipv4'} foreach @hosts; - delete $config{$_}{'wantipv6'} foreach @hosts; + 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; @@ -4374,7 +4374,7 @@ sub nic_noip_update { my @reply = split /\n/, $reply; my $state = 'header'; - foreach my $line (@reply) { + for my $line (@reply) { if ($state eq 'header') { $state = 'body'; @@ -4387,7 +4387,7 @@ sub nic_noip_update { my ($status, $returnedips) = split / /, lc $line; my $h = shift @hosts; - foreach my $ip (split_by_comma($returnedips)) { + for my $ip (split_by_comma($returnedips)) { next if (!$ip); my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; $config{$h}{"status-ipv$ipv"} = $status; @@ -4395,7 +4395,7 @@ sub nic_noip_update { if ($status eq 'good') { $config{$h}{'mtime'} = $now; - foreach my $ip (split_by_comma($returnedips)) { + for my $ip (split_by_comma($returnedips)) { next if (!$ip); my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; $config{$h}{"ipv$ipv"} = $ip; @@ -4406,7 +4406,7 @@ sub nic_noip_update { if ($status eq 'nochg') { warning("updating %s: %s: %s", $h, $status, $errors{$status}); $config{$h}{'mtime'} = $now; - foreach my $ip (split_by_comma($returnedips)) { + for my $ip (split_by_comma($returnedips)) { next if (!$ip); my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; $config{$h}{"ipv$ipv"} = $ip; @@ -4499,7 +4499,7 @@ EoEXAMPLE sub nic_dslreports1_update { debug("\nnic_dslreports1_update -------------------"); ## update each configured host - foreach my $h (@_) { + for my $h (@_) { my $ip = delete $config{$h}{'wantip'}; info("setting IP address to %s for %s", $ip, $h); verbose("UPDATE:", "updating %s", $h); @@ -4524,7 +4524,7 @@ sub nic_dslreports1_update { my @reply = split /\n/, $reply; my $return_code = ''; - foreach my $line (@reply) { + for my $line (@reply) { $return_code = $1 if $line =~ m%^return\s+code\s*:\s*(.*)\s*$%i; } @@ -4581,7 +4581,7 @@ sub nic_domeneshop_update { ## update each configured host ## should improve to update in one pass - foreach my $h (@_) { + for my $h (@_) { my $ip = delete $config{$h}{'wantip'}; info("Setting IP address to %s for %s", $ip, $h); verbose("UPDATE:", "Updating %s", $h); @@ -4676,12 +4676,12 @@ sub nic_zoneedit1_update { my %groups = group_hosts_by([ @_ ], [ qw(login password server zone) ]); ## update each set of hosts that had similar configurations - foreach my $sig (keys %groups) { + for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); my $h = $hosts[0]; my $ip = $config{$h}{'wantip'}; - delete $config{$_}{'wantip'} foreach @hosts; + delete $config{$_}{'wantip'} for @hosts; info("setting IP address to %s for %s", $ip, $hosts); verbose("UPDATE:", "updating %s", $hosts); @@ -4705,7 +4705,7 @@ sub nic_zoneedit1_update { next if !header_ok($hosts, $reply); my @reply = split /\n/, $reply; - foreach my $line (@reply) { + for my $line (@reply) { if ($h && $line =~ /^[^<]*<(SUCCESS|ERROR)\s+([^>]+)>(.*)/) { my ($status, $assignments, $rest) = ($1, $2, $3); my ($left, %var) = parse_assignments($assignments); @@ -4823,14 +4823,14 @@ sub nic_easydns_update { ); ## update each set of hosts that had similar configurations - foreach my $sig (keys %groups) { + for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); my $h = $hosts[0]; my $ipv4 = $config{$h}{'wantipv4'}; my $ipv6 = $config{$h}{'wantipv6'}; - delete $config{$_}{'wantipv4'} foreach @hosts; - delete $config{$_}{'wantipv6'} foreach @hosts; + delete $config{$_}{'wantipv4'} for @hosts; + delete $config{$_}{'wantipv6'} for @hosts; info("setting IP address to %s %s for %s", $ipv4, $ipv6, $hosts); verbose("UPDATE:", "updating %s", $hosts); @@ -4842,7 +4842,7 @@ sub nic_easydns_update { $url .= "hostname=$hosts"; $url .= "&myip="; $url .= $ipv4 if $ipv4; - foreach my $ipv6a ($ipv6) { + for my $ipv6a ($ipv6) { $url .= "&myip="; $url .= $ipv6a } @@ -4867,7 +4867,7 @@ sub nic_easydns_update { my @reply = split /\n/, $reply; my $state = 'header'; - foreach my $line (@reply) { + for my $line (@reply) { if ($state eq 'header') { $state = 'body'; @@ -4958,7 +4958,7 @@ sub nic_namecheap_update { debug("\nnic_namecheap1_update -------------------"); ## update each configured host - foreach my $h (@_) { + for my $h (@_) { my $ip = delete $config{$h}{'wantip'}; info("setting IP address to %s for %s", $ip, $h); verbose("UPDATE:", "updating %s", $h); @@ -5150,7 +5150,7 @@ sub nic_nfsn_update { debug("\nnic_nfsn_update -------------------"); ## update each configured host - foreach my $h (@_) { + for my $h (@_) { my $zone = $config{$h}{'zone'}; my $name; @@ -5264,7 +5264,7 @@ EoEXAMPLE sub nic_njalla_update { debug("\nnic_njalla_update -------------------"); - foreach my $h (@_) { + for my $h (@_) { # Read input params my $ipv4 = delete $config{$h}{'wantipv4'}; my $ipv6 = delete $config{$h}{'wantipv6'}; @@ -5274,7 +5274,7 @@ sub nic_njalla_update { # Build url my $url = "https://$config{$h}{'server'}/update/?h=$h&k=$config{$h}{'password'}"; my $auto = 1; - foreach my $ip ($ipv4, $ipv6) { + for my $ip ($ipv4, $ipv6) { next if (!$ip); $auto = 0; my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; @@ -5375,7 +5375,7 @@ sub nic_sitelutions_update { debug("\nnic_sitelutions_update -------------------"); ## update each configured host - foreach my $h (@_) { + for my $h (@_) { my $ip = delete $config{$h}{'wantip'}; info("setting IP address to %s for %s", $ip, $h); verbose("UPDATE:", "updating %s", $h); @@ -5498,7 +5498,7 @@ sub nic_freedns_update { $record_list_error = "failed to get record list from $url_tmpl"; } - foreach my $h (@_) { + for my $h (@_) { next if (!$h); my $ipv4 = delete $config{$h}{'wantipv4'}; my $ipv6 = delete $config{$h}{'wantipv6'}; @@ -5511,7 +5511,7 @@ sub nic_freedns_update { } # IPv4 and IPv6 handling are similar enough to do in a loop... - foreach my $ip ($ipv4, $ipv6) { + for my $ip ($ipv4, $ipv6) { next if (!$ip); my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A'; @@ -5594,7 +5594,7 @@ EoEXAMPLE ###################################################################### sub nic_1984_update { debug("\nnic_1984_update -------------------"); - foreach my $host (@_) { + for my $host (@_) { my $ip = delete $config{$host}{'wantip'}; info("setting IP address to %s for %s", $ip, $host); verbose("UPDATE:", "updating %s", $host); @@ -5678,7 +5678,7 @@ sub nic_changeip_update { debug("\nnic_changeip_update -------------------"); ## update each configured host - foreach my $h (@_) { + for my $h (@_) { my $ip = delete $config{$h}{'wantip'}; info("setting IP address to %s for %s", $ip, $h); verbose("UPDATE:", "updating %s", $h); @@ -5763,7 +5763,7 @@ sub nic_godaddy_update { my %groups = group_hosts_by([ @_ ], [ qw(server login password zone) ]); ## update each set of hosts that had similar configurations - foreach my $sig (keys %groups) { + for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; # Update each set configured host. @@ -5774,7 +5774,7 @@ sub nic_godaddy_update { my $zone = $config{$host}{'zone'}; (my $hostname = $host) =~ s/\.\Q$zone\E$//; - foreach my $ip ($ipv4, $ipv6) { + for my $ip ($ipv4, $ipv6) { next if (!$ip); info("%s.%s -- Setting IP address to %s.", $hostname, $zone, $ip); @@ -5900,7 +5900,7 @@ sub nic_googledomains_update { my %groups = group_hosts_by([ @_ ], [ qw(server login password) ]); ## update each set of hosts that had similar configurations - foreach my $sig (keys %groups) { + for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $key = $hosts[0]; my $ip = $config{$key}{'wantip'}; @@ -5985,10 +5985,10 @@ sub nic_mythicdyn_update { debug("\nnic_mythicdyn_update --------------------"); # Update each configured host. - foreach my $h (@_) { + for my $h (@_) { info("%s -- Setting IP address.", $h); - foreach my $mythver ('4','6') { + for my $mythver ('4','6') { my $ip = $config{$h}{"wantipv$mythver"}; if (defined($ip)) { @@ -6080,7 +6080,7 @@ sub nic_nsupdate_update { my %groups = group_hosts_by([ @_ ], [ qw(login password server zone) ]); ## update each set of hosts that had similar configurations - foreach my $sig (keys %groups) { + for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); my $h = $hosts[0]; @@ -6092,8 +6092,8 @@ sub nic_nsupdate_update { my $zone = $config{$h}{'zone'}; my $ipv4 = $config{$h}{'wantipv4'}; my $ipv6 = $config{$h}{'wantipv6'}; - delete $config{$_}{'wantipv4'} foreach @hosts; - delete $config{$_}{'wantipv6'} foreach @hosts; + 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); @@ -6104,8 +6104,8 @@ sub nic_nsupdate_update { server $server zone $zone. EoINSTR1 - foreach (@hosts) { - foreach my $ip ($ipv4, $ipv6) { + for (@hosts) { + for my $ip ($ipv4, $ipv6) { next if (!$ip); my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A'; $instructions .= <<"EoINSTR2"; @@ -6125,9 +6125,9 @@ EoINSTR4 my $status = pipecmd($command, $instructions); if ($status eq 1) { - foreach (@hosts) { + for (@hosts) { $config{$_}{'mtime'} = $now; - foreach my $ip ($ipv4, $ipv6) { + for my $ip ($ipv4, $ipv6) { next if (!$ip); my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; $config{$_}{"ipv$ipv"} = $ip; @@ -6136,7 +6136,7 @@ EoINSTR4 } } } else { - foreach (@hosts) { + for (@hosts) { failed("updating %s", $_); } } @@ -6197,7 +6197,7 @@ sub nic_cloudflare_update { my %groups = group_hosts_by([ @_ ], [ qw(ssh login password server wildcard mx backupmx zone) ]); ## update each set of hosts that had similar configurations - foreach my $sig (keys %groups) { + for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); my $key = $hosts[0]; @@ -6248,7 +6248,7 @@ sub nic_cloudflare_update { # IPv4 and IPv6 handling are similar enough to do in a loop... - foreach my $ip ($ipv4, $ipv6) { + for my $ip ($ipv4, $ipv6) { next if (!$ip); my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A'; @@ -6345,7 +6345,7 @@ sub nic_hetzner_update { my %groups = group_hosts_by([ @_ ], [ qw(ssh login password server wildcard mx backupmx zone) ]); ## update each set of hosts that had similar configurations - foreach my $sig (keys %groups) { + for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); my $key = $hosts[0]; @@ -6391,7 +6391,7 @@ sub nic_hetzner_update { # IPv4 and IPv6 handling are similar enough to do in a loop... - foreach my $ip ($ipv4, $ipv6) { + for my $ip ($ipv4, $ipv6) { next if (!$ip); my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A'; @@ -6502,7 +6502,7 @@ sub nic_yandex_update { my %groups = group_hosts_by([ @_ ], [ qw(server login pasword) ]); ## update each set of hosts that had similar configurations - foreach my $sig (keys %groups) { + for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $key = $hosts[0]; my $ip = $config{$key}{'wantip'}; @@ -6617,7 +6617,7 @@ sub nic_duckdns_update { ## update each configured host ## should improve to update in one pass - foreach my $h (@_) { + for my $h (@_) { my $ipv4 = delete $config{$h}{'wantipv4'}; my $ipv6 = delete $config{$h}{'wantipv6'}; info("setting IPv4 address to %s for %s", $ipv4, $h) if $ipv4; @@ -6648,7 +6648,7 @@ sub nic_duckdns_update { my $state = 'noresult'; my $line = ''; - foreach $line (@reply) { + for $line (@reply) { if ($line eq 'OK') { $config{$h}{'ipv4'} = $ipv4 if $ipv4; $config{$h}{'ipv6'} = $ipv6 if $ipv6; @@ -6707,7 +6707,7 @@ EoEXAMPLE sub nic_freemyip_update { debug("\nnic_freemyip_update -------------------"); - foreach my $h (@_) { + for my $h (@_) { my $ip = delete $config{$h}{'wantip'}; info("setting IP address to %s for %s", $ip, $h); verbose("UPDATE:", "updating %s", $h); @@ -6864,7 +6864,7 @@ sub nic_woima_update { my $state = 'header'; my $returnedip = $ip; - foreach my $line (@reply) { + for my $line (@reply) { if ($state eq 'header') { $state = 'body'; @@ -6950,7 +6950,7 @@ sub nic_dondominio_update { ## update each configured host ## should improve to update in one pass - foreach my $h (@_) { + for my $h (@_) { my $ip = delete $config{$h}{'wantip'}; info("setting IP address to %s for %s", $ip, $h); verbose("UPDATE:", "updating %s", $h); @@ -7040,7 +7040,7 @@ sub nic_dnsmadeeasy_update { ## update each configured host ## should improve to update in one pass - foreach my $h (@_) { + for my $h (@_) { my $ip = delete $config{$h}{'wantip'}; info("Setting IP address to %s for %s", $ip, $h); verbose("UPDATE:", "Updating %s", $h); @@ -7114,7 +7114,7 @@ sub nic_ovh_update { ## update each configured host ## should improve to update in one pass - foreach my $h (@_) { + for my $h (@_) { my $ip = delete $config{$h}{'wantip'}; info("setting IP address to %s for %s", $ip, $h); verbose("UPDATE:","updating %s", $h); @@ -7235,7 +7235,7 @@ EoEXAMPLE sub nic_porkbun_update { debug("\nnic_porkbun_update -------------------"); - foreach my $host (@_) { + for my $host (@_) { my ($sub_domain, $domain); if ($config{$host}{'root-domain'} ne '') { # Process 'root-domain' option @@ -7262,7 +7262,7 @@ sub nic_porkbun_update { } info("subdomain %s, root domain %s", $sub_domain, $domain) if $sub_domain ne ''; - foreach my $ipv ('ipv4', 'ipv6') { + for my $ipv ('ipv4', 'ipv6') { my $ip = delete $config{$host}{"want$ipv"}; if (!$ip) { next; @@ -7547,8 +7547,8 @@ EoEXAMPLE sub nic_gandi_update { debug("\nnic_gandi_update -------------------"); # Update each set configured host. - foreach my $h (@_) { - foreach my $ipv ('ipv4', 'ipv6') { + for my $h (@_) { + for my $ipv ('ipv4', 'ipv6') { my $ip = delete $config{$h}{"want$ipv"}; if(!$ip) { next; @@ -7676,7 +7676,7 @@ sub nic_keysystems_update { ## update each configured host ## should improve to update in one pass - foreach my $h (@_) { + for my $h (@_) { my $ip = delete $config{$h}{'wantip'}; info("KEYSYSTEMS setting IP address to %s for %s", $ip, $h); @@ -7812,7 +7812,7 @@ EoEXAMPLE sub nic_enom_update { debug("\nenom_update -------------------"); ## update each configured host - foreach my $h (@_) { + for my $h (@_) { my $ip = delete $config{$h}{'wantip'}; info("setting IP address to %s for %s", $ip, $h); verbose("UPDATE:","updating %s", $h); @@ -7956,7 +7956,7 @@ sub nic_digitalocean_update_one { sub nic_digitalocean_update { debug("\nnic_digitalocean_update -------------------"); - foreach my $h (@_) { + for my $h (@_) { my $ipv4 = delete $config{$h}{'wantipv4'}; my $ipv6 = delete $config{$h}{'wantipv6'}; @@ -8024,9 +8024,9 @@ EoEXAMPLE sub nic_infomaniak_update { debug("\nnic_infomaniak_update -------------------"); - foreach my $h (@_) { + for my $h (@_) { INFOMANIAK_IP_LOOP: - foreach my $v (4, 6) { + for my $v (4, 6) { my $ip = delete $config{$h}{"wantipv$v"}; if (!defined $ip) { @@ -8062,7 +8062,7 @@ sub nic_infomaniak_update { my $reply; - foreach my $url ($url1, $url2) { + for my $url ($url1, $url2) { info("trying update with %s", $url); $reply = geturl(proxy => opt('proxy'), url => $url); if (!defined($reply) || !$reply) { From 0040fc96082428bbabceb9fdd6391dc1a5043248 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Mon, 27 May 2024 18:10:55 -0400 Subject: [PATCH 004/150] dnsexit2: Whitespace fixes --- ddclient.in | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/ddclient.in b/ddclient.in index 1313ba7..e46f373 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4176,14 +4176,14 @@ sub nic_dnsexit2_update { for my $h (@_) { # All the known status my %status = ( - '0' => [ 'good', 'Success! Actions got executed successfully.' ], - '1' => [ 'warning', 'Some execution problems. May not indicate actions failures. Some action may got executed fine and some may have problems.' ], - '2' => [ 'badauth', 'API Key Authentication Error. The API Key is missing or wrong.' ], - '3' => [ 'error', 'Missing Required Definitions. Your JSON file may missing some required definitions.' ], - '4' => [ 'error', 'JSON Data Syntax Error. Your JSON file has syntax error.' ], - '5' => [ 'error', 'JSON Defined Record Type not Supported. Your JSON may try to update some record type not supported by our system.' ], - '6' => [ 'error', 'System Error. Our system problem. May not be your problem. Contact our support if you got such error.' ], - '7' => [ 'error', 'Error getting post data. Our server has problem to receive your JSON posting.' ], + '0' => ['good', 'Success! Actions got executed successfully.'], + '1' => ['warning', 'Some execution problems. May not indicate actions failures. Some action may got executed fine and some may have problems.'], + '2' => ['badauth', 'API Key Authentication Error. The API Key is missing or wrong.'], + '3' => ['error', 'Missing Required Definitions. Your JSON file may missing some required definitions.'], + '4' => ['error', 'JSON Data Syntax Error. Your JSON file has syntax error.'], + '5' => ['error', 'JSON Defined Record Type not Supported. Your JSON may try to update some record type not supported by our system.'], + '6' => ['error', 'System Error. Our system problem. May not be your problem. Contact our support if you got such error.'], + '7' => ['error', 'Error getting post data. Our server has problem to receive your JSON posting.'], ); my $ipv4 = delete $config{$h}{'wantipv4'}; my $ipv6 = delete $config{$h}{'wantipv6'}; @@ -4191,9 +4191,9 @@ sub nic_dnsexit2_update { # Updates for ipv4 and ipv6 need to be combined in a single API call, create Hash of Arrays for tracking my %total_payload; - for my $ip ($ipv4, $ipv6){ + for my $ip ($ipv4, $ipv6) { next if (!$ip); - my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; + my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A'; info("Going to update IPv$ipv address to %s for %s.", $ip, $h); @@ -4212,7 +4212,7 @@ sub nic_dnsexit2_update { my $header = "Content-Type: application/json\nAccept: application/json"; # Set the zone if empty - if ( not defined $config{$h}{'zone'}){ + if (not defined $config{$h}{'zone'}) { debug("Zone not defined, setting to default hostname: %s", $h); $config{$h}{'zone'} = $h } else { @@ -4229,15 +4229,15 @@ sub nic_dnsexit2_update { # Make the call my $reply = geturl( - proxy => opt('proxy'), - url => $url, - headers => $header, - method => 'POST', - data => $data + proxy => opt('proxy'), + url => $url, + headers => $header, + method => 'POST', + data => $data ); # No reply, declare as failed - unless ($reply && header_ok($h, $reply)){ + unless ($reply && header_ok($h, $reply)) { failed("updating %s: Could not connect to %s%s.", $h, $config{$h}{'server'}, $config{$h}{'path'}); last; }; @@ -4249,7 +4249,7 @@ sub nic_dnsexit2_update { debug("HTTP response code: %s", $http_status); # If not 200, bail - if ( $http_status ne '200' ){ + if ($http_status ne '200') { failed("Failed to update Host\n%s", $h); failed("HTTP response code\n%s", $http_status); failed("Full reply\n%s", $reply) unless opt('verbose'); @@ -4262,8 +4262,7 @@ sub nic_dnsexit2_update { debug("%s", $strip_status); if ($strip_status) { debug("HTTP headers are stripped."); - } - else { + } else { warning("Unexpected: no HTTP headers stripped!"); } @@ -4274,16 +4273,16 @@ sub nic_dnsexit2_update { if (defined($response->{'code'}) and defined($response->{'message'})) { if (exists $status{$response->{'code'}}) { # Add the server response data to the applicable array - push( @{ $status {$response->{'code'} } }, $response->{'message'}); + push(@{$status{$response->{'code'}}}, $response->{'message'}); if (defined($response->{'details'})) { - push ( @{ $status {$response->{'code'} } }, $response->{'details'}[0]); + push(@{$status{$response->{'code'}}}, $response->{'details'}[0]); } else { # Keep it symmetrical for simplicity - push ( @{ $status {$response->{'code'} } }, "no details received"); + push(@{$status{$response->{'code'}}}, "no details received"); } # Set data from array - my ($status, $message, $srv_message, $srv_details) = @{ $status {$response->{'code'} } }; + my ($status, $message, $srv_message, $srv_details) = @{$status{$response->{'code'}}}; info("Status: %s -- Message: %s", $status, $message); info("Server Message: %s -- Server Details: %s", $srv_message, $srv_details); $config{$h}{'status-ipv4'} = $status if $ipv4; @@ -4291,9 +4290,9 @@ sub nic_dnsexit2_update { # Handle statuses if ($status eq 'good') { - $config{$h}{'mtime'} = $now; + $config{$h}{'mtime'} = $now; my $tracked_ipv; - for $tracked_ipv ( keys %total_payload ){ + for $tracked_ipv (keys %total_payload) { $config{$h}{"ipv$tracked_ipv"} = $total_payload{$tracked_ipv}{content}; $config{$h}{"status-ipv$tracked_ipv"} = 'good'; success("%s", $message); @@ -4318,6 +4317,7 @@ sub nic_dnsexit2_update { } } } + ###################################################################### ## nic_noip_update ## Note: uses same features as nic_dyndns2_update, less return codes From 63989d96fb549f04251992b834bbd5dcb852311e Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 29 May 2024 17:00:30 -0400 Subject: [PATCH 005/150] dnsexit2: Move variable declaration to loop scope --- ddclient.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index e46f373..235628c 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4291,8 +4291,7 @@ sub nic_dnsexit2_update { # Handle statuses if ($status eq 'good') { $config{$h}{'mtime'} = $now; - my $tracked_ipv; - for $tracked_ipv (keys %total_payload) { + for my $tracked_ipv (keys %total_payload) { $config{$h}{"ipv$tracked_ipv"} = $total_payload{$tracked_ipv}{content}; $config{$h}{"status-ipv$tracked_ipv"} = 'good'; success("%s", $message); From 6cdf5da9f455b928b411c3ffd918953f168da031 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 29 May 2024 17:05:54 -0400 Subject: [PATCH 006/150] dnsexit2: Rename variable for brevity --- ddclient.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ddclient.in b/ddclient.in index 235628c..2908849 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4291,11 +4291,11 @@ sub nic_dnsexit2_update { # Handle statuses if ($status eq 'good') { $config{$h}{'mtime'} = $now; - for my $tracked_ipv (keys %total_payload) { - $config{$h}{"ipv$tracked_ipv"} = $total_payload{$tracked_ipv}{content}; - $config{$h}{"status-ipv$tracked_ipv"} = 'good'; + for my $ipv (keys %total_payload) { + $config{$h}{"ipv$ipv"} = $total_payload{$ipv}{content}; + $config{$h}{"status-ipv$ipv"} = 'good'; success("%s", $message); - success("Updated %s successfully to IPv$tracked_ipv address %s at time %s", $h, $total_payload{$tracked_ipv}{content}, prettytime($config{$h}{'mtime'})); + success("Updated %s successfully to IPv$ipv address %s at time %s", $h, $total_payload{$ipv}{content}, prettytime($config{$h}{'mtime'})); } } elsif ($status eq 'warning') { warning("%s", $message); From 3a5e86b4d237beed85ea2e764036996d11aafeb7 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 29 May 2024 17:10:06 -0400 Subject: [PATCH 007/150] dnsexit2: Convert string interpolation to `sprintf` format specifier Rationale: * For consistency. * It's generally not a good idea to mix interpolation with `sprintf` because the interpolated string itself might coincidentally (or maliciously) contain format specifiers. --- ddclient.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index 2908849..8055787 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4196,7 +4196,7 @@ sub nic_dnsexit2_update { my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A'; - info("Going to update IPv$ipv address to %s for %s.", $ip, $h); + info("Going to update IPv%s address to %s for %s.", $ipv, $ip, $h); $config{$h}{'status-ipv$ipv'} = 'failed'; $total_payload{$ipv} = { name => $h, @@ -4295,7 +4295,7 @@ sub nic_dnsexit2_update { $config{$h}{"ipv$ipv"} = $total_payload{$ipv}{content}; $config{$h}{"status-ipv$ipv"} = 'good'; success("%s", $message); - success("Updated %s successfully to IPv$ipv address %s at time %s", $h, $total_payload{$ipv}{content}, prettytime($config{$h}{'mtime'})); + success("Updated %s successfully to IPv%s address %s at time %s", $h, $ipv, $total_payload{$ipv}{content}, prettytime($config{$h}{'mtime'})); } } elsif ($status eq 'warning') { warning("%s", $message); From 9b1a785c6dcda0822ee0d83f887c2d9171f84f88 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 29 May 2024 17:15:24 -0400 Subject: [PATCH 008/150] dnsexit2: Don't repeat the same success message --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 8055787..6e23bdb 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4290,11 +4290,11 @@ sub nic_dnsexit2_update { # Handle statuses if ($status eq 'good') { + success("%s", $message); $config{$h}{'mtime'} = $now; for my $ipv (keys %total_payload) { $config{$h}{"ipv$ipv"} = $total_payload{$ipv}{content}; $config{$h}{"status-ipv$ipv"} = 'good'; - success("%s", $message); success("Updated %s successfully to IPv%s address %s at time %s", $h, $ipv, $total_payload{$ipv}{content}, prettytime($config{$h}{'mtime'})); } } elsif ($status eq 'warning') { From 3e91fd02bf2ef49ef93f88324375229559a7e1ce Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 29 May 2024 17:21:39 -0400 Subject: [PATCH 009/150] dnsexit2: Invert conditions to improve readability Instead of: if ($success1) { if ($success2) { if ($success3) { # yay } else { # fail } } else { # fail } } else { # fail } do: if (!$success1) { # fail } if (!$success2) { # fail } if (!$success3) { # fail } # yay --- ddclient.in | 82 +++++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/ddclient.in b/ddclient.in index 6e23bdb..ee2068e 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4270,49 +4270,51 @@ sub nic_dnsexit2_update { my $response = decode_json($reply); # It should at least have a 'code' and 'message'. - if (defined($response->{'code'}) and defined($response->{'message'})) { - if (exists $status{$response->{'code'}}) { - # Add the server response data to the applicable array - push(@{$status{$response->{'code'}}}, $response->{'message'}); - if (defined($response->{'details'})) { - push(@{$status{$response->{'code'}}}, $response->{'details'}[0]); - } else { - # Keep it symmetrical for simplicity - push(@{$status{$response->{'code'}}}, "no details received"); - } - - # Set data from array - my ($status, $message, $srv_message, $srv_details) = @{$status{$response->{'code'}}}; - info("Status: %s -- Message: %s", $status, $message); - info("Server Message: %s -- Server Details: %s", $srv_message, $srv_details); - $config{$h}{'status-ipv4'} = $status if $ipv4; - $config{$h}{'status-ipv6'} = $status if $ipv6; - - # Handle statuses - if ($status eq 'good') { - success("%s", $message); - $config{$h}{'mtime'} = $now; - for my $ipv (keys %total_payload) { - $config{$h}{"ipv$ipv"} = $total_payload{$ipv}{content}; - $config{$h}{"status-ipv$ipv"} = 'good'; - success("Updated %s successfully to IPv%s address %s at time %s", $h, $ipv, $total_payload{$ipv}{content}, prettytime($config{$h}{'mtime'})); - } - } elsif ($status eq 'warning') { - warning("%s", $message); - warning("Server response: %s", $srv_message); - } elsif ($status =~ m'^(badauth|error)$') { - failed("%s", $message); - failed("Server response: %s", $srv_message); - } else { - failed("This should not be possible"); - } - } else { - failed("Status code %s is unknown!", $response->{'code'}); - } - } else { + if (!defined($response->{'code'}) || !defined($response->{'message'})) { failed("Did not receive expected \"code\" and \"message\" keys in server response."); failed("Response:"); failed("%s", $response); + next; + } + if (!exists($status{$response->{'code'}})) { + failed("Status code %s is unknown!", $response->{'code'}); + next; + } + # Add the server response data to the applicable array + push(@{$status{$response->{'code'}}}, $response->{'message'}); + if (defined($response->{'details'})) { + push(@{$status{$response->{'code'}}}, $response->{'details'}[0]); + } else { + # Keep it symmetrical for simplicity + push(@{$status{$response->{'code'}}}, "no details received"); + } + + # Set data from array + my ($status, $message, $srv_message, $srv_details) = @{$status{$response->{'code'}}}; + info("Status: %s -- Message: %s", $status, $message); + info("Server Message: %s -- Server Details: %s", $srv_message, $srv_details); + $config{$h}{'status-ipv4'} = $status if $ipv4; + $config{$h}{'status-ipv6'} = $status if $ipv6; + + # Handle statuses + if ($status ne 'good') { + if ($status eq 'warning') { + warning("%s", $message); + warning("Server response: %s", $srv_message); + } elsif ($status =~ m'^(badauth|error)$') { + failed("%s", $message); + failed("Server response: %s", $srv_message); + } else { + failed("This should not be possible"); + } + next; + } + success("%s", $message); + $config{$h}{'mtime'} = $now; + for my $ipv (keys %total_payload) { + $config{$h}{"ipv$ipv"} = $total_payload{$ipv}{content}; + $config{$h}{"status-ipv$ipv"} = 'good'; + success("Updated %s successfully to IPv%s address %s at time %s", $h, $ipv, $total_payload{$ipv}{content}, prettytime($config{$h}{'mtime'})); } } } From ed7f4a68a4a77895ba7c37670c2225599456aa81 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 29 May 2024 17:34:51 -0400 Subject: [PATCH 010/150] dnsexit2: Rewrite circuitous variable assignments --- ddclient.in | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/ddclient.in b/ddclient.in index ee2068e..468817f 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4280,19 +4280,10 @@ sub nic_dnsexit2_update { failed("Status code %s is unknown!", $response->{'code'}); next; } - # Add the server response data to the applicable array - push(@{$status{$response->{'code'}}}, $response->{'message'}); - if (defined($response->{'details'})) { - push(@{$status{$response->{'code'}}}, $response->{'details'}[0]); - } else { - # Keep it symmetrical for simplicity - push(@{$status{$response->{'code'}}}, "no details received"); - } - - # Set data from array - my ($status, $message, $srv_message, $srv_details) = @{$status{$response->{'code'}}}; + my ($status, $message) = @{$status{$response->{'code'}}}; info("Status: %s -- Message: %s", $status, $message); - info("Server Message: %s -- Server Details: %s", $srv_message, $srv_details); + info("Server Message: %s -- Server Details: %s", $response->{'message'}, + defined($response->{'details'}) ? $response->{'details'}[0] : "no details received"); $config{$h}{'status-ipv4'} = $status if $ipv4; $config{$h}{'status-ipv6'} = $status if $ipv6; @@ -4300,10 +4291,10 @@ sub nic_dnsexit2_update { if ($status ne 'good') { if ($status eq 'warning') { warning("%s", $message); - warning("Server response: %s", $srv_message); + warning("Server response: %s", $response->{'message'}); } elsif ($status =~ m'^(badauth|error)$') { failed("%s", $message); - failed("Server response: %s", $srv_message); + failed("Server response: %s", $response->{'message'}); } else { failed("This should not be possible"); } From df81075e4991588c898eadb2f9c85320cb5725b7 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 29 May 2024 17:39:29 -0400 Subject: [PATCH 011/150] dnsexit2: Rename variable to avoid confusion --- ddclient.in | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ddclient.in b/ddclient.in index 468817f..b1dcaab 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4174,8 +4174,7 @@ sub nic_dnsexit2_update { ## Update each configured host (hosts cannot be grouped on this API) for my $h (@_) { - # All the known status - my %status = ( + my %codemeaning = ( '0' => ['good', 'Success! Actions got executed successfully.'], '1' => ['warning', 'Some execution problems. May not indicate actions failures. Some action may got executed fine and some may have problems.'], '2' => ['badauth', 'API Key Authentication Error. The API Key is missing or wrong.'], @@ -4276,11 +4275,11 @@ sub nic_dnsexit2_update { failed("%s", $response); next; } - if (!exists($status{$response->{'code'}})) { + if (!exists($codemeaning{$response->{'code'}})) { failed("Status code %s is unknown!", $response->{'code'}); next; } - my ($status, $message) = @{$status{$response->{'code'}}}; + my ($status, $message) = @{$codemeaning{$response->{'code'}}}; info("Status: %s -- Message: %s", $status, $message); info("Server Message: %s -- Server Details: %s", $response->{'message'}, defined($response->{'details'}) ? $response->{'details'}[0] : "no details received"); From 61d34a9157a480f64a50731041689dc97f1d8d3d Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 29 May 2024 17:37:15 -0400 Subject: [PATCH 012/150] dnsexit2: Move code meanings closer to where it is used --- ddclient.in | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ddclient.in b/ddclient.in index b1dcaab..068fe7e 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4174,16 +4174,6 @@ sub nic_dnsexit2_update { ## Update each configured host (hosts cannot be grouped on this API) for my $h (@_) { - my %codemeaning = ( - '0' => ['good', 'Success! Actions got executed successfully.'], - '1' => ['warning', 'Some execution problems. May not indicate actions failures. Some action may got executed fine and some may have problems.'], - '2' => ['badauth', 'API Key Authentication Error. The API Key is missing or wrong.'], - '3' => ['error', 'Missing Required Definitions. Your JSON file may missing some required definitions.'], - '4' => ['error', 'JSON Data Syntax Error. Your JSON file has syntax error.'], - '5' => ['error', 'JSON Defined Record Type not Supported. Your JSON may try to update some record type not supported by our system.'], - '6' => ['error', 'System Error. Our system problem. May not be your problem. Contact our support if you got such error.'], - '7' => ['error', 'Error getting post data. Our server has problem to receive your JSON posting.'], - ); my $ipv4 = delete $config{$h}{'wantipv4'}; my $ipv6 = delete $config{$h}{'wantipv6'}; @@ -4275,6 +4265,16 @@ sub nic_dnsexit2_update { failed("%s", $response); next; } + my %codemeaning = ( + '0' => ['good', 'Success! Actions got executed successfully.'], + '1' => ['warning', 'Some execution problems. May not indicate actions failures. Some action may got executed fine and some may have problems.'], + '2' => ['badauth', 'API Key Authentication Error. The API Key is missing or wrong.'], + '3' => ['error', 'Missing Required Definitions. Your JSON file may missing some required definitions.'], + '4' => ['error', 'JSON Data Syntax Error. Your JSON file has syntax error.'], + '5' => ['error', 'JSON Defined Record Type not Supported. Your JSON may try to update some record type not supported by our system.'], + '6' => ['error', 'System Error. Our system problem. May not be your problem. Contact our support if you got such error.'], + '7' => ['error', 'Error getting post data. Our server has problem to receive your JSON posting.'], + ); if (!exists($codemeaning{$response->{'code'}})) { failed("Status code %s is unknown!", $response->{'code'}); next; From 18007dda8afd10cf6bb4817941d4f5a648b4cbe1 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 29 May 2024 17:51:00 -0400 Subject: [PATCH 013/150] dnsexit2: Delete unnecessary comments Comments should only be used if the code is doing something subtle/tricky/non-obvious/unusual, otherwise they're distracting and might get out of sync with the code (in which case it becomes difficult to tell if it's the comment or the code that's wrong). If the code is difficult to understand without comments but is doing something ordinary, then the code needs to be modified to improve readability. --- ddclient.in | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/ddclient.in b/ddclient.in index 068fe7e..b534b69 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4194,29 +4194,20 @@ sub nic_dnsexit2_update { ttl => $config{$h}{'ttl'}, }; }; - # Set the URL of the API endpoint my $url = "https://$config{$h}{'server'}$config{$h}{'path'}"; - - # Set additional headers my $header = "Content-Type: application/json\nAccept: application/json"; - - # Set the zone if empty if (not defined $config{$h}{'zone'}) { debug("Zone not defined, setting to default hostname: %s", $h); $config{$h}{'zone'} = $h } else { debug("Zone is: %s", $config{$h}{'zone'}); } - - # Build total JSON payload my @payload_values = values %total_payload; my $data = encode_json({ apikey => $config{$h}{'password'}, domain => $config{$h}{'zone'}, update => \@payload_values }); - - # Make the call my $reply = geturl( proxy => opt('proxy'), url => $url, @@ -4224,20 +4215,13 @@ sub nic_dnsexit2_update { method => 'POST', data => $data ); - - # No reply, declare as failed unless ($reply && header_ok($h, $reply)) { failed("updating %s: Could not connect to %s%s.", $h, $config{$h}{'server'}, $config{$h}{'path'}); last; }; - - # Reply found debug("%s", $reply); - # Extract the HTTP response code (my $http_status) = ($reply =~ m%^s*HTTP/.*\s+(\d+)%i); debug("HTTP response code: %s", $http_status); - - # If not 200, bail if ($http_status ne '200') { failed("Failed to update Host\n%s", $h); failed("HTTP response code\n%s", $http_status); @@ -4254,11 +4238,7 @@ sub nic_dnsexit2_update { } else { warning("Unexpected: no HTTP headers stripped!"); } - - # Decode the remaining reply, it should be JSON. my $response = decode_json($reply); - - # It should at least have a 'code' and 'message'. if (!defined($response->{'code'}) || !defined($response->{'message'})) { failed("Did not receive expected \"code\" and \"message\" keys in server response."); failed("Response:"); @@ -4285,8 +4265,6 @@ sub nic_dnsexit2_update { defined($response->{'details'}) ? $response->{'details'}[0] : "no details received"); $config{$h}{'status-ipv4'} = $status if $ipv4; $config{$h}{'status-ipv6'} = $status if $ipv6; - - # Handle statuses if ($status ne 'good') { if ($status eq 'warning') { warning("%s", $message); From 40d1bc8e51c6da5868dacc21d2177b33c7f6eded Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 28 May 2024 03:08:30 -0400 Subject: [PATCH 014/150] dnsexit2: Clarify comments --- ddclient.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index b534b69..87aa995 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4172,12 +4172,12 @@ EoEXAMPLE sub nic_dnsexit2_update { debug("\nnic_dnsexit2_update -------------------"); - ## Update each configured host (hosts cannot be grouped on this API) + # The DNSExit API does not support updating multiple hosts at a time. for my $h (@_) { my $ipv4 = delete $config{$h}{'wantipv4'}; my $ipv6 = delete $config{$h}{'wantipv6'}; - # Updates for ipv4 and ipv6 need to be combined in a single API call, create Hash of Arrays for tracking + # The IPv4 and IPv6 addresses must be updated together in a single API call. my %total_payload; for my $ip ($ipv4, $ipv6) { From 7c4fe28babc5c94157a80e097f8575c4c2d5e77f Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 29 May 2024 17:57:20 -0400 Subject: [PATCH 015/150] dnsexit2: Simplify IP version loop --- ddclient.in | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/ddclient.in b/ddclient.in index 87aa995..019dc42 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4174,17 +4174,11 @@ sub nic_dnsexit2_update { # The DNSExit API does not support updating multiple hosts at a time. for my $h (@_) { - my $ipv4 = delete $config{$h}{'wantipv4'}; - my $ipv6 = delete $config{$h}{'wantipv6'}; - # The IPv4 and IPv6 addresses must be updated together in a single API call. my %total_payload; - - for my $ip ($ipv4, $ipv6) { - next if (!$ip); - my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; - my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A'; - + for my $ipv ('4', '6') { + my $ip = delete($config{$h}{"wantipv$ipv"}) or next; + my $type = ($ipv eq '6') ? 'AAAA' : 'A'; info("Going to update IPv%s address to %s for %s.", $ipv, $ip, $h); $config{$h}{'status-ipv$ipv'} = 'failed'; $total_payload{$ipv} = { @@ -4263,8 +4257,8 @@ sub nic_dnsexit2_update { info("Status: %s -- Message: %s", $status, $message); info("Server Message: %s -- Server Details: %s", $response->{'message'}, defined($response->{'details'}) ? $response->{'details'}[0] : "no details received"); - $config{$h}{'status-ipv4'} = $status if $ipv4; - $config{$h}{'status-ipv6'} = $status if $ipv6; + $config{$h}{'status-ipv4'} = $status if $total_payload{'4'}; + $config{$h}{'status-ipv6'} = $status if $total_payload{'6'}; if ($status ne 'good') { if ($status eq 'warning') { warning("%s", $message); From 4804e15c127458619286c1e9a88d8964ba5004bb Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 29 May 2024 18:00:29 -0400 Subject: [PATCH 016/150] dnsexit2: Add final comma after last list item for consistency, and to avoid bugs if additional items are added or the items are reordered. --- ddclient.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index 019dc42..915eb3e 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4200,14 +4200,14 @@ sub nic_dnsexit2_update { my $data = encode_json({ apikey => $config{$h}{'password'}, domain => $config{$h}{'zone'}, - update => \@payload_values + update => \@payload_values, }); my $reply = geturl( proxy => opt('proxy'), url => $url, headers => $header, method => 'POST', - data => $data + data => $data, ); unless ($reply && header_ok($h, $reply)) { failed("updating %s: Could not connect to %s%s.", $h, $config{$h}{'server'}, $config{$h}{'path'}); From 2bf6d348b088daf4abef16089e794af97ccf506f Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 29 May 2024 18:05:37 -0400 Subject: [PATCH 017/150] dnsexit2: Reuse the `$url` variable --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 915eb3e..c0572b7 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4210,7 +4210,7 @@ sub nic_dnsexit2_update { data => $data, ); unless ($reply && header_ok($h, $reply)) { - failed("updating %s: Could not connect to %s%s.", $h, $config{$h}{'server'}, $config{$h}{'path'}); + failed("updating %s: Could not connect to %s%s.", $h, $url); last; }; debug("%s", $reply); From 6c89eaf4acb6a914a587700852d8eb180119388a Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 29 May 2024 18:12:23 -0400 Subject: [PATCH 018/150] dnsexit2: Build updates array directly, not hash to array to improve readability, and to make it easier to use `group_hosts_by()` in the future to update multiple hosts at the same time. --- ddclient.in | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/ddclient.in b/ddclient.in index c0572b7..859cbd5 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4175,18 +4175,20 @@ sub nic_dnsexit2_update { # The DNSExit API does not support updating multiple hosts at a time. for my $h (@_) { # The IPv4 and IPv6 addresses must be updated together in a single API call. - my %total_payload; + my %ips; + my @updates; for my $ipv ('4', '6') { my $ip = delete($config{$h}{"wantipv$ipv"}) or next; + $ips{$ipv} = $ip; my $type = ($ipv eq '6') ? 'AAAA' : 'A'; info("Going to update IPv%s address to %s for %s.", $ipv, $ip, $h); $config{$h}{'status-ipv$ipv'} = 'failed'; - $total_payload{$ipv} = { + push(@updates, { name => $h, type => $type, content => $ip, ttl => $config{$h}{'ttl'}, - }; + }); }; my $url = "https://$config{$h}{'server'}$config{$h}{'path'}"; my $header = "Content-Type: application/json\nAccept: application/json"; @@ -4196,11 +4198,10 @@ sub nic_dnsexit2_update { } else { debug("Zone is: %s", $config{$h}{'zone'}); } - my @payload_values = values %total_payload; my $data = encode_json({ apikey => $config{$h}{'password'}, domain => $config{$h}{'zone'}, - update => \@payload_values, + update => \@updates, }); my $reply = geturl( proxy => opt('proxy'), @@ -4257,8 +4258,8 @@ sub nic_dnsexit2_update { info("Status: %s -- Message: %s", $status, $message); info("Server Message: %s -- Server Details: %s", $response->{'message'}, defined($response->{'details'}) ? $response->{'details'}[0] : "no details received"); - $config{$h}{'status-ipv4'} = $status if $total_payload{'4'}; - $config{$h}{'status-ipv6'} = $status if $total_payload{'6'}; + $config{$h}{'status-ipv4'} = $status if $ips{'4'}; + $config{$h}{'status-ipv6'} = $status if $ips{'6'}; if ($status ne 'good') { if ($status eq 'warning') { warning("%s", $message); @@ -4273,10 +4274,11 @@ sub nic_dnsexit2_update { } success("%s", $message); $config{$h}{'mtime'} = $now; - for my $ipv (keys %total_payload) { - $config{$h}{"ipv$ipv"} = $total_payload{$ipv}{content}; + keys(%ips); # Reset internal iterator. + while (my ($ipv, $ip) = each(%ips)) { + $config{$h}{"ipv$ipv"} = $ip; $config{$h}{"status-ipv$ipv"} = 'good'; - success("Updated %s successfully to IPv%s address %s at time %s", $h, $ipv, $total_payload{$ipv}{content}, prettytime($config{$h}{'mtime'})); + success("Updated %s successfully to IPv%s address %s at time %s", $h, $ipv, $ip, prettytime($config{$h}{'mtime'})); } } } From da9f39917ff6fe8c13d2487ed88699032dd493fa Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 29 May 2024 18:27:44 -0400 Subject: [PATCH 019/150] dnsexit2: Inline some unnecessary variables --- ddclient.in | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/ddclient.in b/ddclient.in index 859cbd5..0313ce0 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4180,35 +4180,32 @@ sub nic_dnsexit2_update { for my $ipv ('4', '6') { my $ip = delete($config{$h}{"wantipv$ipv"}) or next; $ips{$ipv} = $ip; - my $type = ($ipv eq '6') ? 'AAAA' : 'A'; info("Going to update IPv%s address to %s for %s.", $ipv, $ip, $h); $config{$h}{'status-ipv$ipv'} = 'failed'; push(@updates, { name => $h, - type => $type, + type => ($ipv eq '6') ? 'AAAA' : 'A', content => $ip, ttl => $config{$h}{'ttl'}, }); }; my $url = "https://$config{$h}{'server'}$config{$h}{'path'}"; - my $header = "Content-Type: application/json\nAccept: application/json"; if (not defined $config{$h}{'zone'}) { debug("Zone not defined, setting to default hostname: %s", $h); $config{$h}{'zone'} = $h } else { debug("Zone is: %s", $config{$h}{'zone'}); } - my $data = encode_json({ - apikey => $config{$h}{'password'}, - domain => $config{$h}{'zone'}, - update => \@updates, - }); my $reply = geturl( proxy => opt('proxy'), url => $url, - headers => $header, + headers => "Content-Type: application/json\nAccept: application/json", method => 'POST', - data => $data, + data => encode_json({ + apikey => $config{$h}{'password'}, + domain => $config{$h}{'zone'}, + update => \@updates, + }), ); unless ($reply && header_ok($h, $reply)) { failed("updating %s: Could not connect to %s%s.", $h, $url); From 46bca5439378a2b906a78970a5707d1efaeaf9b6 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 30 May 2024 04:04:19 -0400 Subject: [PATCH 020/150] dnsexit2: Fix string interpolation --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 0313ce0..e54b4c1 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4181,7 +4181,7 @@ sub nic_dnsexit2_update { my $ip = delete($config{$h}{"wantipv$ipv"}) or next; $ips{$ipv} = $ip; info("Going to update IPv%s address to %s for %s.", $ipv, $ip, $h); - $config{$h}{'status-ipv$ipv'} = 'failed'; + $config{$h}{"status-ipv$ipv"} = 'failed'; push(@updates, { name => $h, type => ($ipv eq '6') ? 'AAAA' : 'A', From 5b7400ae7d4588c4c2ef6b35f2fbf2fdc51b50f0 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 30 May 2024 04:48:44 -0400 Subject: [PATCH 021/150] dnsexit2: Normalize the zone up front --- ddclient.in | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ddclient.in b/ddclient.in index e54b4c1..453d810 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4174,6 +4174,12 @@ sub nic_dnsexit2_update { # The DNSExit API does not support updating multiple hosts at a time. for my $h (@_) { + if (not defined $config{$h}{'zone'}) { + debug("Zone not defined, setting to default hostname: %s", $h); + $config{$h}{'zone'} = $h + } else { + debug("Zone is: %s", $config{$h}{'zone'}); + } # The IPv4 and IPv6 addresses must be updated together in a single API call. my %ips; my @updates; @@ -4190,12 +4196,6 @@ sub nic_dnsexit2_update { }); }; my $url = "https://$config{$h}{'server'}$config{$h}{'path'}"; - if (not defined $config{$h}{'zone'}) { - debug("Zone not defined, setting to default hostname: %s", $h); - $config{$h}{'zone'} = $h - } else { - debug("Zone is: %s", $config{$h}{'zone'}); - } my $reply = geturl( proxy => opt('proxy'), url => $url, From 3c84f7a1b56f771b55d5c779afd9ed9241b8e544 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 30 May 2024 04:49:46 -0400 Subject: [PATCH 022/150] dnsexit2: Delete unnecessary debug messages --- ddclient.in | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ddclient.in b/ddclient.in index 453d810..c9bb379 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4174,12 +4174,7 @@ sub nic_dnsexit2_update { # The DNSExit API does not support updating multiple hosts at a time. for my $h (@_) { - if (not defined $config{$h}{'zone'}) { - debug("Zone not defined, setting to default hostname: %s", $h); - $config{$h}{'zone'} = $h - } else { - debug("Zone is: %s", $config{$h}{'zone'}); - } + $config{$h}{'zone'} //= $h; # The IPv4 and IPv6 addresses must be updated together in a single API call. my %ips; my @updates; From eebb1b8a47fd30ba7fa0ef7076d75992b1cf63fe Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 30 May 2024 16:19:41 -0400 Subject: [PATCH 023/150] dnsexit2: Delete redundant `status-ipv*` assignments --- ddclient.in | 2 -- 1 file changed, 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index c9bb379..fa2512b 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4250,8 +4250,6 @@ sub nic_dnsexit2_update { info("Status: %s -- Message: %s", $status, $message); info("Server Message: %s -- Server Details: %s", $response->{'message'}, defined($response->{'details'}) ? $response->{'details'}[0] : "no details received"); - $config{$h}{'status-ipv4'} = $status if $ips{'4'}; - $config{$h}{'status-ipv6'} = $status if $ips{'6'}; if ($status ne 'good') { if ($status eq 'warning') { warning("%s", $message); From 6e5e2ab63f1f66a8558dd81ad5b48655930ce81d Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 30 May 2024 16:21:25 -0400 Subject: [PATCH 024/150] dnsexit2: Remove `https://` from update service URL This allows the `ssl` setting to control TLS vs. plain HTTP, and makes it possible to create a compatible service that doesn't use TLS (e.g., for testing). --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index fa2512b..7c490f3 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4190,7 +4190,7 @@ sub nic_dnsexit2_update { ttl => $config{$h}{'ttl'}, }); }; - my $url = "https://$config{$h}{'server'}$config{$h}{'path'}"; + my $url = $config{$h}{'server'} . $config{$h}{'path'}; my $reply = geturl( proxy => opt('proxy'), url => $url, From d8a1449a19e9687fdf8ab1c9193284d4a1d421a6 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 30 May 2024 16:23:42 -0400 Subject: [PATCH 025/150] dnsexit2: Fix error message format string This fixes a bug introduced in commit 2bf6d348b088daf4abef16089e794af97ccf506f. --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 7c490f3..1de2df5 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4203,7 +4203,7 @@ sub nic_dnsexit2_update { }), ); unless ($reply && header_ok($h, $reply)) { - failed("updating %s: Could not connect to %s%s.", $h, $url); + failed("updating %s: Could not connect to %s", $h, $url); last; }; debug("%s", $reply); From 2a47b17541d2934a8d5afe27257da2cbbb20be1b Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 30 May 2024 16:26:44 -0400 Subject: [PATCH 026/150] dnsexit2: Include the unexpected status in the error message --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 1de2df5..2cbd994 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4258,7 +4258,7 @@ sub nic_dnsexit2_update { failed("%s", $message); failed("Server response: %s", $response->{'message'}); } else { - failed("This should not be possible"); + failed("Unexpected status: %s", $status); } next; } From 7b95b379aa68634f4ad5453cc16f484bbe2815d7 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 30 May 2024 17:37:59 -0400 Subject: [PATCH 027/150] dnsexit2: Fix extraction and processing of JSON response body --- ddclient.in | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/ddclient.in b/ddclient.in index 2cbd994..6946515 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4215,17 +4215,14 @@ sub nic_dnsexit2_update { failed("Full reply\n%s", $reply) unless opt('verbose'); next; } - - # Strip HTTP response headers - (my $strip_status) = ($reply =~ s/^[\s\S]*?(?=\{"code":)//); - debug("strip_status"); - debug("%s", $strip_status); - if ($strip_status) { + my $body = ($reply =~ s/^.*?\r?\n\r?\n//sr); + debug("body\n%s", $body); + if ($body ne $reply) { debug("HTTP headers are stripped."); } else { warning("Unexpected: no HTTP headers stripped!"); } - my $response = decode_json($reply); + my $response = decode_json($body); if (!defined($response->{'code'}) || !defined($response->{'message'})) { failed("Did not receive expected \"code\" and \"message\" keys in server response."); failed("Response:"); From d28c8ea7add9b721b0fb4560fe91ed0f34ed70e5 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 30 May 2024 17:56:51 -0400 Subject: [PATCH 028/150] dnsexit2: Delete unnecessary debug messages --- ddclient.in | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ddclient.in b/ddclient.in index 6946515..7b4350a 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4216,12 +4216,6 @@ sub nic_dnsexit2_update { next; } my $body = ($reply =~ s/^.*?\r?\n\r?\n//sr); - debug("body\n%s", $body); - if ($body ne $reply) { - debug("HTTP headers are stripped."); - } else { - warning("Unexpected: no HTTP headers stripped!"); - } my $response = decode_json($body); if (!defined($response->{'code'}) || !defined($response->{'message'})) { failed("Did not receive expected \"code\" and \"message\" keys in server response."); From 24a22092ca7cd3e532042a159e57c5b84688aac2 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 30 May 2024 18:02:01 -0400 Subject: [PATCH 029/150] dnsexit2: Don't croak if JSON decoding fails --- ddclient.in | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 7b4350a..6811588 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4216,7 +4216,11 @@ sub nic_dnsexit2_update { next; } my $body = ($reply =~ s/^.*?\r?\n\r?\n//sr); - my $response = decode_json($body); + my $response = eval { decode_json($body); }; + if (!$response) { + failed("failed to parse response: $@"); + next; + } if (!defined($response->{'code'}) || !defined($response->{'message'})) { failed("Did not receive expected \"code\" and \"message\" keys in server response."); failed("Response:"); From f7f4856b935a3e048d8723c28711d8ef6dbf0f7d Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 30 May 2024 18:02:45 -0400 Subject: [PATCH 030/150] dnsexit2: Combine related log messages --- ddclient.in | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ddclient.in b/ddclient.in index 6811588..83ac577 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4222,9 +4222,8 @@ sub nic_dnsexit2_update { next; } if (!defined($response->{'code'}) || !defined($response->{'message'})) { - failed("Did not receive expected \"code\" and \"message\" keys in server response."); - failed("Response:"); - failed("%s", $response); + failed("Did not receive expected 'code' and 'message' keys in server response:\n%s", + $response); next; } my %codemeaning = ( From f976b771d499f3e2a0222f2b51a788c589b9aa68 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 30 May 2024 18:18:55 -0400 Subject: [PATCH 031/150] dnsexit2: Fix logging of erroneous response body --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 83ac577..cbd009a 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4223,7 +4223,7 @@ sub nic_dnsexit2_update { } if (!defined($response->{'code'}) || !defined($response->{'message'})) { failed("Did not receive expected 'code' and 'message' keys in server response:\n%s", - $response); + $body); next; } my %codemeaning = ( From ce0a362fd03c0bfae763a445fcb151ef9ea2b9d0 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 1 Jun 2024 02:48:21 -0400 Subject: [PATCH 032/150] group_hosts_by: Add tests --- Makefile.am | 1 + t/group_hosts_by.pl | 95 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 t/group_hosts_by.pl diff --git a/Makefile.am b/Makefile.am index 459931e..5b61f08 100644 --- a/Makefile.am +++ b/Makefile.am @@ -65,6 +65,7 @@ handwritten_tests = \ t/builtinfw_query.pl \ t/get_ip_from_if.pl \ t/geturl_connectivity.pl \ + t/group_hosts_by.pl \ t/interval_expired.pl \ t/is-and-extract-ipv4.pl \ t/is-and-extract-ipv6.pl \ diff --git a/t/group_hosts_by.pl b/t/group_hosts_by.pl new file mode 100644 index 0000000..cc2cc09 --- /dev/null +++ b/t/group_hosts_by.pl @@ -0,0 +1,95 @@ +use Test::More; +SKIP: { eval { require Test::Warnings; } or skip($@, 1); } +eval { require 'ddclient'; } or BAIL_OUT($@); +eval { require Data::Dumper; } or skip($@, 1); +Data::Dumper->import(); + +my $h1 = 'h1'; +my $h2 = 'h2'; +my $h3 = 'h3'; + +$ddclient::config{$h1} = { + common => 'common', + h1h2 => 'h1 and h2', + unique => 'h1', + falsy => 0, + maybeunset => 'unique', +}; +$ddclient::config{$h2} = { + common => 'common', + h1h2 => 'h1 and h2', + unique => 'h2', + falsy => '', + maybeunset => undef, # should not be grouped with unset +}; +$ddclient::config{$h3} = { + common => 'common', + h1h2 => 'unique', + unique => 'h3', + falsy => undef, + # maybeunset is intentionally not set +}; + +my @test_cases = ( + { + desc => 'empty attribute set yields single group with all hosts', + groupby => [qw()], + want => [[$h1, $h2, $h3]], + }, + { + desc => 'common attribute yields single group with all hosts', + groupby => [qw(common)], + want => [[$h1, $h2, $h3]], + }, + { + desc => 'subset share a value', + groupby => [qw(h1h2)], + want => [[$h1, $h2], [$h3]], + }, + { + desc => 'all unique', + groupby => [qw(unique)], + want => [[$h1], [$h2], [$h3]], + }, + { + desc => 'combination', + groupby => [qw(common h1h2)], + want => [[$h1, $h2], [$h3]], + }, + { + desc => 'falsy values', + groupby => [qw(falsy)], + want => [[$h1], [$h2], [$h3]], + todo => 'support for undef not yet added', + }, + { + desc => 'set, unset, undef', + groupby => [qw(maybeunset)], + want => [[$h1], [$h2], [$h3]], + todo => 'support for unset and undef not yet added', + }, + { + desc => 'missing attribute', + groupby => [qw(thisdoesnotexist)], + want => [[$h1, $h2, $h3]], + }, +); + +for my $tc (@test_cases) { + my %got = ddclient::group_hosts_by([$h1, $h2, $h3], $tc->{groupby}); + # %got is used as a set of sets. Sort everything to make comparison easier. + my @got = sort({ + for (my $i = 0; $i < @$a && $i < @$b; ++$i) { + my $x = $a->[$i] cmp $b->[$i]; + return $x if $x != 0; + } + return @$a <=> @$b; + } map({ [sort(@$_)]; } values(%got))); + TODO: { + local $TODO = $tc->{todo}; + is_deeply(\@got, $tc->{want}, $tc->{desc}) + or diag(Data::Dumper->Dump([\@got, $tc->{want}], [qw(got want)])); + } +} + +done_testing(); From 343fcff6254b1be91d346b2af0f3676a9fd07143 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 28 May 2024 02:54:26 -0400 Subject: [PATCH 033/150] group_hosts_by: Add support for `wantipv4`, `wantipv6` --- ddclient.in | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/ddclient.in b/ddclient.in index cbd009a..ec35fad 100755 --- a/ddclient.in +++ b/ddclient.in @@ -3421,9 +3421,8 @@ sub get_ipv6 { ## group_hosts_by ###################################################################### sub group_hosts_by { -##TODO - Update for wantipv4 and wantipv6 my ($hosts, $attributes) = @_; - my %attrs = (map({ ($_ => 1) } @$attributes), 'wantip' => 1); + my %attrs = map({ ($_ => 1) } @$attributes); my @attrs = sort(keys(%attrs)); my %groups = (); for my $h (@$hosts) { @@ -3980,7 +3979,7 @@ sub nic_dyndns2_update { debug("\nnic_dyndns2_update -------------------"); ## group hosts with identical attributes together - my %groups = group_hosts_by([ @_ ], [ qw(login password server static custom wildcard mx backupmx) ]); + my %groups = group_hosts_by(\@_, [qw(login password server static custom wildcard mx backupmx wantip)]); my %errors = ( 'badauth' => 'Bad authorization (username or password)', @@ -4275,7 +4274,7 @@ sub nic_noip_update { debug("\nnic_noip_update -------------------"); ## group hosts with identical attributes together - my %groups = group_hosts_by([ @_ ], [ qw(login password server static custom wildcard mx backupmx) ]); + my %groups = group_hosts_by(\@_, [qw(login password server static custom wildcard mx backupmx wantip)]); my %errors = ( 'badauth' => 'Invalid username or password', @@ -4622,7 +4621,7 @@ sub nic_zoneedit1_update { debug("\nnic_zoneedit1_update -------------------"); ## group hosts with identical attributes together - my %groups = group_hosts_by([ @_ ], [ qw(login password server zone) ]); + my %groups = group_hosts_by(\@_, [qw(login password server zone wantip)]); ## update each set of hosts that had similar configurations for my $sig (keys %groups) { @@ -5709,7 +5708,7 @@ sub nic_godaddy_update { debug("\nnic_godaddy_update --------------------"); ## group hosts with identical attributes together - my %groups = group_hosts_by([ @_ ], [ qw(server login password zone) ]); + my %groups = group_hosts_by(\@_, [qw(server login password zone wantip)]); ## update each set of hosts that had similar configurations for my $sig (keys %groups) { @@ -5846,7 +5845,7 @@ sub nic_googledomains_update { debug("\nnic_googledomains_update -------------------"); ## group hosts with identical attributes together - my %groups = group_hosts_by([ @_ ], [ qw(server login password) ]); + my %groups = group_hosts_by(\@_, [qw(server login password wantip)]); ## update each set of hosts that had similar configurations for my $sig (keys %groups) { @@ -6026,7 +6025,7 @@ sub nic_nsupdate_update { debug("\nnic_nsupdate_update -------------------"); ## group hosts with identical attributes together - my %groups = group_hosts_by([ @_ ], [ qw(login password server zone) ]); + my %groups = group_hosts_by(\@_, [qw(login password server zone wantip)]); ## update each set of hosts that had similar configurations for my $sig (keys %groups) { @@ -6143,7 +6142,7 @@ sub nic_cloudflare_update { debug("\nnic_cloudflare_update -------------------"); ## group hosts with identical attributes together - my %groups = group_hosts_by([ @_ ], [ qw(ssh login password server wildcard mx backupmx zone) ]); + my %groups = group_hosts_by(\@_, [qw(ssh login password server wildcard mx backupmx zone wantip)]); ## update each set of hosts that had similar configurations for my $sig (keys %groups) { @@ -6291,7 +6290,7 @@ sub nic_hetzner_update { debug("\nnic_hetzner_update -------------------"); ## group hosts with identical attributes together - my %groups = group_hosts_by([ @_ ], [ qw(ssh login password server wildcard mx backupmx zone) ]); + my %groups = group_hosts_by(\@_, [qw(ssh login password server wildcard mx backupmx zone wantip)]); ## update each set of hosts that had similar configurations for my $sig (keys %groups) { @@ -6448,7 +6447,7 @@ sub nic_yandex_update { debug("\nnic_yandex_update -------------------"); ## group hosts with identical attributes together - my %groups = group_hosts_by([ @_ ], [ qw(server login pasword) ]); + my %groups = group_hosts_by(\@_, [qw(server login pasword wantip)]); ## update each set of hosts that had similar configurations for my $sig (keys %groups) { @@ -7339,7 +7338,7 @@ EoEXAMPLE } sub nic_cloudns_update { - my %groups = group_hosts_by([ @_ ], [ qw(dynurl) ]); + my %groups = group_hosts_by(\@_, [qw(dynurl wantip)]); for my $hr (values(%groups)) { my @hosts = @$hr; my $hosts = join(',', @hosts); From f4802fc53440c970f5762b86bff107ddb0bc47f7 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 28 May 2024 02:57:22 -0400 Subject: [PATCH 034/150] Fix `group_hosts_by` call for IPv6-enabled services --- ddclient.in | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ddclient.in b/ddclient.in index ec35fad..24cae4f 100755 --- a/ddclient.in +++ b/ddclient.in @@ -3979,7 +3979,7 @@ sub nic_dyndns2_update { debug("\nnic_dyndns2_update -------------------"); ## group hosts with identical attributes together - my %groups = group_hosts_by(\@_, [qw(login password server static custom wildcard mx backupmx wantip)]); + my %groups = group_hosts_by(\@_, [qw(login password server static custom wildcard mx backupmx wantipv4 wantipv6)]); my %errors = ( 'badauth' => 'Bad authorization (username or password)', @@ -4274,7 +4274,7 @@ sub nic_noip_update { debug("\nnic_noip_update -------------------"); ## group hosts with identical attributes together - my %groups = group_hosts_by(\@_, [qw(login password server static custom wildcard mx backupmx wantip)]); + my %groups = group_hosts_by(\@_, [qw(login password server static custom wildcard mx backupmx wantipv4 wantipv6)]); my %errors = ( 'badauth' => 'Invalid username or password', @@ -5708,7 +5708,7 @@ sub nic_godaddy_update { debug("\nnic_godaddy_update --------------------"); ## group hosts with identical attributes together - my %groups = group_hosts_by(\@_, [qw(server login password zone wantip)]); + my %groups = group_hosts_by(\@_, [qw(server login password zone wantipv4 wantipv6)]); ## update each set of hosts that had similar configurations for my $sig (keys %groups) { @@ -6025,7 +6025,7 @@ sub nic_nsupdate_update { debug("\nnic_nsupdate_update -------------------"); ## group hosts with identical attributes together - my %groups = group_hosts_by(\@_, [qw(login password server zone wantip)]); + my %groups = group_hosts_by(\@_, [qw(login password server zone wantipv4 wantipv6)]); ## update each set of hosts that had similar configurations for my $sig (keys %groups) { @@ -6142,7 +6142,7 @@ sub nic_cloudflare_update { debug("\nnic_cloudflare_update -------------------"); ## group hosts with identical attributes together - my %groups = group_hosts_by(\@_, [qw(ssh login password server wildcard mx backupmx zone wantip)]); + my %groups = group_hosts_by(\@_, [qw(ssh login password server wildcard mx backupmx zone wantipv4 wantipv6)]); ## update each set of hosts that had similar configurations for my $sig (keys %groups) { @@ -6290,7 +6290,7 @@ sub nic_hetzner_update { debug("\nnic_hetzner_update -------------------"); ## group hosts with identical attributes together - my %groups = group_hosts_by(\@_, [qw(ssh login password server wildcard mx backupmx zone wantip)]); + my %groups = group_hosts_by(\@_, [qw(ssh login password server wildcard mx backupmx zone wantipv4 wantipv6)]); ## update each set of hosts that had similar configurations for my $sig (keys %groups) { From e60e6e804b164d850c1ef1781c03577fbe95d05c Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 28 May 2024 02:58:32 -0400 Subject: [PATCH 035/150] group_hosts_by: Use `Data::Dumper` to make the group signature This is a bit more robust than manually making the group signature because it gracefully handles corner cases such as `undef`. --- configure.ac | 2 +- ddclient.in | 5 ++++- t/group_hosts_by.pl | 9 ++------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/configure.ac b/configure.ac index f65b8b3..d1fc02c 100644 --- a/configure.ac +++ b/configure.ac @@ -49,6 +49,7 @@ AC_SUBST([PERL]) # package doesn't depend on all of them, so their availability can't # be assumed. m4_foreach_w([_m], [ + Data::Dumper File::Basename File::Path File::Temp @@ -63,7 +64,6 @@ m4_foreach_w([_m], [ # then some tests will fail. Only prints a warning if not installed. m4_foreach_w([_m], [ B - Data::Dumper File::Spec::Functions File::Temp ], [AX_PROG_PERL_MODULES([_m], [], diff --git a/ddclient.in b/ddclient.in index 24cae4f..2bf68e8 100755 --- a/ddclient.in +++ b/ddclient.in @@ -15,6 +15,7 @@ package ddclient; require v5.10.1; use strict; use warnings; +use Data::Dumper; use File::Basename; use File::Path qw(make_path); use File::Temp; @@ -3425,8 +3426,10 @@ sub group_hosts_by { my %attrs = map({ ($_ => 1) } @$attributes); my @attrs = sort(keys(%attrs)); my %groups = (); + my $d = Data::Dumper->new([])->Indent(0)->Sortkeys(1)->Terse(1)->Useqq(1); for my $h (@$hosts) { - my $sig = join(',', map({ sprintf("%s=%s", $_, $config{$h}{$_} // '') } @attrs)); + my %cfg = map({ ($_ => $config{$h}{$_}); } grep(exists($config{$h}{$_}), @attrs)); + my $sig = $d->Reset()->Values([\%cfg])->Dump(); push @{$groups{$sig}}, $h; } return %groups; diff --git a/t/group_hosts_by.pl b/t/group_hosts_by.pl index cc2cc09..61acd0f 100644 --- a/t/group_hosts_by.pl +++ b/t/group_hosts_by.pl @@ -60,13 +60,11 @@ my @test_cases = ( desc => 'falsy values', groupby => [qw(falsy)], want => [[$h1], [$h2], [$h3]], - todo => 'support for undef not yet added', }, { desc => 'set, unset, undef', groupby => [qw(maybeunset)], want => [[$h1], [$h2], [$h3]], - todo => 'support for unset and undef not yet added', }, { desc => 'missing attribute', @@ -85,11 +83,8 @@ for my $tc (@test_cases) { } return @$a <=> @$b; } map({ [sort(@$_)]; } values(%got))); - TODO: { - local $TODO = $tc->{todo}; - is_deeply(\@got, $tc->{want}, $tc->{desc}) - or diag(Data::Dumper->Dump([\@got, $tc->{want}], [qw(got want)])); - } + is_deeply(\@got, $tc->{want}, $tc->{desc}) + or diag(Data::Dumper->Dump([\@got, $tc->{want}], [qw(got want)])); } done_testing(); From 282bb01e1761707297eb9d47d97848b50c36ad8d Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 1 Jun 2024 03:46:56 -0400 Subject: [PATCH 036/150] Bump version to v3.12.0~alpha Enough has changed to warrant a minor revision bump. --- ChangeLog.md | 2 +- README.md | 11 +++++++++-- ddclient.in | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index eea6429..5a9bfae 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,7 +3,7 @@ This document describes notable changes. For details, see the [source code repository history](https://github.com/ddclient/ddclient/commits/master). -## v3.11.3~alpha (unreleased work-in-progress) +## v3.12.0~alpha (unreleased work-in-progress) ### Breaking changes diff --git a/README.md b/README.md index e6fd0cb..c55db17 100644 --- a/README.md +++ b/README.md @@ -130,9 +130,16 @@ Note that any issues prior to version v3.9.1 will not be listed here. If a fix is committed but not yet part of any tagged release, the notes here will reference the not-yet-released version number. ### v3.11.2 - v3.9.1: SSL parameter breaks HTTP-only IP acquisition -The `ssl` parameter forces all connections to use HTTPS. While technically working as expected, this behavior keeps coming up as a pain point when using HTTP-only IP querying sites such as http://checkip.dyndns.org. For the future (v3.11.3), the behavior is changed to respect `http://` in a URL. A separate parameter to disallow all HTTP connections or warn about them may be added later. -**Fix**: v3.11.3 will use HTTP to connect to URLs starting with `http://`. See [here](https://github.com/ddclient/ddclient/pull/608) for more info. +The `ssl` parameter forces all connections to use HTTPS. While technically +working as expected, this behavior keeps coming up as a pain point when using +HTTP-only IP querying sites such as http://checkip.dyndns.org. Starting with +v3.12.0, the behavior is changed to respect `http://` in a URL. A separate +parameter to disallow all HTTP connections or warn about them may be added +later. + +**Fix**: v3.12.0 uses HTTP to connect to URLs starting with `http://`. See +[here](https://github.com/ddclient/ddclient/pull/608) for more info. **Workaround**: Disable the SSL parameter diff --git a/ddclient.in b/ddclient.in index 2bf68e8..f686da5 100755 --- a/ddclient.in +++ b/ddclient.in @@ -63,7 +63,7 @@ use Sys::Hostname; # # For consistency and to match user expectations, the release part of the version is always three # components: MAJOR.MINOR.PATCH. -use version 0.77; our $VERSION = version->declare('v3.11.3.0_0'); +use version 0.77; our $VERSION = version->declare('v3.12.0.0_0'); sub parse_version { my ($v) = @_; From ec2d5f7f6951f797ae5ea54a1b6ada84dcad202f Mon Sep 17 00:00:00 2001 From: jortkoopmans Date: Mon, 27 May 2024 00:42:40 +0200 Subject: [PATCH 037/150] dnsexit2: Add tests Needs LWP::UserAgent. --- .github/workflows/ci.yml | 2 + Makefile.am | 1 + configure.ac | 2 + t/dnsexit2.pl | 212 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 t/dnsexit2.pl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de4d443..d4cd166 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,7 @@ jobs: libtest-tcp-perl \ libtest-warnings-perl \ liburi-perl \ + libwww-perl \ net-tools \ make \ ; @@ -102,6 +103,7 @@ jobs: perl-Test-TCP \ perl-Test-Warnings \ perl-core \ + perl-libwww-perl \ net-tools \ ; - name: autogen diff --git a/Makefile.am b/Makefile.am index 5b61f08..fc78825 100644 --- a/Makefile.am +++ b/Makefile.am @@ -63,6 +63,7 @@ AM_PL_LOG_FLAGS = -Mstrict -w \ -MDevel::Autoflush handwritten_tests = \ t/builtinfw_query.pl \ + t/dnsexit2.pl \ t/get_ip_from_if.pl \ t/geturl_connectivity.pl \ t/group_hosts_by.pl \ diff --git a/configure.ac b/configure.ac index d1fc02c..649b7e3 100644 --- a/configure.ac +++ b/configure.ac @@ -80,6 +80,8 @@ m4_foreach_w([_m], [ HTTP::Message::PSGI HTTP::Request HTTP::Response + JSON::PP + LWP::UserAgent Scalar::Util Test::MockModule Test::TCP diff --git a/t/dnsexit2.pl b/t/dnsexit2.pl new file mode 100644 index 0000000..ca25e31 --- /dev/null +++ b/t/dnsexit2.pl @@ -0,0 +1,212 @@ +use Test::More; +eval { require JSON::PP; } or plan(skip_all => $@); +JSON::PP->import(qw(encode_json decode_json)); +eval { require 'ddclient'; } or BAIL_OUT($@); +eval { require ddclient::Test::Fake::HTTPD; } or plan(skip_all => $@); +eval { require LWP::UserAgent; } or plan(skip_all => $@); + +ddclient::load_json_support('dnsexit2'); + +my @requests; # Declare global variable to store requests, used for tests. +my @httpd_requests; # Declare variable specificly used for the httpd process (which cannot be shared with tests). +my $httpd = ddclient::Test::Fake::HTTPD->new(); + +$httpd->run(sub { + my ($req) = @_; + if ($req->uri->as_string eq '/get_requests') { + return [200, ['Content-Type' => 'application/json'], [encode_json(\@httpd_requests)]]; + } elsif ($req->uri->as_string eq '/reset_requests') { + @httpd_requests = (); + return [200, ['Content-Type' => 'application/json'], [encode_json({ message => 'OK' })]]; + } + my $request_info = { + method => $req->method, + uri => $req->uri->as_string, + content => $req->content, + headers => $req->headers->as_string + }; + push @httpd_requests, $request_info; + return [200, ['Content-Type' => 'application/json'], [encode_json({ + code => 0, + message => 'Success' + })]]; +}); + +diag(sprintf("started IPv4 server running at %s", $httpd->endpoint())); + +my $ua = LWP::UserAgent->new; + +sub test_nic_dnsexit2_update { + my ($config, @hostnames) = @_; + %ddclient::config = %$config; + ddclient::nic_dnsexit2_update(@hostnames); +} + +sub decode_and_sort_array { + my ($data) = @_; + if (!ref $data) { + $data = decode_json($data); + } + @{$data->{update}} = sort { $a->{type} cmp $b->{type} } @{$data->{update}}; + return $data; +} + +sub reset_test_data { + my $response = $ua->get($httpd->endpoint . '/reset_requests'); + die "Failed to reset requests" unless $response->is_success; + @requests = (); +} + +sub get_requests { + my $res = $ua->get($httpd->endpoint . '/get_requests'); + die "Failed to get requests: " . $res->status_line unless $res->is_success; + return @{decode_json($res->decoded_content)}; +} + +subtest 'Testing nic_dnsexit2_update' => sub { + my %config = ( + 'host.my.zone.com' => { + 'ssl' => 'no', + 'verbose' => 'yes', + 'usev4' => 'ipv4', + 'wantipv4' => '8.8.4.4', + 'usev6' => 'ipv6', + 'wantipv6' => '2001:4860:4860::8888', + 'protocol' => 'dnsexit2', + 'password' => 'mytestingpassword', + 'zone' => 'my.zone.com', + 'server' => $httpd->host_port(), + 'path' => '/update', + 'ttl' => 5 + }); + test_nic_dnsexit2_update(\%config, 'host.my.zone.com'); + @requests = get_requests(); + is($requests[0]->{method}, 'POST', 'Method is correct'); + is($requests[0]->{uri}, '/update', 'URI contains correct path'); + like($requests[0]->{headers}, qr/Content-Type: application\/json/, 'Content-Type header is correct'); + like($requests[0]->{headers}, qr/Accept: application\/json/, 'Accept header is correct'); + my $data = decode_and_sort_array($requests[0]->{content}); + my $expected_data = decode_and_sort_array({ + 'domain' => 'my.zone.com', + 'apikey' => 'mytestingpassword', + 'update' => [ + { + 'type' => 'A', + 'name' => 'host', + 'content' => '8.8.4.4', + 'ttl' => 5, + }, + { + 'type' => 'AAAA', + 'name' => 'host', + 'content' => '2001:4860:4860::8888', + 'ttl' => 5, + } + ] + }); + TODO: { + local $TODO = "https://github.com/ddclient/ddclient/issues/673"; + is_deeply($data, $expected_data, 'Data is correct'); + } + reset_test_data(); +}; + +subtest 'Testing nic_dnsexit2_update without a zone set' => sub { + my %config = ( + 'myhost.zone.com' => { + 'ssl' => 'yes', + 'verbose' => 'yes', + 'usev4' => 'ipv4', + 'wantipv4' => '8.8.4.4', + 'protocol' => 'dnsexit2', + 'password' => 'anotherpassword', + 'server' => $httpd->host_port(), + 'path' => '/update-alt', + 'ttl' => 10 + }); + test_nic_dnsexit2_update(\%config, 'myhost.zone.com'); + @requests = get_requests(); + my $data = decode_and_sort_array($requests[0]->{content}); + my $expected_data = decode_and_sort_array({ + 'domain' => 'myhost.zone.com', + 'apikey' => 'anotherpassword', + 'update' => [ + { + 'type' => 'A', + 'name' => '', + 'content' => '8.8.4.4', + 'ttl' => 10, + } + ] + }); + TODO: { + local $TODO = "https://github.com/ddclient/ddclient/issues/673"; + is_deeply($data, $expected_data, 'Data is correct'); + } + reset_test_data($ua); +}; + +subtest 'Testing nic_dnsexit2_update with two hostnames, one with a zone and one without' => sub { + my %config = ( + 'host1.zone.com' => { + 'ssl' => 'yes', + 'verbose' => 'yes', + 'usev4' => 'ipv4', + 'wantipv4' => '8.8.4.4', + 'protocol' => 'dnsexit2', + 'password' => 'testingpassword', + 'server' => $httpd->host_port(), + 'path' => '/update', + 'ttl' => 5 + }, + 'host2.zone.com' => { + 'ssl' => 'yes', + 'verbose' => 'yes', + 'usev6' => 'ipv6', + 'wantipv6' => '2001:4860:4860::8888', + 'protocol' => 'dnsexit2', + 'password' => 'testingpassword', + 'server' => $httpd->host_port(), + 'path' => '/update', + 'ttl' => 10, + 'zone' => 'zone.com' + } + ); + test_nic_dnsexit2_update(\%config, 'host1.zone.com', 'host2.zone.com'); + my $expected_data1 = decode_and_sort_array({ + 'domain' => 'host1.zone.com', + 'apikey' => 'testingpassword', + 'update' => [ + { + 'type' => 'A', + 'name' => '', + 'content' => '8.8.4.4', + 'ttl' => 5, + } + ] + }); + my $expected_data2 = decode_and_sort_array({ + 'domain' => 'zone.com', + 'apikey' => 'testingpassword', + 'update' => [ + { + 'type' => 'AAAA', + 'name' => 'host2', + 'content' => '2001:4860:4860::8888', + 'ttl' => 10, + } + ] + }); + @requests = get_requests(); + for my $i (0..1) { + my $data = decode_and_sort_array($requests[$i]->{content}); + TODO: { + local $TODO = "https://github.com/ddclient/ddclient/issues/673"; + is_deeply($data, $expected_data1, 'Data is correct for call host1') if $i == 0; + is_deeply($data, $expected_data2, 'Data is correct for call host2') if $i == 1; + } + } + reset_test_data(); +}; + +done_testing(); From 216741c9ceb32f1c80b04378f6387db9c33af8f9 Mon Sep 17 00:00:00 2001 From: jortkoopmans Date: Mon, 27 May 2024 00:42:40 +0200 Subject: [PATCH 038/150] dnsexit2: Fix when provided with a zone and a non-identical hostname Trim the zone from the hostname in the request to fix issue. --- ChangeLog.md | 2 ++ ddclient.in | 17 ++++++++++++++--- t/dnsexit2.pl | 17 ++++------------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 5a9bfae..f376ad9 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -75,6 +75,8 @@ repository history](https://github.com/ddclient/ddclient/commits/master). [#667](https://github.com/ddclient/ddclient/pull/667) * Fixed unnecessary repeated updates for some services. [#670](https://github.com/ddclient/ddclient/pull/670) + * Fixed DNSExit provider when configured with a zone and non-identical + hostname. [#673](https://github.com/ddclient/ddclient/issues/673) ## 2023-11-23 v3.11.2 diff --git a/ddclient.in b/ddclient.in index f686da5..bcd1197 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4140,6 +4140,8 @@ o 'dnsexit2' The 'dnsexit2' protocol is the updated protocol for the (free) dynamic hostname services of 'DNSExit' (www.dnsexit.com). Their API is accepting JSON payload. +Note that we only update the record, it must already exist in the DNSExit system +(A and/or AAAA records where applicable). Configuration variables applicable to the 'dnsexit2' protocol are: protocol=dnsexit2 ## @@ -4173,10 +4175,19 @@ EoEXAMPLE ###################################################################### sub nic_dnsexit2_update { debug("\nnic_dnsexit2_update -------------------"); - - # The DNSExit API does not support updating multiple hosts at a time. + # The DNSExit API does not support updating hosts with different zones at the same time, + # handling update per host. for my $h (@_) { $config{$h}{'zone'} //= $h; + my $name = $h; + # Remove the zone suffix from $name. If the zone eq $name, $name can be left alone or + # set to the empty string; both have identical semantics. For consistency, always + # remove the zone even if it means $name becomes the empty string. + if ($name =~ s/(?:^|\.)\Q$config{$h}{'zone'}\E$//) { + # The zone was successfully trimmed from $name. + } else { + fatal("Hostname %s does not end with the zone %s", $h, $config{$h}{'zone'}); + } # The IPv4 and IPv6 addresses must be updated together in a single API call. my %ips; my @updates; @@ -4186,7 +4197,7 @@ sub nic_dnsexit2_update { info("Going to update IPv%s address to %s for %s.", $ipv, $ip, $h); $config{$h}{"status-ipv$ipv"} = 'failed'; push(@updates, { - name => $h, + name => $name, type => ($ipv eq '6') ? 'AAAA' : 'A', content => $ip, ttl => $config{$h}{'ttl'}, diff --git a/t/dnsexit2.pl b/t/dnsexit2.pl index ca25e31..b32c688 100644 --- a/t/dnsexit2.pl +++ b/t/dnsexit2.pl @@ -104,10 +104,7 @@ subtest 'Testing nic_dnsexit2_update' => sub { } ] }); - TODO: { - local $TODO = "https://github.com/ddclient/ddclient/issues/673"; - is_deeply($data, $expected_data, 'Data is correct'); - } + is_deeply($data, $expected_data, 'Data is correct'); reset_test_data(); }; @@ -139,10 +136,7 @@ subtest 'Testing nic_dnsexit2_update without a zone set' => sub { } ] }); - TODO: { - local $TODO = "https://github.com/ddclient/ddclient/issues/673"; - is_deeply($data, $expected_data, 'Data is correct'); - } + is_deeply($data, $expected_data, 'Data is correct'); reset_test_data($ua); }; @@ -200,11 +194,8 @@ subtest 'Testing nic_dnsexit2_update with two hostnames, one with a zone and one @requests = get_requests(); for my $i (0..1) { my $data = decode_and_sort_array($requests[$i]->{content}); - TODO: { - local $TODO = "https://github.com/ddclient/ddclient/issues/673"; - is_deeply($data, $expected_data1, 'Data is correct for call host1') if $i == 0; - is_deeply($data, $expected_data2, 'Data is correct for call host2') if $i == 1; - } + is_deeply($data, $expected_data1, 'Data is correct for call host1') if $i == 0; + is_deeply($data, $expected_data2, 'Data is correct for call host2') if $i == 1; } reset_test_data(); }; From 11d0c84639be90178310cd11cca3dcf0cf34b838 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 31 May 2024 19:03:04 -0400 Subject: [PATCH 039/150] dnsexit2: Don't skip remaining hosts on connect error or non-2xx A non-2xx status code might be host-specific, so ddclient should continue with the next host. We could skip the remaining hosts if there is a connection failure, but it doesn't hurt to retry. --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index bcd1197..46ed478 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4217,7 +4217,7 @@ sub nic_dnsexit2_update { ); unless ($reply && header_ok($h, $reply)) { failed("updating %s: Could not connect to %s", $h, $url); - last; + next; }; debug("%s", $reply); (my $http_status) = ($reply =~ m%^s*HTTP/.*\s+(\d+)%i); From 73a67b728d1dc0875666e75c21a31aba96fec7c3 Mon Sep 17 00:00:00 2001 From: jortkoopmans Date: Mon, 27 May 2024 00:42:40 +0200 Subject: [PATCH 040/150] dnsexit2: Move body of `for` loop to a separate function For improved readability. --- ddclient.in | 197 +++++++++++++++++++++++++++------------------------- 1 file changed, 101 insertions(+), 96 deletions(-) diff --git a/ddclient.in b/ddclient.in index 46ed478..53115d0 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4179,104 +4179,109 @@ sub nic_dnsexit2_update { # handling update per host. for my $h (@_) { $config{$h}{'zone'} //= $h; - my $name = $h; - # Remove the zone suffix from $name. If the zone eq $name, $name can be left alone or - # set to the empty string; both have identical semantics. For consistency, always - # remove the zone even if it means $name becomes the empty string. - if ($name =~ s/(?:^|\.)\Q$config{$h}{'zone'}\E$//) { - # The zone was successfully trimmed from $name. + dnsexit2_update_host($h); + } +} + +sub dnsexit2_update_host { + my ($h) = @_; + my $name = $h; + # Remove the zone suffix from $name. If the zone eq $name, $name can be left alone or + # set to the empty string; both have identical semantics. For consistency, always + # remove the zone even if it means $name becomes the empty string. + if ($name =~ s/(?:^|\.)\Q$config{$h}{'zone'}\E$//) { + # The zone was successfully trimmed from $name. + } else { + fatal("Hostname %s does not end with the zone %s", $h, $config{$h}{'zone'}); + } + # The IPv4 and IPv6 addresses must be updated together in a single API call. + my %ips; + my @updates; + for my $ipv ('4', '6') { + my $ip = delete($config{$h}{"wantipv$ipv"}) or next; + $ips{$ipv} = $ip; + info("Going to update IPv%s address to %s for %s.", $ipv, $ip, $h); + $config{$h}{"status-ipv$ipv"} = 'failed'; + push(@updates, { + name => $name, + type => ($ipv eq '6') ? 'AAAA' : 'A', + content => $ip, + ttl => $config{$h}{'ttl'}, + }); + }; + my $url = $config{$h}{'server'} . $config{$h}{'path'}; + my $reply = geturl( + proxy => opt('proxy'), + url => $url, + headers => "Content-Type: application/json\nAccept: application/json", + method => 'POST', + data => encode_json({ + apikey => $config{$h}{'password'}, + domain => $config{$h}{'zone'}, + update => \@updates, + }), + ); + unless ($reply && header_ok($h, $reply)) { + failed("updating %s: Could not connect to %s", $h, $url); + return; + }; + debug("%s", $reply); + (my $http_status) = ($reply =~ m%^s*HTTP/.*\s+(\d+)%i); + debug("HTTP response code: %s", $http_status); + if ($http_status ne '200') { + failed("Failed to update Host\n%s", $h); + failed("HTTP response code\n%s", $http_status); + failed("Full reply\n%s", $reply) unless opt('verbose'); + return; + } + my $body = ($reply =~ s/^.*?\r?\n\r?\n//sr); + my $response = eval { decode_json($body); }; + if (!$response) { + failed("failed to parse response: $@"); + return; + } + if (!defined($response->{'code'}) || !defined($response->{'message'})) { + failed("Did not receive expected 'code' and 'message' keys in server response:\n%s", + $body); + return; + } + my %codemeaning = ( + '0' => ['good', 'Success! Actions got executed successfully.'], + '1' => ['warning', 'Some execution problems. May not indicate actions failures. Some action may got executed fine and some may have problems.'], + '2' => ['badauth', 'API Key Authentication Error. The API Key is missing or wrong.'], + '3' => ['error', 'Missing Required Definitions. Your JSON file may missing some required definitions.'], + '4' => ['error', 'JSON Data Syntax Error. Your JSON file has syntax error.'], + '5' => ['error', 'JSON Defined Record Type not Supported. Your JSON may try to update some record type not supported by our system.'], + '6' => ['error', 'System Error. Our system problem. May not be your problem. Contact our support if you got such error.'], + '7' => ['error', 'Error getting post data. Our server has problem to receive your JSON posting.'], + ); + if (!exists($codemeaning{$response->{'code'}})) { + failed("Status code %s is unknown!", $response->{'code'}); + return; + } + my ($status, $message) = @{$codemeaning{$response->{'code'}}}; + info("Status: %s -- Message: %s", $status, $message); + info("Server Message: %s -- Server Details: %s", $response->{'message'}, + defined($response->{'details'}) ? $response->{'details'}[0] : "no details received"); + if ($status ne 'good') { + if ($status eq 'warning') { + warning("%s", $message); + warning("Server response: %s", $response->{'message'}); + } elsif ($status =~ m'^(badauth|error)$') { + failed("%s", $message); + failed("Server response: %s", $response->{'message'}); } else { - fatal("Hostname %s does not end with the zone %s", $h, $config{$h}{'zone'}); - } - # The IPv4 and IPv6 addresses must be updated together in a single API call. - my %ips; - my @updates; - for my $ipv ('4', '6') { - my $ip = delete($config{$h}{"wantipv$ipv"}) or next; - $ips{$ipv} = $ip; - info("Going to update IPv%s address to %s for %s.", $ipv, $ip, $h); - $config{$h}{"status-ipv$ipv"} = 'failed'; - push(@updates, { - name => $name, - type => ($ipv eq '6') ? 'AAAA' : 'A', - content => $ip, - ttl => $config{$h}{'ttl'}, - }); - }; - my $url = $config{$h}{'server'} . $config{$h}{'path'}; - my $reply = geturl( - proxy => opt('proxy'), - url => $url, - headers => "Content-Type: application/json\nAccept: application/json", - method => 'POST', - data => encode_json({ - apikey => $config{$h}{'password'}, - domain => $config{$h}{'zone'}, - update => \@updates, - }), - ); - unless ($reply && header_ok($h, $reply)) { - failed("updating %s: Could not connect to %s", $h, $url); - next; - }; - debug("%s", $reply); - (my $http_status) = ($reply =~ m%^s*HTTP/.*\s+(\d+)%i); - debug("HTTP response code: %s", $http_status); - if ($http_status ne '200') { - failed("Failed to update Host\n%s", $h); - failed("HTTP response code\n%s", $http_status); - failed("Full reply\n%s", $reply) unless opt('verbose'); - next; - } - my $body = ($reply =~ s/^.*?\r?\n\r?\n//sr); - my $response = eval { decode_json($body); }; - if (!$response) { - failed("failed to parse response: $@"); - next; - } - if (!defined($response->{'code'}) || !defined($response->{'message'})) { - failed("Did not receive expected 'code' and 'message' keys in server response:\n%s", - $body); - next; - } - my %codemeaning = ( - '0' => ['good', 'Success! Actions got executed successfully.'], - '1' => ['warning', 'Some execution problems. May not indicate actions failures. Some action may got executed fine and some may have problems.'], - '2' => ['badauth', 'API Key Authentication Error. The API Key is missing or wrong.'], - '3' => ['error', 'Missing Required Definitions. Your JSON file may missing some required definitions.'], - '4' => ['error', 'JSON Data Syntax Error. Your JSON file has syntax error.'], - '5' => ['error', 'JSON Defined Record Type not Supported. Your JSON may try to update some record type not supported by our system.'], - '6' => ['error', 'System Error. Our system problem. May not be your problem. Contact our support if you got such error.'], - '7' => ['error', 'Error getting post data. Our server has problem to receive your JSON posting.'], - ); - if (!exists($codemeaning{$response->{'code'}})) { - failed("Status code %s is unknown!", $response->{'code'}); - next; - } - my ($status, $message) = @{$codemeaning{$response->{'code'}}}; - info("Status: %s -- Message: %s", $status, $message); - info("Server Message: %s -- Server Details: %s", $response->{'message'}, - defined($response->{'details'}) ? $response->{'details'}[0] : "no details received"); - if ($status ne 'good') { - if ($status eq 'warning') { - warning("%s", $message); - warning("Server response: %s", $response->{'message'}); - } elsif ($status =~ m'^(badauth|error)$') { - failed("%s", $message); - failed("Server response: %s", $response->{'message'}); - } else { - failed("Unexpected status: %s", $status); - } - next; - } - success("%s", $message); - $config{$h}{'mtime'} = $now; - keys(%ips); # Reset internal iterator. - while (my ($ipv, $ip) = each(%ips)) { - $config{$h}{"ipv$ipv"} = $ip; - $config{$h}{"status-ipv$ipv"} = 'good'; - success("Updated %s successfully to IPv%s address %s at time %s", $h, $ipv, $ip, prettytime($config{$h}{'mtime'})); + failed("Unexpected status: %s", $status); } + return; + } + success("%s", $message); + $config{$h}{'mtime'} = $now; + keys(%ips); # Reset internal iterator. + while (my ($ipv, $ip) = each(%ips)) { + $config{$h}{"ipv$ipv"} = $ip; + $config{$h}{"status-ipv$ipv"} = 'good'; + success("Updated %s successfully to IPv%s address %s at time %s", $h, $ipv, $ip, prettytime($config{$h}{'mtime'})); } } From 09d8d0426e278011eb460c0443ff238c6d2298c0 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 25 May 2024 16:59:53 -0400 Subject: [PATCH 041/150] geturl: Don't suppress curl's STDERR This makes it easier for users to troubleshoot problems. --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 53115d0..f99f352 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2673,7 +2673,7 @@ sub curl_cmd { print($tfh @params); } close($tfh); - my $reply = qx{ $system_curl --config $tmpfile 2>/dev/null; }; + my $reply = qx{ $system_curl --config $tmpfile; }; if ((my $rc = $?>>8) != 0) { warning("CURL error (%d) %s", $rc, $curl_codes{$rc} // "Unknown return code. Check $system_curl is installed and its manpage."); } From 8e901c3db6db196fa9b1b9728a7920e60eac0d5f Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sun, 26 May 2024 02:18:24 -0400 Subject: [PATCH 042/150] geturl: Avoid the shell when invoking curl --- ddclient.in | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/ddclient.in b/ddclient.in index f99f352..6b32de4 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2635,7 +2635,7 @@ sub curl_cmd { my @params = @_; my $tmpfile; my $tfh; - my $system_curl = quotemeta(subst_var('@CURL@', 'curl')); + my $curl = subst_var('@CURL@', 'curl'); my %curl_codes = ( ## Subset of error codes from https://curl.haxx.se/docs/manpage.html 2 => "Failed to initialize. (Most likely a bug in ddclient, please open issue at https://github.com/ddclient/ddclient)", 3 => "URL malformed. The syntax was not correct", @@ -2653,11 +2653,11 @@ sub curl_cmd { 67 => "The user name, password, or similar was not accepted and curl failed to log in.", 77 => "Problem with reading the SSL CA cert (path? access rights?).", 78 => "The resource referenced in the URL does not exist.", - 127 => "$system_curl was not found", + 127 => "$curl was not found", ); - debug("CURL: %s", $system_curl); - fatal("curl not found") if ($system_curl eq ''); + debug("CURL: %s", $curl); + fatal("curl not found") if ($curl eq ''); return '' if (scalar(@params) == 0); ## no parameters provided # Hard code to /tmp rather than use system TMPDIR to protect from malicious @@ -2673,9 +2673,13 @@ sub curl_cmd { print($tfh @params); } close($tfh); - my $reply = qx{ $system_curl --config $tmpfile; }; + # Use open's list form (as opposed to qx, backticks, or the scalar form of open) to avoid the + # shell and reduce the risk of a shell injection vulnerability. + open(my $cfh, '-|', $curl, '--config', $tmpfile) or fatal("failed to run curl ($curl): $!"); + my $reply = do { local $/; <$cfh>; }; + close($cfh); # Closing $cfh waits for the process to exit and sets $?. if ((my $rc = $?>>8) != 0) { - warning("CURL error (%d) %s", $rc, $curl_codes{$rc} // "Unknown return code. Check $system_curl is installed and its manpage."); + warning("CURL error (%d) %s", $rc, $curl_codes{$rc} // "Unknown return code. Check $curl is installed and its manpage."); } return $reply; } From 31dbd8e4ede4c9909bad9de9383411859c3a3095 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sun, 26 May 2024 02:24:53 -0400 Subject: [PATCH 043/150] geturl: Set raw (binary) mode when reading from curl --- Makefile.am | 1 + ddclient.in | 16 ++++++++++++---- t/geturl_response.pl | 27 +++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 t/geturl_response.pl diff --git a/Makefile.am b/Makefile.am index fc78825..738f301 100644 --- a/Makefile.am +++ b/Makefile.am @@ -66,6 +66,7 @@ handwritten_tests = \ t/dnsexit2.pl \ t/get_ip_from_if.pl \ t/geturl_connectivity.pl \ + t/geturl_response.pl \ t/group_hosts_by.pl \ t/interval_expired.pl \ t/is-and-extract-ipv4.pl \ diff --git a/ddclient.in b/ddclient.in index 6b32de4..fa6e5ec 100755 --- a/ddclient.in +++ b/ddclient.in @@ -125,6 +125,7 @@ if ($program =~ /test/i) { $cachedir = '.'; $savedir = 'URL'; } +our @curl = (subst_var('@CURL@', 'curl')); our $emailbody = ''; my $last_emailbody = ''; @@ -2635,7 +2636,7 @@ sub curl_cmd { my @params = @_; my $tmpfile; my $tfh; - my $curl = subst_var('@CURL@', 'curl'); + my $curl = join(' ', @curl); my %curl_codes = ( ## Subset of error codes from https://curl.haxx.se/docs/manpage.html 2 => "Failed to initialize. (Most likely a bug in ddclient, please open issue at https://github.com/ddclient/ddclient)", 3 => "URL malformed. The syntax was not correct", @@ -2657,7 +2658,7 @@ sub curl_cmd { ); debug("CURL: %s", $curl); - fatal("curl not found") if ($curl eq ''); + fatal("curl not found") if ($curl[0] eq ''); return '' if (scalar(@params) == 0); ## no parameters provided # Hard code to /tmp rather than use system TMPDIR to protect from malicious @@ -2674,8 +2675,15 @@ sub curl_cmd { } close($tfh); # Use open's list form (as opposed to qx, backticks, or the scalar form of open) to avoid the - # shell and reduce the risk of a shell injection vulnerability. - open(my $cfh, '-|', $curl, '--config', $tmpfile) or fatal("failed to run curl ($curl): $!"); + # shell and reduce the risk of a shell injection vulnerability. ':raw' mode is used because + # HTTP is defined in terms of octets (bytes), not characters. In raw mode, each byte from curl + # is mapped to a same-valued codepoint (byte value 0x78 becomes character U+0078, 0xff becomes + # U+00ff). The caller is responsible for decoding the byte sequence if necessary. + open(my $cfh, '-|:raw', @curl, '--config', $tmpfile) + or fatal("failed to run curl ($curl): $!"); + # According to , adding ':raw' to the open + # mode is buggy with Perl < v5.14. Call binmode on the filehandle just in case. + binmode($cfh) or fatal("binmode failed: $!"); my $reply = do { local $/; <$cfh>; }; close($cfh); # Closing $cfh waits for the process to exit and sets $?. if ((my $rc = $?>>8) != 0) { diff --git a/t/geturl_response.pl b/t/geturl_response.pl new file mode 100644 index 0000000..beb1a92 --- /dev/null +++ b/t/geturl_response.pl @@ -0,0 +1,27 @@ +use Test::More; +SKIP: { eval { require Test::Warnings; } or skip($@, 1); } +eval { require 'ddclient'; } or BAIL_OUT($@); + +# Fake curl. Use the printf utility, which can process escapes. This allows Perl to drive the fake +# curl with plain ASCII and get arbitrary bytes back, avoiding problems caused by any encoding that +# might be done by Perl (e.g., "use open ':encoding(UTF-8)';"). +my @fakecurl = ('sh', '-c', 'printf %b "$1"', '--'); + +my @test_cases = ( + { + desc => 'binary body', + # Body is UTF-8 encoded ✨ (U+2728 Sparkles) followed by a 0xff byte (invalid UTF-8). + printf => join('\r\n', ('HTTP/1.1 200 OK', '', '\0342\0234\0250\0377')), + # The raw bytes should come through as equally valued codepoints. They must not be decoded. + want => "HTTP/1.1 200 OK\n\n\xe2\x9c\xa8\xff", + }, +); + +for my $tc (@test_cases) { + @ddclient::curl = (@fakecurl, $tc->{printf}); + $ddclient::curl if 0; # suppress spurious warning "Name used only once: possible typo" + my $got = ddclient::geturl(url => 'http://ignored'); + is($got, $tc->{want}, $tc->{desc}); +} + +done_testing(); From 1c1642acfd277ef2fc191d938158daa9f5be6639 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sun, 26 May 2024 02:26:23 -0400 Subject: [PATCH 044/150] configure.ac: Allow users to specify path to curl --- configure.ac | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 649b7e3..5ca6da7 100644 --- a/configure.ac +++ b/configure.ac @@ -36,7 +36,18 @@ AC_PROG_MKDIR_P AC_PATH_PROG([FIND], [find]) AS_IF([test -z "${FIND}"], [AC_MSG_ERROR(['find' utility not found])]) -AC_PATH_PROG([CURL], [curl]) +AC_ARG_WITH([curl], + [AS_HELP_STRING([[--with-curl[=CURL]]], [use CURL as absolute path to curl executable])], + [], + [with_curl=yes]) +AS_CASE([${with_curl}], + [[yes]], [AC_PATH_PROG([CURL], [curl])], + [[no]], [CURL=], + [ + AC_MSG_CHECKING([for curl]) + CURL=${with_curl} + AC_MSG_RESULT([${CURL}]) + ]); AS_IF([test -z "${CURL}"], [AC_MSG_ERROR([curl not found])]) AX_WITH_PROG([PERL], perl) From a0240345bfa79719c4c5fbc3336935bfaded015d Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Mon, 3 Jun 2024 22:01:32 -0400 Subject: [PATCH 045/150] Use `Module->import(...)` instead of `import(Module, ...)` This matches the documentation of the `use` statement. --- ddclient.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ddclient.in b/ddclient.in index fa6e5ec..571e16e 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2610,9 +2610,9 @@ On Debian, the package libdigest-sha1-perl or libdigest-sha-perl must be install EOM } if ($sha1_loaded) { - import Digest::SHA1 (qw/sha1_hex/); + Digest::SHA1->import(qw/sha1_hex/); } elsif ($sha_loaded) { - import Digest::SHA (qw/sha1_hex/); + Digest::SHA->import(qw/sha1_hex/); } } ###################################################################### @@ -2626,7 +2626,7 @@ sub load_json_support { Error loading the Perl module JSON::PP needed for $why update. EOM } - import JSON::PP (qw/decode_json encode_json/); + JSON::PP->import(qw/decode_json encode_json/); } ###################################################################### From 1e1e100d7f3c9a87643f0fef972366b3ac5faf4b Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Mon, 3 Jun 2024 22:19:45 -0400 Subject: [PATCH 046/150] Prefer `Digest::SHA` over `Digest::SHA1` `Digest::SHA` is a core module; `Digest::SHA1` is not. --- ddclient.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ddclient.in b/ddclient.in index 571e16e..51ce3f9 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2609,10 +2609,10 @@ Error loading the Perl module Digest::SHA1 or Digest::SHA needed for $why update On Debian, the package libdigest-sha1-perl or libdigest-sha-perl must be installed. EOM } - if ($sha1_loaded) { - Digest::SHA1->import(qw/sha1_hex/); - } elsif ($sha_loaded) { + if ($sha_loaded) { Digest::SHA->import(qw/sha1_hex/); + } elsif ($sha1_loaded) { + Digest::SHA1->import(qw/sha1_hex/); } } ###################################################################### From 1401ff4aeaa918106f22bb0ab3cb30b01e22dfcf Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Mon, 3 Jun 2024 22:37:59 -0400 Subject: [PATCH 047/150] Only attempt to load `Digest::SHA` `Digest::SHA` has been a core module for a long time, and `Digest::SHA1` has not been updated in a long time. --- ChangeLog.md | 4 ++++ ddclient.in | 19 ++++++------------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index f376ad9..6ac8d0c 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -15,6 +15,10 @@ repository history](https://github.com/ddclient/ddclient/commits/master). [5b104ad1](https://github.com/ddclient/ddclient/commit/5b104ad116c023c3760129cab6e141f04f72b406) * All log messages are now written to STDERR, not a mix of STDOUT and STDERR. [#676](https://github.com/ddclient/ddclient/pull/676) + * For `--protocol=freedns` and `--protocol=nfsn`, the core module + `Digest::SHA` is now required. Previously, `Digest::SHA1` was used (if + available) as an alternative to `Digest::SHA`. + [#685](https://github.com/ddclient/ddclient/pull/685) ### New features diff --git a/ddclient.in b/ddclient.in index 51ce3f9..b758420 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2600,21 +2600,14 @@ sub encode_base64 ($;$) { ## load_sha1_support ###################################################################### sub load_sha1_support { - my $why = shift; - my $sha1_loaded = eval { require Digest::SHA1 }; - my $sha_loaded = eval { require Digest::SHA }; - unless ($sha1_loaded || $sha_loaded) { - fatal("%s", <<"EOM"); -Error loading the Perl module Digest::SHA1 or Digest::SHA needed for $why update. -On Debian, the package libdigest-sha1-perl or libdigest-sha-perl must be installed. + my ($protocol) = @_; + eval { require Digest::SHA; } or fatal(<<"EOM"); +Error loading the Perl module Digest::SHA needed for $protocol update. +On Debian, the package libdigest-sha-perl must be installed. EOM - } - if ($sha_loaded) { - Digest::SHA->import(qw/sha1_hex/); - } elsif ($sha1_loaded) { - Digest::SHA1->import(qw/sha1_hex/); - } + Digest::SHA->import(qw/sha1_hex/); } + ###################################################################### ## load_json_support ###################################################################### From bb658d763ac7aa6e78b7305c42291dfc06878afe Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Mon, 3 Jun 2024 22:09:23 -0400 Subject: [PATCH 048/150] Simplify loading of `JSON::PP` --- ddclient.in | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/ddclient.in b/ddclient.in index b758420..9af3867 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2612,13 +2612,9 @@ EOM ## load_json_support ###################################################################### sub load_json_support { - my $why = shift; - my $json_loaded = eval { require JSON::PP }; - unless ($json_loaded) { - fatal("%s", <<"EOM"); -Error loading the Perl module JSON::PP needed for $why update. -EOM - } + my ($protocol) = @_; + eval { require JSON::PP; } + or fatal("Error loading the Perl module JSON::PP needed for $protocol update."); JSON::PP->import(qw/decode_json encode_json/); } From b58a10b3e3de3c53ba540bd7403456e72b6bfbad Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 17 May 2024 22:22:02 -0400 Subject: [PATCH 049/150] header_ok: Add unit tests --- Makefile.am | 1 + t/header_ok.pl | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 t/header_ok.pl diff --git a/Makefile.am b/Makefile.am index 738f301..54c1831 100644 --- a/Makefile.am +++ b/Makefile.am @@ -68,6 +68,7 @@ handwritten_tests = \ t/geturl_connectivity.pl \ t/geturl_response.pl \ t/group_hosts_by.pl \ + t/header_ok.pl \ t/interval_expired.pl \ t/is-and-extract-ipv4.pl \ t/is-and-extract-ipv6.pl \ diff --git a/t/header_ok.pl b/t/header_ok.pl new file mode 100644 index 0000000..e835757 --- /dev/null +++ b/t/header_ok.pl @@ -0,0 +1,67 @@ +use Test::More; +SKIP: { eval { require Test::Warnings; } or skip($@, 1); } +eval { require 'ddclient'; } or BAIL_OUT($@); +my $have_mock = eval { require Test::MockModule; }; + +my $failmsg; +my $module; +if ($have_mock) { + $module = Test::MockModule->new('ddclient'); + # Note: 'mock' is used instead of 'redefine' because 'redefine' is not available in the versions + # of Test::MockModule distributed with old Debian and Ubuntu releases. + $module->mock('failed', sub { $failmsg //= ''; $failmsg .= sprintf(shift, @_) . "\n"; }); +} + +my @test_cases = ( + { + desc => 'malformed not OK', + input => 'malformed', + want => 0, + wantmsg => qr/unexpected/, + }, + { + desc => 'HTTP/1.1 200 OK', + input => 'HTTP/1.1 200 OK', + want => 1, + }, + { + desc => 'HTTP/2 200 OK', + input => 'HTTP/2 200 OK', + want => 1, + }, + { + desc => 'HTTP/3 200 OK', + input => 'HTTP/3 200 OK', + want => 1, + }, + { + desc => '401 not OK', + input => 'HTTP/1.1 401 bad', + want => 0, + wantmsg => qr/authentication failed/, + }, + { + desc => '403 not OK', + input => 'HTTP/1.1 403 bad', + want => 0, + wantmsg => qr/not authorized/, + }, + { + desc => 'other 4xx not OK', + input => 'HTTP/1.1 456 bad', + want => 0, + }, +); + +for my $tc (@test_cases) { + subtest $tc->{desc} => sub { + $failmsg = ''; + is(ddclient::header_ok('host', $tc->{input}), $tc->{want}, 'return value matches'); + SKIP: { + skip('Test::MockModule not available') if !$have_mock; + like($failmsg, $tc->{wantmsg} // qr/^$/, 'fail message matches'); + } + }; +} + +done_testing(); From adbac91be721055b506a60a69a8b74d7673e0549 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 17 May 2024 21:27:59 -0400 Subject: [PATCH 050/150] header_ok: Only keep first line of argument This allows callers to pass the entire response without generating overly long error messages. --- ddclient.in | 1 + t/header_ok.pl | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/ddclient.in b/ddclient.in index 9af3867..a5b2800 100755 --- a/ddclient.in +++ b/ddclient.in @@ -3774,6 +3774,7 @@ sub nic_updateable { ###################################################################### sub header_ok { my ($host, $line) = @_; + $line =~ s/\r?\n.*//s; my $ok = 0; if ($line =~ m%^s*HTTP/.*\s+(\d+)%i) { diff --git a/t/header_ok.pl b/t/header_ok.pl index e835757..5797341 100644 --- a/t/header_ok.pl +++ b/t/header_ok.pl @@ -51,6 +51,12 @@ my @test_cases = ( input => 'HTTP/1.1 456 bad', want => 0, }, + { + desc => 'only first line is logged on error', + input => "HTTP/1.1 404 not found\n\nbody", + want => 0, + wantmsg => qr/(?!body)/, + }, ); for my $tc (@test_cases) { From 7fe7fd0e188aa7071f78dacf96ddbceb8aa9e805 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 17 May 2024 22:24:12 -0400 Subject: [PATCH 051/150] header_ok: Refactor for readability --- ddclient.in | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/ddclient.in b/ddclient.in index a5b2800..cc21be6 100755 --- a/ddclient.in +++ b/ddclient.in @@ -3775,24 +3775,21 @@ sub nic_updateable { sub header_ok { my ($host, $line) = @_; $line =~ s/\r?\n.*//s; - my $ok = 0; - - if ($line =~ m%^s*HTTP/.*\s+(\d+)%i) { - my $result = $1; - - if ($result =~ m/^2\d\d$/) { - $ok = 1; - - } elsif ($result eq '401') { - failed("updating %s: authentication failed (%s)", $host, $line); - } elsif ($result eq '403') { - failed("updating %s: not authorized (%s)", $host, $line); - } - - } else { - failed("updating %s: unexpected line (%s)", $host, $line); + # TODO: What is this leading /s*/? Is it a typo of /\s*/? + my ($result) = ($line =~ qr%^s*HTTP/.*\s+(\d+)%i); + if (!defined($result)) { + failed('updating %s: unexpected HTTP response: %s', $host, $line); + return 0; + } elsif ($result eq '401') { + failed("updating %s: authentication failed (%s)", $host, $line); + return 0; + } elsif ($result eq '403') { + failed("updating %s: not authorized (%s)", $host, $line); + return 0; + } elsif ($result !~ qr/^2\d\d$/) { + return 0; } - return $ok; + return 1; } ###################################################################### From 211d59fccca01a7e63b4b52d9ef22d8d293a2272 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 17 May 2024 23:21:25 -0400 Subject: [PATCH 052/150] header_ok: Log all non-2xx HTTP status codes --- ddclient.in | 17 ++++++++--------- t/header_ok.pl | 9 +++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ddclient.in b/ddclient.in index cc21be6..84d8e3d 100755 --- a/ddclient.in +++ b/ddclient.in @@ -3776,17 +3776,16 @@ sub header_ok { my ($host, $line) = @_; $line =~ s/\r?\n.*//s; # TODO: What is this leading /s*/? Is it a typo of /\s*/? - my ($result) = ($line =~ qr%^s*HTTP/.*\s+(\d+)%i); - if (!defined($result)) { + my ($code, $msg) = ($line =~ qr%^s*HTTP/.*\s+(\d+)\s*(?:\s+([^\s].*))?$%i); + if (!defined($code)) { failed('updating %s: unexpected HTTP response: %s', $host, $line); return 0; - } elsif ($result eq '401') { - failed("updating %s: authentication failed (%s)", $host, $line); - return 0; - } elsif ($result eq '403') { - failed("updating %s: not authorized (%s)", $host, $line); - return 0; - } elsif ($result !~ qr/^2\d\d$/) { + } elsif ($code !~ qr/^2\d\d$/) { + my %msgs = ( + '401' => 'authentication failed', + '403' => 'not authorized', + ); + failed('updating %s: %s %s', $host, $code, $msg // $msgs{$code} // ''); return 0; } return 1; diff --git a/t/header_ok.pl b/t/header_ok.pl index 5797341..197cc8d 100644 --- a/t/header_ok.pl +++ b/t/header_ok.pl @@ -35,14 +35,14 @@ my @test_cases = ( want => 1, }, { - desc => '401 not OK', - input => 'HTTP/1.1 401 bad', + desc => '401 not OK, fallback message', + input => 'HTTP/1.1 401 ', want => 0, wantmsg => qr/authentication failed/, }, { - desc => '403 not OK', - input => 'HTTP/1.1 403 bad', + desc => '403 not OK, fallback message', + input => 'HTTP/1.1 403 ', want => 0, wantmsg => qr/not authorized/, }, @@ -50,6 +50,7 @@ my @test_cases = ( desc => 'other 4xx not OK', input => 'HTTP/1.1 456 bad', want => 0, + wantmsg => qr/bad/, }, { desc => 'only first line is logged on error', From 2d4a93d5e79964beaf0ff5a3793c0ce6acb089a3 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 14 May 2024 23:51:37 -0400 Subject: [PATCH 053/150] header_ok: Fix typo(?) in HTTP response regular expression --- ddclient.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index 84d8e3d..eaf40e8 100755 --- a/ddclient.in +++ b/ddclient.in @@ -3775,8 +3775,7 @@ sub nic_updateable { sub header_ok { my ($host, $line) = @_; $line =~ s/\r?\n.*//s; - # TODO: What is this leading /s*/? Is it a typo of /\s*/? - my ($code, $msg) = ($line =~ qr%^s*HTTP/.*\s+(\d+)\s*(?:\s+([^\s].*))?$%i); + my ($code, $msg) = ($line =~ qr%^\s*HTTP/.*\s+(\d+)\s*(?:\s+([^\s].*))?$%i); if (!defined($code)) { failed('updating %s: unexpected HTTP response: %s', $host, $line); return 0; From 0c42478ea7ba8b4c02a051e3b6054a7e635babf1 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 6 Jun 2024 19:45:59 -0400 Subject: [PATCH 054/150] ci: Switch from RedHat UBI to AlmaLinux --- .github/workflows/ci.yml | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4cd166..7b40407 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,14 +62,14 @@ jobs: - fedora:39 - fedora:latest - fedora:rawhide + - almalinux:8 + - almalinux:latest # RedHat UBI is mostly garbage due to a profound lack of basic # packages. It is tested anyway because it's the closest available - # approximation of RHEL. Some of the packages needed for some tests - # aren't available, so those tests will be skipped. I guess it's - # still better than nothing. + # approximation of RHEL, aside from AlmaLinux. Some of the packages + # needed for some tests aren't available, so those tests will be + # skipped. I guess it's still better than nothing. - registry.access.redhat.com/ubi7/ubi:latest - - registry.access.redhat.com/ubi8/ubi:latest - - registry.access.redhat.com/ubi9/ubi:latest runs-on: ubuntu-latest container: image: ${{ matrix.image }} @@ -79,6 +79,16 @@ jobs: # ubi7 is too old for checkout@v4. - if: ${{ matrix.image == 'registry.access.redhat.com/ubi7/ubi:latest' }} uses: actions/checkout@v3 + - name: enable repositories (AlmaLinux 8) + if: ${{ matrix.image == 'almalinux:8' }} + run: | + dnf --refresh install -y 'dnf-command(config-manager)' epel-release && + dnf config-manager --set-enabled powertools + - name: enable repositories (AlmaLinux latest) + if: ${{ matrix.image == 'almalinux:latest' }} + run: | + dnf --refresh install -y 'dnf-command(config-manager)' epel-release && + dnf config-manager --set-enabled crb - name: install dependencies # The --skip-broken argument works around RedHat UBI's missing packages. # (They're only used for testing, so it's OK to not install them.) From 288a30ab1e96915be3a91b2be967375911dc19c7 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 13 Jun 2024 00:19:21 -0400 Subject: [PATCH 055/150] Whitespace fixes --- ddclient.in | 372 +++++++++++++++++++++++----------------------------- 1 file changed, 164 insertions(+), 208 deletions(-) diff --git a/ddclient.in b/ddclient.in index eaf40e8..1911612 100755 --- a/ddclient.in +++ b/ddclient.in @@ -649,7 +649,7 @@ my %variables = ( 'max-interval' => setv(T_DELAY, 0, 0, interval('25d'), 0), 'min-error-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), - 'warned-min-interval' => setv(T_ANY, 0, 1, 0, undef), + 'warned-min-interval' => setv(T_ANY, 0, 1, 0, undef), 'warned-min-error-interval' => setv(T_ANY, 0, 1, 0, undef), }, 'dyndns-common-defaults' => { @@ -1274,10 +1274,12 @@ sub main { read_cache(opt('cache'), \%cache); print_info() if opt('debug') && opt('verbose'); - fatal("invalid argument '--use=%s'; possible values are:\n%s", $opt{'use'}, join("\n", ip_strategies_usage())) + fatal("invalid argument '--use=%s'; possible values are:\n%s", + $opt{'use'}, join("\n", ip_strategies_usage())) unless exists $ip_strategies{lc opt('use')}; if (defined($opt{'usev6'})) { - usage("invalid argument '--usev6=%s'; possible values are:\n%s", $opt{'usev6'}, join("\n",ipv6_strategies_usage())) + usage("invalid argument '--usev6=%s'; possible values are:\n%s", + $opt{'usev6'}, join("\n", ipv6_strategies_usage())) unless exists $ipv6_strategies{lc opt('usev6')}; } @@ -1354,12 +1356,12 @@ sub update_nics { next if $config{$h}{'protocol'} ne lc($s); $examined{$h} = 1; # we only do this once per 'use' and argument combination - my $use = opt('use', $h) // 'disabled'; - my $usev4 = opt('usev4', $h) // 'disabled'; - my $usev6 = opt('usev6', $h) // 'disabled'; - $use = 'disabled' if ($use eq 'no'); # backward compatibility - $usev6 = 'disabled' if ($usev6 eq 'no'); # backward compatibility - $use = 'disabled' if ($usev4 ne 'disabled') || ($usev6 ne 'disabled'); + my $use = opt('use', $h) // 'disabled'; + my $usev4 = opt('usev4', $h) // 'disabled'; + my $usev6 = opt('usev6', $h) // 'disabled'; + $use = 'disabled' if ($use eq 'no'); # backward compatibility + $usev6 = 'disabled' if ($usev6 eq 'no'); # backward compatibility + $use = 'disabled' if ($usev4 ne 'disabled') || ($usev6 ne 'disabled'); my $arg_ip = opt('ip', $h) // ''; my $arg_ipv4 = opt('ipv4', $h) // ''; my $arg_ipv6 = opt('ipv6', $h) // ''; @@ -1444,8 +1446,7 @@ sub update_nics { $config{$h}{'wantip'} = $ipv4 if (!$ip && $ipv4); $config{$h}{'wantip'} = $ipv6 if (!$ip && !$ipv4 && $ipv6); - if (!$ip && !$ipv4 && !$ipv6) - { + if (!$ip && !$ipv4 && !$ipv6) { warning("Could not determine an IP for %s", $h); next; } @@ -1761,18 +1762,14 @@ sub _read_config { ## verify that keywords are valid...and check the value for my $k (keys %locals) { # Handle '_env' keyword suffix - if ($k =~ /(.*)_env$/) - { + if ($k =~ /(.*)_env$/) { debug("Loading value for $1 from environment variable $locals{$k}."); - if (exists($ENV{$locals{$k}})) - { + if (exists($ENV{$locals{$k}})) { # Set the value to the value of the environment variable $locals{$1} = $ENV{$locals{$k}}; # Remove the '_env' suffix from the key $k = $1; - } - else - { + } else { warning("Environment variable '$locals{$k}' not set for keyword '$k' (ignored)"); delete $locals{$k}; next; @@ -1789,7 +1786,9 @@ sub _read_config { if (!defined($value)) { warning("Invalid Value for keyword '%s' = '%s'", $k, $locals{$k}); delete $locals{$k}; - } else { $locals{$k} = $value; } + } else { + $locals{$k} = $value; + } } } if (exists($locals{'host'})) { @@ -1818,7 +1817,7 @@ sub _read_config { $config{$h} = {%locals, %{$config{$h}}}; } else { ## save a copy of the current globals - $config{$h} = { %locals }; + $config{$h} = {%locals}; $config{$h}{'host'} = $h; } } @@ -1956,9 +1955,9 @@ sub init_config { # Make sure any _env suffixed variables look at their original entry $k = $1 if $k =~ /^(.*)_env$/; - my $def = $variables{'merged'}{$k}; + my $def = $variables{'merged'}{$k}; my $ovalue = $globals{$k} // $def->{'default'}; - my $value = check_value($ovalue, $def); + my $value = check_value($ovalue, $def); if ($def->{'required'} && !defined $value) { $value = default($k); warning("'%s=%s' is an invalid %s. (using default of %s)", $k, $ovalue, $def->{'type'}, $value); @@ -1973,8 +1972,8 @@ sub init_config { $proto = $config{$h}{'protocol'}; $proto = opt('protocol') if !defined($proto); - load_sha1_support($proto) if (grep (/^$proto$/, ("freedns", "nfsn"))); - load_json_support($proto) if (grep (/^$proto$/, ("1984", "cloudflare", "digitalocean", "gandi", "godaddy", "hetzner", "yandex", "nfsn", "njalla", "porkbun", "dnsexit2"))); + load_sha1_support($proto) if (grep(/^$proto$/, ("freedns", "nfsn"))); + load_json_support($proto) if (grep(/^$proto$/, ("1984", "cloudflare", "digitalocean", "gandi", "godaddy", "hetzner", "yandex", "nfsn", "njalla", "porkbun", "dnsexit2"))); if (!exists($services{$proto})) { warning("skipping host: %s: unrecognized protocol '%s'", $h, $proto); @@ -1982,7 +1981,7 @@ sub init_config { } else { my $svars = $services{$proto}{'variables'}; - my $conf = { 'protocol' => $proto }; + my $conf = {'protocol' => $proto}; for my $k (keys %$svars) { # Make sure any _env suffixed variables look at their original entry @@ -2492,7 +2491,7 @@ sub interval_expired { return 0 if ($config{$host}{$interval} // 0) == 'inf'; return 1 if !exists $cache{$host}; - return 1 if !exists $cache{$host}{$time} || !$cache{$host}{$time}; + return 1 if !exists $cache{$host}{$time} || !$cache{$host}{$time}; return 1 if !exists $config{$host}{$interval} || !$config{$host}{$interval}; return $now > ($cache{$host}{$time} + $config{$host}{$interval}); @@ -2546,11 +2545,11 @@ sub check_value { } elsif ($type eq T_USEV4) { $value = lc $value; - return undef if ! exists $ipv4_strategies{$value}; + return undef if !exists $ipv4_strategies{$value}; } elsif ($type eq T_USEV6) { $value = lc $value; - return undef if ! exists $ipv6_strategies{$value}; + return undef if !exists $ipv6_strategies{$value}; } elsif ($type eq T_FILE) { return undef if $value eq ""; @@ -2816,41 +2815,35 @@ sub geturl { ###################################################################### sub get_ip { my $use = lc shift; - $use = 'disabled' if ($use eq 'no'); # backward compatibility - my $h = shift; + $use = 'disabled' if ($use eq 'no'); # backward compatibility + my $h = shift; my ($ip, $arg, $reply, $url, $skip) = (undef, opt($use, $h), ''); $arg = '' unless $arg; if ($use eq 'ip') { - $ip = opt('ip', $h); + $ip = opt('ip', $h); if (!is_ipv4($ip) && !is_ipv6($ip)) { warning("'%s' is not a valid IPv4 or IPv6 address", $ip // ''); $ip = undef; } $arg = 'ip'; - } elsif ($use eq 'if') { $ip = get_ip_from_interface($arg); - } elsif ($use eq 'cmd') { if ($arg) { $skip = opt('cmd-skip', $h); $reply = `$arg`; $reply = '' if $?; } - } elsif ($use eq 'web') { $url = opt('web', $h) // ''; $skip = opt('web-skip', $h); - if (exists $builtinweb{$url}) { warning("googledomains is deprecated! See https://github.com/ddclient/ddclient/issues/622 for more info.") if ($url eq 'googledomains'); - $skip //= $builtinweb{$url}->{'skip'}; - $url = $builtinweb{$url}->{'url'}; + $url = $builtinweb{$url}->{'url'}; } $arg = $url; - if ($url) { $reply = geturl( proxy => opt('proxy', $h), @@ -2858,11 +2851,9 @@ sub get_ip { ssl_validate => opt('web-ssl-validate', $h), ) // ''; } - } elsif ($use eq 'disabled') { ## This is a no-op... Do not get an IP address for this host/service $reply = ''; - } elsif ($use eq 'fw' || defined(my $fw = $builtinfw{$use})) { # Note that --use=firewallname uses --fw=arg, not --firewallname=arg. $arg = opt('fw', $h) // ''; @@ -2877,7 +2868,6 @@ sub get_ip { $url = "http://$url$fw->{'url'}" unless $url =~ /\//; } } - if ($url) { $reply = geturl( url => $url, @@ -2887,7 +2877,6 @@ sub get_ip { ssl_validate => opt('fw-ssl-validate', $h), ) // ''; } - } else { warning("ignoring unsupported '--use=$use'"); } @@ -3215,7 +3204,6 @@ sub get_ip_from_interface { sub get_ipv4 { my $usev4 = lc(shift); ## Method to obtain IP address my $h = shift; ## Host/service making the request - my $ipv4 = undef; ## Found IPv4 address my $reply = ''; ## Text returned from various methods my $url = ''; ## URL of website or firewall @@ -3224,17 +3212,15 @@ sub get_ipv4 { if ($usev4 eq 'ipv4') { ## Static IPv4 address is provided in "ipv4=
" - $ipv4 = $arg; + $ipv4 = $arg; if (!is_ipv4($ipv4)) { - warning("'%s' is not a valid IPv4",$ipv4 // ''); + warning("'%s' is not a valid IPv4", $ipv4 // ''); $ipv4 = undef; } $arg = 'ipv4'; # For debug message at end of function - } elsif ($usev4 eq 'ifv4') { ## Obtain IPv4 address from interface mamed in "ifv4=" - $ipv4 = get_ip_from_interface($arg,4); - + $ipv4 = get_ip_from_interface($arg, 4); } elsif ($usev4 eq 'cmdv4') { ## Obtain IPv4 address by executing the command in "cmdv4=" warning("'--cmd-skip' ignored for '--usev4=$usev4'") if (opt('verbose') && opt('cmd-skip', $h)); @@ -3243,30 +3229,27 @@ sub get_ipv4 { $reply = qx{$sys_cmd}; $reply = '' if $?; } - } elsif ($usev4 eq 'webv4') { ## Obtain IPv4 address by accessing website at url in "webv4=" $url = $arg; $skip = opt('webv4-skip', $h); if (exists $builtinweb{$url}) { warning("googledomains is deprecated! See https://github.com/ddclient/ddclient/issues/622 for more info.") if ($url eq 'googledomains'); - $skip //= $builtinweb{$url}->{'skip'}; - $url = $builtinweb{$url}->{'url'}; + $url = $builtinweb{$url}->{'url'}; $arg = $url; } if ($url) { - $reply = geturl( proxy => opt('proxy', $h), + $reply = geturl( + proxy => opt('proxy', $h), url => $url, ipversion => 4, # when using a URL to find IPv4 address we should force use of IPv4 ssl_validate => opt('web-ssl-validate', $h), ) // ''; } - } elsif ($usev4 eq 'disabled') { ## This is a no-op... Do not get an IPv4 address for this host/service $reply = ''; - } elsif ($usev4 eq 'fwv4' || defined(my $fw = $builtinfw{$usev4})) { warning("'--fw' is deprecated for '--usev4=$usev4'; use '--fwv4' instead") if (!defined(opt('fwv4', $h)) && defined(opt('fw', $h))); @@ -3295,7 +3278,6 @@ sub get_ipv4 { ssl_validate => opt('fw-ssl-validate', $h), ) // ''; } - } else { warning("ignoring unsupported '--usev4=$usev4'"); } @@ -3321,7 +3303,6 @@ sub get_ipv6 { my $usev6 = lc(shift); ## Method to obtain IP address $usev6 = 'disabled' if ($usev6 eq 'no'); # backward compatibility my $h = shift; ## Host/service making the request - my $ipv6 = undef; ## Found IPv6 address my $reply = ''; ## Text returned from various methods my $url = ''; ## URL of website or firewall @@ -3336,19 +3317,17 @@ sub get_ipv6 { } $ipv6 = $arg; if (!is_ipv6($ipv6)) { - warning("'%s' is not a valid IPv6",$ipv6 // ''); + warning("'%s' is not a valid IPv6", $ipv6 // ''); $ipv6 = undef; } $arg = 'ipv6'; # For debug message at end of function - - } elsif ($usev6 eq 'ifv6' || $usev6 eq 'if' ) { + } elsif ($usev6 eq 'ifv6' || $usev6 eq 'if') { ## Obtain IPv6 address from interface mamed in "ifv6=" if ($usev6 eq 'if') { warning("'--usev6=if' is deprecated. Use '--usev6=ifv6'"); $arg = opt('ifv6', $h) // $arg; } - $ipv6 = get_ip_from_interface($arg,6); - + $ipv6 = get_ip_from_interface($arg, 6); } elsif ($usev6 eq 'cmdv6' || $usev6 eq 'cmd') { ## Obtain IPv6 address by executing the command in "cmdv6=" if ($usev6 eq 'cmd') { @@ -3361,7 +3340,6 @@ sub get_ipv6 { $reply = qx{$sys_cmd}; $reply = '' if $?; } - } elsif ($usev6 eq 'webv6' || $usev6 eq 'web') { ## Obtain IPv6 address by accessing website at url in "webv6=" if ($usev6 eq 'web') { @@ -3374,9 +3352,8 @@ sub get_ipv6 { $skip = opt('webv6-skip', $h); if (exists $builtinweb{$url}) { warning("googledomains is deprecated! See https://github.com/ddclient/ddclient/issues/622 for more info.") if ($url eq 'googledomains'); - $skip //= $builtinweb{$url}->{'skip'}; - $url = $builtinweb{$url}->{'url'}; + $url = $builtinweb{$url}->{'url'}; $arg = $url; } if ($url) { @@ -3387,10 +3364,8 @@ sub get_ipv6 { ssl_validate => opt('web-ssl-validate', $h), ) // ''; } - } elsif ($usev6 eq 'disabled') { $reply = ''; - } elsif ($usev6 eq 'fwv6' || defined(my $fw = $builtinfw{$usev6})) { $skip = opt('fwv6-skip', $h) // $fw->{'skip'}; if ($fw && defined(my $query = $fw->{'queryv6'})) { @@ -3399,10 +3374,8 @@ sub get_ipv6 { } else { warning("'--usev6=%s' is not implemented and does nothing", $usev6); } - } else { warning("ignoring unsupported '--usev6=$usev6'"); - } ## Set to loopback address if no text set yet @@ -3587,15 +3560,15 @@ sub nic_updateable { ); $update = 1; - } elsif ( ($use ne 'disabled') - && ((!exists($cache{$host}{'ip'})) || ("$cache{$host}{'ip'}" ne "$ip"))) { + } elsif (($use ne 'disabled') && ((!exists($cache{$host}{'ip'})) || + ("$cache{$host}{'ip'}" ne "$ip"))) { ## Check whether to update IP address for the "--use" method" if ((($cache{$host}{'status'} // '') eq 'good') && !interval_expired($host, 'mtime', 'min-interval')) { warning("skipping update of %s from %s to %s.\nlast updated %s.\nWait at least %s between update attempts.", $host, - ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : ''), + ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : ''), $ip, ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''), prettyinterval($config{$host}{'min-interval'}) @@ -3605,15 +3578,14 @@ sub nic_updateable { $cache{$host}{'warned-min-interval'} = $now; } elsif ((($cache{$host}{'status'} // '') ne 'good') && - !interval_expired($host, 'atime', 'min-error-interval')) { + !interval_expired($host, 'atime', 'min-error-interval')) { - if ( opt('verbose') - || ( ! $cache{$host}{'warned-min-error-interval'} - && (($warned_ip{$host} // 0) < $inv_ip_warn_count)) ) { + if (opt('verbose') || (!$cache{$host}{'warned-min-error-interval'} && + (($warned_ip{$host} // 0) < $inv_ip_warn_count))) { warning("skipping update of %s from %s to %s.\nlast updated %s but last attempt on %s failed.\nWait at least %s between update attempts.", $host, - ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : ''), + ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : ''), $ip, ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''), ($cache{$host}{'atime'} ? prettytime($cache{$host}{'atime'}) : ''), @@ -3632,15 +3604,15 @@ sub nic_updateable { $update = 1; } - } elsif ( ($usev4 ne 'disabled') - && ((!exists($cache{$host}{'ipv4'})) || ("$cache{$host}{'ipv4'}" ne "$ipv4"))) { + } elsif (($usev4 ne 'disabled') && ((!exists($cache{$host}{'ipv4'})) || + ("$cache{$host}{'ipv4'}" ne "$ipv4"))) { ## Check whether to update IPv4 address for the "--usev4" method" if ((($cache{$host}{'status-ipv4'} // '') eq 'good') && !interval_expired($host, 'mtime', 'min-interval')) { warning("skipping update of %s from %s to %s.\nlast updated %s.\nWait at least %s between update attempts.", $host, - ($cache{$host}{'ipv4'} ? $cache{$host}{'ipv4'} : ''), + ($cache{$host}{'ipv4'} ? $cache{$host}{'ipv4'} : ''), $ipv4, ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''), prettyinterval($config{$host}{'min-interval'}) @@ -3650,15 +3622,14 @@ sub nic_updateable { $cache{$host}{'warned-min-interval'} = $now; } elsif ((($cache{$host}{'status-ipv4'} // '') ne 'good') && - !interval_expired($host, 'atime', 'min-error-interval')) { + !interval_expired($host, 'atime', 'min-error-interval')) { - if ( opt('verbose') - || ( ! $cache{$host}{'warned-min-error-interval'} - && (($warned_ipv4{$host} // 0) < $inv_ip_warn_count)) ) { + if (opt('verbose') || (!$cache{$host}{'warned-min-error-interval'} && + (($warned_ipv4{$host} // 0) < $inv_ip_warn_count))) { warning("skipping update of %s from %s to %s.\nlast updated %s but last attempt on %s failed.\nWait at least %s between update attempts.", $host, - ($cache{$host}{'ipv4'} ? $cache{$host}{'ipv4'} : ''), + ($cache{$host}{'ipv4'} ? $cache{$host}{'ipv4'} : ''), $ipv4, ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''), ($cache{$host}{'atime'} ? prettytime($cache{$host}{'atime'}) : ''), @@ -3677,15 +3648,15 @@ sub nic_updateable { $update = 1; } - } elsif ( ($usev6 ne 'disabled') - && ((!exists($cache{$host}{'ipv6'})) || ("$cache{$host}{'ipv6'}" ne "$ipv6"))) { + } elsif (($usev6 ne 'disabled') && ((!exists($cache{$host}{'ipv6'})) || + ("$cache{$host}{'ipv6'}" ne "$ipv6"))) { ## Check whether to update IPv6 address for the "--usev6" method" if ((($cache{$host}{'status-ipv6'} // '') eq 'good') && !interval_expired($host, 'mtime', 'min-interval')) { warning("skipping update of %s from %s to %s.\nlast updated %s.\nWait at least %s between update attempts.", $host, - ($cache{$host}{'ipv6'} ? $cache{$host}{'ipv6'} : ''), + ($cache{$host}{'ipv6'} ? $cache{$host}{'ipv6'} : ''), $ipv6, ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''), prettyinterval($config{$host}{'min-interval'}) @@ -3695,15 +3666,14 @@ sub nic_updateable { $cache{$host}{'warned-min-interval'} = $now; } elsif ((($cache{$host}{'status-ipv6'} // '') ne 'good') && - !interval_expired($host, 'atime', 'min-error-interval')) { + !interval_expired($host, 'atime', 'min-error-interval')) { - if ( opt('verbose') - || ( ! $cache{$host}{'warned-min-error-interval'} - && (($warned_ipv6{$host} // 0) < $inv_ip_warn_count)) ) { + if (opt('verbose') || (!$cache{$host}{'warned-min-error-interval'} && + (($warned_ipv6{$host} // 0) < $inv_ip_warn_count))) { warning("skipping update of %s from %s to %s.\nlast updated %s but last attempt on %s failed.\nWait at least %s between update attempts.", $host, - ($cache{$host}{'ipv6'} ? $cache{$host}{'ipv6'} : ''), + ($cache{$host}{'ipv6'} ? $cache{$host}{'ipv6'} : ''), $ipv6, ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''), ($cache{$host}{'atime'} ? prettytime($cache{$host}{'atime'}) : ''), @@ -3896,8 +3866,8 @@ sub nic_dyndns1_update { failed("updating %s: %s", $h, $title); } else { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; $config{$h}{'status'} = 'good'; success("updating %s: %s: IP address set to %s (%s)", $h, $return_code, $ip, $title); } @@ -4079,8 +4049,8 @@ sub nic_dyndns2_update { if ($status eq 'good') { for my $h (@hosts) { - $config{$h}{'ipv4'} = $ipv4 if $ipv4; - $config{$h}{'ipv6'} = $ipv6 if $ipv6; + $config{$h}{'ipv4'} = $ipv4 if $ipv4; + $config{$h}{'ipv6'} = $ipv6 if $ipv6; $config{$h}{'mtime'} = $now; } @@ -4092,9 +4062,9 @@ sub nic_dyndns2_update { 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}{'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; } @@ -4499,8 +4469,8 @@ sub nic_dslreports1_update { failed("updating %s", $h); } else { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; $config{$h}{'status'} = 'good'; success("updating %s: %s: IP address set to %s", $h, $return_code, $ip); } @@ -4575,8 +4545,8 @@ sub nic_domeneshop_update { my $status = shift(@reply); my $message = pop(@reply); if ($status =~ /204/) { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; $config{$h}{'status'} = 'good'; success("updating %s: good: IP address set to %s", $h, $ip); } else { @@ -4681,8 +4651,8 @@ sub nic_zoneedit1_update { $status_ip = $var{'IP'} if exists $var{'IP'}; if ($status eq 'SUCCESS' || ($status eq 'ERROR' && $var{'CODE'} eq '707')) { - $config{$h}{'ip'} = $status_ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $status_ip; + $config{$h}{'mtime'} = $now; $config{$h}{'status'} = 'good'; success("updating %s: IP address set to %s (%s: %s)", $h, $ip, $status_code, $status_text); @@ -4847,8 +4817,8 @@ sub nic_easydns_update { $config{$h}{'status-ipv4'} = $status if $ipv4; $config{$h}{'status-ipv6'} = $status if $ipv6; if ($status eq 'NOERROR') { - $config{$h}{'ipv4'} = $ipv4; - $config{$h}{'ipv6'} = $ipv6; + $config{$h}{'ipv4'} = $ipv4; + $config{$h}{'ipv6'} = $ipv6; $config{$h}{'mtime'} = $now; success("updating %s: %s: IP address set to %s %s", $h, $status, $ipv4, $ipv6); @@ -4917,8 +4887,6 @@ EoEXAMPLE ## ###################################################################### sub nic_namecheap_update { - - debug("\nnic_namecheap1_update -------------------"); ## update each configured host @@ -4947,8 +4915,8 @@ sub nic_namecheap_update { my @reply = split /\n/, $reply; if (grep /0/i, @reply) { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; $config{$h}{'status'} = 'good'; success("updating %s: good: IP address set to %s", $h, $ip); } else { @@ -5177,8 +5145,8 @@ sub nic_nfsn_update { my $add_resp = nic_nfsn_make_request($h, $add_path, 'POST', $add_body); if (header_ok($h, $add_resp)) { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; $config{$h}{'status'} = 'good'; success("updating %s: good: IP address set to %s", $h, $ip); } else { @@ -5334,8 +5302,6 @@ EoEXAMPLE ## ###################################################################### sub nic_sitelutions_update { - - debug("\nnic_sitelutions_update -------------------"); ## update each configured host @@ -5361,8 +5327,8 @@ sub nic_sitelutions_update { my @reply = split /\n/, $reply; if (grep /success/i, @reply) { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; $config{$h}{'status'} = 'good'; success("updating %s: good: IP address set to %s", $h, $ip); } else { @@ -5637,8 +5603,6 @@ EoEXAMPLE ## ###################################################################### sub nic_changeip_update { - - debug("\nnic_changeip_update -------------------"); ## update each configured host @@ -5667,8 +5631,8 @@ sub nic_changeip_update { my @reply = split /\n/, $reply; if (grep /success/i, @reply) { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; $config{$h}{'status'} = 'good'; success("updating %s: good: IP address set to %s", $h, $ip); } else { @@ -5895,8 +5859,8 @@ sub nic_googledomains_update { next if !header_ok($host, $reply); # Cache - $config{$host}{'ip'} = $ip; - $config{$host}{'mtime'} = $now; + $config{$host}{'ip'} = $ip; + $config{$host}{'mtime'} = $now; $config{$host}{'status'} = 'good'; } } @@ -5972,7 +5936,7 @@ sub nic_mythicdyn_update { my $ok = header_ok($h, $reply); if ($ok) { - $config{$h}{'mtime'} = $now; + $config{$h}{'mtime'} = $now; $config{$h}{"status-ipv$mythver"} = "good"; success("%s -- IPV%s Updated successfully.", $h, $mythver); @@ -6094,7 +6058,7 @@ EoINSTR4 for my $ip ($ipv4, $ipv6) { next if (!$ip); my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; - $config{$_}{"ipv$ipv"} = $ip; + $config{$_}{"ipv$ipv"} = $ip; $config{$_}{"status-ipv$ipv"} = 'good'; success("updating %s: good: IPv%s address set to %s", $_, $ipv, $ip); } @@ -6537,8 +6501,8 @@ sub nic_yandex_update { } # Cache - $config{$host}{'ip'} = $ip; - $config{$host}{'mtime'} = $now; + $config{$host}{'ip'} = $ip; + $config{$host}{'mtime'} = $now; $config{$host}{'status'} = 'good'; } } @@ -6614,9 +6578,9 @@ sub nic_duckdns_update { for $line (@reply) { if ($line eq 'OK') { - $config{$h}{'ipv4'} = $ipv4 if $ipv4; - $config{$h}{'ipv6'} = $ipv6 if $ipv6; - $config{$h}{'mtime'} = $now; + $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; $state = 'result'; @@ -6697,8 +6661,8 @@ sub nic_freemyip_update { my @reply = split /\n/, $reply; my $returned = pop(@reply); if ($returned =~ /OK/) { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; $config{$h}{'status'} = 'good'; success("updating %s: good: IP address set to %s", $h, $ip); } else { @@ -6845,15 +6809,15 @@ sub nic_woima_update { $config{$h}{'status'} = $status; if ($status eq 'good') { - $config{$h}{'ip'} = $ip; + $config{$h}{'ip'} = $ip; $config{$h}{'mtime'} = $now; success("updating %s: %s: IP address set to %s", $h, $status, $ip); } elsif (exists $errors{$status}) { if ($status eq 'nochg') { warning("updating %s: %s: %s", $h, $status, $errors{$status}); - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; $config{$h}{'status'} = 'good'; } else { @@ -6945,8 +6909,8 @@ sub nic_dondominio_update { my @reply = split /\n/, $reply; my $returned = pop(@reply); if ($returned =~ /OK/ || $returned =~ /IP:$ip/) { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; $config{$h}{'status'} = 'good'; success("updating %s: good: IP address set to %s", $h, $ip); } else { @@ -7031,8 +6995,8 @@ sub nic_dnsmadeeasy_update { my @reply = split /\n/, $reply; my $returned = pop(@reply); if ($returned =~ 'success') { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; $config{$h}{'status'} = 'good'; success("Updating %s: good: IP address set to %s", $h, $ip); } else { @@ -7105,8 +7069,8 @@ sub nic_ovh_update { my @reply = split /\n/, $reply; my $returned = List::Util::first { $_ =~ /good/ || $_ =~ /nochg/ } @reply; if ($returned) { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; $config{$h}{'status'} = 'good'; if ($returned =~ /good/) { success("updating %s: good: IP address set to %s", $h, $ip); @@ -7450,8 +7414,8 @@ sub nic_dinahosting_update { failed("updating %s: error %d: %s", $code, $message); next; } - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; $config{$h}{'status'} = 'good'; success("updating %s: IP address set to %s", $h, $ip); } @@ -7560,8 +7524,8 @@ sub nic_gandi_update { } if($response->{'rrset_values'}->[0] eq $ip && (!defined($config{$h}{'ttl'}) || $response->{'rrset_ttl'} eq $config{$h}{'ttl'})) { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; $config{$h}{"status-$ipv"} = "good"; success("updating %s: skipped: address was already set to %s.", $h, $ip); next; @@ -7584,14 +7548,12 @@ sub nic_gandi_update { } $ok = header_ok($h, $reply); if ($ok) { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; $config{$h}{"status-$ipv"} = "good"; - success("%s -- Updated successfully to %s.", $h, $ip); } else { $config{$h}{"status-$ipv"} = "bad"; - if (defined($response->{status}) && $response->{status} eq "error") { my @errors; for my $err (@{$response->{errors}}) { @@ -7636,39 +7598,36 @@ EoEXAMPLE ## response contains "code 200" on succesfull completion ###################################################################### sub nic_keysystems_update { - debug("\nnic_keysystems_update -------------------"); + debug("\nnic_keysystems_update -------------------"); - ## update each configured host - ## should improve to update in one pass - for my $h (@_) { - my $ip = delete $config{$h}{'wantip'}; - info("KEYSYSTEMS setting IP address to %s for %s", $ip, $h); + ## update each configured host + ## should improve to update in one pass + for my $h (@_) { + my $ip = delete $config{$h}{'wantip'}; + info("KEYSYSTEMS setting IP address to %s for %s", $ip, $h); - my $url = "http://$config{$h}{'server'}/update.php?hostname=$h&password=$config{$h}{'password'}&ip=$ip"; + my $url = "http://$config{$h}{'server'}/update.php?hostname=$h&password=$config{$h}{'password'}&ip=$ip"; - # Try to get URL - my $reply = geturl(proxy => opt('proxy'), url => $url) // ''; + # Try to get URL + my $reply = geturl(proxy => opt('proxy'), url => $url) // ''; - # No response, declare as failed - if (!defined($reply) || !$reply) { - failed("KEYSYSTEMS updating %s: Could not connect to %s.", $h, $config{$h}{'server'}); - last; - } - last if !header_ok($h, $reply); + # No response, declare as failed + if (!defined($reply) || !$reply) { + failed("KEYSYSTEMS updating %s: Could not connect to %s.", $h, $config{$h}{'server'}); + last; + } + last if !header_ok($h, $reply); - if ($reply =~ /code = 200/) - { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; - $config{$h}{'status'} = 'good'; - success("updating %s: good: IP address set to %s", $h, $ip); + if ($reply =~ /code = 200/) { + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; + $config{$h}{'status'} = 'good'; + success("updating %s: good: IP address set to %s", $h, $ip); + } else { + $config{$h}{'status'} = 'failed'; + failed("updating %s: Server said: '$reply'", $h); + } } - else - { - $config{$h}{'status'} = 'failed'; - failed("updating %s: Server said: '$reply'", $h); - } - } } ###################################################################### @@ -7696,41 +7655,38 @@ EoEXAMPLE ## response contains "success" on succesfull completion ###################################################################### sub nic_regfishde_update { - debug("\nnic_regfishde_update -------------------"); + debug("\nnic_regfishde_update -------------------"); - ## update configured host - for my $h (@_) { - my $ip = delete $config{$h}{'wantip'}; - my $ipv6 = delete $config{$h}{'wantip'}; + ## update configured host + for my $h (@_) { + my $ip = delete $config{$h}{'wantip'}; + my $ipv6 = delete $config{$h}{'wantip'}; - info("regfish.de setting IP address to %s for %s", $ip, $h); + info("regfish.de setting IP address to %s for %s", $ip, $h); - my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; - my $url = "https://$config{$h}{'server'}/?fqdn=$h&ipv$ipv=$ip&forcehost=1&token=$config{$h}{'password'}"; + my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; + my $url = "https://$config{$h}{'server'}/?fqdn=$h&ipv$ipv=$ip&forcehost=1&token=$config{$h}{'password'}"; - # Try to get URL - my $reply = geturl(proxy => opt('proxy'), url => $url); + # Try to get URL + my $reply = geturl(proxy => opt('proxy'), url => $url); - # No response, give error - if (!defined($reply) || !$reply) { - failed("regfish.de updating %s: failed: %s.", $h, $config{$h}{'server'}); - last; - } - last if !header_ok($h, $reply); + # No response, give error + if (!defined($reply) || !$reply) { + failed("regfish.de updating %s: failed: %s.", $h, $config{$h}{'server'}); + last; + } + last if !header_ok($h, $reply); - if ($reply =~ /success/) - { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; - $config{$h}{'status'} = 'good'; - success("updating %s: good: IP address set to %s", $h, $ip); + if ($reply =~ /success/) { + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; + $config{$h}{'status'} = 'good'; + success("updating %s: good: IP address set to %s", $h, $ip); + } else { + $config{$h}{'status'} = 'failed'; + failed("updating %s: Server said: '$reply'", $h); + } } - else - { - $config{$h}{'status'} = 'failed'; - failed("updating %s: Server said: '$reply'", $h); - } - } } ###################################################################### @@ -7804,8 +7760,8 @@ sub nic_enom_update { my @reply = split /\n/, $reply; if (grep /Done=true/i, @reply) { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; $config{$h}{'status'} = 'good'; success("updating %s: good: IP address set to %s", $h, $ip); } else { @@ -7913,8 +7869,8 @@ sub nic_digitalocean_update_one { } $config{$h}{"status-$ipv"} = 'good'; - $config{$h}{"ip-$ipv"} = $ip; - $config{$h}{"mtime"} = $now; + $config{$h}{"ip-$ipv"} = $ip; + $config{$h}{"mtime"} = $now; } sub nic_digitalocean_update { From 160344514f0431684ddaa26c5d14e2eb4a028b8c Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 22 Jun 2024 01:52:03 -0400 Subject: [PATCH 056/150] Improve readability by simplifying `if` conditions --- ddclient.in | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/ddclient.in b/ddclient.in index 1911612..73421fa 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1876,7 +1876,7 @@ sub init_config { $opt{'daemon'} = minimum('daemon') if opt('daemon') > 0 && opt('daemon') < minimum('daemon'); ## define or modify host options specified on the command-line - if (exists $opt{'options'} && defined $opt{'options'}) { + if (defined($opt{'options'})) { ## collect cmdline configuration options. my %options = (); for my $opt (split_by_comma($opt{'options'})) { @@ -3560,10 +3560,9 @@ sub nic_updateable { ); $update = 1; - } elsif (($use ne 'disabled') && ((!exists($cache{$host}{'ip'})) || - ("$cache{$host}{'ip'}" ne "$ip"))) { + } elsif ($use ne 'disabled' && ($cache{$host}{'ip'} // '') ne $ip) { ## Check whether to update IP address for the "--use" method" - if ((($cache{$host}{'status'} // '') eq 'good') && + if (($cache{$host}{'status'} // '') eq 'good' && !interval_expired($host, 'mtime', 'min-interval')) { warning("skipping update of %s from %s to %s.\nlast updated %s.\nWait at least %s between update attempts.", @@ -3577,11 +3576,11 @@ sub nic_updateable { $cache{$host}{'warned-min-interval'} = $now; - } elsif ((($cache{$host}{'status'} // '') ne 'good') && + } elsif (($cache{$host}{'status'} // '') ne 'good' && !interval_expired($host, 'atime', 'min-error-interval')) { if (opt('verbose') || (!$cache{$host}{'warned-min-error-interval'} && - (($warned_ip{$host} // 0) < $inv_ip_warn_count))) { + ($warned_ip{$host} // 0) < $inv_ip_warn_count)) { warning("skipping update of %s from %s to %s.\nlast updated %s but last attempt on %s failed.\nWait at least %s between update attempts.", $host, @@ -3604,10 +3603,9 @@ sub nic_updateable { $update = 1; } - } elsif (($usev4 ne 'disabled') && ((!exists($cache{$host}{'ipv4'})) || - ("$cache{$host}{'ipv4'}" ne "$ipv4"))) { + } elsif ($usev4 ne 'disabled' && ($cache{$host}{'ipv4'} // '') ne $ipv4) { ## Check whether to update IPv4 address for the "--usev4" method" - if ((($cache{$host}{'status-ipv4'} // '') eq 'good') && + if (($cache{$host}{'status-ipv4'} // '') eq 'good' && !interval_expired($host, 'mtime', 'min-interval')) { warning("skipping update of %s from %s to %s.\nlast updated %s.\nWait at least %s between update attempts.", @@ -3621,11 +3619,11 @@ sub nic_updateable { $cache{$host}{'warned-min-interval'} = $now; - } elsif ((($cache{$host}{'status-ipv4'} // '') ne 'good') && + } elsif (($cache{$host}{'status-ipv4'} // '') ne 'good' && !interval_expired($host, 'atime', 'min-error-interval')) { if (opt('verbose') || (!$cache{$host}{'warned-min-error-interval'} && - (($warned_ipv4{$host} // 0) < $inv_ip_warn_count))) { + ($warned_ipv4{$host} // 0) < $inv_ip_warn_count)) { warning("skipping update of %s from %s to %s.\nlast updated %s but last attempt on %s failed.\nWait at least %s between update attempts.", $host, @@ -3648,10 +3646,9 @@ sub nic_updateable { $update = 1; } - } elsif (($usev6 ne 'disabled') && ((!exists($cache{$host}{'ipv6'})) || - ("$cache{$host}{'ipv6'}" ne "$ipv6"))) { + } elsif ($usev6 ne 'disabled' && ($cache{$host}{'ipv6'} // '') ne $ipv6) { ## Check whether to update IPv6 address for the "--usev6" method" - if ((($cache{$host}{'status-ipv6'} // '') eq 'good') && + if (($cache{$host}{'status-ipv6'} // '') eq 'good' && !interval_expired($host, 'mtime', 'min-interval')) { warning("skipping update of %s from %s to %s.\nlast updated %s.\nWait at least %s between update attempts.", @@ -3665,11 +3662,11 @@ sub nic_updateable { $cache{$host}{'warned-min-interval'} = $now; - } elsif ((($cache{$host}{'status-ipv6'} // '') ne 'good') && + } elsif (($cache{$host}{'status-ipv6'} // '') ne 'good' && !interval_expired($host, 'atime', 'min-error-interval')) { if (opt('verbose') || (!$cache{$host}{'warned-min-error-interval'} && - (($warned_ipv6{$host} // 0) < $inv_ip_warn_count))) { + ($warned_ipv6{$host} // 0) < $inv_ip_warn_count)) { warning("skipping update of %s from %s to %s.\nlast updated %s but last attempt on %s failed.\nWait at least %s between update attempts.", $host, From 5c38af2ed54739e3e9f989bb926b3c2f740bb82b Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 13 Jun 2024 04:04:59 -0400 Subject: [PATCH 057/150] Improve readability by moving code out of unnecessary blocks Invert conditions and move out unnecessary blocks to reduce indentation and make it easier to see control flow. --- ddclient.in | 114 +++++++++++++++++++++++++--------------------------- 1 file changed, 55 insertions(+), 59 deletions(-) diff --git a/ddclient.in b/ddclient.in index 73421fa..7f0c4c6 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1577,10 +1577,9 @@ sub read_cache { %opt = %saved; for my $h (keys %cache) { - if (exists $config->{$h}) { - for (qw(atime mtime wtime ip status)) { - $config->{$h}{$_} = $cache{$h}{$_} if exists $cache{$h}{$_}; - } + next if !exists($config->{$h}); + for (qw(atime mtime wtime ip status)) { + $config->{$h}{$_} = $cache{$h}{$_} if exists $cache{$h}{$_}; } } } @@ -1764,65 +1763,63 @@ sub _read_config { # Handle '_env' keyword suffix if ($k =~ /(.*)_env$/) { debug("Loading value for $1 from environment variable $locals{$k}."); - if (exists($ENV{$locals{$k}})) { - # Set the value to the value of the environment variable - $locals{$1} = $ENV{$locals{$k}}; - # Remove the '_env' suffix from the key - $k = $1; - } else { + if (!exists($ENV{$locals{$k}})) { warning("Environment variable '$locals{$k}' not set for keyword '$k' (ignored)"); delete $locals{$k}; next; } + # Set the value to the value of the environment variable + $locals{$1} = $ENV{$locals{$k}}; + # Remove the '_env' suffix from the key + $k = $1; } $locals{$k} = $passwords{$k} if defined $passwords{$k}; if (!exists $variables{'merged'}{$k}) { warning("unrecognized keyword '%s' (ignored)", $k); delete $locals{$k}; - } else { - my $def = $variables{'merged'}{$k}; - my $value = check_value($locals{$k}, $def); - if (!defined($value)) { - warning("Invalid Value for keyword '%s' = '%s'", $k, $locals{$k}); - delete $locals{$k}; - } else { - $locals{$k} = $value; - } + next; } + my $def = $variables{'merged'}{$k}; + my $value = check_value($locals{$k}, $def); + if (!defined($value)) { + warning("Invalid Value for keyword '%s' = '%s'", $k, $locals{$k}); + delete $locals{$k}; + next; + } + $locals{$k} = $value; } + %passwords = (); if (exists($locals{'host'})) { $args[0] = @args ? "$args[0],$locals{host}" : "$locals{host}"; } ## accumulate globals if ($#args < 0) { map { $globals{$_} = $locals{$_} } keys %locals; + next; } ## process this host definition - if (@args) { - my ($host, $login, $password) = @args; + my ($host, $login, $password) = @args; - ## add in any globals.. - %locals = (%globals, %locals); + ## add in any globals.. + %locals = (%globals, %locals); - ## override login and password if specified the old way. - $locals{'login'} = $login if defined $login; - $locals{'password'} = $password if defined $password; + ## override login and password if specified the old way. + $locals{'login'} = $login if defined $login; + $locals{'password'} = $password if defined $password; - ## allow {host} to be a comma separated list of hosts - for my $h (split_by_comma($host)) { - if ($config{$h}) { - ## host already defined, merging configs - $config{$h} = {%locals, %{$config{$h}}}; - } else { - ## save a copy of the current globals - $config{$h} = {%locals}; - $config{$h}{'host'} = $h; - } + ## allow {host} to be a comma separated list of hosts + for my $h (split_by_comma($host)) { + if ($config{$h}) { + ## host already defined, merging configs + $config{$h} = {%locals, %{$config{$h}}}; + } else { + ## save a copy of the current globals + $config{$h} = {%locals}; + $config{$h}{'host'} = $h; } } - %passwords = (); } close(FD); @@ -1978,29 +1975,28 @@ sub init_config { if (!exists($services{$proto})) { warning("skipping host: %s: unrecognized protocol '%s'", $h, $proto); delete $config{$h}; - - } else { - my $svars = $services{$proto}{'variables'}; - my $conf = {'protocol' => $proto}; - - for my $k (keys %$svars) { - # Make sure any _env suffixed variables look at their original entry - $k = $1 if $k =~ /^(.*)_env$/; - - my $def = $svars->{$k}; - my $ovalue = $config{$h}{$k} // $def->{'default'}; - my $value = check_value($ovalue, $def); - if ($def->{'required'} && !defined $value) { - warning("skipping host: %s: '%s=%s' is an invalid %s.", $h, $k, $ovalue, $def->{'type'}); - delete $config{$h}; - next HOST; - } - $conf->{$k} = $value; - - } - $config{$h} = $conf; - $config{$h}{'cacheable'} = [ @{$services{$proto}{'cacheable'}} ]; + next; } + + my $svars = $services{$proto}{'variables'}; + my $conf = {'protocol' => $proto}; + + for my $k (keys %$svars) { + # Make sure any _env suffixed variables look at their original entry + $k = $1 if $k =~ /^(.*)_env$/; + + my $def = $svars->{$k}; + my $ovalue = $config{$h}{$k} // $def->{'default'}; + my $value = check_value($ovalue, $def); + if ($def->{'required'} && !defined $value) { + warning("skipping host: %s: '%s=%s' is an invalid %s.", $h, $k, $ovalue, $def->{'type'}); + delete $config{$h}; + next HOST; + } + $conf->{$k} = $value; + } + $config{$h} = $conf; + $config{$h}{'cacheable'} = [ @{$services{$proto}{'cacheable'}} ]; } } From 0d712f7bbcaef4f23e8e8c94f5c614a3c4ed9c40 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 13 Jun 2024 20:23:11 -0400 Subject: [PATCH 058/150] Improve readability of `wantip`, `wantipv4`, `wantipv6` fallback --- ddclient.in | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/ddclient.in b/ddclient.in index 7f0c4c6..3118030 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1396,8 +1396,6 @@ sub update_nics { if !$daemon || opt('verbose'); } } - # And remember it as the IP address we want to send to the DNS service. - $config{$h}{'wantip'} = $ip; } if ($usev4 ne 'disabled') { @@ -1415,8 +1413,6 @@ sub update_nics { if !$daemon || opt('verbose'); } } - # And remember it as the IPv4 address we want to send to the DNS service. - $config{$h}{'wantipv4'} = $ipv4; } if ($usev6 ne 'disabled') { @@ -1434,17 +1430,14 @@ sub update_nics { if !$daemon || opt('verbose'); } } - # And remember it as the IP address we want to send to the DNS service. - $config{$h}{'wantipv6'} = $ipv6; } - # DNS service update functions should only have to handle 'wantipv4' and 'wantipv6' - $config{$h}{'wantipv4'} = $ipv4 = $ip if (!$ipv4 && is_ipv4($ip)); - $config{$h}{'wantipv6'} = $ipv6 = $ip if (!$ipv6 && is_ipv6($ip)); - # If we don't have 'wantip', we fill it from 'wantipv4' or 'wantipv6' - # so old provider implementations continue to work until we update them all. - $config{$h}{'wantip'} = $ipv4 if (!$ip && $ipv4); - $config{$h}{'wantip'} = $ipv6 if (!$ip && !$ipv4 && $ipv6); + $ip //= $ipv4 // $ipv6; + $ipv4 //= $ip if is_ipv4($ip); + $ipv6 //= $ip if is_ipv6($ip); + $config{$h}{'wantip'} = $ip; + $config{$h}{'wantipv4'} = $ipv4; + $config{$h}{'wantipv6'} = $ipv6; if (!$ip && !$ipv4 && !$ipv6) { warning("Could not determine an IP for %s", $h); From ab60675660ea49ab05608f0466580cc2e997caf6 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 15 Jun 2024 20:39:47 -0400 Subject: [PATCH 059/150] Improve readability of cache var update logic --- ddclient.in | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/ddclient.in b/ddclient.in index 3118030..9b4e67e 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1513,9 +1513,15 @@ sub write_cache { ## merge the updated host entries into the cache. for my $h (keys %config) { if (!exists $cache{$h} || $config{$h}{'update'}) { - map { defined($config{$h}{$_}) ? ($cache{$h}{$_} = $config{$h}{$_}) : () } @{$config{$h}{'cacheable'}}; + my $vars = $services{$config{$h}{protocol}}{variables}; + for my $v (keys(%$vars)) { + next if !$vars->{$v}{cache} || !defined($config{$h}{$v}); + $cache{$h}{$v} = $config{$h}{$v}; + } } else { - map { $cache{$h}{$_} = $config{$h}{$_} } qw(atime wtime status); + for my $v (qw(atime wtime status)) { + $cache{$h}{$v} = $config{$h}{$v}; + } } } @@ -1929,15 +1935,6 @@ sub init_config { map { $hosts{$_} = undef } @hosts; map { delete $config{$_} unless exists $hosts{$_} } keys %config; - ## collect the cacheable variables. - for my $proto (keys %services) { - my @cacheable = (); - for my $k (keys %{$services{$proto}{'variables'}}) { - push @cacheable, $k if $services{$proto}{'variables'}{$k}{'cache'}; - } - $services{$proto}{'cacheable'} = [ @cacheable ]; - } - ## sanity check.. ## make sure config entries have all defaults and they meet minimums ## first the globals... @@ -1989,7 +1986,6 @@ sub init_config { $conf->{$k} = $value; } $config{$h} = $conf; - $config{$h}{'cacheable'} = [ @{$services{$proto}{'cacheable'}} ]; } } From 9a5500a66779ab1f240cdf5cfc4454e36673899b Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 22 Jun 2024 00:20:24 -0400 Subject: [PATCH 060/150] Improve readability via minor logic tweaks Change some code that is unnecessarily complicated or otherwise not the right tool for the job to improve readability. --- ddclient.in | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/ddclient.in b/ddclient.in index 9b4e67e..fb42347 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1790,11 +1790,11 @@ sub _read_config { } %passwords = (); if (exists($locals{'host'})) { - $args[0] = @args ? "$args[0],$locals{host}" : "$locals{host}"; + $args[0] = (@args ? "$args[0]," : '') . $locals{host}; } ## accumulate globals - if ($#args < 0) { - map { $globals{$_} = $locals{$_} } keys %locals; + if (!@args) { + %globals = (%globals, %locals); next; } @@ -1810,14 +1810,8 @@ sub _read_config { ## allow {host} to be a comma separated list of hosts for my $h (split_by_comma($host)) { - if ($config{$h}) { - ## host already defined, merging configs - $config{$h} = {%locals, %{$config{$h}}}; - } else { - ## save a copy of the current globals - $config{$h} = {%locals}; - $config{$h}{'host'} = $h; - } + $config{$h} = {%locals, %{$config{$h} // {}}}; + $config{$h}{'host'} = $h; } } close(FD); @@ -1927,7 +1921,7 @@ sub init_config { @hosts = split_by_comma($opt{'host'}); } if (opt('retry')) { - @hosts = map { $_ if ($cache{$_}{'status'} // '') ne 'good' } keys %cache; + @hosts = grep(($cache{$_}{'status'} // '') ne 'good', keys(%cache)); } ## remove any other hosts @@ -1955,10 +1949,7 @@ sub init_config { ## now the host definitions... HOST: for my $h (keys %config) { - my $proto; - $proto = $config{$h}{'protocol'}; - $proto = opt('protocol') if !defined($proto); - + my $proto = opt('protocol', $h); load_sha1_support($proto) if (grep(/^$proto$/, ("freedns", "nfsn"))); load_json_support($proto) if (grep(/^$proto$/, ("1984", "cloudflare", "digitalocean", "gandi", "godaddy", "hetzner", "yandex", "nfsn", "njalla", "porkbun", "dnsexit2"))); @@ -2005,7 +1996,7 @@ sub process_args { push @spec, $key . $specifier; ## define the default value which can be overwritten later - $opt{$key} = undef unless exists($opt{$key}); + $opt{$key} //= undef; next unless $arg_usage; From 3d73e7c23113f27ad3e2c209fbdc64ba202c37e2 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 22 Jun 2024 00:39:15 -0400 Subject: [PATCH 061/150] Add TODO comments for confusing bits of code --- ddclient.in | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/ddclient.in b/ddclient.in index fb42347..119e7a0 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1578,6 +1578,11 @@ sub read_cache { for my $h (keys %cache) { next if !exists($config->{$h}); for (qw(atime mtime wtime ip status)) { + # TODO: Isn't $config equal to \%cache here? If so, this is a no-op. What was the + # original intention behind this? To copy %cache values into %config? If so, is + # it better to just delete this and live with the current behavior (which doesn't + # seem to be causing users any problems) or to "fix" it to match the original + # intention, which might introduce a bug? $config->{$h}{$_} = $cache{$h}{$_} if exists $cache{$h}{$_}; } } @@ -1810,6 +1815,12 @@ sub _read_config { ## allow {host} to be a comma separated list of hosts for my $h (split_by_comma($host)) { + # TODO: Shouldn't %locals go after $config{h}? Later lines should override earlier + # lines, no? Otherwise, later assignments will have a mixed effect: assignments to new + # variables will take effect but assignments to variables that already have a value + # will not. One problem with swapping the order: due to the `%locals = (%globals, + # %locals)` line above, any values in %globals would override any locals in the + # previous host line. $config{$h} = {%locals, %{$config{$h} // {}}}; $config{$h}{'host'} = $h; } @@ -1904,7 +1915,13 @@ sub init_config { ## override global options with those on the command-line. for my $o (keys %opt) { + # TODO: Isn't $opt{$o} guaranteed to be defined? Otherwise $o wouldn't appear in the keys + # of %opt, right? + # TODO: Why is this limited to $variables{'global-defaults'}? Why not + # $variables{'merged'}? if (defined $opt{$o} && exists $variables{'global-defaults'}{$o}) { + # TODO: What's the point of this? The opt() function will fall back to %globals if + # %opt doesn't have a value, so this shouldn't be necessary. $globals{$o} = $opt{$o}; } } @@ -1920,6 +1937,7 @@ sub init_config { if (opt('host')) { @hosts = split_by_comma($opt{'host'}); } + # TODO: This function is called before the recap file is read. How is this supposed to work? if (opt('retry')) { @hosts = grep(($cache{$_}{'status'} // '') ne 'good', keys(%cache)); } @@ -1936,10 +1954,18 @@ sub init_config { # Make sure any _env suffixed variables look at their original entry $k = $1 if $k =~ /^(.*)_env$/; + # TODO: This might grab an arbitrary protocol-specific variable, which could cause + # surprising behavior. my $def = $variables{'merged'}{$k}; + # TODO: Isn't $globals{$k} guaranteed to be defined here? Otherwise $k wouldn't appear in + # %globals. my $ovalue = $globals{$k} // $def->{'default'}; + # TODO: Didn't _read_config already check the value? Or is the purpose of this to check + # the value of command-line options ($opt{$k}) which were merged into %globals above? my $value = check_value($ovalue, $def); if ($def->{'required'} && !defined $value) { + # TODO: What's the point of this? The opt() function will fall back to the default + # value if $globals{$k} is undefined. $value = default($k); warning("'%s=%s' is an invalid %s. (using default of %s)", $k, $ovalue, $def->{'type'}, $value); } From 1718ceab70f92ff352ed0d85c7066678797c2ea3 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 15 Jun 2024 02:45:23 -0400 Subject: [PATCH 062/150] Add comments documenting the cached variables --- ddclient.in | 52 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/ddclient.in b/ddclient.in index 119e7a0..26f5b59 100755 --- a/ddclient.in +++ b/ddclient.in @@ -635,21 +635,53 @@ my %variables = ( 'cmd-skip' => setv(T_STRING,0, 0, undef, undef), 'cmdv4' => setv(T_PROG, 0, 0, '', undef), 'cmdv6' => setv(T_PROG, 0, 0, '', undef), - - 'ip' => setv(T_IP, 0, 1, undef, undef), #TODO remove from cache? - 'ipv4' => setv(T_IPV4, 0, 1, undef, undef), - 'ipv6' => setv(T_IPV6, 0, 1, undef, undef), - 'wtime' => setv(T_DELAY, 0, 1, 0, interval('30s')), - 'mtime' => setv(T_NUMBER,0, 1, 0, undef), - 'atime' => setv(T_NUMBER,0, 1, 0, undef), - 'status' => setv(T_ANY, 0, 1, undef, undef), #TODO remove from cache? - 'status-ipv4' => setv(T_ANY, 0, 1, undef, undef), - 'status-ipv6' => setv(T_ANY, 0, 1, undef, undef), 'min-interval' => setv(T_DELAY, 0, 0, interval('30s'), 0), 'max-interval' => setv(T_DELAY, 0, 0, interval('25d'), 0), 'min-error-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), + # As a cached value, this is the IP address (IPv4 or IPv6, but almost always IPv4) most + # recently saved at the DDNS service. As a setting, this is the desired IP address that + # should be saved at the DDNS service. Unfortunately, these two meanings are conflated, + # causing the bug "skipped: IP address was already set to a.b.c.d" when the IP was never + # set to a.b.c.d. + # TODO: Move the cached value elsewhere to fix the bug. + 'ip' => setv(T_IP, 0, 1, undef, undef), + # As `ip`, but only IPv4 addresses. + 'ipv4' => setv(T_IPV4, 0, 1, undef, undef), + # As `ip`, but only IPv6 addresses. + 'ipv6' => setv(T_IPV6, 0, 1, undef, undef), + # Timestamp (seconds since epoch) indicating the earliest time the next update is + # permitted. + # TODO: Create a timestamp type and change this to that type. + 'wtime' => setv(T_DELAY, 0, 1, 0, interval('30s')), + # Timestamp (seconds since epoch) indicating when an IP address was last sent to the DDNS + # service, even if the IP address was not different from what was already stored. + # TODO: Create a timestamp type and change this to that type. + 'mtime' => setv(T_NUMBER,0, 1, 0, undef), + # Timestamp (seconds since epoch) of the most recent attempt to update the DDNS service + # (including attempts to update with the same IP address). This equals mtime if the most + # recent attempt was successful, otherwise it will be more recent than mtime. + # TODO: Create a timestamp type and change this to that type. + 'atime' => setv(T_NUMBER,0, 1, 0, undef), + # Disposition of the most recent (or currently in progress) attempt to update the DDNS + # service with the IP address in `wantip`. Anything other than `good`, including undef, is + # treated as a failure. + 'status' => setv(T_ANY, 0, 1, undef, undef), + # As `status`, but with `wantipv4`. + 'status-ipv4' => setv(T_ANY, 0, 1, undef, undef), + # As `status`, but with `wantipv6`. + 'status-ipv6' => setv(T_ANY, 0, 1, undef, undef), + # Timestamp (seconds since epoch) of the most recent attempt that would have been made had + # `min-interval` not inhibited the attempt. This is reset to 0 once an attempt is actually + # made. This is used as a boolean to suppress repeated warnings to the user that indicate + # that `min-interval` has inhibited an update attempt. + # TODO: Change to a boolean and rename to improve readability. 'warned-min-interval' => setv(T_ANY, 0, 1, 0, undef), + # Timestamp (seconds since epoch) of the most recent attempt that would have been made had + # `min-error-interval` not inhibited the attempt. This is reset to 0 once an attempt is + # actually made. This is used as a boolean to suppress repeated warnings to the user that + # indicate that `min-error-interval` has inhibited an update attempt. + # TODO: Change to a boolean and rename to improve readability. 'warned-min-error-interval' => setv(T_ANY, 0, 1, 0, undef), }, 'dyndns-common-defaults' => { From 49bd1b73474d97c7f8361f487d3bf3e2f0ec487b Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Mon, 17 Jun 2024 22:38:16 -0400 Subject: [PATCH 063/150] Rename `%services` to `%protocols` for consistency --- ddclient.in | 110 ++++++++++++++++++++++++++-------------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/ddclient.in b/ddclient.in index 26f5b59..e6588ef 100755 --- a/ddclient.in +++ b/ddclient.in @@ -603,7 +603,7 @@ my %variables = ( 'ssl_ca_file' => setv(T_FILE, 0, 0, undef, undef), 'redirect' => setv(T_NUMBER,0, 0, 0, undef) }, - 'service-common-defaults' => { + 'protocol-common-defaults' => { 'server' => setv(T_FQDNP, 1, 0, 'members.dyndns.org', undef), 'login' => setv(T_LOGIN, 1, 0, '', undef), 'password' => setv(T_PASSWD,1, 0, '', undef), @@ -691,13 +691,13 @@ my %variables = ( 'wildcard' => setv(T_BOOL, 0, 1, 0, undef), }, ); -my %services = ( +my %protocols = ( '1984' => { 'updateable' => undef, 'update' => \&nic_1984_update, 'examples' => \&nic_1984_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'login' => undef, 'server' => setv(T_FQDNP, 1, 0, 'api.1984.is', undef), }, @@ -707,7 +707,7 @@ my %services = ( 'update' => \&nic_changeip_update, 'examples' => \&nic_changeip_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), 'server' => setv(T_FQDNP, 1, 0, 'nic.changeip.com', undef), }, @@ -717,7 +717,7 @@ my %services = ( 'update' => \&nic_cloudflare_update, 'examples' => \&nic_cloudflare_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'backupmx' => setv(T_BOOL, 0, 1, 0, undef), 'login' => setv(T_LOGIN, 0, 0, 'token', undef), 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), @@ -734,7 +734,7 @@ my %services = ( 'update' => \&nic_cloudns_update, 'examples' => \&nic_cloudns_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'login' => undef, 'password' => undef, 'dynurl' => setv(T_STRING, 1, 0, undef, undef), @@ -745,7 +745,7 @@ my %services = ( 'update' => \&nic_digitalocean_update, 'examples' => \&nic_digitalocean_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'login' => undef, 'server' => setv(T_FQDNP, 1, 0, 'api.digitalocean.com', undef), 'zone' => setv(T_FQDN, 1, 0, '', undef), @@ -756,7 +756,7 @@ my %services = ( 'update' => \&nic_dinahosting_update, 'examples' => \&nic_dinahosting_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'min-error-interval' => setv(T_DELAY, 0, 0, interval('8m'), 0), 'script' => setv(T_STRING, 0, 1, '/special/api.php', undef), 'server' => setv(T_FQDNP, 1, 0, 'dinahosting.com', undef), @@ -767,7 +767,7 @@ my %services = ( 'update' => \&nic_dnsmadeeasy_update, 'examples' => \&nic_dnsmadeeasy_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'script' => setv(T_STRING, 1, 1, '/servlet/updateip', undef), 'server' => setv(T_FQDNP, 1, 0, 'cp.dnsmadeeasy.com', undef), }, @@ -777,7 +777,7 @@ my %services = ( 'update' => \&nic_dondominio_update, 'examples' => \&nic_dondominio_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'server' => setv(T_FQDNP, 1, 0, 'dondns.dondominio.com', undef), }, }, @@ -786,7 +786,7 @@ my %services = ( 'update' => \&nic_dslreports1_update, 'examples' => \&nic_dslreports1_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'server' => setv(T_FQDNP, 1, 0, 'www.dslreports.com', undef), }, }, @@ -795,7 +795,7 @@ my %services = ( 'update' => \&nic_domeneshop_update, 'examples' => \&nic_domeneshop_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'server' => setv(T_FQDNP, 1, 0, 'api.domeneshop.no', undef), }, }, @@ -804,7 +804,7 @@ my %services = ( 'update' => \&nic_duckdns_update, 'examples' => \&nic_duckdns_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'login' => undef, 'server' => setv(T_FQDNP, 1, 0, 'www.duckdns.org', undef), }, @@ -814,7 +814,7 @@ my %services = ( 'update' => \&nic_dyndns1_update, 'examples' => \&nic_dyndns1_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, %{$variables{'dyndns-common-defaults'}}, }, }, @@ -823,7 +823,7 @@ my %services = ( 'update' => \&nic_dyndns2_update, 'examples' => \&nic_dyndns2_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, %{$variables{'dyndns-common-defaults'}}, 'custom' => setv(T_BOOL, 0, 1, 0, undef), 'script' => setv(T_STRING, 1, 1, '/nic/update', undef), @@ -834,7 +834,7 @@ my %services = ( 'update' => \&nic_easydns_update, 'examples' => \&nic_easydns_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'backupmx' => setv(T_BOOL, 0, 1, 0, undef), 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), 'mx' => setv(T_OFQDN, 0, 1, '', undef), @@ -848,7 +848,7 @@ my %services = ( 'update' => \&nic_freedns_update, 'examples' => \&nic_freedns_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), 'server' => setv(T_FQDNP, 1, 0, 'freedns.afraid.org', undef), }, @@ -858,7 +858,7 @@ my %services = ( 'update' => \&nic_freemyip_update, 'examples' => \&nic_freemyip_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'login' => undef, 'server' => setv(T_FQDNP, 1, 0, 'freemyip.com', undef), }, @@ -868,7 +868,7 @@ my %services = ( 'update' => \&nic_gandi_update, 'examples' => \&nic_gandi_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'login' => undef, 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), 'server' => setv(T_FQDNP, 1, 0, 'api.gandi.net', undef), @@ -883,7 +883,7 @@ my %services = ( 'update' => \&nic_godaddy_update, 'examples' => \&nic_godaddy_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), 'server' => setv(T_FQDNP, 1, 0, 'api.godaddy.com/v1/domains', undef), 'ttl' => setv(T_NUMBER, 1, 0, 600, undef), @@ -895,7 +895,7 @@ my %services = ( 'update' => \&nic_googledomains_update, 'examples' => \&nic_googledomains_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), 'server' => setv(T_FQDNP, 1, 0, 'domains.google.com', undef), }, @@ -905,7 +905,7 @@ my %services = ( 'update' => \&nic_hetzner_update, 'examples' => \&nic_hetzner_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'login' => setv(T_LOGIN, 0, 0, 'token', undef), 'min-interval' => setv(T_DELAY, 0, 0, interval('1m'), 0), 'server' => setv(T_FQDNP, 1, 0, 'dns.hetzner.com/api/v1', undef), @@ -918,7 +918,7 @@ my %services = ( 'update' => \&nic_mythicdyn_update, 'examples' => \&nic_mythicdyn_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), 'server' => setv(T_FQDNP, 1, 0, 'api.mythic-beasts.com', undef), }, @@ -928,7 +928,7 @@ my %services = ( 'update' => \&nic_namecheap_update, 'examples' => \&nic_namecheap_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), 'server' => setv(T_FQDNP, 1, 0, 'dynamicdns.park-your-domain.com', undef), }, @@ -938,7 +938,7 @@ my %services = ( 'update' => \&nic_nfsn_update, 'examples' => \&nic_nfsn_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'min_interval' => setv(T_FQDNP, 0, 0, 0, interval('5m')), 'server' => setv(T_FQDNP, 1, 0, 'api.nearlyfreespeech.net', undef), 'ttl' => setv(T_NUMBER, 1, 0, 300, undef), @@ -950,7 +950,7 @@ my %services = ( 'update' => \&nic_njalla_update, 'examples' => \&nic_njalla_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'login' => undef, 'server' => setv(T_FQDNP, 1, 0, 'njal.la', undef), 'quietreply' => setv(T_BOOL, 0, 1, 0, undef), @@ -961,7 +961,7 @@ my %services = ( 'update' => \&nic_noip_update, 'examples' => \&nic_noip_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'custom' => setv(T_BOOL, 0, 1, 0, undef), 'server' => setv(T_FQDNP, 1, 0, 'dynupdate.no-ip.com', undef), 'static' => setv(T_BOOL, 0, 1, 0, undef), @@ -972,7 +972,7 @@ my %services = ( 'update' => \&nic_nsupdate_update, 'examples' => \&nic_nsupdate_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'login' => setv(T_LOGIN, 1, 0, '/usr/bin/nsupdate', undef), 'tcp' => setv(T_BOOL, 0, 1, 0, undef), 'ttl' => setv(T_NUMBER, 0, 1, 600, undef), @@ -984,7 +984,7 @@ my %services = ( 'update' => \&nic_ovh_update, 'examples' => \&nic_ovh_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'script' => setv(T_STRING, 1, 1, '/nic/update', undef), 'server' => setv(T_FQDNP, 1, 0, 'www.ovh.com', undef), }, @@ -994,7 +994,7 @@ my %services = ( 'update' => \&nic_porkbun_update, 'examples' => \&nic_porkbun_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'login' => undef, 'password' => undef, 'apikey' => setv(T_PASSWD, 1, 0, '', undef), @@ -1008,7 +1008,7 @@ my %services = ( 'update' => \&nic_sitelutions_update, 'examples' => \&nic_sitelutions_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'server' => setv(T_FQDNP, 1, 0, 'www.sitelutions.com', undef), 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), }, @@ -1018,7 +1018,7 @@ my %services = ( 'update' => \&nic_woima_update, 'examples' => \&nic_woima_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'backupmx' => setv(T_BOOL, 0, 1, 0, undef), 'custom' => setv(T_BOOL, 0, 1, 0, undef), 'mx' => setv(T_OFQDN, 0, 1, '', undef), @@ -1033,7 +1033,7 @@ my %services = ( 'update' => \&nic_yandex_update, 'examples' => \&nic_yandex_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), 'server' => setv(T_FQDNP, 1, 0, 'pddimp.yandex.ru', undef), }, @@ -1043,7 +1043,7 @@ my %services = ( 'update' => \&nic_zoneedit1_update, 'examples' => \&nic_zoneedit1_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, interval('10m'), 0), 'server' => setv(T_FQDNP, 1, 0, 'dynamic.zoneedit.com', undef), 'zone' => setv(T_OFQDN, 0, 0, undef, undef), @@ -1054,7 +1054,7 @@ my %services = ( 'update' => \&nic_keysystems_update, 'examples' => \&nic_keysystems_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'login' => undef, 'server' => setv(T_FQDNP, 1, 0, 'dynamicdns.key-systems.net', undef), }, @@ -1064,7 +1064,7 @@ my %services = ( 'update' => \&nic_dnsexit2_update, 'examples' => \&nic_dnsexit2_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'login' => undef, 'ssl' => setv(T_BOOL, 0, 0, 1, undef), 'server' => setv(T_FQDNP, 1, 0, 'api.dnsexit.com', undef), @@ -1078,7 +1078,7 @@ my %services = ( 'update' => \&nic_regfishde_update, 'examples' => \&nic_regfishde_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'login' => undef, 'server' => setv(T_FQDNP, 1, 0, 'dyndns.regfish.de', undef), }, @@ -1088,7 +1088,7 @@ my %services = ( 'update' => \&nic_enom_update, 'examples' => \&nic_enom_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'server' => setv(T_FQDNP, 1, 0, 'dynamic.name-services.com', undef), 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), }, @@ -1098,7 +1098,7 @@ my %services = ( 'update' => \&nic_infomaniak_update, 'examples' => \&nic_infomaniak_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, }, }, 'emailonly' => { @@ -1106,7 +1106,7 @@ my %services = ( 'update' => \&nic_emailonly_update, 'examples' => \&nic_emailonly_examples, 'variables' => { - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, 'login' => undef, 'password' => undef, # Change default to never re-notify if IP address has not changed. @@ -1115,14 +1115,14 @@ my %services = ( }, ); # Delete undefined variables to make it easier to cancel previously defined variables. -for my $svc (values(%services)) { - my $vars = $svc->{variables}; +for my $proto (values(%protocols)) { + my $vars = $proto->{variables}; delete(@$vars{grep(!defined($vars->{$_}), keys(%$vars))}); } $variables{'merged'} = { - map({ %{$services{$_}{'variables'}} } keys(%services)), + map({ %{$protocols{$_}{'variables'}} } keys(%protocols)), %{$variables{'dyndns-common-defaults'}}, - %{$variables{'service-common-defaults'}}, + %{$variables{'protocol-common-defaults'}}, %{$variables{'global-defaults'}}, }; @@ -1136,7 +1136,7 @@ $opt{'list-devices'} = sub { exit(0); }; $opt{'list-protocols'} = sub { - printf("%s\n", $_) for sort(keys(%services)); + printf("%s\n", $_) for sort(keys(%protocols)); exit(0); }; $opt{'list-web-services'} = sub { @@ -1379,13 +1379,13 @@ sub update_nics { my %ipv4list = (); my %ipv6list = (); - for my $s (sort keys %services) { + for my $p (sort keys %protocols) { my (@hosts, %ipsv4, %ipsv6) = (); - my $updateable = $services{$s}{'updateable'}; - my $update = $services{$s}{'update'}; + my $updateable = $protocols{$p}{'updateable'}; + my $update = $protocols{$p}{'update'}; for my $h (sort keys %config) { - next if $config{$h}{'protocol'} ne lc($s); + next if $config{$h}{'protocol'} ne lc($p); $examined{$h} = 1; # we only do this once per 'use' and argument combination my $use = opt('use', $h) // 'disabled'; @@ -1545,7 +1545,7 @@ sub write_cache { ## merge the updated host entries into the cache. for my $h (keys %config) { if (!exists $cache{$h} || $config{$h}{'update'}) { - my $vars = $services{$config{$h}{protocol}}{variables}; + my $vars = $protocols{$config{$h}{protocol}}{variables}; for my $v (keys(%$vars)) { next if !$vars->{$v}{cache} || !defined($config{$h}{$v}); $cache{$h}{$v} = $config{$h}{$v}; @@ -2011,13 +2011,13 @@ sub init_config { load_sha1_support($proto) if (grep(/^$proto$/, ("freedns", "nfsn"))); load_json_support($proto) if (grep(/^$proto$/, ("1984", "cloudflare", "digitalocean", "gandi", "godaddy", "hetzner", "yandex", "nfsn", "njalla", "porkbun", "dnsexit2"))); - if (!exists($services{$proto})) { + if (!exists($protocols{$proto})) { warning("skipping host: %s: unrecognized protocol '%s'", $h, $proto); delete $config{$h}; next; } - my $svars = $services{$proto}{'variables'}; + my $svars = $protocols{$proto}{'variables'}; my $conf = {'protocol' => $proto}; for my $k (keys %$svars) { @@ -2571,7 +2571,7 @@ sub check_value { } elsif ($type eq T_PROTO) { $value = lc $value; - return undef if !exists $services{$value}; + return undef if !exists $protocols{$value}; } elsif ($type eq T_USE) { $value = lc $value; @@ -3478,8 +3478,8 @@ sub encode_www_form_urlencoded { sub nic_examples { my $examples = ""; my $separator = ""; - for my $s (sort keys %services) { - my $subr = $services{$s}{'examples'}; + for my $p (sort keys %protocols) { + my $subr = $protocols{$p}{'examples'}; my $example; if (defined($subr) && ($example = &$subr())) { From b426b370fd75744b57233b441d8b756434b04c7b Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 18 Jun 2024 01:01:36 -0400 Subject: [PATCH 064/150] Rename `%cache` to `%recap` for readability --- Makefile.am | 2 +- ddclient.in | 215 ++++++++++++++------------- t/{write_cache.pl => write_recap.pl} | 2 +- 3 files changed, 114 insertions(+), 105 deletions(-) rename t/{write_cache.pl => write_recap.pl} (97%) diff --git a/Makefile.am b/Makefile.am index 54c1831..0426025 100644 --- a/Makefile.am +++ b/Makefile.am @@ -77,7 +77,7 @@ handwritten_tests = \ t/parse_assignments.pl \ t/skip.pl \ t/ssl-validate.pl \ - t/write_cache.pl + t/write_recap.pl generated_tests = \ t/version.pl TESTS = $(handwritten_tests) $(generated_tests) diff --git a/ddclient.in b/ddclient.in index e6588ef..b9ec46c 100755 --- a/ddclient.in +++ b/ddclient.in @@ -142,9 +142,24 @@ $ENV{'PATH'} = (exists($ENV{PATH}) ? "$ENV{PATH}:" : "") . "/sbin:/usr/sbin:/bin our %globals; our %config; -our %cache; + +# %recap holds details about recent updates (and attempts) that are needed to implement various +# service-specific and protocol-independent mechanisms such as `min-interval`. This data is +# persisted in the cache file (`--cache`) so that it survives ddclient restarts. This hash maps a +# hostname to a hashref containing those protocol variables that have their `recap` property set to +# true. +# +# A note about terminology: This was previously named `%cache`, but "cache" implies that the +# purpose is to reduce the cost or latency of data retrieval or computation, and that deletion only +# affects performance. That is not the case here, so the variable was renamed. "Recap" is meant +# to evoke the "previously on" clips that play before TV episodes, which are designed to give you +# just enough context to recall the state. The recap is written to the cache file, so-named for +# historical reasons. (Renaming "cache file" to "recap file" is more difficult due to +# compatibility concerns with the public `--cache` option.) +our %recap; + my $result; -my $saved_cache; +my $saved_recap; my %saved_opt; my $daemon; # Control how many times warning message logged for invalid IP addresses @@ -536,7 +551,7 @@ sub setv { return { 'type' => shift, 'required' => shift, - 'cache' => shift, + 'recap' => shift, 'default' => shift, 'minimum' => shift, }; @@ -639,12 +654,12 @@ my %variables = ( 'max-interval' => setv(T_DELAY, 0, 0, interval('25d'), 0), 'min-error-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), - # As a cached value, this is the IP address (IPv4 or IPv6, but almost always IPv4) most + # As a recap value, this is the IP address (IPv4 or IPv6, but almost always IPv4) most # recently saved at the DDNS service. As a setting, this is the desired IP address that # should be saved at the DDNS service. Unfortunately, these two meanings are conflated, # causing the bug "skipped: IP address was already set to a.b.c.d" when the IP was never # set to a.b.c.d. - # TODO: Move the cached value elsewhere to fix the bug. + # TODO: Move the recap value elsewhere to fix the bug. 'ip' => setv(T_IP, 0, 1, undef, undef), # As `ip`, but only IPv4 addresses. 'ipv4' => setv(T_IPV4, 0, 1, undef, undef), @@ -1251,7 +1266,7 @@ my @opt = ( sub main { ## process args my $opt_usage = process_args(@opt); - $saved_cache = ''; + $saved_recap = ''; %saved_opt = %opt; $result = 'OK'; @@ -1303,7 +1318,7 @@ sub main { read_config($opt{'file'} // default('file'), \%config, \%globals); init_config(); - read_cache(opt('cache'), \%cache); + read_recap(opt('cache'), \%recap); print_info() if opt('debug') && opt('verbose'); fatal("invalid argument '--use=%s'; possible values are:\n%s", @@ -1506,7 +1521,7 @@ sub update_nics { $h, $config{$h}{'protocol'} // ''); } } - write_cache(opt('cache')); + write_recap(opt('cache')); } ###################################################################### @@ -1537,36 +1552,33 @@ sub write_pid { } ###################################################################### -## write_cache($file) +## write_recap($file) ###################################################################### -sub write_cache { +sub write_recap { my ($file) = @_; - ## merge the updated host entries into the cache. for my $h (keys %config) { - if (!exists $cache{$h} || $config{$h}{'update'}) { + if (!exists $recap{$h} || $config{$h}{'update'}) { my $vars = $protocols{$config{$h}{protocol}}{variables}; for my $v (keys(%$vars)) { - next if !$vars->{$v}{cache} || !defined($config{$h}{$v}); - $cache{$h}{$v} = $config{$h}{$v}; + next if !$vars->{$v}{recap} || !defined($config{$h}{$v}); + $recap{$h}{$v} = $config{$h}{$v}; } } else { for my $v (qw(atime wtime status)) { - $cache{$h}{$v} = $config{$h}{$v}; + $recap{$h}{$v} = $config{$h}{$v}; } } } - ## construct the cache file. - my $cache = ""; - for my $h (sort keys %cache) { - my $opt = join(',', map { "$_=" . ($cache{$h}{$_} // '') } sort keys %{$cache{$h}}); + my $recap = ""; + for my $h (sort keys %recap) { + my $opt = join(',', map { "$_=" . ($recap{$h}{$_} // '') } sort keys %{$recap{$h}}); - $cache .= sprintf "%s%s%s\n", $opt, ($opt ? ' ' : ''), $h; + $recap .= sprintf "%s%s%s\n", $opt, ($opt ? ' ' : ''), $h; } - $file = '' if defined($saved_cache) && $cache eq $saved_cache; + $file = '' if defined($saved_recap) && $recap eq $saved_recap; - ## write the updates and other entries to the cache file. if ($file) { (undef, my $dir) = fileparse($file); make_path($dir, { error => \my $err }) if !-d $dir; @@ -1578,7 +1590,7 @@ sub write_cache { return; } - $saved_cache = undef; + $saved_recap = undef; local *FD; if (!open(FD, ">", $file)) { warning("Failed to create cache file %s: %s", $file, $!); @@ -1586,36 +1598,35 @@ sub write_cache { } printf FD "## %s-%s\n", $program, $version; printf FD "## last updated at %s (%d)\n", prettytime($now), $now; - printf FD "%s", $cache; + printf FD "%s", $recap; close(FD); } } ###################################################################### -## read_cache($file) - called before reading the .conf +## read_recap($file) - called before reading the .conf ###################################################################### -sub read_cache { +sub read_recap { my $file = shift; my $config = shift; my $globals = {}; %{$config} = (); - ## read the cache file ignoring anything on the command-line. if (-e $file) { my %saved = %opt; %opt = (); - $saved_cache = _read_config($config, $globals, "##\\s*$program-$version\\s*", $file); + $saved_recap = _read_config($config, $globals, "##\\s*$program-$version\\s*", $file); %opt = %saved; - for my $h (keys %cache) { + for my $h (keys(%recap)) { next if !exists($config->{$h}); for (qw(atime mtime wtime ip status)) { - # TODO: Isn't $config equal to \%cache here? If so, this is a no-op. What was the - # original intention behind this? To copy %cache values into %config? If so, is + # TODO: Isn't $config equal to \%recap here? If so, this is a no-op. What was the + # original intention behind this? To copy %recap values into %config? If so, is # it better to just delete this and live with the current behavior (which doesn't # seem to be causing users any problems) or to "fix" it to match the original # intention, which might introduce a bug? - $config->{$h}{$_} = $cache{$h}{$_} if exists $cache{$h}{$_}; + $config->{$h}{$_} = $recap{$h}{$_} if exists $recap{$h}{$_}; } } } @@ -1964,14 +1975,14 @@ sub init_config { } fatal("options --retry and --daemon cannot be used together") if (opt('retry') && opt('daemon')); - ## determine hosts to update (those on the cmd-line, config-file, or failed cached) + ## determine hosts to update (those on the cmd-line, config-file, or failed in recap) my @hosts = keys %config; if (opt('host')) { @hosts = split_by_comma($opt{'host'}); } # TODO: This function is called before the recap file is read. How is this supposed to work? if (opt('retry')) { - @hosts = grep(($cache{$_}{'status'} // '') ne 'good', keys(%cache)); + @hosts = grep(($recap{$_}{'status'} // '') ne 'good', keys(%recap)); } ## remove any other hosts @@ -2272,7 +2283,7 @@ sub save_file { ## print_opt ## print_globals ## print_config -## print_cache +## print_recap ## print_info ###################################################################### sub _print_hash { @@ -2302,12 +2313,12 @@ sub print_hash { sub print_opt { print_hash("opt", \%opt); } sub print_globals { print_hash("globals", \%globals); } sub print_config { print_hash("config", \%config); } -sub print_cache { print_hash("cache", \%cache); } +sub print_recap { print_hash("recap", \%recap); } sub print_info { print_opt(); print_globals(); print_config(); - print_cache(); + print_recap(); } ###################################################################### ## pipecmd - run an external command @@ -2524,11 +2535,11 @@ sub interval_expired { my ($host, $time, $interval) = @_; return 0 if ($config{$host}{$interval} // 0) == 'inf'; - return 1 if !exists $cache{$host}; - return 1 if !exists $cache{$host}{$time} || !$cache{$host}{$time}; + return 1 if !exists $recap{$host}; + return 1 if !exists $recap{$host}{$time} || !$recap{$host}{$time}; return 1 if !exists $config{$host}{$interval} || !$config{$host}{$interval}; - return $now > ($cache{$host}{$time} + $config{$host}{$interval}); + return $now > ($recap{$host}{$time} + $config{$host}{$interval}); } @@ -3574,54 +3585,54 @@ sub nic_updateable { info("forcing update of %s.", $host); $update = 1; - } elsif (!exists($cache{$host})) { - info("forcing updating %s because no cached entry exists.", $host); + } elsif (!exists($recap{$host})) { + info("forcing updating %s because no recap entry exists in cache file.", $host); $update = 1; - } elsif ($cache{$host}{'wtime'} && $cache{$host}{'wtime'} > $now) { + } elsif ($recap{$host}{'wtime'} && $recap{$host}{'wtime'} > $now) { warning("cannot update %s from %s to %s until after %s.", $host, - ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : ''), $ip, - prettytime($cache{$host}{'wtime'}) + ($recap{$host}{'ip'} ? $recap{$host}{'ip'} : ''), $ip, + prettytime($recap{$host}{'wtime'}) ); - } elsif ($cache{$host}{'mtime'} && interval_expired($host, 'mtime', 'max-interval')) { + } elsif ($recap{$host}{'mtime'} && interval_expired($host, 'mtime', 'max-interval')) { warning("forcing update of %s from %s to %s; %s since last update on %s.", $host, - ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : ''), $ip, + ($recap{$host}{'ip'} ? $recap{$host}{'ip'} : ''), $ip, prettyinterval($config{$host}{'max-interval'}), - prettytime($cache{$host}{'mtime'}) + prettytime($recap{$host}{'mtime'}) ); $update = 1; - } elsif ($use ne 'disabled' && ($cache{$host}{'ip'} // '') ne $ip) { + } elsif ($use ne 'disabled' && ($recap{$host}{'ip'} // '') ne $ip) { ## Check whether to update IP address for the "--use" method" - if (($cache{$host}{'status'} // '') eq 'good' && + if (($recap{$host}{'status'} // '') eq 'good' && !interval_expired($host, 'mtime', 'min-interval')) { warning("skipping update of %s from %s to %s.\nlast updated %s.\nWait at least %s between update attempts.", $host, - ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : ''), + ($recap{$host}{'ip'} ? $recap{$host}{'ip'} : ''), $ip, - ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''), + ($recap{$host}{'mtime'} ? prettytime($recap{$host}{'mtime'}) : ''), prettyinterval($config{$host}{'min-interval'}) ) - if opt('verbose') || !($cache{$host}{'warned-min-interval'} // 0); + if opt('verbose') || !($recap{$host}{'warned-min-interval'} // 0); - $cache{$host}{'warned-min-interval'} = $now; + $recap{$host}{'warned-min-interval'} = $now; - } elsif (($cache{$host}{'status'} // '') ne 'good' && + } elsif (($recap{$host}{'status'} // '') ne 'good' && !interval_expired($host, 'atime', 'min-error-interval')) { - if (opt('verbose') || (!$cache{$host}{'warned-min-error-interval'} && + if (opt('verbose') || (!$recap{$host}{'warned-min-error-interval'} && ($warned_ip{$host} // 0) < $inv_ip_warn_count)) { warning("skipping update of %s from %s to %s.\nlast updated %s but last attempt on %s failed.\nWait at least %s between update attempts.", $host, - ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : ''), + ($recap{$host}{'ip'} ? $recap{$host}{'ip'} : ''), $ip, - ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''), - ($cache{$host}{'atime'} ? prettytime($cache{$host}{'atime'}) : ''), + ($recap{$host}{'mtime'} ? prettytime($recap{$host}{'mtime'}) : ''), + ($recap{$host}{'atime'} ? prettytime($recap{$host}{'atime'}) : ''), prettyinterval($config{$host}{'min-error-interval'}) ); if (!$ip && !opt('verbose')) { @@ -3631,40 +3642,40 @@ sub nic_updateable { } } - $cache{$host}{'warned-min-error-interval'} = $now; + $recap{$host}{'warned-min-error-interval'} = $now; } else { $update = 1; } - } elsif ($usev4 ne 'disabled' && ($cache{$host}{'ipv4'} // '') ne $ipv4) { + } elsif ($usev4 ne 'disabled' && ($recap{$host}{'ipv4'} // '') ne $ipv4) { ## Check whether to update IPv4 address for the "--usev4" method" - if (($cache{$host}{'status-ipv4'} // '') eq 'good' && + if (($recap{$host}{'status-ipv4'} // '') eq 'good' && !interval_expired($host, 'mtime', 'min-interval')) { warning("skipping update of %s from %s to %s.\nlast updated %s.\nWait at least %s between update attempts.", $host, - ($cache{$host}{'ipv4'} ? $cache{$host}{'ipv4'} : ''), + ($recap{$host}{'ipv4'} ? $recap{$host}{'ipv4'} : ''), $ipv4, - ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''), + ($recap{$host}{'mtime'} ? prettytime($recap{$host}{'mtime'}) : ''), prettyinterval($config{$host}{'min-interval'}) ) - if opt('verbose') || !($cache{$host}{'warned-min-interval'} // 0); + if opt('verbose') || !($recap{$host}{'warned-min-interval'} // 0); - $cache{$host}{'warned-min-interval'} = $now; + $recap{$host}{'warned-min-interval'} = $now; - } elsif (($cache{$host}{'status-ipv4'} // '') ne 'good' && + } elsif (($recap{$host}{'status-ipv4'} // '') ne 'good' && !interval_expired($host, 'atime', 'min-error-interval')) { - if (opt('verbose') || (!$cache{$host}{'warned-min-error-interval'} && + if (opt('verbose') || (!$recap{$host}{'warned-min-error-interval'} && ($warned_ipv4{$host} // 0) < $inv_ip_warn_count)) { warning("skipping update of %s from %s to %s.\nlast updated %s but last attempt on %s failed.\nWait at least %s between update attempts.", $host, - ($cache{$host}{'ipv4'} ? $cache{$host}{'ipv4'} : ''), + ($recap{$host}{'ipv4'} ? $recap{$host}{'ipv4'} : ''), $ipv4, - ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''), - ($cache{$host}{'atime'} ? prettytime($cache{$host}{'atime'}) : ''), + ($recap{$host}{'mtime'} ? prettytime($recap{$host}{'mtime'}) : ''), + ($recap{$host}{'atime'} ? prettytime($recap{$host}{'atime'}) : ''), prettyinterval($config{$host}{'min-error-interval'}) ); if (!$ipv4 && !opt('verbose')) { @@ -3674,40 +3685,40 @@ sub nic_updateable { } } - $cache{$host}{'warned-min-error-interval'} = $now; + $recap{$host}{'warned-min-error-interval'} = $now; } else { $update = 1; } - } elsif ($usev6 ne 'disabled' && ($cache{$host}{'ipv6'} // '') ne $ipv6) { + } elsif ($usev6 ne 'disabled' && ($recap{$host}{'ipv6'} // '') ne $ipv6) { ## Check whether to update IPv6 address for the "--usev6" method" - if (($cache{$host}{'status-ipv6'} // '') eq 'good' && + if (($recap{$host}{'status-ipv6'} // '') eq 'good' && !interval_expired($host, 'mtime', 'min-interval')) { warning("skipping update of %s from %s to %s.\nlast updated %s.\nWait at least %s between update attempts.", $host, - ($cache{$host}{'ipv6'} ? $cache{$host}{'ipv6'} : ''), + ($recap{$host}{'ipv6'} ? $recap{$host}{'ipv6'} : ''), $ipv6, - ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''), + ($recap{$host}{'mtime'} ? prettytime($recap{$host}{'mtime'}) : ''), prettyinterval($config{$host}{'min-interval'}) ) - if opt('verbose') || !($cache{$host}{'warned-min-interval'} // 0); + if opt('verbose') || !($recap{$host}{'warned-min-interval'} // 0); - $cache{$host}{'warned-min-interval'} = $now; + $recap{$host}{'warned-min-interval'} = $now; - } elsif (($cache{$host}{'status-ipv6'} // '') ne 'good' && + } elsif (($recap{$host}{'status-ipv6'} // '') ne 'good' && !interval_expired($host, 'atime', 'min-error-interval')) { - if (opt('verbose') || (!$cache{$host}{'warned-min-error-interval'} && + if (opt('verbose') || (!$recap{$host}{'warned-min-error-interval'} && ($warned_ipv6{$host} // 0) < $inv_ip_warn_count)) { warning("skipping update of %s from %s to %s.\nlast updated %s but last attempt on %s failed.\nWait at least %s between update attempts.", $host, - ($cache{$host}{'ipv6'} ? $cache{$host}{'ipv6'} : ''), + ($recap{$host}{'ipv6'} ? $recap{$host}{'ipv6'} : ''), $ipv6, - ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''), - ($cache{$host}{'atime'} ? prettytime($cache{$host}{'atime'}) : ''), + ($recap{$host}{'mtime'} ? prettytime($recap{$host}{'mtime'}) : ''), + ($recap{$host}{'atime'} ? prettytime($recap{$host}{'atime'}) : ''), prettyinterval($config{$host}{'min-error-interval'}) ); if (!$ipv6 && !opt('verbose')) { @@ -3717,7 +3728,7 @@ sub nic_updateable { } } - $cache{$host}{'warned-min-error-interval'} = $now; + $recap{$host}{'warned-min-error-interval'} = $now; } else { $update = 1; @@ -3725,14 +3736,14 @@ sub nic_updateable { } elsif (defined($sub) && &$sub($host)) { $update = 1; - } elsif ((defined($cache{$host}{'static'}) && defined($config{$host}{'static'}) && - ($cache{$host}{'static'} ne $config{$host}{'static'})) || - (defined($cache{$host}{'wildcard'}) && defined($config{$host}{'wildcard'}) && - ($cache{$host}{'wildcard'} ne $config{$host}{'wildcard'})) || - (defined($cache{$host}{'mx'}) && defined($config{$host}{'mx'}) && - ($cache{$host}{'mx'} ne $config{$host}{'mx'})) || - (defined($cache{$host}{'backupmx'}) && defined($config{$host}{'backupmx'}) && - ($cache{$host}{'backupmx'} ne $config{$host}{'backupmx'}))) { + } elsif ((defined($recap{$host}{'static'}) && defined($config{$host}{'static'}) && + ($recap{$host}{'static'} ne $config{$host}{'static'})) || + (defined($recap{$host}{'wildcard'}) && defined($config{$host}{'wildcard'}) && + ($recap{$host}{'wildcard'} ne $config{$host}{'wildcard'})) || + (defined($recap{$host}{'mx'}) && defined($config{$host}{'mx'}) && + ($recap{$host}{'mx'} ne $config{$host}{'mx'})) || + (defined($recap{$host}{'backupmx'}) && defined($config{$host}{'backupmx'}) && + ($recap{$host}{'backupmx'} ne $config{$host}{'backupmx'}))) { info("updating %s because host settings have been changed.", $host); $update = 1; @@ -3750,9 +3761,9 @@ sub nic_updateable { } } - $config{$host}{'status'} = $cache{$host}{'status'}; - $config{$host}{'status-ipv4'} = $cache{$host}{'status-ipv4'}; - $config{$host}{'status-ipv6'} = $cache{$host}{'status-ipv6'}; + $config{$host}{'status'} = $recap{$host}{'status'}; + $config{$host}{'status-ipv4'} = $recap{$host}{'status-ipv4'}; + $config{$host}{'status-ipv6'} = $recap{$host}{'status-ipv6'}; $config{$host}{'update'} = $update; if ($update) { $config{$host}{'status'} = undef; @@ -3763,8 +3774,8 @@ sub nic_updateable { $config{$host}{'warned-min-interval'} = 0; $config{$host}{'warned-min-error-interval'} = 0; - delete $cache{$host}{'warned-min-interval'}; - delete $cache{$host}{'warned-min-error-interval'}; + delete $recap{$host}{'warned-min-interval'}; + delete $recap{$host}{'warned-min-error-interval'}; } return $update; @@ -3911,7 +3922,7 @@ sub nic_dyndns2_updateable { my $host = shift; my $update = 0; - if ($config{$host}{'mx'} ne $cache{$host}{'mx'}) { + if ($config{$host}{'mx'} ne $recap{$host}{'mx'}) { info("forcing updating %s because 'mx' has changed to %s.", $host, $config{$host}{'mx'}); $update = 1; @@ -3919,7 +3930,7 @@ sub nic_dyndns2_updateable { info("forcing updating %s because 'backupmx' has changed to %s.", $host, ynu($config{$host}{'backupmx'}, "YES", "NO", "NO")); $update = 1; - } elsif ($config{$host}{'static'} ne $cache{$host}{'static'}) { + } elsif ($config{$host}{'static'} ne $recap{$host}{'static'}) { info("forcing updating %s because 'static' has changed to %s.", $host, ynu($config{$host}{'static'}, "YES", "NO", "NO")); $update = 1; @@ -4711,7 +4722,7 @@ sub nic_easydns_updateable { my $host = shift; my $update = 0; - if ($config{$host}{'mx'} ne $cache{$host}{'mx'}) { + if ($config{$host}{'mx'} ne $recap{$host}{'mx'}) { info("forcing updating %s because 'mx' has changed to %s.", $host, $config{$host}{'mx'}); $update = 1; @@ -4719,7 +4730,7 @@ sub nic_easydns_updateable { info("forcing updating %s because 'backupmx' has changed to %s.", $host, ynu($config{$host}{'backupmx'}, "YES", "NO", "NO")); $update = 1; - } elsif ($config{$host}{'static'} ne $cache{$host}{'static'}) { + } elsif ($config{$host}{'static'} ne $recap{$host}{'static'}) { info("forcing updating %s because 'static' has changed to %s.", $host, ynu($config{$host}{'static'}, "YES", "NO", "NO")); $update = 1; @@ -5889,7 +5900,6 @@ sub nic_googledomains_update { } next if !header_ok($host, $reply); - # Cache $config{$host}{'ip'} = $ip; $config{$host}{'mtime'} = $now; $config{$host}{'status'} = 'good'; @@ -6531,7 +6541,6 @@ sub nic_yandex_update { success("%s -- Updated Successfully to %s", $host, $ip); } - # Cache $config{$host}{'ip'} = $ip; $config{$host}{'mtime'} = $now; $config{$host}{'status'} = 'good'; diff --git a/t/write_cache.pl b/t/write_recap.pl similarity index 97% rename from t/write_cache.pl rename to t/write_recap.pl index 94e959b..9f9c661 100644 --- a/t/write_cache.pl +++ b/t/write_recap.pl @@ -35,7 +35,7 @@ my @test_cases = ( for my $tc (@test_cases) { $warning = undef; - ddclient::write_cache($tc->{f}); + ddclient::write_recap($tc->{f}); subtest $tc->{name} => sub { if (defined($tc->{warning_regex})) { like($warning, $tc->{warning_regex}, "expected warning message"); From 1be8438c705433f6e0271304cbff78e78e2ca3ff Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 18 Jun 2024 02:17:42 -0400 Subject: [PATCH 065/150] Rename `updateable` to `force_update` for readability --- ddclient.in | 96 ++++++++++++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/ddclient.in b/ddclient.in index b9ec46c..0f4cf74 100755 --- a/ddclient.in +++ b/ddclient.in @@ -708,7 +708,7 @@ my %variables = ( ); my %protocols = ( '1984' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_1984_update, 'examples' => \&nic_1984_examples, 'variables' => { @@ -718,7 +718,7 @@ my %protocols = ( }, }, 'changeip' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_changeip_update, 'examples' => \&nic_changeip_examples, 'variables' => { @@ -728,7 +728,7 @@ my %protocols = ( }, }, 'cloudflare' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_cloudflare_update, 'examples' => \&nic_cloudflare_examples, 'variables' => { @@ -745,7 +745,7 @@ my %protocols = ( }, }, 'cloudns' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_cloudns_update, 'examples' => \&nic_cloudns_examples, 'variables' => { @@ -756,7 +756,7 @@ my %protocols = ( }, }, 'digitalocean' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_digitalocean_update, 'examples' => \&nic_digitalocean_examples, 'variables' => { @@ -767,7 +767,7 @@ my %protocols = ( }, }, 'dinahosting' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_dinahosting_update, 'examples' => \&nic_dinahosting_examples, 'variables' => { @@ -778,7 +778,7 @@ my %protocols = ( }, }, 'dnsmadeeasy' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_dnsmadeeasy_update, 'examples' => \&nic_dnsmadeeasy_examples, 'variables' => { @@ -788,7 +788,7 @@ my %protocols = ( }, }, 'dondominio' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_dondominio_update, 'examples' => \&nic_dondominio_examples, 'variables' => { @@ -797,7 +797,7 @@ my %protocols = ( }, }, 'dslreports1' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_dslreports1_update, 'examples' => \&nic_dslreports1_examples, 'variables' => { @@ -806,7 +806,7 @@ my %protocols = ( }, }, 'domeneshop' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_domeneshop_update, 'examples' => \&nic_domeneshop_examples, 'variables' => { @@ -815,7 +815,7 @@ my %protocols = ( }, }, 'duckdns' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_duckdns_update, 'examples' => \&nic_duckdns_examples, 'variables' => { @@ -825,7 +825,7 @@ my %protocols = ( }, }, 'dyndns1' => { - 'updateable' => \&nic_dyndns2_updateable, + 'force_update' => \&nic_dyndns2_force_update, 'update' => \&nic_dyndns1_update, 'examples' => \&nic_dyndns1_examples, 'variables' => { @@ -834,7 +834,7 @@ my %protocols = ( }, }, 'dyndns2' => { - 'updateable' => \&nic_dyndns2_updateable, + 'force_update' => \&nic_dyndns2_force_update, 'update' => \&nic_dyndns2_update, 'examples' => \&nic_dyndns2_examples, 'variables' => { @@ -845,7 +845,7 @@ my %protocols = ( }, }, 'easydns' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_easydns_update, 'examples' => \&nic_easydns_examples, 'variables' => { @@ -859,7 +859,7 @@ my %protocols = ( }, }, 'freedns' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_freedns_update, 'examples' => \&nic_freedns_examples, 'variables' => { @@ -869,7 +869,7 @@ my %protocols = ( }, }, 'freemyip' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_freemyip_update, 'examples' => \&nic_freemyip_examples, 'variables' => { @@ -879,7 +879,7 @@ my %protocols = ( }, }, 'gandi' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_gandi_update, 'examples' => \&nic_gandi_examples, 'variables' => { @@ -894,7 +894,7 @@ my %protocols = ( } }, 'godaddy' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_godaddy_update, 'examples' => \&nic_godaddy_examples, 'variables' => { @@ -906,7 +906,7 @@ my %protocols = ( }, }, 'googledomains' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_googledomains_update, 'examples' => \&nic_googledomains_examples, 'variables' => { @@ -916,7 +916,7 @@ my %protocols = ( }, }, 'hetzner' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_hetzner_update, 'examples' => \&nic_hetzner_examples, 'variables' => { @@ -929,7 +929,7 @@ my %protocols = ( }, }, 'mythicdyn' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_mythicdyn_update, 'examples' => \&nic_mythicdyn_examples, 'variables' => { @@ -939,7 +939,7 @@ my %protocols = ( }, }, 'namecheap' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_namecheap_update, 'examples' => \&nic_namecheap_examples, 'variables' => { @@ -949,7 +949,7 @@ my %protocols = ( }, }, 'nfsn' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_nfsn_update, 'examples' => \&nic_nfsn_examples, 'variables' => { @@ -961,7 +961,7 @@ my %protocols = ( }, }, 'njalla' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_njalla_update, 'examples' => \&nic_njalla_examples, 'variables' => { @@ -972,7 +972,7 @@ my %protocols = ( }, }, 'noip' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_noip_update, 'examples' => \&nic_noip_examples, 'variables' => { @@ -983,7 +983,7 @@ my %protocols = ( }, }, 'nsupdate' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_nsupdate_update, 'examples' => \&nic_nsupdate_examples, 'variables' => { @@ -995,7 +995,7 @@ my %protocols = ( }, }, 'ovh' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_ovh_update, 'examples' => \&nic_ovh_examples, 'variables' => { @@ -1005,7 +1005,7 @@ my %protocols = ( }, }, 'porkbun' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_porkbun_update, 'examples' => \&nic_porkbun_examples, 'variables' => { @@ -1019,7 +1019,7 @@ my %protocols = ( }, }, 'sitelutions' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_sitelutions_update, 'examples' => \&nic_sitelutions_examples, 'variables' => { @@ -1029,7 +1029,7 @@ my %protocols = ( }, }, 'woima' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_woima_update, 'examples' => \&nic_woima_examples, 'variables' => { @@ -1044,7 +1044,7 @@ my %protocols = ( }, }, 'yandex' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_yandex_update, 'examples' => \&nic_yandex_examples, 'variables' => { @@ -1054,7 +1054,7 @@ my %protocols = ( }, }, 'zoneedit1' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_zoneedit1_update, 'examples' => \&nic_zoneedit1_examples, 'variables' => { @@ -1065,7 +1065,7 @@ my %protocols = ( }, }, 'keysystems' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_keysystems_update, 'examples' => \&nic_keysystems_examples, 'variables' => { @@ -1075,7 +1075,7 @@ my %protocols = ( }, }, 'dnsexit2' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_dnsexit2_update, 'examples' => \&nic_dnsexit2_examples, 'variables' => { @@ -1089,7 +1089,7 @@ my %protocols = ( }, }, 'regfishde' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_regfishde_update, 'examples' => \&nic_regfishde_examples, 'variables' => { @@ -1099,7 +1099,7 @@ my %protocols = ( }, }, 'enom' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_enom_update, 'examples' => \&nic_enom_examples, 'variables' => { @@ -1109,7 +1109,7 @@ my %protocols = ( }, }, 'infomaniak' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_infomaniak_update, 'examples' => \&nic_infomaniak_examples, 'variables' => { @@ -1117,7 +1117,7 @@ my %protocols = ( }, }, 'emailonly' => { - 'updateable' => undef, + 'force_update' => undef, 'update' => \&nic_emailonly_update, 'examples' => \&nic_emailonly_examples, 'variables' => { @@ -1396,7 +1396,7 @@ sub update_nics { for my $p (sort keys %protocols) { my (@hosts, %ipsv4, %ipsv6) = (); - my $updateable = $protocols{$p}{'updateable'}; + my $force_update = $protocols{$p}{'force_update'}; my $update = $protocols{$p}{'update'}; for my $h (sort keys %config) { @@ -1491,7 +1491,7 @@ sub update_nics { next; } - next if !nic_updateable($h, $updateable); + next if !nic_updateable($h, $force_update); push @hosts, $h; $ipsv4{$ipv4} = $h if ($ipv4); @@ -3805,7 +3805,7 @@ sub header_ok { ###################################################################### ## DDNS providers # A DDNS provider consists of an example function, the update -# function, and an optional updateable function. +# function, and an optional force_update function. # # The example function simply returns a string for the help message, # explaining how to configure the provider @@ -3813,7 +3813,7 @@ sub header_ok { # The update function performs the actual record update. # It receives an array of hosts as its argument. # -# The updateable function allows a provider implementation to force +# The force_update function allows a provider implementation to force # an update even if ddclient has itself determined no update is # necessary. The function shall return 1 if an update should be # performed, else 0. @@ -3916,9 +3916,9 @@ sub nic_dyndns1_update { } } ###################################################################### -## nic_dyndns2_updateable +## nic_dyndns2_force_update ###################################################################### -sub nic_dyndns2_updateable { +sub nic_dyndns2_force_update { my $host = shift; my $update = 0; @@ -4633,9 +4633,9 @@ EoEXAMPLE } ###################################################################### -## nic_zoneedit1_updateable +## nic_zoneedit1_force_update ###################################################################### -sub nic_zoneedit1_updateable { +sub nic_zoneedit1_force_update { return 0; } @@ -4716,9 +4716,9 @@ sub nic_zoneedit1_update { } } ###################################################################### -## nic_easydns_updateable +## nic_easydns_force_update ###################################################################### -sub nic_easydns_updateable { +sub nic_easydns_force_update { my $host = shift; my $update = 0; From b363fb48a51fd902335c0c99cd9a992cf1f8c4cf Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 13 Jun 2024 03:55:01 -0400 Subject: [PATCH 066/150] Fix string equality check The `$proto` interpolation wasn't quoted with `\Q` and `\E`, so metacharacters in `$proto` could break the matching. Switch from a regex to an expression to fix the equality check and improve readability. --- ddclient.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index 0f4cf74..c0bec0f 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2019,8 +2019,8 @@ sub init_config { HOST: for my $h (keys %config) { my $proto = opt('protocol', $h); - load_sha1_support($proto) if (grep(/^$proto$/, ("freedns", "nfsn"))); - load_json_support($proto) if (grep(/^$proto$/, ("1984", "cloudflare", "digitalocean", "gandi", "godaddy", "hetzner", "yandex", "nfsn", "njalla", "porkbun", "dnsexit2"))); + load_sha1_support($proto) if (grep($_ eq $proto, ("freedns", "nfsn"))); + load_json_support($proto) if (grep($_ eq $proto, ("1984", "cloudflare", "digitalocean", "gandi", "godaddy", "hetzner", "yandex", "nfsn", "njalla", "porkbun", "dnsexit2"))); if (!exists($protocols{$proto})) { warning("skipping host: %s: unrecognized protocol '%s'", $h, $proto); From e1e8d5711aa39d0ac63fd9a337958f0dc53d41d6 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 13 Jun 2024 22:47:58 -0400 Subject: [PATCH 067/150] Fix `get_ip` argument in `--query` when testing `--fw` --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index c0bec0f..f9a1222 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2127,7 +2127,7 @@ sub test_possible_ip { } } local $opt{'use'} = 'fw'; - printf "use=fw, fw=%s address is %s\n", opt('fw'), get_ip(opt('fw')) // 'NOT FOUND' + printf "use=fw, fw=%s address is %s\n", opt('fw'), get_ip('fw') // 'NOT FOUND' if !exists $builtinfw{opt('fw')}; } From 27b50a3b93e5752836b0747e34802a92de4d84ed Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 14 Jun 2024 00:54:27 -0400 Subject: [PATCH 068/150] Fix `--usev4=cisco`, `--usev4=cisco-asa` warning messages --- ddclient.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index f9a1222..4a05159 100755 --- a/ddclient.in +++ b/ddclient.in @@ -210,9 +210,9 @@ our %builtinweb = ( sub query_cisco { my ($h, $asa, $v4) = @_; - warning("'--if' is deprecated for '--usev4=ifv4; use '--ifv4' instead") + warning("'--if' is deprecated for '--usev4=cisco%s; use '--ifv4' instead", $asa ? '-asa' : '') if ($v4 && !defined(opt('ifv4')) && defined(opt('if', $h))); - warning("'--fw' is deprecated for '--usev4=fwv4; use '--fwv4' instead") + warning("'--fw' is deprecated for '--usev4=cisco%s; use '--fwv4' instead", $asa ? '-asa' : '') if ($v4 && !defined(opt('fwv4')) && defined(opt('fw', $h))); my $if = ($v4 ? opt('ifv4', $h) : undef) // opt('if', $h); my $fw = ($v4 ? opt('fwv4', $h) : undef) // opt('fw', $h); From c7c8c5f097e187fe7569cff3fe7e812b5fbd4df7 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 14 Jun 2024 03:48:15 -0400 Subject: [PATCH 069/150] Fix `usev4`, `usev6` output for `--query` --- ddclient.in | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ddclient.in b/ddclient.in index 4a05159..760509e 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2147,7 +2147,7 @@ sub test_possible_ip { # Now force IPv4 printf "----- Test_possible_ip with 'get_ipv4' ------\n"; - printf "use=ipv4, ipv4=%s address is %s\n", opt('ipv4'), get_ipv4('ipv4') // 'NOT FOUND' + printf "usev4=ipv4, ipv4=%s address is %s\n", opt('ipv4'), get_ipv4('ipv4') // 'NOT FOUND' if defined opt('ipv4'); { @@ -2161,27 +2161,27 @@ sub test_possible_ip { warning("failed to get list of interfaces") if !@ifs; for my $if (@ifs) { local $opt{'ifv4'} = $if; - printf "use=ifv4, ifv4=%s address is %s\n", opt('ifv4'), get_ipv4('ifv4') // 'NOT FOUND'; + printf "usev4=ifv4, ifv4=%s address is %s\n", opt('ifv4'), get_ipv4('ifv4') // 'NOT FOUND'; } } { local $opt{'usev4'} = 'webv4'; for my $web (sort keys %builtinweb) { local $opt{'webv4'} = $web; - printf "use=webv4, webv4=$web address is %s\n", get_ipv4('webv4') // 'NOT FOUND' + printf "usev4=webv4, webv4=$web address is %s\n", get_ipv4('webv4') // 'NOT FOUND' if ($web !~ "6") ## Don't bother if web site only supports IPv6; } - printf "use=webv4, webv4=%s address is %s\n", opt('webv4'), get_ipv4('webv4') // 'NOT FOUND' + printf "usev4=webv4, webv4=%s address is %s\n", opt('webv4'), get_ipv4('webv4') // 'NOT FOUND' if ! exists $builtinweb{opt('webv4')}; } if (opt('cmdv4')) { local $opt{'usev4'} = 'cmdv4'; - printf "use=cmdv4, cmdv4=%s address is %s\n", opt('cmdv4'), get_ipv4('cmdv4') // 'NOT FOUND'; + printf "usev4=cmdv4, cmdv4=%s address is %s\n", opt('cmdv4'), get_ipv4('cmdv4') // 'NOT FOUND'; } # Now force IPv6 printf "----- Test_possible_ip with 'get_ipv6' -----\n"; - printf "use=ipv6, ipv6=%s address is %s\n", opt('ipv6'), get_ipv6('ipv6') // 'NOT FOUND' + printf "usev6=ipv6, ipv6=%s address is %s\n", opt('ipv6'), get_ipv6('ipv6') // 'NOT FOUND' if defined opt('ipv6'); { @@ -2195,22 +2195,22 @@ sub test_possible_ip { warning("failed to get list of interfaces") if !@ifs; for my $if (@ifs) { local $opt{'ifv6'} = $if; - printf "use=ifv6, ifv6=%s address is %s\n", opt('ifv6'), get_ipv6('ifv6') // 'NOT FOUND'; + printf "usev6=ifv6, ifv6=%s address is %s\n", opt('ifv6'), get_ipv6('ifv6') // 'NOT FOUND'; } } { local $opt{'usev6'} = 'webv6'; for my $web (sort keys %builtinweb) { local $opt{'webv6'} = $web; - printf "use=webv6, webv6=$web address is %s\n", get_ipv6('webv6') // 'NOT FOUND' + printf "usev6=webv6, webv6=$web address is %s\n", get_ipv6('webv6') // 'NOT FOUND' if ($web !~ "4"); ## Don't bother if web site only supports IPv4 } - printf "use=webv6, webv6=%s address is %s\n", opt('webv6'), get_ipv6('webv6') // 'NOT FOUND' + printf "usev6=webv6, webv6=%s address is %s\n", opt('webv6'), get_ipv6('webv6') // 'NOT FOUND' if ! exists $builtinweb{opt('webv6')}; } if (opt('cmdv6')) { local $opt{'usev6'} = 'cmdv6'; - printf "use=cmdv6, cmdv6=%s address is %s\n", opt('cmdv6'), get_ipv6('cmdv6') // 'NOT FOUND'; + printf "usev6=cmdv6, cmdv6=%s address is %s\n", opt('cmdv6'), get_ipv6('cmdv6') // 'NOT FOUND'; } exit 0 unless opt('debug'); From 7ac6eda7cc5212f8c9608a4f343f38cdaca5c234 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 14 Jun 2024 03:51:23 -0400 Subject: [PATCH 070/150] Fix missing local `use*` override in `--query` --- ddclient.in | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/ddclient.in b/ddclient.in index 760509e..ec768e2 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2101,9 +2101,10 @@ sub test_possible_ip { local $opt{'debug'} = 0; printf "----- Test_possible_ip with 'get_ip' -----\n"; - printf "use=ip, ip=%s address is %s\n", opt('ip'), get_ip('ip') // 'NOT FOUND' - if defined opt('ip'); - + if (defined(opt('ip'))) { + local $opt{'use'} = 'ip'; + printf "use=ip, ip=%s address is %s\n", opt('ip'), get_ip('ip') // 'NOT FOUND'; + } { local $opt{'use'} = 'if'; # Note: The `ip` command adds a `@eth0` suffix to the names of VLAN @@ -2147,10 +2148,12 @@ sub test_possible_ip { # Now force IPv4 printf "----- Test_possible_ip with 'get_ipv4' ------\n"; - printf "usev4=ipv4, ipv4=%s address is %s\n", opt('ipv4'), get_ipv4('ipv4') // 'NOT FOUND' - if defined opt('ipv4'); - + if (defined(opt('ipv4'))) { + local $opt{'usev4'} = 'ipv4'; + printf "usev4=ipv4, ipv4=%s address is %s\n", opt('ipv4'), get_ipv4('ipv4') // 'NOT FOUND'; + } { + local $opt{'usev4'} = 'ifv4'; # Note: The `ip` command adds a `@eth0` suffix to the names of VLAN # interfaces. That `@eth0` suffix is NOT part of the interface name. my @ifs = map({ /^[^\s:]*:\s*([^\s:@]+)/ ? $1 : () } @@ -2181,10 +2184,12 @@ sub test_possible_ip { # Now force IPv6 printf "----- Test_possible_ip with 'get_ipv6' -----\n"; - printf "usev6=ipv6, ipv6=%s address is %s\n", opt('ipv6'), get_ipv6('ipv6') // 'NOT FOUND' - if defined opt('ipv6'); - + if (defined(opt('ipv6'))) { + local $opt{'usev6'} = 'ipv6'; + printf "usev6=ipv6, ipv6=%s address is %s\n", opt('ipv6'), get_ipv6('ipv6') // 'NOT FOUND'; + } { + local $opt{'usev6'} = 'ifv6'; # Note: The `ip` command adds a `@eth0` suffix to the names of VLAN # interfaces. That `@eth0` suffix is NOT part of the interface name. my @ifs = map({ /^[^\s:]*:\s*([^\s:@]+)/ ? $1 : () } From dafde8becb72ee4ef1557e6f6f23da68b3095589 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 15 Jun 2024 19:12:45 -0400 Subject: [PATCH 071/150] Fix erroneous `backupmx` recap check for `dyndns1`, `dyndns2` --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index ec768e2..473d8e6 100755 --- a/ddclient.in +++ b/ddclient.in @@ -3931,7 +3931,7 @@ sub nic_dyndns2_force_update { info("forcing updating %s because 'mx' has changed to %s.", $host, $config{$host}{'mx'}); $update = 1; - } elsif ($config{$host}{'mx'} && (ynu($config{$host}{'backupmx'}, 1, 2, 3) ne ynu($config{$host}{'backupmx'}, 1, 2, 3))) { + } elsif ($config{$host}{'mx'} && (ynu($config{$host}{'backupmx'}, 1, 2, 3) ne ynu($recap{$host}{'backupmx'}, 1, 2, 3))) { info("forcing updating %s because 'backupmx' has changed to %s.", $host, ynu($config{$host}{'backupmx'}, "YES", "NO", "NO")); $update = 1; From b154d8ef98b48f93acdc83befb99003abd492b24 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 15 Jun 2024 20:46:48 -0400 Subject: [PATCH 072/150] Fix missing v4, v6 variants in recap update Fixes an oversight when IPv6 support was added. --- ddclient.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index 473d8e6..38481a3 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1565,7 +1565,7 @@ sub write_recap { $recap{$h}{$v} = $config{$h}{$v}; } } else { - for my $v (qw(atime wtime status)) { + for my $v (qw(atime wtime status status-ipv4 status-ivp6)) { $recap{$h}{$v} = $config{$h}{$v}; } } @@ -1620,7 +1620,7 @@ sub read_recap { for my $h (keys(%recap)) { next if !exists($config->{$h}); - for (qw(atime mtime wtime ip status)) { + for (qw(atime mtime wtime ip ipv4 ipv6 status status-ipv4 status-ipv6)) { # TODO: Isn't $config equal to \%recap here? If so, this is a no-op. What was the # original intention behind this? To copy %recap values into %config? If so, is # it better to just delete this and live with the current behavior (which doesn't From 99dfd7f84d3ca6d7f4353687a6754a8488e51c3a Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 21 Jun 2024 21:43:25 -0400 Subject: [PATCH 073/150] Don't assume that `--use` is defined --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 38481a3..e3827f8 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1323,7 +1323,7 @@ sub main { fatal("invalid argument '--use=%s'; possible values are:\n%s", $opt{'use'}, join("\n", ip_strategies_usage())) - unless exists $ip_strategies{lc opt('use')}; + if defined(opt('use')) && !$ip_strategies{lc(opt('use'))}; if (defined($opt{'usev6'})) { usage("invalid argument '--usev6=%s'; possible values are:\n%s", $opt{'usev6'}, join("\n", ipv6_strategies_usage())) From 32bf975bfae15d764f4e9eaa71f7c75b0336c094 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 21 Jun 2024 21:45:02 -0400 Subject: [PATCH 074/150] Fix call to wrong function name with bad `--usev6` --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index e3827f8..c3fdded 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1325,7 +1325,7 @@ sub main { $opt{'use'}, join("\n", ip_strategies_usage())) if defined(opt('use')) && !$ip_strategies{lc(opt('use'))}; if (defined($opt{'usev6'})) { - usage("invalid argument '--usev6=%s'; possible values are:\n%s", + fatal("invalid argument '--usev6=%s'; possible values are:\n%s", $opt{'usev6'}, join("\n", ipv6_strategies_usage())) unless exists $ipv6_strategies{lc opt('usev6')}; } From 61577d29ae4a04e23a55839c7a0e17853bc3dae4 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 15 Jun 2024 19:35:57 -0400 Subject: [PATCH 075/150] njalla: Fix configuration change during update If the user enabled `quietreply`, it should not become false after the first update. Users might not notice a problem because I think ddclient re-reads the config file before every check. --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index c3fdded..7f20bdc 100755 --- a/ddclient.in +++ b/ddclient.in @@ -5247,7 +5247,7 @@ sub nic_njalla_update { # Read input params my $ipv4 = delete $config{$h}{'wantipv4'}; my $ipv6 = delete $config{$h}{'wantipv6'}; - my $quietreply = delete $config{$h}{'quietreply'}; + my $quietreply = $config{$h}{'quietreply'}; my $ip_output = ''; # Build url From 0cde2e3f96d51fda5def045c6f53aa7e442887fe Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 21 Jun 2024 00:04:50 -0400 Subject: [PATCH 076/150] infomaniak: Fix `mtime` update `mtime` should always be updated whenever the IP address is updated, otherwise ddclient will keep force updating over and over. --- ChangeLog.md | 2 ++ ddclient.in | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 6ac8d0c..6431684 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -81,6 +81,8 @@ repository history](https://github.com/ddclient/ddclient/commits/master). [#670](https://github.com/ddclient/ddclient/pull/670) * Fixed DNSExit provider when configured with a zone and non-identical hostname. [#673](https://github.com/ddclient/ddclient/issues/673) + * `infomaniak`: Fixed frequent forced updates after 25 days (`max-interval`). + [#691](https://github.com/ddclient/ddclient/issues/691) ## 2023-11-23 v3.11.2 diff --git a/ddclient.in b/ddclient.in index 7f20bdc..29fca39 100755 --- a/ddclient.in +++ b/ddclient.in @@ -8042,7 +8042,7 @@ sub nic_infomaniak_update { if (defined $updated && $updated) { info($msg); $config{$h}{"ipv$v"} = $ip; - $config{$h}{'mtime'} = $config{$h}{'mtime'} // $now; + $config{$h}{'mtime'} = $now; $config{$h}{"status-ipv$v"} = 'good'; next INFOMANIAK_IP_LOOP; } From 9d49a33ac620a99b76b5651fb46d2c8d3639e7c9 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 21 Jun 2024 01:15:51 -0400 Subject: [PATCH 077/150] regfishde: Fix IPv6 support --- ChangeLog.md | 2 ++ ddclient.in | 33 +++++++++++++++++---------------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 6431684..8f1eba9 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -83,6 +83,8 @@ repository history](https://github.com/ddclient/ddclient/commits/master). hostname. [#673](https://github.com/ddclient/ddclient/issues/673) * `infomaniak`: Fixed frequent forced updates after 25 days (`max-interval`). [#691](https://github.com/ddclient/ddclient/issues/691) + * `regfishde`: Fixed IPv6 support. + [#691](https://github.com/ddclient/ddclient/issues/691) ## 2023-11-23 v3.11.2 diff --git a/ddclient.in b/ddclient.in index 29fca39..88419a6 100755 --- a/ddclient.in +++ b/ddclient.in @@ -7704,13 +7704,13 @@ sub nic_regfishde_update { ## update configured host for my $h (@_) { - my $ip = delete $config{$h}{'wantip'}; - my $ipv6 = delete $config{$h}{'wantip'}; - - info("regfish.de setting IP address to %s for %s", $ip, $h); - - my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; - my $url = "https://$config{$h}{'server'}/?fqdn=$h&ipv$ipv=$ip&forcehost=1&token=$config{$h}{'password'}"; + my $ipv4 = delete $config{$h}{'wantipv4'}; + my $ipv6 = delete $config{$h}{'wantipv6'}; + info("regfish.de setting IPv4 address to %s for %s", $ipv4, $h) if $ipv4; + info("regfish.de setting IPv6 address to %s for %s", $ipv6, $h) if $ipv6; + my $url = "https://$config{$h}{'server'}/?fqdn=$h&forcehost=1&token=$config{$h}{'password'}"; + $url .= "&ipv4=$ipv4" if $ipv4; + $url .= "&ipv6=$ipv6" if $ipv6; # Try to get URL my $reply = geturl(proxy => opt('proxy'), url => $url); @@ -7721,16 +7721,17 @@ sub nic_regfishde_update { last; } last if !header_ok($h, $reply); - - if ($reply =~ /success/) { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; - $config{$h}{'status'} = 'good'; - success("updating %s: good: IP address set to %s", $h, $ip); - } else { - $config{$h}{'status'} = 'failed'; - failed("updating %s: Server said: '$reply'", $h); + if ($reply !~ /success/) { + failed("updating %s: Server said: '%s'", $h, $reply); + next; } + $config{$h}{'ipv4'} = $ipv4 if $ipv4; + $config{$h}{'ipv6'} = $ipv6 if $ipv6; + $config{$h}{'status-ipv4'} = 'good' if $ipv4; + $config{$h}{'status-ipv6'} = 'good' if $ipv6; + $config{$h}{'mtime'} = $now; + success("updating %s: good: IPv4 address set to %s", $h, $ipv4) if $ipv4; + success("updating %s: good: IPv6 address set to %s", $h, $ipv6) if $ipv6; } } From 134e47b61d1896f48e3f302286b146d35e9c5d58 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 25 Jun 2024 21:50:51 -0400 Subject: [PATCH 078/150] infomaniak: Delete unnecessary newlines --- ddclient.in | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/ddclient.in b/ddclient.in index 88419a6..5062119 100755 --- a/ddclient.in +++ b/ddclient.in @@ -7989,19 +7989,15 @@ EoEXAMPLE ###################################################################### sub nic_infomaniak_update { debug("\nnic_infomaniak_update -------------------"); - for my $h (@_) { INFOMANIAK_IP_LOOP: for my $v (4, 6) { my $ip = delete $config{$h}{"wantipv$v"}; - if (!defined $ip) { debug("ipv%d not wanted, skipping", $v); next; } - info("setting IP address to %s for %s", $ip, $h); - # No change in IP => nochg # Bad auth => badauth # Bad domain name => nohost @@ -8014,20 +8010,16 @@ sub nic_infomaniak_update { 'nohost' => (0, sprintf("Bad domain name %s or bad IP %s", $h, $ip)), 'badauth' => (0, sprintf("Bad authentication for %s", $h)), ); - my $url1 = "https://$config{$h}{'login'}:$config{$h}{'password'}"; $url1 .= "\@infomaniak.com/nic/update"; $url1 .= "?hostname=$h"; $url1 .= "&myip=$ip"; - my $url2 = "https://infomaniak.com/nic/update"; $url2 .= "?hostname=$h"; $url2 .= "&myip=$ip"; $url2 .= "&username=$config{$h}{'login'}"; $url2 .= "&password=$config{$h}{'password'}"; - my $reply; - for my $url ($url1, $url2) { info("trying update with %s", $url); $reply = geturl(proxy => opt('proxy'), url => $url); @@ -8035,23 +8027,19 @@ sub nic_infomaniak_update { info("could not update %s using url %s, trying next one", $h, $url); next; } - my ($status) = split / /, $reply, 1; my ($updated, $msg) = $statuses{$status} // (0, sprintf("Unknown reply from Infomaniak: %s", $reply)); - if (defined $updated && $updated) { info($msg); $config{$h}{"ipv$v"} = $ip; $config{$h}{'mtime'} = $now; $config{$h}{"status-ipv$v"} = 'good'; next INFOMANIAK_IP_LOOP; - } - else { + } else { warning($msg); } } - $config{$h}{"status-ipv$v"} = 'failed'; failed("updating %s: could not update IP on Infomaniak", $h); } From bab9d9483e2ebecae04d99bfccbd3a828942c639 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 25 Jun 2024 21:55:46 -0400 Subject: [PATCH 079/150] infomaniak: Move variable declaration to definition --- ddclient.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index 5062119..a5ce3fd 100755 --- a/ddclient.in +++ b/ddclient.in @@ -8019,10 +8019,9 @@ sub nic_infomaniak_update { $url2 .= "&myip=$ip"; $url2 .= "&username=$config{$h}{'login'}"; $url2 .= "&password=$config{$h}{'password'}"; - my $reply; for my $url ($url1, $url2) { info("trying update with %s", $url); - $reply = geturl(proxy => opt('proxy'), url => $url); + my $reply = geturl(proxy => opt('proxy'), url => $url); if (!defined($reply) || !$reply) { info("could not update %s using url %s, trying next one", $h, $url); next; From ac9f937c88892923120bbbc7204e33b452c589a4 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 25 Jun 2024 21:56:12 -0400 Subject: [PATCH 080/150] infomaniak: Delete unnecessary defined checks `undef` is falsy so there's no need to check whether the value is defined. --- ddclient.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index a5ce3fd..b8f8f34 100755 --- a/ddclient.in +++ b/ddclient.in @@ -8022,14 +8022,14 @@ sub nic_infomaniak_update { for my $url ($url1, $url2) { info("trying update with %s", $url); my $reply = geturl(proxy => opt('proxy'), url => $url); - if (!defined($reply) || !$reply) { + if (!$reply) { info("could not update %s using url %s, trying next one", $h, $url); next; } my ($status) = split / /, $reply, 1; my ($updated, $msg) = $statuses{$status} // (0, sprintf("Unknown reply from Infomaniak: %s", $reply)); - if (defined $updated && $updated) { + if ($updated) { info($msg); $config{$h}{"ipv$v"} = $ip; $config{$h}{'mtime'} = $now; From a5dedeed3c6f894bb23dbc9d56bb50739c3c1612 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 25 Jun 2024 22:06:34 -0400 Subject: [PATCH 081/150] infomaniak: Fix geturl call * Pass login and password via `login` and `password` options to avoid issues with escaping special characters. * Don't attempt twice -- if the first attempt fails, the second will almost certainly fail as well. (The two attempted URLs were equivalent, differing only in how the login and password were passed.) --- ddclient.in | 52 +++++++++++++++++++++------------------------------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/ddclient.in b/ddclient.in index b8f8f34..a8ec647 100755 --- a/ddclient.in +++ b/ddclient.in @@ -7990,7 +7990,6 @@ EoEXAMPLE sub nic_infomaniak_update { debug("\nnic_infomaniak_update -------------------"); for my $h (@_) { - INFOMANIAK_IP_LOOP: for my $v (4, 6) { my $ip = delete $config{$h}{"wantipv$v"}; if (!defined $ip) { @@ -8010,37 +8009,28 @@ sub nic_infomaniak_update { 'nohost' => (0, sprintf("Bad domain name %s or bad IP %s", $h, $ip)), 'badauth' => (0, sprintf("Bad authentication for %s", $h)), ); - my $url1 = "https://$config{$h}{'login'}:$config{$h}{'password'}"; - $url1 .= "\@infomaniak.com/nic/update"; - $url1 .= "?hostname=$h"; - $url1 .= "&myip=$ip"; - my $url2 = "https://infomaniak.com/nic/update"; - $url2 .= "?hostname=$h"; - $url2 .= "&myip=$ip"; - $url2 .= "&username=$config{$h}{'login'}"; - $url2 .= "&password=$config{$h}{'password'}"; - for my $url ($url1, $url2) { - info("trying update with %s", $url); - my $reply = geturl(proxy => opt('proxy'), url => $url); - if (!$reply) { - info("could not update %s using url %s, trying next one", $h, $url); - next; - } - my ($status) = split / /, $reply, 1; - my ($updated, $msg) = - $statuses{$status} // (0, sprintf("Unknown reply from Infomaniak: %s", $reply)); - if ($updated) { - info($msg); - $config{$h}{"ipv$v"} = $ip; - $config{$h}{'mtime'} = $now; - $config{$h}{"status-ipv$v"} = 'good'; - next INFOMANIAK_IP_LOOP; - } else { - warning($msg); - } + my $reply = geturl( + proxy => opt('proxy'), + url => "https://infomaniak.com/nic/update?hostname=$h&myip=$ip", + login => $config{$h}{'login'}, + password => $config{$h}{'password'}, + ); + if (!$reply) { + failed("could not update %s", $h); + next; + } + my ($status) = split / /, $reply, 1; + my ($updated, $msg) = + $statuses{$status} // (0, sprintf("Unknown reply from Infomaniak: %s", $reply)); + if ($updated) { + success($msg); + $config{$h}{"ipv$v"} = $ip; + $config{$h}{'mtime'} = $now; + $config{$h}{"status-ipv$v"} = 'good'; + next; + } else { + failed($msg); } - $config{$h}{"status-ipv$v"} = 'failed'; - failed("updating %s: could not update IP on Infomaniak", $h); } } } From 29e86d9a9155a7a8cb9cef286a32c28fa492627d Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 25 Jun 2024 22:10:30 -0400 Subject: [PATCH 082/150] infomaniak: Rename variable for readability --- ddclient.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index a8ec647..8fc38a7 100755 --- a/ddclient.in +++ b/ddclient.in @@ -8020,9 +8020,9 @@ sub nic_infomaniak_update { next; } my ($status) = split / /, $reply, 1; - my ($updated, $msg) = + my ($ok, $msg) = $statuses{$status} // (0, sprintf("Unknown reply from Infomaniak: %s", $reply)); - if ($updated) { + if ($ok) { success($msg); $config{$h}{"ipv$v"} = $ip; $config{$h}{'mtime'} = $now; From d2f0e042f495c311ffccb26523c1408d47f146ba Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 25 Jun 2024 22:12:08 -0400 Subject: [PATCH 083/150] infomaniak: Invert condition to improve readability --- ddclient.in | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ddclient.in b/ddclient.in index 8fc38a7..1a2594f 100755 --- a/ddclient.in +++ b/ddclient.in @@ -8022,15 +8022,14 @@ sub nic_infomaniak_update { my ($status) = split / /, $reply, 1; my ($ok, $msg) = $statuses{$status} // (0, sprintf("Unknown reply from Infomaniak: %s", $reply)); - if ($ok) { - success($msg); - $config{$h}{"ipv$v"} = $ip; - $config{$h}{'mtime'} = $now; - $config{$h}{"status-ipv$v"} = 'good'; - next; - } else { + if (!$ok) { failed($msg); + next; } + success($msg); + $config{$h}{"ipv$v"} = $ip; + $config{$h}{'mtime'} = $now; + $config{$h}{"status-ipv$v"} = 'good'; } } } From 8e24c92b1e4951aca4f85b84ee8bab9beb654e74 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 25 Jun 2024 22:16:49 -0400 Subject: [PATCH 084/150] infomaniak: Fix response parsing --- ChangeLog.md | 2 ++ ddclient.in | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 8f1eba9..b6e7622 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -83,6 +83,8 @@ repository history](https://github.com/ddclient/ddclient/commits/master). hostname. [#673](https://github.com/ddclient/ddclient/issues/673) * `infomaniak`: Fixed frequent forced updates after 25 days (`max-interval`). [#691](https://github.com/ddclient/ddclient/issues/691) + * `infomaniak`: Fixed incorrect parsing of server response. + [#692](https://github.com/ddclient/ddclient/issues/692) * `regfishde`: Fixed IPv6 support. [#691](https://github.com/ddclient/ddclient/issues/691) diff --git a/ddclient.in b/ddclient.in index 1a2594f..df3c168 100755 --- a/ddclient.in +++ b/ddclient.in @@ -8019,9 +8019,10 @@ sub nic_infomaniak_update { failed("could not update %s", $h); next; } - my ($status) = split / /, $reply, 1; + (my $body = $reply) =~ s/^.*?\n\n//s; + my ($status) = split(/ /, $body, 2); my ($ok, $msg) = - $statuses{$status} // (0, sprintf("Unknown reply from Infomaniak: %s", $reply)); + $statuses{$status} // (0, sprintf("Unknown reply from Infomaniak: %s", $body)); if (!$ok) { failed($msg); next; From 7d99da77cc179bd1b1fbd21423771be1e57e3b56 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 25 Jun 2024 22:22:02 -0400 Subject: [PATCH 085/150] header_ok: Fail if the reply is falsy --- ddclient.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ddclient.in b/ddclient.in index df3c168..877d8a3 100755 --- a/ddclient.in +++ b/ddclient.in @@ -3791,6 +3791,10 @@ sub nic_updateable { ###################################################################### sub header_ok { my ($host, $line) = @_; + if (!$line) { + failed("updating %s: no response from server", $host); + return 0; + } $line =~ s/\r?\n.*//s; my ($code, $msg) = ($line =~ qr%^\s*HTTP/.*\s+(\d+)\s*(?:\s+([^\s].*))?$%i); if (!defined($code)) { From 9ba583175a5213f0fb2ff9ee75b3ca24f6b4aeae Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 25 Jun 2024 22:23:27 -0400 Subject: [PATCH 086/150] infomaniak: Fail if the HTTP status code is not 2xx --- ddclient.in | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ddclient.in b/ddclient.in index 877d8a3..7142ff9 100755 --- a/ddclient.in +++ b/ddclient.in @@ -8019,10 +8019,7 @@ sub nic_infomaniak_update { login => $config{$h}{'login'}, password => $config{$h}{'password'}, ); - if (!$reply) { - failed("could not update %s", $h); - next; - } + next if !header_ok($h, $reply); (my $body = $reply) =~ s/^.*?\n\n//s; my ($status) = split(/ /, $body, 2); my ($ok, $msg) = From 948567c456cf7b845885d549288b6f350921b978 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 25 Jun 2024 22:27:55 -0400 Subject: [PATCH 087/150] infomaniak: Unrequire `server` setting The `infomaniak` protocol doesn't use `server`. --- ddclient.in | 1 + 1 file changed, 1 insertion(+) diff --git a/ddclient.in b/ddclient.in index 7142ff9..8409b93 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1114,6 +1114,7 @@ my %protocols = ( 'examples' => \&nic_infomaniak_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, + 'server' => undef, }, }, 'emailonly' => { From 5757f7e07d46a4dea30185f5511e0c9fc216e643 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 27 Jun 2024 00:03:25 -0400 Subject: [PATCH 088/150] Restore accidentally deleted `--fw` command-line argument This was mistakenly deleted in commit 908b7285032fc7b969430de9b766eaf1e9719d56. --- ddclient.in | 1 + 1 file changed, 1 insertion(+) diff --git a/ddclient.in b/ddclient.in index 8409b93..c1e168b 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1212,6 +1212,7 @@ my @opt = ( "", " Options related to '--use=fw', '--usev4=fwv4', '--usev6=fwv6', and '--usev6=fw'", " as well as '--use=', '--usev4=', and '--usev6=':", + ["fw", "=s", "--fw= : deprecated, use '--fwv4' or '--fwv6'"], ["fw-skip", "=s", "--fw-skip= : deprecated, use '--fwv4-skip' or '--fwv6-skip'"], ["fwv4", "=s", "--fwv4= : obtain IPv4 address from device with IP address
or URL "], ["fwv4-skip", "=s", "--fwv4-skip= : skip any IP addresses before in the text returned from the device"], From 66bb07450f941d1432b18d53a5931f2cfbf83c34 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 28 Jun 2024 04:16:01 -0400 Subject: [PATCH 089/150] nfsn: Fix spelling of `min-interval` variable name --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index c1e168b..f91e290 100755 --- a/ddclient.in +++ b/ddclient.in @@ -954,7 +954,7 @@ my %protocols = ( 'examples' => \&nic_nfsn_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'min_interval' => setv(T_FQDNP, 0, 0, 0, interval('5m')), + 'min-interval' => setv(T_FQDNP, 0, 0, 0, interval('5m')), 'server' => setv(T_FQDNP, 1, 0, 'api.nearlyfreespeech.net', undef), 'ttl' => setv(T_NUMBER, 1, 0, 300, undef), 'zone' => setv(T_FQDN, 1, 0, undef, undef), From e32b9436fb6cbd0f66bc753d35d17e8bc3840970 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 28 Jun 2024 04:17:34 -0400 Subject: [PATCH 090/150] nfsn: Fix type of `min-interval` variable --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index f91e290..6381757 100755 --- a/ddclient.in +++ b/ddclient.in @@ -954,7 +954,7 @@ my %protocols = ( 'examples' => \&nic_nfsn_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'min-interval' => setv(T_FQDNP, 0, 0, 0, interval('5m')), + 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), 'server' => setv(T_FQDNP, 1, 0, 'api.nearlyfreespeech.net', undef), 'ttl' => setv(T_NUMBER, 1, 0, 300, undef), 'zone' => setv(T_FQDN, 1, 0, undef, undef), From be9e305e737160b0a7db5337c10b5dc4e85103de Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 28 Jun 2024 04:10:33 -0400 Subject: [PATCH 091/150] Fix definition of `wtime` variable --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 6381757..c6d22d0 100755 --- a/ddclient.in +++ b/ddclient.in @@ -668,7 +668,7 @@ my %variables = ( # Timestamp (seconds since epoch) indicating the earliest time the next update is # permitted. # TODO: Create a timestamp type and change this to that type. - 'wtime' => setv(T_DELAY, 0, 1, 0, interval('30s')), + 'wtime' => setv(T_NUMBER,0, 1, undef, undef), # Timestamp (seconds since epoch) indicating when an IP address was last sent to the DDNS # service, even if the IP address was not different from what was already stored. # TODO: Create a timestamp type and change this to that type. From b8a0a264413255891e3699a3414188a952c528cd Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 28 Jun 2024 02:33:33 -0400 Subject: [PATCH 092/150] Remove defaults from required variables without sensible defaults Required variables with defaults don't make sense; remove the default values on variables that don't have sensible defaults, such as login and password. --- ddclient.in | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ddclient.in b/ddclient.in index c6d22d0..0a86822 100755 --- a/ddclient.in +++ b/ddclient.in @@ -587,8 +587,8 @@ my %variables = ( 'fwv4-skip' => setv(T_STRING,0, 0, undef, undef), 'fwv6' => setv(T_ANY, 0, 0, '', undef), 'fwv6-skip' => setv(T_STRING,0, 0, undef, undef), - 'fw-login' => setv(T_LOGIN, 1, 0, '', undef), - 'fw-password' => setv(T_PASSWD,1, 0, '', undef), + 'fw-login' => setv(T_LOGIN, 1, 0, undef, undef), + 'fw-password' => setv(T_PASSWD,1, 0, undef, undef), 'cmd' => setv(T_PROG, 0, 0, '', undef), 'cmd-skip' => setv(T_STRING,0, 0, undef, undef), 'cmdv4' => setv(T_PROG, 0, 0, '', undef), @@ -620,9 +620,9 @@ my %variables = ( }, 'protocol-common-defaults' => { 'server' => setv(T_FQDNP, 1, 0, 'members.dyndns.org', undef), - 'login' => setv(T_LOGIN, 1, 0, '', undef), - 'password' => setv(T_PASSWD,1, 0, '', undef), - 'host' => setv(T_STRING,1, 1, '', undef), + 'login' => setv(T_LOGIN, 1, 0, undef, undef), + 'password' => setv(T_PASSWD,1, 0, undef, undef), + 'host' => setv(T_STRING,1, 1, undef, undef), 'use' => setv(T_USE, 0, 0, 'ip', undef), 'usev4' => setv(T_USEV4, 0, 0, 'disabled', undef), @@ -741,7 +741,7 @@ my %protocols = ( 'static' => setv(T_BOOL, 0, 1, 0, undef), 'ttl' => setv(T_NUMBER, 1, 0, 1, undef), 'wildcard' => setv(T_BOOL, 0, 1, 0, undef), - 'zone' => setv(T_FQDN, 1, 0, '', undef), + 'zone' => setv(T_FQDN, 1, 0, undef, undef), }, }, 'cloudns' => { @@ -763,7 +763,7 @@ my %protocols = ( %{$variables{'protocol-common-defaults'}}, 'login' => undef, 'server' => setv(T_FQDNP, 1, 0, 'api.digitalocean.com', undef), - 'zone' => setv(T_FQDN, 1, 0, '', undef), + 'zone' => setv(T_FQDN, 1, 0, undef, undef), }, }, 'dinahosting' => { @@ -902,7 +902,7 @@ my %protocols = ( 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), 'server' => setv(T_FQDNP, 1, 0, 'api.godaddy.com/v1/domains', undef), 'ttl' => setv(T_NUMBER, 1, 0, 600, undef), - 'zone' => setv(T_FQDN, 1, 0, '', undef), + 'zone' => setv(T_FQDN, 1, 0, undef, undef), }, }, 'googledomains' => { @@ -925,7 +925,7 @@ my %protocols = ( 'min-interval' => setv(T_DELAY, 0, 0, interval('1m'), 0), 'server' => setv(T_FQDNP, 1, 0, 'dns.hetzner.com/api/v1', undef), 'ttl' => setv(T_NUMBER, 0, 0, 60, 60), - 'zone' => setv(T_FQDN, 1, 0, '', undef), + 'zone' => setv(T_FQDN, 1, 0, undef, undef), }, }, 'mythicdyn' => { @@ -991,7 +991,7 @@ my %protocols = ( 'login' => setv(T_LOGIN, 1, 0, '/usr/bin/nsupdate', undef), 'tcp' => setv(T_BOOL, 0, 1, 0, undef), 'ttl' => setv(T_NUMBER, 0, 1, 600, undef), - 'zone' => setv(T_STRING, 1, 1, '', undef), + 'zone' => setv(T_STRING, 1, 1, undef, undef), }, }, 'ovh' => { @@ -1012,8 +1012,8 @@ my %protocols = ( %{$variables{'protocol-common-defaults'}}, 'login' => undef, 'password' => undef, - 'apikey' => setv(T_PASSWD, 1, 0, '', undef), - 'secretapikey' => setv(T_PASSWD, 1, 0, '', undef), + 'apikey' => setv(T_PASSWD, 1, 0, undef, undef), + 'secretapikey' => setv(T_PASSWD, 1, 0, undef, undef), 'root-domain' => setv(T_OFQDN, 0, 0, '', undef), 'on-root-domain' => setv(T_BOOL, 0, 0, 0, undef), }, From ba6a27918625a59207c9a2355d8fb62dd4a0775b Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 28 Jun 2024 03:40:35 -0400 Subject: [PATCH 093/150] Convert unnecessarily required variables to optional Users are not required to provide values for these variables; either the default is reasonable or the variable can be left unset. --- ddclient.in | 90 ++++++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/ddclient.in b/ddclient.in index 0a86822..578847f 100755 --- a/ddclient.in +++ b/ddclient.in @@ -587,8 +587,8 @@ my %variables = ( 'fwv4-skip' => setv(T_STRING,0, 0, undef, undef), 'fwv6' => setv(T_ANY, 0, 0, '', undef), 'fwv6-skip' => setv(T_STRING,0, 0, undef, undef), - 'fw-login' => setv(T_LOGIN, 1, 0, undef, undef), - 'fw-password' => setv(T_PASSWD,1, 0, undef, undef), + 'fw-login' => setv(T_LOGIN, 0, 0, undef, undef), + 'fw-password' => setv(T_PASSWD,0, 0, undef, undef), 'cmd' => setv(T_PROG, 0, 0, '', undef), 'cmd-skip' => setv(T_STRING,0, 0, undef, undef), 'cmdv4' => setv(T_PROG, 0, 0, '', undef), @@ -619,7 +619,7 @@ my %variables = ( 'redirect' => setv(T_NUMBER,0, 0, 0, undef) }, 'protocol-common-defaults' => { - 'server' => setv(T_FQDNP, 1, 0, 'members.dyndns.org', undef), + 'server' => setv(T_FQDNP, 0, 0, 'members.dyndns.org', undef), 'login' => setv(T_LOGIN, 1, 0, undef, undef), 'password' => setv(T_PASSWD,1, 0, undef, undef), 'host' => setv(T_STRING,1, 1, undef, undef), @@ -714,7 +714,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'login' => undef, - 'server' => setv(T_FQDNP, 1, 0, 'api.1984.is', undef), + 'server' => setv(T_FQDNP, 0, 0, 'api.1984.is', undef), }, }, 'changeip' => { @@ -724,7 +724,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), - 'server' => setv(T_FQDNP, 1, 0, 'nic.changeip.com', undef), + 'server' => setv(T_FQDNP, 0, 0, 'nic.changeip.com', undef), }, }, 'cloudflare' => { @@ -737,9 +737,9 @@ my %protocols = ( 'login' => setv(T_LOGIN, 0, 0, 'token', undef), 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), 'mx' => setv(T_OFQDN, 0, 1, '', undef), - 'server' => setv(T_FQDNP, 1, 0, 'api.cloudflare.com/client/v4', undef), + 'server' => setv(T_FQDNP, 0, 0, 'api.cloudflare.com/client/v4', undef), 'static' => setv(T_BOOL, 0, 1, 0, undef), - 'ttl' => setv(T_NUMBER, 1, 0, 1, undef), + 'ttl' => setv(T_NUMBER, 0, 0, 1, undef), 'wildcard' => setv(T_BOOL, 0, 1, 0, undef), 'zone' => setv(T_FQDN, 1, 0, undef, undef), }, @@ -762,7 +762,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'login' => undef, - 'server' => setv(T_FQDNP, 1, 0, 'api.digitalocean.com', undef), + 'server' => setv(T_FQDNP, 0, 0, 'api.digitalocean.com', undef), 'zone' => setv(T_FQDN, 1, 0, undef, undef), }, }, @@ -774,7 +774,7 @@ my %protocols = ( %{$variables{'protocol-common-defaults'}}, 'min-error-interval' => setv(T_DELAY, 0, 0, interval('8m'), 0), 'script' => setv(T_STRING, 0, 1, '/special/api.php', undef), - 'server' => setv(T_FQDNP, 1, 0, 'dinahosting.com', undef), + 'server' => setv(T_FQDNP, 0, 0, 'dinahosting.com', undef), }, }, 'dnsmadeeasy' => { @@ -783,8 +783,8 @@ my %protocols = ( 'examples' => \&nic_dnsmadeeasy_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'script' => setv(T_STRING, 1, 1, '/servlet/updateip', undef), - 'server' => setv(T_FQDNP, 1, 0, 'cp.dnsmadeeasy.com', undef), + 'script' => setv(T_STRING, 0, 1, '/servlet/updateip', undef), + 'server' => setv(T_FQDNP, 0, 0, 'cp.dnsmadeeasy.com', undef), }, }, 'dondominio' => { @@ -793,7 +793,7 @@ my %protocols = ( 'examples' => \&nic_dondominio_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'server' => setv(T_FQDNP, 1, 0, 'dondns.dondominio.com', undef), + 'server' => setv(T_FQDNP, 0, 0, 'dondns.dondominio.com', undef), }, }, 'dslreports1' => { @@ -802,7 +802,7 @@ my %protocols = ( 'examples' => \&nic_dslreports1_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'server' => setv(T_FQDNP, 1, 0, 'www.dslreports.com', undef), + 'server' => setv(T_FQDNP, 0, 0, 'www.dslreports.com', undef), }, }, 'domeneshop' => { @@ -811,7 +811,7 @@ my %protocols = ( 'examples' => \&nic_domeneshop_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'server' => setv(T_FQDNP, 1, 0, 'api.domeneshop.no', undef), + 'server' => setv(T_FQDNP, 0, 0, 'api.domeneshop.no', undef), }, }, 'duckdns' => { @@ -821,7 +821,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'login' => undef, - 'server' => setv(T_FQDNP, 1, 0, 'www.duckdns.org', undef), + 'server' => setv(T_FQDNP, 0, 0, 'www.duckdns.org', undef), }, }, 'dyndns1' => { @@ -841,7 +841,7 @@ my %protocols = ( %{$variables{'protocol-common-defaults'}}, %{$variables{'dyndns-common-defaults'}}, 'custom' => setv(T_BOOL, 0, 1, 0, undef), - 'script' => setv(T_STRING, 1, 1, '/nic/update', undef), + 'script' => setv(T_STRING, 0, 1, '/nic/update', undef), }, }, 'easydns' => { @@ -853,8 +853,8 @@ my %protocols = ( 'backupmx' => setv(T_BOOL, 0, 1, 0, undef), 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), 'mx' => setv(T_OFQDN, 0, 1, '', undef), - 'server' => setv(T_FQDNP, 1, 0, 'api.cp.easydns.com', undef), - 'script' => setv(T_STRING, 1, 1, '/dyn/generic.php', undef), + 'server' => setv(T_FQDNP, 0, 0, 'api.cp.easydns.com', undef), + 'script' => setv(T_STRING, 0, 1, '/dyn/generic.php', undef), 'wildcard' => setv(T_BOOL, 0, 1, 0, undef), }, }, @@ -865,7 +865,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), - 'server' => setv(T_FQDNP, 1, 0, 'freedns.afraid.org', undef), + 'server' => setv(T_FQDNP, 0, 0, 'freedns.afraid.org', undef), }, }, 'freemyip' => { @@ -875,7 +875,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'login' => undef, - 'server' => setv(T_FQDNP, 1, 0, 'freemyip.com', undef), + 'server' => setv(T_FQDNP, 0, 0, 'freemyip.com', undef), }, }, 'gandi' => { @@ -886,8 +886,8 @@ my %protocols = ( %{$variables{'protocol-common-defaults'}}, 'login' => undef, 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), - 'server' => setv(T_FQDNP, 1, 0, 'api.gandi.net', undef), - 'script' => setv(T_STRING, 1, 1, '/v5', undef), + 'server' => setv(T_FQDNP, 0, 0, 'api.gandi.net', undef), + 'script' => setv(T_STRING, 0, 1, '/v5', undef), 'use-personal-access-token' => setv(T_BOOL, 0, 0, 0, undef), 'ttl' => setv(T_DELAY, 0, 0, undef, interval('5m')), 'zone' => setv(T_FQDN, 1, 0, undef, undef), @@ -900,8 +900,8 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), - 'server' => setv(T_FQDNP, 1, 0, 'api.godaddy.com/v1/domains', undef), - 'ttl' => setv(T_NUMBER, 1, 0, 600, undef), + 'server' => setv(T_FQDNP, 0, 0, 'api.godaddy.com/v1/domains', undef), + 'ttl' => setv(T_NUMBER, 0, 0, 600, undef), 'zone' => setv(T_FQDN, 1, 0, undef, undef), }, }, @@ -912,7 +912,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), - 'server' => setv(T_FQDNP, 1, 0, 'domains.google.com', undef), + 'server' => setv(T_FQDNP, 0, 0, 'domains.google.com', undef), }, }, 'hetzner' => { @@ -923,7 +923,7 @@ my %protocols = ( %{$variables{'protocol-common-defaults'}}, 'login' => setv(T_LOGIN, 0, 0, 'token', undef), 'min-interval' => setv(T_DELAY, 0, 0, interval('1m'), 0), - 'server' => setv(T_FQDNP, 1, 0, 'dns.hetzner.com/api/v1', undef), + 'server' => setv(T_FQDNP, 0, 0, 'dns.hetzner.com/api/v1', undef), 'ttl' => setv(T_NUMBER, 0, 0, 60, 60), 'zone' => setv(T_FQDN, 1, 0, undef, undef), }, @@ -935,7 +935,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), - 'server' => setv(T_FQDNP, 1, 0, 'api.mythic-beasts.com', undef), + 'server' => setv(T_FQDNP, 0, 0, 'api.mythic-beasts.com', undef), }, }, 'namecheap' => { @@ -945,7 +945,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), - 'server' => setv(T_FQDNP, 1, 0, 'dynamicdns.park-your-domain.com', undef), + 'server' => setv(T_FQDNP, 0, 0, 'dynamicdns.park-your-domain.com', undef), }, }, 'nfsn' => { @@ -955,8 +955,8 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), - 'server' => setv(T_FQDNP, 1, 0, 'api.nearlyfreespeech.net', undef), - 'ttl' => setv(T_NUMBER, 1, 0, 300, undef), + 'server' => setv(T_FQDNP, 0, 0, 'api.nearlyfreespeech.net', undef), + 'ttl' => setv(T_NUMBER, 0, 0, 300, undef), 'zone' => setv(T_FQDN, 1, 0, undef, undef), }, }, @@ -967,7 +967,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'login' => undef, - 'server' => setv(T_FQDNP, 1, 0, 'njal.la', undef), + 'server' => setv(T_FQDNP, 0, 0, 'njal.la', undef), 'quietreply' => setv(T_BOOL, 0, 1, 0, undef), }, }, @@ -978,7 +978,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'custom' => setv(T_BOOL, 0, 1, 0, undef), - 'server' => setv(T_FQDNP, 1, 0, 'dynupdate.no-ip.com', undef), + 'server' => setv(T_FQDNP, 0, 0, 'dynupdate.no-ip.com', undef), 'static' => setv(T_BOOL, 0, 1, 0, undef), }, }, @@ -988,7 +988,7 @@ my %protocols = ( 'examples' => \&nic_nsupdate_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'login' => setv(T_LOGIN, 1, 0, '/usr/bin/nsupdate', undef), + 'login' => setv(T_LOGIN, 0, 0, '/usr/bin/nsupdate', undef), 'tcp' => setv(T_BOOL, 0, 1, 0, undef), 'ttl' => setv(T_NUMBER, 0, 1, 600, undef), 'zone' => setv(T_STRING, 1, 1, undef, undef), @@ -1000,8 +1000,8 @@ my %protocols = ( 'examples' => \&nic_ovh_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'script' => setv(T_STRING, 1, 1, '/nic/update', undef), - 'server' => setv(T_FQDNP, 1, 0, 'www.ovh.com', undef), + 'script' => setv(T_STRING, 0, 1, '/nic/update', undef), + 'server' => setv(T_FQDNP, 0, 0, 'www.ovh.com', undef), }, }, 'porkbun' => { @@ -1024,7 +1024,7 @@ my %protocols = ( 'examples' => \&nic_sitelutions_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'server' => setv(T_FQDNP, 1, 0, 'www.sitelutions.com', undef), + 'server' => setv(T_FQDNP, 0, 0, 'www.sitelutions.com', undef), 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), }, }, @@ -1037,8 +1037,8 @@ my %protocols = ( 'backupmx' => setv(T_BOOL, 0, 1, 0, undef), 'custom' => setv(T_BOOL, 0, 1, 0, undef), 'mx' => setv(T_OFQDN, 0, 1, '', undef), - 'script' => setv(T_STRING, 1, 1, '/nic/update', undef), - 'server' => setv(T_FQDNP, 1, 0, 'dyn.woima.fi', undef), + 'script' => setv(T_STRING, 0, 1, '/nic/update', undef), + 'server' => setv(T_FQDNP, 0, 0, 'dyn.woima.fi', undef), 'static' => setv(T_BOOL, 0, 1, 0, undef), 'wildcard' => setv(T_BOOL, 0, 1, 0, undef), }, @@ -1050,7 +1050,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), - 'server' => setv(T_FQDNP, 1, 0, 'pddimp.yandex.ru', undef), + 'server' => setv(T_FQDNP, 0, 0, 'pddimp.yandex.ru', undef), }, }, 'zoneedit1' => { @@ -1060,7 +1060,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'min-interval' => setv(T_DELAY, 0, 0, interval('10m'), 0), - 'server' => setv(T_FQDNP, 1, 0, 'dynamic.zoneedit.com', undef), + 'server' => setv(T_FQDNP, 0, 0, 'dynamic.zoneedit.com', undef), 'zone' => setv(T_OFQDN, 0, 0, undef, undef), }, }, @@ -1071,7 +1071,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'login' => undef, - 'server' => setv(T_FQDNP, 1, 0, 'dynamicdns.key-systems.net', undef), + 'server' => setv(T_FQDNP, 0, 0, 'dynamicdns.key-systems.net', undef), }, }, 'dnsexit2' => { @@ -1082,9 +1082,9 @@ my %protocols = ( %{$variables{'protocol-common-defaults'}}, 'login' => undef, 'ssl' => setv(T_BOOL, 0, 0, 1, undef), - 'server' => setv(T_FQDNP, 1, 0, 'api.dnsexit.com', undef), + 'server' => setv(T_FQDNP, 0, 0, 'api.dnsexit.com', undef), 'path' => setv(T_STRING, 0, 0, '/dns/', undef), - 'ttl' => setv(T_NUMBER, 1, 0, 5, 0), + 'ttl' => setv(T_NUMBER, 0, 0, 5, 0), 'zone' => setv(T_STRING, 0, 0, undef, undef), }, }, @@ -1095,7 +1095,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'login' => undef, - 'server' => setv(T_FQDNP, 1, 0, 'dyndns.regfish.de', undef), + 'server' => setv(T_FQDNP, 0, 0, 'dyndns.regfish.de', undef), }, }, 'enom' => { @@ -1104,7 +1104,7 @@ my %protocols = ( 'examples' => \&nic_enom_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'server' => setv(T_FQDNP, 1, 0, 'dynamic.name-services.com', undef), + 'server' => setv(T_FQDNP, 0, 0, 'dynamic.name-services.com', undef), 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), }, }, From 88eb2ed4fe9879670ff808dc0703834aac654181 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 28 Jun 2024 03:43:41 -0400 Subject: [PATCH 094/150] Use `undef` as the default of truly optional variables --- ddclient.in | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/ddclient.in b/ddclient.in index 578847f..6f0a34a 100755 --- a/ddclient.in +++ b/ddclient.in @@ -132,7 +132,7 @@ my $last_emailbody = ''; ## If run as *d (e.g., ddclientd) then daemonize by default (but allow ## flags and options to override). -my $daemon_default = ($programd =~ /d$/) ? interval('5m') : 0; +my $daemon_default = ($programd =~ /d$/) ? interval('5m') : undef; use vars qw($file $lineno); local $file = ''; @@ -562,7 +562,7 @@ my %variables = ( 'foreground' => setv(T_BOOL, 0, 0, 0, undef), 'file' => setv(T_FILE, 0, 0, "$etc/$program.conf", undef), 'cache' => setv(T_FILE, 0, 0, "$cachedir/$program.cache", undef), - 'pid' => setv(T_FILE, 0, 0, "", undef), + 'pid' => setv(T_FILE, 0, 0, undef, undef), 'proxy' => setv(T_FQDNP, 0, 0, undef, undef), 'protocol' => setv(T_PROTO, 0, 0, 'dyndns2', undef), @@ -581,18 +581,18 @@ my %variables = ( 'webv4-skip' => setv(T_STRING,0, 0, undef, undef), 'webv6' => setv(T_STRING,0, 0, 'ipify-ipv6', undef), 'webv6-skip' => setv(T_STRING,0, 0, undef, undef), - 'fw' => setv(T_ANY, 0, 0, '', undef), + 'fw' => setv(T_ANY, 0, 0, undef, undef), 'fw-skip' => setv(T_STRING,0, 0, undef, undef), - 'fwv4' => setv(T_ANY, 0, 0, '', undef), + 'fwv4' => setv(T_ANY, 0, 0, undef, undef), 'fwv4-skip' => setv(T_STRING,0, 0, undef, undef), - 'fwv6' => setv(T_ANY, 0, 0, '', undef), + 'fwv6' => setv(T_ANY, 0, 0, undef, undef), 'fwv6-skip' => setv(T_STRING,0, 0, undef, undef), 'fw-login' => setv(T_LOGIN, 0, 0, undef, undef), 'fw-password' => setv(T_PASSWD,0, 0, undef, undef), - 'cmd' => setv(T_PROG, 0, 0, '', undef), + 'cmd' => setv(T_PROG, 0, 0, undef, undef), 'cmd-skip' => setv(T_STRING,0, 0, undef, undef), - 'cmdv4' => setv(T_PROG, 0, 0, '', undef), - 'cmdv6' => setv(T_PROG, 0, 0, '', undef), + 'cmdv4' => setv(T_PROG, 0, 0, undef, undef), + 'cmdv6' => setv(T_PROG, 0, 0, undef, undef), 'timeout' => setv(T_DELAY, 0, 0, interval('120s'), interval('120s')), 'retry' => setv(T_BOOL, 0, 0, 0, undef), @@ -601,8 +601,8 @@ my %variables = ( 'syslog' => setv(T_BOOL, 0, 0, 0, undef), 'facility' => setv(T_STRING,0, 0, 'daemon', undef), 'priority' => setv(T_STRING,0, 0, 'notice', undef), - 'mail' => setv(T_EMAIL, 0, 0, '', undef), - 'mail-failure' => setv(T_EMAIL, 0, 0, '', undef), + 'mail' => setv(T_EMAIL, 0, 0, undef, undef), + 'mail-failure' => setv(T_EMAIL, 0, 0, undef, undef), 'max-warn' => setv(T_NUMBER,0, 0, 1, undef), 'exec' => setv(T_BOOL, 0, 0, 1, undef), @@ -611,9 +611,9 @@ my %variables = ( 'quiet' => setv(T_BOOL, 0, 0, 0, undef), 'help' => setv(T_BOOL, 0, 0, 0, undef), 'test' => setv(T_BOOL, 0, 0, 0, undef), - 'geturl' => setv(T_STRING,0, 0, '', undef), + 'geturl' => setv(T_STRING,0, 0, undef, undef), - 'postscript' => setv(T_POSTS, 0, 0, '', undef), + 'postscript' => setv(T_POSTS, 0, 0, undef, undef), 'ssl_ca_dir' => setv(T_FILE, 0, 0, undef, undef), 'ssl_ca_file' => setv(T_FILE, 0, 0, undef, undef), 'redirect' => setv(T_NUMBER,0, 0, 0, undef) @@ -637,19 +637,19 @@ my %variables = ( 'webv4-skip' => setv(T_STRING,0, 0, undef, undef), 'webv6' => setv(T_STRING,0, 0, 'ipify-ipv6', undef), 'webv6-skip' => setv(T_STRING,0, 0, undef, undef), - 'fw' => setv(T_ANY, 0, 0, '', undef), + 'fw' => setv(T_ANY, 0, 0, undef, undef), 'fw-skip' => setv(T_STRING,0, 0, undef, undef), - 'fw-login' => setv(T_LOGIN, 0, 0, '', undef), - 'fw-password' => setv(T_PASSWD,0, 0, '', undef), + 'fw-login' => setv(T_LOGIN, 0, 0, undef, undef), + 'fw-password' => setv(T_PASSWD,0, 0, undef, undef), 'fw-ssl-validate' => setv(T_BOOL, 0, 0, 1, undef), - 'fwv4' => setv(T_ANY, 0, 0, '', undef), + 'fwv4' => setv(T_ANY, 0, 0, undef, undef), 'fwv4-skip' => setv(T_STRING,0, 0, undef, undef), - 'fwv6' => setv(T_ANY, 0, 0, '', undef), + 'fwv6' => setv(T_ANY, 0, 0, undef, undef), 'fwv6-skip' => setv(T_STRING,0, 0, undef, undef), - 'cmd' => setv(T_PROG, 0, 0, '', undef), + 'cmd' => setv(T_PROG, 0, 0, undef, undef), 'cmd-skip' => setv(T_STRING,0, 0, undef, undef), - 'cmdv4' => setv(T_PROG, 0, 0, '', undef), - 'cmdv6' => setv(T_PROG, 0, 0, '', undef), + 'cmdv4' => setv(T_PROG, 0, 0, undef, undef), + 'cmdv6' => setv(T_PROG, 0, 0, undef, undef), 'min-interval' => setv(T_DELAY, 0, 0, interval('30s'), 0), 'max-interval' => setv(T_DELAY, 0, 0, interval('25d'), 0), 'min-error-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), @@ -701,7 +701,7 @@ my %variables = ( }, 'dyndns-common-defaults' => { 'backupmx' => setv(T_BOOL, 0, 1, 0, undef), - 'mx' => setv(T_OFQDN, 0, 1, '', 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), }, @@ -736,7 +736,7 @@ my %protocols = ( 'backupmx' => setv(T_BOOL, 0, 1, 0, undef), 'login' => setv(T_LOGIN, 0, 0, 'token', undef), 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), - 'mx' => setv(T_OFQDN, 0, 1, '', undef), + 'mx' => setv(T_OFQDN, 0, 1, undef, undef), 'server' => setv(T_FQDNP, 0, 0, 'api.cloudflare.com/client/v4', undef), 'static' => setv(T_BOOL, 0, 1, 0, undef), 'ttl' => setv(T_NUMBER, 0, 0, 1, undef), @@ -852,7 +852,7 @@ my %protocols = ( %{$variables{'protocol-common-defaults'}}, 'backupmx' => setv(T_BOOL, 0, 1, 0, undef), 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), - 'mx' => setv(T_OFQDN, 0, 1, '', undef), + 'mx' => setv(T_OFQDN, 0, 1, undef, undef), 'server' => setv(T_FQDNP, 0, 0, 'api.cp.easydns.com', undef), 'script' => setv(T_STRING, 0, 1, '/dyn/generic.php', undef), 'wildcard' => setv(T_BOOL, 0, 1, 0, undef), @@ -1014,7 +1014,7 @@ my %protocols = ( 'password' => undef, 'apikey' => setv(T_PASSWD, 1, 0, undef, undef), 'secretapikey' => setv(T_PASSWD, 1, 0, undef, undef), - 'root-domain' => setv(T_OFQDN, 0, 0, '', undef), + 'root-domain' => setv(T_OFQDN, 0, 0, undef, undef), 'on-root-domain' => setv(T_BOOL, 0, 0, 0, undef), }, }, @@ -1036,7 +1036,7 @@ my %protocols = ( %{$variables{'protocol-common-defaults'}}, 'backupmx' => setv(T_BOOL, 0, 1, 0, undef), 'custom' => setv(T_BOOL, 0, 1, 0, undef), - 'mx' => setv(T_OFQDN, 0, 1, '', undef), + 'mx' => setv(T_OFQDN, 0, 1, undef, undef), 'script' => setv(T_STRING, 0, 1, '/nic/update', undef), 'server' => setv(T_FQDNP, 0, 0, 'dyn.woima.fi', undef), 'static' => setv(T_BOOL, 0, 1, 0, undef), @@ -7216,7 +7216,7 @@ sub nic_porkbun_update { for my $host (@_) { my ($sub_domain, $domain); - if ($config{$host}{'root-domain'} ne '') { + if ($config{$host}{'root-domain'}) { # Process 'root-domain' option $domain = $config{$host}{'root-domain'}; $sub_domain = $host; From 399f8a8b32f8bae079799ddc6c6330d7f4f3c6c4 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 28 Jun 2024 01:51:21 -0400 Subject: [PATCH 095/150] Adjust variable defaults to pass validity checks Change the default of every variable whose default (non-`undef`) doesn't pass through `check_value` unmodified. --- ddclient.in | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ddclient.in b/ddclient.in index 6f0a34a..5e08e3a 100755 --- a/ddclient.in +++ b/ddclient.in @@ -723,7 +723,7 @@ my %protocols = ( 'examples' => \&nic_changeip_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), + 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), interval('5m')), 'server' => setv(T_FQDNP, 0, 0, 'nic.changeip.com', undef), }, }, @@ -864,7 +864,7 @@ my %protocols = ( 'examples' => \&nic_freedns_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), + 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), interval('5m')), 'server' => setv(T_FQDNP, 0, 0, 'freedns.afraid.org', undef), }, }, @@ -885,7 +885,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'login' => undef, - 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), + 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), interval('5m')), 'server' => setv(T_FQDNP, 0, 0, 'api.gandi.net', undef), 'script' => setv(T_STRING, 0, 1, '/v5', undef), 'use-personal-access-token' => setv(T_BOOL, 0, 0, 0, undef), @@ -944,7 +944,7 @@ my %protocols = ( 'examples' => \&nic_namecheap_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), + 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), interval('5m')), 'server' => setv(T_FQDNP, 0, 0, 'dynamicdns.park-your-domain.com', undef), }, }, @@ -954,7 +954,7 @@ my %protocols = ( 'examples' => \&nic_nfsn_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), + 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), interval('5m')), 'server' => setv(T_FQDNP, 0, 0, 'api.nearlyfreespeech.net', undef), 'ttl' => setv(T_NUMBER, 0, 0, 300, undef), 'zone' => setv(T_FQDN, 1, 0, undef, undef), @@ -1025,7 +1025,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'server' => setv(T_FQDNP, 0, 0, 'www.sitelutions.com', undef), - 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), + 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), interval('5m')), }, }, 'woima' => { @@ -1105,7 +1105,7 @@ my %protocols = ( 'variables' => { %{$variables{'protocol-common-defaults'}}, 'server' => setv(T_FQDNP, 0, 0, 'dynamic.name-services.com', undef), - 'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')), + 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), interval('5m')), }, }, 'infomaniak' => { From eab72ef6d7044ac7d638d0ab071570d2618db3aa Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 28 Jun 2024 02:41:22 -0400 Subject: [PATCH 096/150] Require a defined value if the variable is required --- ddclient.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ddclient.in b/ddclient.in index 5e08e3a..3cd9a0c 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2562,6 +2562,10 @@ sub check_value { if (!defined $value && !$required) { ; + } elsif (!defined($value) && $required) { + # None of the types have 'undef' as a valid value, so check definedness once here for + # convenience. + die("$type is required\n"); } elsif ($type eq T_DELAY) { $value = interval($value); From 49f555176460be3238ecfe8fb8ad3ac3dfae021e Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 28 Jun 2024 01:51:21 -0400 Subject: [PATCH 097/150] Add variable default value tests --- Makefile.am | 1 + ddclient.in | 4 ++-- t/variable_defaults.pl | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 t/variable_defaults.pl diff --git a/Makefile.am b/Makefile.am index 0426025..90d3597 100644 --- a/Makefile.am +++ b/Makefile.am @@ -77,6 +77,7 @@ handwritten_tests = \ t/parse_assignments.pl \ t/skip.pl \ t/ssl-validate.pl \ + t/variable_defaults.pl \ t/write_recap.pl generated_tests = \ t/version.pl diff --git a/ddclient.in b/ddclient.in index 3cd9a0c..a415b2a 100755 --- a/ddclient.in +++ b/ddclient.in @@ -556,7 +556,7 @@ sub setv { 'minimum' => shift, }; } -my %variables = ( +our %variables = ( 'global-defaults' => { 'daemon' => setv(T_DELAY, 0, 0, $daemon_default, interval('60s')), 'foreground' => setv(T_BOOL, 0, 0, 0, undef), @@ -706,7 +706,7 @@ my %variables = ( 'wildcard' => setv(T_BOOL, 0, 1, 0, undef), }, ); -my %protocols = ( +our %protocols = ( '1984' => { 'force_update' => undef, 'update' => \&nic_1984_update, diff --git a/t/variable_defaults.pl b/t/variable_defaults.pl new file mode 100644 index 0000000..09dc92c --- /dev/null +++ b/t/variable_defaults.pl @@ -0,0 +1,32 @@ +use Test::More; +SKIP: { eval { require Test::Warnings; } or skip($@, 1); } +eval { require 'ddclient'; } or BAIL_OUT($@); + +my %variable_collections = ( + map({ ($_ => $ddclient::variables{$_}) } grep($_ ne 'merged', keys(%ddclient::variables))), + map({ ("protocol=$_" => $ddclient::protocols{$_}{variables}); } keys(%ddclient::protocols)), +); +my %seen; +my @test_cases = ( + map({ + my $vcn = $_; + my $vc = $variable_collections{$_}; + map({ + my $def = $vc->{$_}; + my $seen = exists($seen{$def}); + $seen{$def} = undef; + ({desc => "$vcn $_", def => $vc->{$_}}) x !$seen; + } sort(keys(%$vc))); + } sort(keys(%variable_collections))), +); +for my $tc (@test_cases) { + if ($tc->{def}{required}) { + is($tc->{def}{default}, undef, "'$tc->{desc}' (required) has no default"); + } else { + my $norm; + my $valid = eval { $norm = ddclient::check_value($tc->{def}{default}, $tc->{def}); 1; }; + ok($valid, "'$tc->{desc}' (optional) has a valid default"); + is($norm, $tc->{def}{default}, "'$tc->{desc}' default normalizes to itself") if $valid; + } +} +done_testing(); From ae7a9dce2afd60ca3fc6835215dbda1cc11d9bca Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 29 Jun 2024 01:59:03 -0400 Subject: [PATCH 098/150] Fix variable name typo This fixes a bug introduced in commit b154d8ef98b48f93acdc83befb99003abd492b24 --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index a415b2a..25bd8e4 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1567,7 +1567,7 @@ sub write_recap { $recap{$h}{$v} = $config{$h}{$v}; } } else { - for my $v (qw(atime wtime status status-ipv4 status-ivp6)) { + for my $v (qw(atime wtime status status-ipv4 status-ipv6)) { $recap{$h}{$v} = $config{$h}{$v}; } } From de39ac7bcc3a52dfe2895a3facfc4f9f3801fc40 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 29 Jun 2024 02:00:40 -0400 Subject: [PATCH 099/150] Fix undef warning when `daemon` is unset This fixes a bug introduced in commit 88eb2ed4fe9879670ff808dc0703834aac654181 --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 25bd8e4..ecfa7a6 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1919,7 +1919,7 @@ sub init_config { ## parse an interval expression (such as '5m') into number of seconds $opt{'daemon'} = interval(opt('daemon')) if defined($opt{'daemon'}); ## make sure the interval isn't too short - $opt{'daemon'} = minimum('daemon') if opt('daemon') > 0 && opt('daemon') < minimum('daemon'); + $opt{'daemon'} = minimum('daemon') if opt('daemon') && opt('daemon') < minimum('daemon'); ## define or modify host options specified on the command-line if (defined($opt{'options'})) { From be3c2060eb3c014fc569af0a2f41381500c48d23 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 29 Jun 2024 02:14:16 -0400 Subject: [PATCH 100/150] Fix undefined hash reference warning This fixes a bug introduced in commit 5e3e10d32ec11a9cd9d1275b73e111907bdafa4c For some reason Perl is OK with: my $x = undef; my @k = keys(%$x); # empty list, no warnings but not: my $x = undef; my %h = %$x; my @k = keys(%h); --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index ecfa7a6..dca3bd0 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1950,7 +1950,7 @@ sub init_config { ## merge options into host definitions or globals if (@hosts) { for my $h (@hosts) { - $config{$h} = {%{$config{$h}}, %options}; + $config{$h} = {%{$config{$h} // {}}, %options}; } $opt{'host'} = join(',', @hosts); } else { From 04bdd68415edfe48175fda30e76b96123a3a6ed5 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 29 Jun 2024 02:26:16 -0400 Subject: [PATCH 101/150] Always set the `host` variable The `host` variable is required, so always set it to avoid error messages when validating the host configuration. --- ddclient.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index dca3bd0..2cd0162 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1950,7 +1950,7 @@ sub init_config { ## merge options into host definitions or globals if (@hosts) { for my $h (@hosts) { - $config{$h} = {%{$config{$h} // {}}, %options}; + $config{$h} = {%{$config{$h} // {}}, %options, 'host' => $h}; } $opt{'host'} = join(',', @hosts); } else { @@ -2031,7 +2031,7 @@ sub init_config { } my $svars = $protocols{$proto}{'variables'}; - my $conf = {'protocol' => $proto}; + my $conf = {'host' => $h, 'protocol' => $proto}; for my $k (keys %$svars) { # Make sure any _env suffixed variables look at their original entry From d8317a730d1614263309e3764ec885e9dfd1a009 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 29 Jun 2024 02:28:43 -0400 Subject: [PATCH 102/150] Fix `die` that should be `return undef` I got ahead of myself -- I intend to replace `return undef` with `die` in a future commit, and somehow this one jumped the gun. This fixes a bug introduced in commit eab72ef6d7044ac7d638d0ab071570d2618db3aa --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 2cd0162..2bc470d 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2565,7 +2565,7 @@ sub check_value { } elsif (!defined($value) && $required) { # None of the types have 'undef' as a valid value, so check definedness once here for # convenience. - die("$type is required\n"); + return undef; } elsif ($type eq T_DELAY) { $value = interval($value); From 1f31b0e570777ed8a81752dba48a95e18502e054 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 29 Jun 2024 02:32:14 -0400 Subject: [PATCH 103/150] Prevent autovivification of empty definitions for unknown variables --- ddclient.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ddclient.in b/ddclient.in index 2bc470d..958b394 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2405,10 +2405,12 @@ sub split_by_comma { } sub default { my $v = shift; + return undef if !defined($variables{'merged'}{$v}); return $variables{'merged'}{$v}{'default'}; } sub minimum { my $v = shift; + return undef if !defined($variables{'merged'}{$v}); return $variables{'merged'}{$v}{'minimum'}; } sub opt { From 89c84f9f0760a625d249efc1babf0f4746dd43e8 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 29 Jun 2024 02:34:28 -0400 Subject: [PATCH 104/150] Ignore (with warning) unknown vars in `--options` --- ddclient.in | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ddclient.in b/ddclient.in index 958b394..716682d 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2002,6 +2002,11 @@ sub init_config { # TODO: This might grab an arbitrary protocol-specific variable, which could cause # surprising behavior. my $def = $variables{'merged'}{$k}; + if (!$def) { + warning("ignoring unknown setting '$k=$globals{$k}'"); + delete($globals{$k}); + next; + } # TODO: Isn't $globals{$k} guaranteed to be defined here? Otherwise $k wouldn't appear in # %globals. my $ovalue = $globals{$k} // $def->{'default'}; From f5b369a7ef1c1344aafdfa26fda09526e121763f Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 29 Jun 2024 02:36:44 -0400 Subject: [PATCH 105/150] Fix undef warning when encountering an unset but required var This fixes a bug probably introduced in commit b8a0a264413255891e3699a3414188a952c528cd --- ddclient.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 716682d..082cc8c 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2046,7 +2046,8 @@ sub init_config { my $ovalue = $config{$h}{$k} // $def->{'default'}; my $value = check_value($ovalue, $def); if ($def->{'required'} && !defined $value) { - warning("skipping host: %s: '%s=%s' is an invalid %s.", $h, $k, $ovalue, $def->{'type'}); + $ovalue //= '(not set)'; + warning("skipping host $h: invalid $def->{type} variable value '$k=$ovalue'"); delete $config{$h}; next HOST; } From 7b6f640c9bd52987fcf19fd498c01534d4d9609d Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 11 Jul 2024 00:13:30 -0400 Subject: [PATCH 106/150] ci: Remove Red Hat UBI 7 UBI 7 is at end of maintenance and can't run newer versions of node used by some workflows. --- .github/workflows/ci.yml | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b40407..8ad8ff9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,21 +64,11 @@ jobs: - fedora:rawhide - almalinux:8 - almalinux:latest - # RedHat UBI is mostly garbage due to a profound lack of basic - # packages. It is tested anyway because it's the closest available - # approximation of RHEL, aside from AlmaLinux. Some of the packages - # needed for some tests aren't available, so those tests will be - # skipped. I guess it's still better than nothing. - - registry.access.redhat.com/ubi7/ubi:latest runs-on: ubuntu-latest container: image: ${{ matrix.image }} steps: - - if: ${{ matrix.image != 'registry.access.redhat.com/ubi7/ubi:latest' }} - uses: actions/checkout@v4 - # ubi7 is too old for checkout@v4. - - if: ${{ matrix.image == 'registry.access.redhat.com/ubi7/ubi:latest' }} - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: enable repositories (AlmaLinux 8) if: ${{ matrix.image == 'almalinux:8' }} run: | @@ -93,12 +83,7 @@ jobs: # The --skip-broken argument works around RedHat UBI's missing packages. # (They're only used for testing, so it's OK to not install them.) run: | - inst="dnf --refresh --skip-broken install -y" - case '${{ matrix.image }}' in - # RedHat UBI 7 (RHEL 7) doesn't have dnf. - *ubi7*) inst="yum --skip-broken install -y";; - esac - ${inst} \ + dnf --refresh --skip-broken install -y \ automake \ findutils \ iproute \ From 6e7a4fb460a75cd7620ee66c9b9feab5c9708f20 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 10 Jul 2024 20:07:03 -0400 Subject: [PATCH 107/150] Split subtest into two subtests This makes it easier to debug failures. --- t/get_ip_from_if.pl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/t/get_ip_from_if.pl b/t/get_ip_from_if.pl index 6f08e5d..1b1cd35 100644 --- a/t/get_ip_from_if.pl +++ b/t/get_ip_from_if.pl @@ -39,7 +39,7 @@ subtest "get_ip_from_interface tests" => sub { } }; -subtest "Get default interface and IP for test system" => sub { +subtest "Get default interface and IP for test system (IPv4)" => sub { my $interface = ddclient::get_default_interface(4); if ($interface) { isnt($interface, "lo", "Check for loopback 'lo'"); @@ -49,7 +49,10 @@ subtest "Get default interface and IP for test system" => sub { is($ip1, $ip2, "Check IPv4 from default interface"); ok(ddclient::is_ipv4($ip1), "Valid IPv4 from get_ip_from_interface($interface)"); } - $interface = ddclient::get_default_interface(6); +}; + +subtest "Get default interface and IP for test system (IPv6)" => sub { + my $interface = ddclient::get_default_interface(6); if ($interface) { isnt($interface, "lo", "Check for loopback 'lo'"); isnt($interface, "lo0", "Check for loopback 'lo0'"); From 01d2db06c1fc7003adf584a8849ac75f065a302c Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 10 Jul 2024 20:11:02 -0400 Subject: [PATCH 108/150] Invert conditions for readability --- t/get_ip_from_if.pl | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/t/get_ip_from_if.pl b/t/get_ip_from_if.pl index 1b1cd35..f49baed 100644 --- a/t/get_ip_from_if.pl +++ b/t/get_ip_from_if.pl @@ -41,26 +41,24 @@ subtest "get_ip_from_interface tests" => sub { subtest "Get default interface and IP for test system (IPv4)" => sub { my $interface = ddclient::get_default_interface(4); - if ($interface) { - isnt($interface, "lo", "Check for loopback 'lo'"); - isnt($interface, "lo0", "Check for loopback 'lo0'"); - my $ip1 = ddclient::get_ip_from_interface("default", 4); - my $ip2 = ddclient::get_ip_from_interface($interface, 4); - is($ip1, $ip2, "Check IPv4 from default interface"); - ok(ddclient::is_ipv4($ip1), "Valid IPv4 from get_ip_from_interface($interface)"); - } + return if !$interface; + isnt($interface, "lo", "Check for loopback 'lo'"); + isnt($interface, "lo0", "Check for loopback 'lo0'"); + my $ip1 = ddclient::get_ip_from_interface("default", 4); + my $ip2 = ddclient::get_ip_from_interface($interface, 4); + is($ip1, $ip2, "Check IPv4 from default interface"); + ok(ddclient::is_ipv4($ip1), "Valid IPv4 from get_ip_from_interface($interface)"); }; subtest "Get default interface and IP for test system (IPv6)" => sub { my $interface = ddclient::get_default_interface(6); - if ($interface) { - isnt($interface, "lo", "Check for loopback 'lo'"); - isnt($interface, "lo0", "Check for loopback 'lo0'"); - my $ip1 = ddclient::get_ip_from_interface("default", 6); - my $ip2 = ddclient::get_ip_from_interface($interface, 6); - is($ip1, $ip2, "Check IPv6 from default interface"); - ok(ddclient::is_ipv6($ip1), "Valid IPv6 from get_ip_from_interface($interface)"); - } + return if !$interface; + isnt($interface, "lo", "Check for loopback 'lo'"); + isnt($interface, "lo0", "Check for loopback 'lo0'"); + my $ip1 = ddclient::get_ip_from_interface("default", 6); + my $ip2 = ddclient::get_ip_from_interface($interface, 6); + is($ip1, $ip2, "Check IPv6 from default interface"); + ok(ddclient::is_ipv6($ip1), "Valid IPv6 from get_ip_from_interface($interface)"); }; done_testing(); From 6af76afde9231079136dfaef58c1944494782dd0 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 10 Jul 2024 20:12:18 -0400 Subject: [PATCH 109/150] Use `skip_all` if test precondition is not met Subtests can't have zero checks. --- t/get_ip_from_if.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/get_ip_from_if.pl b/t/get_ip_from_if.pl index f49baed..61943c2 100644 --- a/t/get_ip_from_if.pl +++ b/t/get_ip_from_if.pl @@ -41,7 +41,7 @@ subtest "get_ip_from_interface tests" => sub { subtest "Get default interface and IP for test system (IPv4)" => sub { my $interface = ddclient::get_default_interface(4); - return if !$interface; + plan(skip_all => 'no IPv4 interface') if !$interface; isnt($interface, "lo", "Check for loopback 'lo'"); isnt($interface, "lo0", "Check for loopback 'lo0'"); my $ip1 = ddclient::get_ip_from_interface("default", 4); @@ -52,7 +52,7 @@ subtest "Get default interface and IP for test system (IPv4)" => sub { subtest "Get default interface and IP for test system (IPv6)" => sub { my $interface = ddclient::get_default_interface(6); - return if !$interface; + plan(skip_all => 'no IPv6 interface') if !$interface; isnt($interface, "lo", "Check for loopback 'lo'"); isnt($interface, "lo0", "Check for loopback 'lo0'"); my $ip1 = ddclient::get_ip_from_interface("default", 6); From fa0bfde3cb2e4972b68060760facd88e975896dc Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 10 Jul 2024 22:39:33 -0400 Subject: [PATCH 110/150] Don't assume the default interface has a globally routable IP --- t/get_ip_from_if.pl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/t/get_ip_from_if.pl b/t/get_ip_from_if.pl index 61943c2..15c66a1 100644 --- a/t/get_ip_from_if.pl +++ b/t/get_ip_from_if.pl @@ -47,7 +47,10 @@ subtest "Get default interface and IP for test system (IPv4)" => sub { my $ip1 = ddclient::get_ip_from_interface("default", 4); my $ip2 = ddclient::get_ip_from_interface($interface, 4); is($ip1, $ip2, "Check IPv4 from default interface"); - ok(ddclient::is_ipv4($ip1), "Valid IPv4 from get_ip_from_interface($interface)"); + SKIP: { + skip('default interface does not have an appropriate IPv4 addresses') if !$ip1; + ok(ddclient::is_ipv4($ip1), "Valid IPv4 from get_ip_from_interface($interface)"); + } }; subtest "Get default interface and IP for test system (IPv6)" => sub { @@ -58,7 +61,10 @@ subtest "Get default interface and IP for test system (IPv6)" => sub { my $ip1 = ddclient::get_ip_from_interface("default", 6); my $ip2 = ddclient::get_ip_from_interface($interface, 6); is($ip1, $ip2, "Check IPv6 from default interface"); - ok(ddclient::is_ipv6($ip1), "Valid IPv6 from get_ip_from_interface($interface)"); + SKIP: { + skip('default interface does not have an appropriate IPv6 addresses') if !$ip1; + ok(ddclient::is_ipv6($ip1), "Valid IPv6 from get_ip_from_interface($interface)"); + } }; done_testing(); From 4d9d0646cfa89eb3b6f78605480ba3587fcf868b Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 15:54:26 -0400 Subject: [PATCH 111/150] Delete unnecessary comments --- ddclient.in | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/ddclient.in b/ddclient.in index 082cc8c..cf423cc 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4013,7 +4013,6 @@ EoEXAMPLE sub nic_dyndns2_update { debug("\nnic_dyndns2_update -------------------"); - ## group hosts with identical attributes together my %groups = group_hosts_by(\@_, [qw(login password server static custom wildcard mx backupmx wantipv4 wantipv6)]); my %errors = ( @@ -4035,7 +4034,6 @@ sub nic_dyndns2_update { 'nochg' => 'No update required; unnecessary attempts to change to the current address are considered abusive', ); - ## update each set of hosts that had similar configurations for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); @@ -4324,7 +4322,6 @@ sub dnsexit2_update_host { sub nic_noip_update { debug("\nnic_noip_update -------------------"); - ## group hosts with identical attributes together my %groups = group_hosts_by(\@_, [qw(login password server static custom wildcard mx backupmx wantipv4 wantipv6)]); my %errors = ( @@ -4338,7 +4335,6 @@ sub nic_noip_update { 'nochg' => 'No update required; unnecessary attempts to change to the current address are considered abusive', ); - ## update each set of hosts that had similar configurations for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); @@ -4671,10 +4667,8 @@ sub nic_zoneedit1_force_update { sub nic_zoneedit1_update { debug("\nnic_zoneedit1_update -------------------"); - ## group hosts with identical attributes together my %groups = group_hosts_by(\@_, [qw(login password server zone wantip)]); - ## update each set of hosts that had similar configurations for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); @@ -4811,7 +4805,6 @@ EoEXAMPLE sub nic_easydns_update { debug("\nnic_easydns_update -------------------"); - ## each host is in a group by itself my %groups = map { $_ => [ $_ ] } @_; my %errors = ( @@ -4821,7 +4814,6 @@ sub nic_easydns_update { 'TOOSOON' => 'Update frequency is too short.', ); - ## update each set of hosts that had similar configurations for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); @@ -4911,7 +4903,6 @@ sub nic_easydns_update { if $state ne 'results2'; } } -###################################################################### ###################################################################### ## nic_namecheap_examples @@ -5752,14 +5743,11 @@ EoEXAMPLE sub nic_godaddy_update { debug("\nnic_godaddy_update --------------------"); - ## group hosts with identical attributes together my %groups = group_hosts_by(\@_, [qw(server login password zone wantipv4 wantipv6)]); - ## update each set of hosts that had similar configurations for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; - # Update each set configured host. for my $host (@hosts) { my $ipv4 = delete $config{$host}{'wantipv4'}; my $ipv6 = delete $config{$host}{'wantipv6'}; @@ -5889,23 +5877,19 @@ EoEXAMPLE sub nic_googledomains_update { debug("\nnic_googledomains_update -------------------"); - ## group hosts with identical attributes together my %groups = group_hosts_by(\@_, [qw(server login password wantip)]); - ## update each set of hosts that had similar configurations for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $key = $hosts[0]; my $ip = $config{$key}{'wantip'}; - # FQDNs for my $host (@hosts) { delete $config{$host}{'wantip'}; info("setting IP address to %s for %s", $ip, $host); verbose("UPDATE:", "updating %s", $host); - # Update the DNS record my $url = "https://$config{$host}{'server'}/nic/update"; $url .= "?hostname=$host"; $url .= "&myip="; @@ -6068,10 +6052,8 @@ EoEXAMPLE sub nic_nsupdate_update { debug("\nnic_nsupdate_update -------------------"); - ## group hosts with identical attributes together my %groups = group_hosts_by(\@_, [qw(login password server zone wantipv4 wantipv6)]); - ## update each set of hosts that had similar configurations for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); @@ -6185,10 +6167,8 @@ EoEXAMPLE sub nic_cloudflare_update { debug("\nnic_cloudflare_update -------------------"); - ## group hosts with identical attributes together my %groups = group_hosts_by(\@_, [qw(ssh login password server wildcard mx backupmx zone wantipv4 wantipv6)]); - ## update each set of hosts that had similar configurations for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); @@ -6202,7 +6182,6 @@ sub nic_cloudflare_update { $headers .= "X-Auth-Key: $config{$key}{'password'}\n"; } - # FQDNs for my $domain (@hosts) { my $ipv4 = delete $config{$domain}{'wantipv4'}; my $ipv6 = delete $config{$domain}{'wantipv6'}; @@ -6333,10 +6312,8 @@ EoEXAMPLE sub nic_hetzner_update { debug("\nnic_hetzner_update -------------------"); - ## group hosts with identical attributes together my %groups = group_hosts_by(\@_, [qw(ssh login password server wildcard mx backupmx zone wantipv4 wantipv6)]); - ## update each set of hosts that had similar configurations for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); @@ -6345,7 +6322,6 @@ sub nic_hetzner_update { my $headers = "Auth-API-Token: $config{$key}{'password'}\n"; $headers .= "Content-Type: application/json"; - # FQDNs for my $domain (@hosts) { (my $hostname = $domain) =~ s/\.$config{$key}{zone}$//; my $ipv4 = delete $config{$domain}{'wantipv4'}; @@ -6490,17 +6466,14 @@ EoEXAMPLE sub nic_yandex_update { debug("\nnic_yandex_update -------------------"); - ## group hosts with identical attributes together my %groups = group_hosts_by(\@_, [qw(server login pasword wantip)]); - ## update each set of hosts that had similar configurations for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $key = $hosts[0]; my $ip = $config{$key}{'wantip'}; my $headers = "PddToken: $config{$key}{'password'}\n"; - # FQDNs for my $host (@hosts) { delete $config{$host}{'wantip'}; From b488cb2235ebe3696b7b6a9f67a3ade21135c006 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 15:52:04 -0400 Subject: [PATCH 112/150] Unwrap error message Normally I prefer lines to be less than 100 characters long, but error messages are an exception because it makes it easier to search the source code for the error message. --- ddclient.in | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ddclient.in b/ddclient.in index cf423cc..cf38427 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4024,9 +4024,7 @@ sub nic_dyndns2_update { '!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. More info can be found on ' . - 'https://www.dyndns.com/support/abuse.html', + 'abuse', => 'The hostname specified is blocked for abuse; you should receive an email notification which provides an unblock request link. More info can be found on https://www.dyndns.com/support/abuse.html', 'numhost' => 'System error: Too many or too few hosts found. Contact support@dyndns.org', 'dnserr' => 'System error: DNS error encountered. Contact support@dyndns.org', From 161c6235572ab86f2b0e73841cd7385e47f4eab1 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 15:54:01 -0400 Subject: [PATCH 113/150] Whitespace fixes --- ddclient.in | 216 +++++++++++++++++++++------------------------------- 1 file changed, 87 insertions(+), 129 deletions(-) diff --git a/ddclient.in b/ddclient.in index cf38427..741890b 100755 --- a/ddclient.in +++ b/ddclient.in @@ -977,9 +977,9 @@ our %protocols = ( 'examples' => \&nic_noip_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'custom' => setv(T_BOOL, 0, 1, 0, undef), - 'server' => setv(T_FQDNP, 0, 0, 'dynupdate.no-ip.com', undef), - 'static' => setv(T_BOOL, 0, 1, 0, undef), + 'custom' => setv(T_BOOL, 0, 1, 0, undef), + 'server' => setv(T_FQDNP, 0, 0, 'dynupdate.no-ip.com', undef), + 'static' => setv(T_BOOL, 0, 1, 0, undef), }, }, 'nsupdate' => { @@ -4007,58 +4007,48 @@ Example ${program}.conf file entries: my-toplevel-domain.com,my-other-domain.com EoEXAMPLE } + ###################################################################### ## nic_dyndns2_update ###################################################################### sub nic_dyndns2_update { debug("\nnic_dyndns2_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(login password server static custom wildcard mx backupmx wantipv4 wantipv6)]); - 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', + '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. More info can be found on https://www.dyndns.com/support/abuse.html', - - 'numhost' => 'System error: Too many or too few hosts found. 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', + '!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. More info can be found on https://www.dyndns.com/support/abuse.html', + 'numhost' => 'System error: Too many or too few hosts found. 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', ); - for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); - my $h = $hosts[0]; - my $ipv4 = $config{$h}{'wantipv4'}; - my $ipv6 = $config{$h}{'wantipv6'}; + my $h = $hosts[0]; + my $ipv4 = $config{$h}{'wantipv4'}; + my $ipv6 = $config{$h}{'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); - ## Select the DynDNS system to update my $url = "http://$config{$h}{'server'}$config{$h}{'script'}?system="; if ($config{$h}{'custom'}) { warning("updating %s: 'custom' and 'static' may not be used together. ('static' ignored)", $hosts) if $config{$h}{'static'}; $url .= 'custom'; - } elsif ($config{$h}{'static'}) { $url .= 'statdns'; - } else { $url .= 'dyndns'; } - $url .= "&hostname=$hosts"; $url .= "&myip="; $url .= $ipv4 if $ipv4; @@ -4066,14 +4056,12 @@ sub nic_dyndns2_update { $url .= "," if $ipv4; $url .= $ipv6; } - ## some args are not valid for a custom domain. $url .= "&wildcard=ON" if ynu($config{$h}{'wildcard'}, 1, 0, 0); if ($config{$h}{'mx'}) { $url .= "&mx=$config{$h}{'mx'}"; $url .= "&backmx=" . ynu($config{$h}{'backupmx'}, 'YES', 'NO'); } - my $reply = geturl( proxy => opt('proxy'), url => $url, @@ -4085,43 +4073,33 @@ sub nic_dyndns2_update { next; } next if !header_ok($hosts, $reply); - - my @reply = split /\n/, $reply; - my $state = 'header'; - + my @reply = split /\n/, $reply; + my $state = 'header'; 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; - for my $h (@hosts) { $config{$h}{'status-ipv4'} = $status if $ipv4; $config{$h}{'status-ipv6'} = $status if $ipv6; } - if ($status eq 'good') { 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; @@ -4129,26 +4107,20 @@ sub nic_dyndns2_update { $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'; + 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); } @@ -4319,26 +4291,23 @@ sub dnsexit2_update_host { ###################################################################### sub nic_noip_update { debug("\nnic_noip_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(login password server static custom wildcard mx backupmx wantipv4 wantipv6)]); - my %errors = ( - 'badauth' => 'Invalid username or password', + 'badauth' => 'Invalid username or password', 'badagent' => 'Invalid user agent', - 'nohost' => 'The hostname specified does not exist in the database', + 'nohost' => 'The hostname specified does not exist in the database', '!donator' => 'The offline setting was set, when the user is not a donator', - 'abuse', => 'The hostname specified is blocked for abuse; open a trouble ticket at https://www.no-ip.com', - 'numhost' => 'System error: Too many or too few hosts found. open a trouble ticket at https://www.no-ip.com', - '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', + 'abuse', => 'The hostname specified is blocked for abuse; open a trouble ticket at https://www.no-ip.com', + 'numhost' => 'System error: Too many or too few hosts found. open a trouble ticket at https://www.no-ip.com', + '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 $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); - my $h = $hosts[0]; - my $ipv4 = $config{$h}{'wantipv4'}; - my $ipv6 = $config{$h}{'wantipv6'}; + my $h = $hosts[0]; + my $ipv4 = $config{$h}{'wantipv4'}; + my $ipv6 = $config{$h}{'wantipv6'}; delete $config{$_}{'wantipv4'} for @hosts; delete $config{$_}{'wantipv6'} for @hosts; @@ -4411,10 +4380,10 @@ sub nic_noip_update { } elsif ($status =~ /w(\d+)(.)/) { my ($wait, $units) = ($1, lc $2); - my ($sec, $scale) = ($wait, 1); + my ($sec, $scale) = ($wait, 1); - ($scale, $units) = (1, 'seconds') if $units eq 's'; - ($scale, $units) = (60, 'minutes') if $units eq 'm'; + ($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; @@ -4430,6 +4399,7 @@ sub nic_noip_update { if $state ne 'results2'; } } + ###################################################################### ## nic_noip_examples ###################################################################### @@ -4664,14 +4634,12 @@ sub nic_zoneedit1_force_update { ###################################################################### sub nic_zoneedit1_update { debug("\nnic_zoneedit1_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(login password server zone wantip)]); - for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); - my $h = $hosts[0]; - my $ip = $config{$h}{'wantip'}; + my $h = $hosts[0]; + my $ip = $config{$h}{'wantip'}; delete $config{$_}{'wantip'} for @hosts; info("setting IP address to %s for %s", $ip, $hosts); @@ -4680,7 +4648,7 @@ sub nic_zoneedit1_update { my $url = ''; $url .= "https://$config{$h}{'server'}/auth/dynamic.html"; $url .= "?host=$hosts"; - $url .= "&dnsto=$ip" if $ip; + $url .= "&dnsto=$ip" if $ip; $url .= "&zone=$config{$h}{'zone'}" if defined $config{$h}{'zone'}; my $reply = geturl( @@ -4705,7 +4673,7 @@ sub nic_zoneedit1_update { my ($status_code, $status_text, $status_ip) = ('999', '', $ip); $status_code = $var{'CODE'} if exists $var{'CODE'}; $status_text = $var{'TEXT'} if exists $var{'TEXT'}; - $status_ip = $var{'IP'} if exists $var{'IP'}; + $status_ip = $var{'IP'} if exists $var{'IP'}; if ($status eq 'SUCCESS' || ($status eq 'ERROR' && $var{'CODE'} eq '707')) { $config{$h}{'ip'} = $status_ip; @@ -4719,7 +4687,7 @@ sub nic_zoneedit1_update { failed("updating %s: %s: %s", $h, $status_code, $status_text); } shift @hosts; - $h = $hosts[0]; + $h = $hosts[0]; $hosts = join(',', @hosts); } $line = $rest; @@ -4730,6 +4698,7 @@ sub nic_zoneedit1_update { if @hosts; } } + ###################################################################### ## nic_easydns_force_update ###################################################################### @@ -4753,6 +4722,7 @@ sub nic_easydns_force_update { } return $update; } + ###################################################################### ## nic_easydns_examples ###################################################################### @@ -4797,27 +4767,25 @@ Example ${program}.conf file entries: my-toplevel-domain.com,my-other-domain.com EoEXAMPLE } + ###################################################################### ## nic_easydns_update ###################################################################### sub nic_easydns_update { debug("\nnic_easydns_update -------------------"); - my %groups = map { $_ => [ $_ ] } @_; - my %errors = ( - 'NOACCESS' => 'Authentication failed. This happens if the username/password OR host or domain are wrong.', + 'NOACCESS' => 'Authentication failed. This happens if the username/password OR host or domain are wrong.', 'NOSERVICE' => 'Dynamic DNS is not turned on for this domain.', - 'ILLEGAL' => 'Client sent data that is not allowed in a dynamic DNS update.', - 'TOOSOON' => 'Update frequency is too short.', + 'ILLEGAL' => 'Client sent data that is not allowed in a dynamic DNS update.', + 'TOOSOON' => 'Update frequency is too short.', ); - for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); - my $h = $hosts[0]; - my $ipv4 = $config{$h}{'wantipv4'}; - my $ipv6 = $config{$h}{'wantipv6'}; + my $h = $hosts[0]; + my $ipv4 = $config{$h}{'wantipv4'}; + my $ipv6 = $config{$h}{'wantipv6'}; delete $config{$_}{'wantipv4'} for @hosts; delete $config{$_}{'wantipv6'} for @hosts; @@ -4827,7 +4795,7 @@ sub nic_easydns_update { #'https://api.cp.easydns.com/dyn/generic.php?hostname=test.burry.ca&myip=10.20.30.40&wildcard=ON' my $url; - $url = "https://$config{$h}{'server'}$config{$h}{'script'}?"; + $url = "https://$config{$h}{'server'}$config{$h}{'script'}?"; $url .= "hostname=$hosts"; $url .= "&myip="; $url .= $ipv4 if $ipv4; @@ -4880,10 +4848,10 @@ sub nic_easydns_update { } elsif ($status =~ /TOOSOON/) { ## make sure we wait at least a little my ($wait, $units) = (5, 'm'); - my ($sec, $scale) = ($wait, 1); + my ($sec, $scale) = ($wait, 1); - ($scale, $units) = (1, 'seconds') if $units eq 's'; - ($scale, $units) = (60, 'minutes') if $units eq 'm'; + ($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'; $config{$h}{'wtime'} = $now + $sec; warning("updating %s: %s: wait %d %s before further updates", $h, $status, $wait, $units); @@ -5735,17 +5703,15 @@ Example ${program}.conf file entries: host1.example.com,host2.example.com EoEXAMPLE } + ###################################################################### ## nic_godaddy_update ###################################################################### sub nic_godaddy_update { debug("\nnic_godaddy_update --------------------"); - my %groups = group_hosts_by(\@_, [qw(server login password zone wantipv4 wantipv6)]); - for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; - for my $host (@hosts) { my $ipv4 = delete $config{$host}{'wantipv4'}; my $ipv6 = delete $config{$host}{'wantipv6'}; @@ -5776,11 +5742,11 @@ sub nic_godaddy_update { $header .= "Accept: application/json\n"; $header .= "Authorization: sso-key $config{$host}{'login'}:$config{$host}{'password'}\n"; my $reply = geturl( - proxy => opt('proxy'), - url => $url, + proxy => opt('proxy'), + url => $url, headers => $header, - method => 'PUT', - data => $data, + method => 'PUT', + data => $data, ); unless ($reply) { failed("%s.%s -- Could not connect to %s.", $hostname, $zone, $config{$host}{'server'}); @@ -5869,18 +5835,17 @@ Example ${program}.conf file entries: my-toplevel-domain.com,my-other-domain.com EoEXAMPLE } + ###################################################################### ## nic_googledomains_update ###################################################################### sub nic_googledomains_update { debug("\nnic_googledomains_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(server login password wantip)]); - for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; - my $key = $hosts[0]; - my $ip = $config{$key}{'wantip'}; + my $key = $hosts[0]; + my $ip = $config{$key}{'wantip'}; for my $host (@hosts) { delete $config{$host}{'wantip'}; @@ -6049,21 +6014,19 @@ EoEXAMPLE ###################################################################### sub nic_nsupdate_update { debug("\nnic_nsupdate_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(login password server zone wantipv4 wantipv6)]); - for my $sig (keys %groups) { - my @hosts = @{$groups{$sig}}; - my $hosts = join(',', @hosts); - my $h = $hosts[0]; - my $binary = $config{$h}{'login'}; + my @hosts = @{$groups{$sig}}; + my $hosts = join(',', @hosts); + my $h = $hosts[0]; + my $binary = $config{$h}{'login'}; my $keyfile = $config{$h}{'password'}; - my $server = $config{$h}{'server'}; + my $server = $config{$h}{'server'}; ## nsupdate requires a port number to be separated by whitepace, not colon $server =~ s/:/ /; - my $zone = $config{$h}{'zone'}; - my $ipv4 = $config{$h}{'wantipv4'}; - my $ipv6 = $config{$h}{'wantipv6'}; + my $zone = $config{$h}{'zone'}; + my $ipv4 = $config{$h}{'wantipv4'}; + my $ipv6 = $config{$h}{'wantipv6'}; delete $config{$_}{'wantipv4'} for @hosts; delete $config{$_}{'wantipv6'} for @hosts; @@ -6079,7 +6042,7 @@ EoINSTR1 for (@hosts) { for my $ip ($ipv4, $ipv6) { next if (!$ip); - my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A'; + my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A'; $instructions .= <<"EoINSTR2"; update delete $_. $type update add $_. $config{$_}{'ttl'} $type $ip @@ -6101,7 +6064,7 @@ EoINSTR4 $config{$_}{'mtime'} = $now; for my $ip ($ipv4, $ipv6) { next if (!$ip); - my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; + my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; $config{$_}{"ipv$ipv"} = $ip; $config{$_}{"status-ipv$ipv"} = 'good'; success("updating %s: good: IPv%s address set to %s", $_, $ipv, $ip); @@ -6159,19 +6122,17 @@ Example ${program}.conf file entries: my-toplevel-domain.com,my-other-domain.com EoEXAMPLE } + ###################################################################### ## nic_cloudflare_update ###################################################################### sub nic_cloudflare_update { debug("\nnic_cloudflare_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(ssh login password server wildcard mx backupmx zone wantipv4 wantipv6)]); - for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); - my $key = $hosts[0]; - + my $key = $hosts[0]; my $headers = "Content-Type: application/json\n"; if ($config{$key}{'login'} eq 'token') { $headers .= "Authorization: Bearer $config{$key}{'password'}\n"; @@ -6181,8 +6142,8 @@ sub nic_cloudflare_update { } for my $domain (@hosts) { - my $ipv4 = delete $config{$domain}{'wantipv4'}; - my $ipv6 = delete $config{$domain}{'wantipv6'}; + my $ipv4 = delete $config{$domain}{'wantipv4'}; + my $ipv6 = delete $config{$domain}{'wantipv6'}; info("getting Cloudflare Zone ID for %s", $domain); @@ -6219,7 +6180,7 @@ sub nic_cloudflare_update { # IPv4 and IPv6 handling are similar enough to do in a loop... for my $ip ($ipv4, $ipv6) { next if (!$ip); - my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; + my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A'; info("updating %s: setting IPv$ipv address to %s", $domain, $ip); @@ -6304,26 +6265,25 @@ Example ${program}.conf file entries: my-toplevel-domain.com,my-other-domain.com EoEXAMPLE } + ###################################################################### ## nic_hetzner_update ###################################################################### sub nic_hetzner_update { debug("\nnic_hetzner_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(ssh login password server wildcard mx backupmx zone wantipv4 wantipv6)]); - for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); - my $key = $hosts[0]; + my $key = $hosts[0]; my $headers = "Auth-API-Token: $config{$key}{'password'}\n"; $headers .= "Content-Type: application/json"; for my $domain (@hosts) { (my $hostname = $domain) =~ s/\.$config{$key}{zone}$//; - my $ipv4 = delete $config{$domain}{'wantipv4'}; - my $ipv6 = delete $config{$domain}{'wantipv6'}; + my $ipv4 = delete $config{$domain}{'wantipv4'}; + my $ipv6 = delete $config{$domain}{'wantipv6'}; info("getting Hetzner Zone ID for %s", $domain); @@ -6355,11 +6315,10 @@ sub nic_hetzner_update { } info("Zone ID is %s", $zone_id); - # IPv4 and IPv6 handling are similar enough to do in a loop... for my $ip ($ipv4, $ipv6) { next if (!$ip); - my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; + my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A'; info("updating %s: setting IPv$ipv address to %s", $domain, $ip); @@ -6390,11 +6349,11 @@ sub nic_hetzner_update { if ($dns_rec_id) { debug("updating %s: DNS '$type' record ID: $dns_rec_id", $domain); - $url = "https://$config{$key}{'server'}/records/$dns_rec_id"; + $url = "https://$config{$key}{'server'}/records/$dns_rec_id"; $http_method = "PUT"; } else { debug("creating %s: DNS '$type'", $domain); - $url = "https://$config{$key}{'server'}/records"; + $url = "https://$config{$key}{'server'}/records"; $http_method = "POST"; } my $data = "{\"zone_id\":\"$zone_id\", \"name\": \"$hostname\", \"value\": \"$ip\", \"type\": \"$type\", \"ttl\": $config{$domain}{'ttl'}}"; @@ -6455,6 +6414,7 @@ Example ${program}.conf file entries: record.myhost.com,other.myhost.com EoEXAMPLE } + ###################################################################### ## nic_yandex_update ## @@ -6463,13 +6423,11 @@ EoEXAMPLE ###################################################################### sub nic_yandex_update { debug("\nnic_yandex_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(server login pasword wantip)]); - for my $sig (keys %groups) { - my @hosts = @{$groups{$sig}}; - my $key = $hosts[0]; - my $ip = $config{$key}{'wantip'}; + my @hosts = @{$groups{$sig}}; + my $key = $hosts[0]; + my $ip = $config{$key}{'wantip'}; my $headers = "PddToken: $config{$key}{'password'}\n"; for my $host (@hosts) { From 39e3322fc02b64be294937b4c4fafa15084a1666 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 16:01:49 -0400 Subject: [PATCH 114/150] dyndns2: Add missing `script` to `group_hosts_by` attributes --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 741890b..19a6034 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4013,7 +4013,7 @@ EoEXAMPLE ###################################################################### sub nic_dyndns2_update { debug("\nnic_dyndns2_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(login password server static custom wildcard mx backupmx wantipv4 wantipv6)]); + 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', From 1faa315794f39db0f5a82584923999c639ea7198 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 17:48:56 -0400 Subject: [PATCH 115/150] noip: Delete unused variables --- ddclient.in | 2 -- 1 file changed, 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index 19a6034..8db04fc 100755 --- a/ddclient.in +++ b/ddclient.in @@ -977,9 +977,7 @@ our %protocols = ( 'examples' => \&nic_noip_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'custom' => setv(T_BOOL, 0, 1, 0, undef), 'server' => setv(T_FQDNP, 0, 0, 'dynupdate.no-ip.com', undef), - 'static' => setv(T_BOOL, 0, 1, 0, undef), }, }, 'nsupdate' => { From c8ee25ef8275f304fc9d75b8a9e9253c33528a46 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 17:46:21 -0400 Subject: [PATCH 116/150] noip: Remove unused attributes from `group_hosts_by` arguments --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 8db04fc..785cf12 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4289,7 +4289,7 @@ sub dnsexit2_update_host { ###################################################################### sub nic_noip_update { debug("\nnic_noip_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(login password server static custom wildcard mx backupmx wantipv4 wantipv6)]); + my %groups = group_hosts_by(\@_, [qw(login password server wantipv4 wantipv6)]); my %errors = ( 'badauth' => 'Invalid username or password', 'badagent' => 'Invalid user agent', From 217bc998fc22e4ee4c4a732d701894d329630a48 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 18:03:37 -0400 Subject: [PATCH 117/150] easydns: Remove unnecessary single host groupings --- ddclient.in | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/ddclient.in b/ddclient.in index 785cf12..bef4c32 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4771,30 +4771,24 @@ EoEXAMPLE ###################################################################### sub nic_easydns_update { debug("\nnic_easydns_update -------------------"); - my %groups = map { $_ => [ $_ ] } @_; my %errors = ( 'NOACCESS' => 'Authentication failed. This happens if the username/password OR host or domain are wrong.', 'NOSERVICE' => 'Dynamic DNS is not turned on for this domain.', 'ILLEGAL' => 'Client sent data that is not allowed in a dynamic DNS update.', 'TOOSOON' => 'Update frequency is too short.', ); - for my $sig (keys %groups) { - my @hosts = @{$groups{$sig}}; - my $hosts = join(',', @hosts); - my $h = $hosts[0]; - my $ipv4 = $config{$h}{'wantipv4'}; - my $ipv6 = $config{$h}{'wantipv6'}; - delete $config{$_}{'wantipv4'} for @hosts; - delete $config{$_}{'wantipv6'} for @hosts; + for my $h (@_) { + my $ipv4 = delete $config{$h}{'wantipv4'}; + my $ipv6 = delete $config{$h}{'wantipv6'}; - info("setting IP address to %s %s for %s", $ipv4, $ipv6, $hosts); - verbose("UPDATE:", "updating %s", $hosts); + info("setting IP address to %s %s for %s", $ipv4, $ipv6, $h); + verbose("UPDATE:", "updating %s", $h); #'https://api.cp.easydns.com/dyn/generic.php?hostname=test.burry.ca&myip=10.20.30.40&wildcard=ON' my $url; $url = "https://$config{$h}{'server'}$config{$h}{'script'}?"; - $url .= "hostname=$hosts"; + $url .= "hostname=$h"; $url .= "&myip="; $url .= $ipv4 if $ipv4; for my $ipv6a ($ipv6) { @@ -4815,10 +4809,10 @@ sub nic_easydns_update { password => $config{$h}{'password'}, ) // ''; if ($reply eq '') { - failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'}); + failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'}); next; } - next if !header_ok($hosts, $reply); + next if !header_ok($h, $reply); my @reply = split /\n/, $reply; my $state = 'header'; @@ -4833,7 +4827,6 @@ sub nic_easydns_update { $state = 'results2'; my ($status) = $line =~ /^(\S*)\b.*/; - my $h = shift @hosts; $config{$h}{'status-ipv4'} = $status if $ipv4; $config{$h}{'status-ipv6'} = $status if $ipv6; @@ -4863,7 +4856,7 @@ sub nic_easydns_update { last; } } - failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'}) + failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'}) if $state ne 'results2'; } } From 5db77f7c31d6c992a61c4a6ecfe3266f9e063d67 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 18:28:27 -0400 Subject: [PATCH 118/150] zoneedit1: Add TODO comments for problematic bits of code --- ddclient.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ddclient.in b/ddclient.in index bef4c32..c3b97b6 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4662,6 +4662,8 @@ sub nic_zoneedit1_update { next if !header_ok($hosts, $reply); my @reply = split /\n/, $reply; + # TODO: This is awkward and fragile -- it assumes that each line in the response body + # corresponds with each host in @hosts (and in the same order). for my $line (@reply) { if ($h && $line =~ /^[^<]*<(SUCCESS|ERROR)\s+([^>]+)>(.*)/) { my ($status, $assignments, $rest) = ($1, $2, $3); @@ -4692,6 +4694,7 @@ sub nic_zoneedit1_update { redo if $line; } } + # TODO: Shouldn't this log join(',' @hosts) instead of $hosts? failed("updating %s: no response from %s", $hosts, $config{$h}{'server'}) if @hosts; } From e8a6d1479f838eceb215faa3d3485b379a637583 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 18:36:20 -0400 Subject: [PATCH 119/150] godaddy: Remove unnecessary host groupings Each host is already updated individually so there's no point in grouping the hosts. --- ddclient.in | 162 +++++++++++++++++++++++++--------------------------- 1 file changed, 79 insertions(+), 83 deletions(-) diff --git a/ddclient.in b/ddclient.in index c3b97b6..947e811 100755 --- a/ddclient.in +++ b/ddclient.in @@ -5703,96 +5703,92 @@ EoEXAMPLE ###################################################################### sub nic_godaddy_update { debug("\nnic_godaddy_update --------------------"); - my %groups = group_hosts_by(\@_, [qw(server login password zone wantipv4 wantipv6)]); - for my $sig (keys %groups) { - my @hosts = @{$groups{$sig}}; - for my $host (@hosts) { - my $ipv4 = delete $config{$host}{'wantipv4'}; - my $ipv6 = delete $config{$host}{'wantipv6'}; + for my $host (@_) { + my $ipv4 = delete $config{$host}{'wantipv4'}; + my $ipv6 = delete $config{$host}{'wantipv6'}; - my $zone = $config{$host}{'zone'}; - (my $hostname = $host) =~ s/\.\Q$zone\E$//; + my $zone = $config{$host}{'zone'}; + (my $hostname = $host) =~ s/\.\Q$zone\E$//; - for my $ip ($ipv4, $ipv6) { - next if (!$ip); + for my $ip ($ipv4, $ipv6) { + next if (!$ip); - info("%s.%s -- Setting IP address to %s.", $hostname, $zone, $ip); - verbose("UPDATE:", "updating %s.%s", $hostname, $zone); + info("%s.%s -- Setting IP address to %s.", $hostname, $zone, $ip); + verbose("UPDATE:", "updating %s.%s", $hostname, $zone); - my $ipversion = ($ip eq ($ipv6 // '')) ? '6' : '4'; - my $status = \$config{$host}{"status-ipv$ipversion"}; - my $rrset_type = ($ipversion eq '6') ? 'AAAA' : 'A'; - my $data = encode_json([ { - data => $ip, - defined($config{$host}{'ttl'}) ? (ttl => $config{$host}{'ttl'}) : (), - name => $hostname, - type => $rrset_type, - } ]); + my $ipversion = ($ip eq ($ipv6 // '')) ? '6' : '4'; + my $status = \$config{$host}{"status-ipv$ipversion"}; + my $rrset_type = ($ipversion eq '6') ? 'AAAA' : 'A'; + my $data = encode_json([ { + data => $ip, + defined($config{$host}{'ttl'}) ? (ttl => $config{$host}{'ttl'}) : (), + name => $hostname, + type => $rrset_type, + } ]); - my $url = "https://$config{$host}{'server'}"; - $url .= "/${zone}/records/${rrset_type}/${hostname}"; + my $url = "https://$config{$host}{'server'}"; + $url .= "/${zone}/records/${rrset_type}/${hostname}"; - my $header = "Content-Type: application/json\n"; - $header .= "Accept: application/json\n"; - $header .= "Authorization: sso-key $config{$host}{'login'}:$config{$host}{'password'}\n"; - my $reply = geturl( - proxy => opt('proxy'), - url => $url, - headers => $header, - method => 'PUT', - data => $data, - ); - unless ($reply) { - failed("%s.%s -- Could not connect to %s.", $hostname, $zone, $config{$host}{'server'}); - next; - } - - (my $code) = ($reply =~ m%^s*HTTP/.*\s+(\d+)%i); - my $ok = header_ok($host, $reply); - my $msg; - $reply =~ s/^.*?\n\n//s; # extract payload - my $response = eval {decode_json($reply)}; - if (!defined($response) && $code != "200") { - $$status = "bad"; - - failed("%s.%s -- Unexpected or empty service response, cannot parse data.", $hostname, $zone); - } elsif (defined($response->{code})) { - info("%s.%s -- %s - %s.", $hostname, $zone, $response->{code}, $response->{message}); - } - if ($ok) { - # read data - $config{$host}{"ipv$ipversion"} = $ip; - $config{$host}{'mtime'} = $now; - $$status = 'good'; - - success("%s.%s -- Updated successfully to %s (status: %s).", $hostname, $zone, $ip, $code); - next; - } elsif ($code == "400") { - $msg = 'GoDaddy API URL ($url) was malformed.'; - } elsif ($code == "401") { # authentication error - if ($config{$host}{'login'} && $config{$host}{'login'}) { - $msg = 'login or password option incorrect.'; - } else { - $msg = 'login or password option missing.'; - } - $msg .= ' Correct values can be obtained from from https://developer.godaddy.com/keys/.'; - } elsif ($code == "403") { - $msg = 'Customer identified by login and password options denied permission.'; - } elsif ($code == "404") { - $msg = "\"${hostname}.${zone}\" not found at GoDaddy, please check zone option and login/password."; - } elsif ($code == "422") { - $msg = "\"${hostname}.${zone}\" has invalid domain or lacks A/AAAA record."; - } elsif ($code == "429") { - $msg = 'Too many requests to GoDaddy within brief period.'; - } elsif ($code == "503") { - $msg = "\"${hostname}.${zone}\" is unavailable."; - } else { - $msg = 'Unexpected service response.'; - } - - $$status = 'bad'; - failed("%s.%s -- %s", $hostname, $zone, $msg); + my $header = "Content-Type: application/json\n"; + $header .= "Accept: application/json\n"; + $header .= "Authorization: sso-key $config{$host}{'login'}:$config{$host}{'password'}\n"; + my $reply = geturl( + proxy => opt('proxy'), + url => $url, + headers => $header, + method => 'PUT', + data => $data, + ); + unless ($reply) { + failed("%s.%s -- Could not connect to %s.", $hostname, $zone, $config{$host}{'server'}); + next; } + + (my $code) = ($reply =~ m%^s*HTTP/.*\s+(\d+)%i); + my $ok = header_ok($host, $reply); + my $msg; + $reply =~ s/^.*?\n\n//s; # extract payload + my $response = eval {decode_json($reply)}; + if (!defined($response) && $code != "200") { + $$status = "bad"; + + failed("%s.%s -- Unexpected or empty service response, cannot parse data.", $hostname, $zone); + } elsif (defined($response->{code})) { + info("%s.%s -- %s - %s.", $hostname, $zone, $response->{code}, $response->{message}); + } + if ($ok) { + # read data + $config{$host}{"ipv$ipversion"} = $ip; + $config{$host}{'mtime'} = $now; + $$status = 'good'; + + success("%s.%s -- Updated successfully to %s (status: %s).", $hostname, $zone, $ip, $code); + next; + } elsif ($code == "400") { + $msg = 'GoDaddy API URL ($url) was malformed.'; + } elsif ($code == "401") { # authentication error + if ($config{$host}{'login'} && $config{$host}{'login'}) { + $msg = 'login or password option incorrect.'; + } else { + $msg = 'login or password option missing.'; + } + $msg .= ' Correct values can be obtained from from https://developer.godaddy.com/keys/.'; + } elsif ($code == "403") { + $msg = 'Customer identified by login and password options denied permission.'; + } elsif ($code == "404") { + $msg = "\"${hostname}.${zone}\" not found at GoDaddy, please check zone option and login/password."; + } elsif ($code == "422") { + $msg = "\"${hostname}.${zone}\" has invalid domain or lacks A/AAAA record."; + } elsif ($code == "429") { + $msg = 'Too many requests to GoDaddy within brief period.'; + } elsif ($code == "503") { + $msg = "\"${hostname}.${zone}\" is unavailable."; + } else { + $msg = 'Unexpected service response.'; + } + + $$status = 'bad'; + failed("%s.%s -- %s", $hostname, $zone, $msg); } } } From 40e4aee74fcac07cf0df0ba503f0b9bc67234259 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 18:54:22 -0400 Subject: [PATCH 120/150] nsupdate: Add missing `tcp` to `group_hosts_by` attributes --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 947e811..455fe47 100755 --- a/ddclient.in +++ b/ddclient.in @@ -6004,7 +6004,7 @@ EoEXAMPLE ###################################################################### sub nic_nsupdate_update { debug("\nnic_nsupdate_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(login password server zone wantipv4 wantipv6)]); + my %groups = group_hosts_by(\@_, [qw(login password server tcp zone wantipv4 wantipv6)]); for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); From 8e201853237a619089f75fa6238183858e45a77b Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 18:47:57 -0400 Subject: [PATCH 121/150] googledomains: Remove unnecessary host groupings Each host is already updated individually so there's no point in grouping the hosts. --- ddclient.in | 52 ++++++++++++++++++++++------------------------------ 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/ddclient.in b/ddclient.in index 455fe47..bbfa58c 100755 --- a/ddclient.in +++ b/ddclient.in @@ -5831,39 +5831,31 @@ EoEXAMPLE ###################################################################### sub nic_googledomains_update { debug("\nnic_googledomains_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(server login password wantip)]); - for my $sig (keys %groups) { - my @hosts = @{$groups{$sig}}; - my $key = $hosts[0]; - my $ip = $config{$key}{'wantip'}; + for my $host (@_) { + my $ip = delete $config{$host}{'wantip'}; + info("setting IP address to %s for %s", $ip, $host); + verbose("UPDATE:", "updating %s", $host); - for my $host (@hosts) { - delete $config{$host}{'wantip'}; + my $url = "https://$config{$host}{'server'}/nic/update"; + $url .= "?hostname=$host"; + $url .= "&myip="; + $url .= $ip if $ip; - info("setting IP address to %s for %s", $ip, $host); - verbose("UPDATE:", "updating %s", $host); - - my $url = "https://$config{$host}{'server'}/nic/update"; - $url .= "?hostname=$host"; - $url .= "&myip="; - $url .= $ip if $ip; - - my $reply = geturl( - proxy => opt('proxy'), - url => $url, - login => $config{$host}{'login'}, - password => $config{$host}{'password'}, - ); - unless ($reply) { - failed("updating %s: Could not connect to %s.", $host, $config{$host}{'server'}); - next; - } - next if !header_ok($host, $reply); - - $config{$host}{'ip'} = $ip; - $config{$host}{'mtime'} = $now; - $config{$host}{'status'} = 'good'; + my $reply = geturl( + proxy => opt('proxy'), + url => $url, + login => $config{$host}{'login'}, + password => $config{$host}{'password'}, + ); + unless ($reply) { + failed("updating %s: Could not connect to %s.", $host, $config{$host}{'server'}); + next; } + next if !header_ok($host, $reply); + + $config{$host}{'ip'} = $ip; + $config{$host}{'mtime'} = $now; + $config{$host}{'status'} = 'good'; } } From 45677c0403167a40f9d814ffa27f53f5681ab2b8 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 19:05:36 -0400 Subject: [PATCH 122/150] cloudflare: Remove unnecessary attributes from `group_hosts_by` args --- ddclient.in | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ddclient.in b/ddclient.in index bbfa58c..4790eb0 100755 --- a/ddclient.in +++ b/ddclient.in @@ -6110,7 +6110,7 @@ EoEXAMPLE ###################################################################### sub nic_cloudflare_update { debug("\nnic_cloudflare_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(ssh login password server wildcard mx backupmx zone wantipv4 wantipv6)]); + my %groups = group_hosts_by(\@_, [qw(login password)]); for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); @@ -6130,15 +6130,15 @@ sub nic_cloudflare_update { info("getting Cloudflare Zone ID for %s", $domain); # Get zone ID - my $url = "https://$config{$key}{'server'}/zones/?"; - $url .= "name=" . $config{$key}{'zone'}; + my $url = "https://$config{$domain}{'server'}/zones/?"; + $url .= "name=" . $config{$domain}{'zone'}; my $reply = geturl(proxy => opt('proxy'), url => $url, headers => $headers ); unless ($reply && header_ok($domain, $reply)) { - failed("updating %s: Could not connect to %s.", $domain, $config{$key}{'server'}); + failed("updating %s: Could not connect to %s.", $domain, $config{$domain}{'server'}); next; } @@ -6151,9 +6151,9 @@ sub nic_cloudflare_update { } # Pull the ID out of the json, messy - my ($zone_id) = map {$_->{name} eq $config{$key}{'zone'} ? $_->{id} : ()} @{$response->{result}}; + my ($zone_id) = map {$_->{name} eq $config{$domain}{'zone'} ? $_->{id} : ()} @{$response->{result}}; unless ($zone_id) { - failed("updating %s: No zone ID found.", $config{$key}{'zone'}); + failed("updating %s: No zone ID found.", $config{$domain}{'zone'}); next; } info("Zone ID is %s", $zone_id); @@ -6169,14 +6169,14 @@ sub nic_cloudflare_update { $config{$domain}{"status-ipv$ipv"} = 'failed'; # Get DNS 'A' or 'AAAA' record ID - $url = "https://$config{$key}{'server'}/zones/$zone_id/dns_records?"; + $url = "https://$config{$domain}{'server'}/zones/$zone_id/dns_records?"; $url .= "type=$type&name=$domain"; $reply = geturl(proxy => opt('proxy'), url => $url, headers => $headers ); unless ($reply && header_ok($domain, $reply)) { - failed("updating %s: Could not connect to %s.", $domain, $config{$key}{'server'}); + failed("updating %s: Could not connect to %s.", $domain, $config{$domain}{'server'}); next; } # Strip header @@ -6194,7 +6194,7 @@ sub nic_cloudflare_update { } debug("updating %s: DNS '$type' record ID: $dns_rec_id", $domain); # Set domain - $url = "https://$config{$key}{'server'}/zones/$zone_id/dns_records/$dns_rec_id"; + $url = "https://$config{$domain}{'server'}/zones/$zone_id/dns_records/$dns_rec_id"; my $data = "{\"content\":\"$ip\"}"; $reply = geturl(proxy => opt('proxy'), url => $url, From 5ae0fd302457ba8cc88ca2baf6fe0aaae192fbec Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 19:16:04 -0400 Subject: [PATCH 123/150] hetzner: Delete unused `login` variable --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 4790eb0..c16b452 100755 --- a/ddclient.in +++ b/ddclient.in @@ -921,7 +921,7 @@ our %protocols = ( 'examples' => \&nic_hetzner_examples, 'variables' => { %{$variables{'protocol-common-defaults'}}, - 'login' => setv(T_LOGIN, 0, 0, 'token', undef), + 'login' => undef, 'min-interval' => setv(T_DELAY, 0, 0, interval('1m'), 0), 'server' => setv(T_FQDNP, 0, 0, 'dns.hetzner.com/api/v1', undef), 'ttl' => setv(T_NUMBER, 0, 0, 60, 60), From 216c9c6010d26f1016199f0e0f8d5acc3c82fa03 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 19:33:49 -0400 Subject: [PATCH 124/150] hetzner: Remove unnecessary host groupings Each host is already updated individually so there's no point in grouping the hosts. --- ddclient.in | 177 +++++++++++++++++++++++++--------------------------- 1 file changed, 85 insertions(+), 92 deletions(-) diff --git a/ddclient.in b/ddclient.in index c16b452..6211064 100755 --- a/ddclient.in +++ b/ddclient.in @@ -6253,114 +6253,107 @@ EoEXAMPLE ###################################################################### sub nic_hetzner_update { debug("\nnic_hetzner_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(ssh login password server wildcard mx backupmx zone wantipv4 wantipv6)]); - for my $sig (keys %groups) { - my @hosts = @{$groups{$sig}}; - my $hosts = join(',', @hosts); - my $key = $hosts[0]; - - my $headers = "Auth-API-Token: $config{$key}{'password'}\n"; + for my $domain (@_) { + my $headers = "Auth-API-Token: $config{$domain}{'password'}\n"; $headers .= "Content-Type: application/json"; - for my $domain (@hosts) { - (my $hostname = $domain) =~ s/\.$config{$key}{zone}$//; - my $ipv4 = delete $config{$domain}{'wantipv4'}; - my $ipv6 = delete $config{$domain}{'wantipv6'}; + (my $hostname = $domain) =~ s/\.$config{$domain}{zone}$//; + my $ipv4 = delete $config{$domain}{'wantipv4'}; + my $ipv6 = delete $config{$domain}{'wantipv6'}; - info("getting Hetzner Zone ID for %s", $domain); + info("getting Hetzner Zone ID for %s", $domain); - # Get zone ID - my $url = "https://$config{$key}{'server'}/zones?name=" . $config{$key}{'zone'}; + # Get zone ID + my $url = "https://$config{$domain}{'server'}/zones?name=" . $config{$domain}{'zone'}; - my $reply = geturl(proxy => opt('proxy'), - url => $url, - headers => $headers - ); + my $reply = geturl(proxy => opt('proxy'), + url => $url, + headers => $headers + ); + unless ($reply && header_ok($domain, $reply)) { + failed("updating %s: Could not connect to %s.", $domain, $config{$domain}{'server'}); + next; + } + + # Strip header + $reply =~ qr/{(?:[^{}]*|(?R))*}/mp; + my $response = eval {decode_json(${^MATCH})}; + unless ($response && $response->{zones}) { + failed("updating %s: invalid json or result.", $domain); + next; + } + + # Pull the ID out of the json, messy + my ($zone_id) = map {$_->{name} eq $config{$domain}{'zone'} ? $_->{id} : ()} @{$response->{zones}}; + unless ($zone_id) { + failed("updating %s: No zone ID found.", $config{$domain}{'zone'}); + next; + } + info("Zone ID is %s", $zone_id); + + # IPv4 and IPv6 handling are similar enough to do in a loop... + for my $ip ($ipv4, $ipv6) { + next if (!$ip); + my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; + my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A'; + + info("updating %s: setting IPv$ipv address to %s", $domain, $ip); + $config{$domain}{"status-ipv$ipv"} = 'failed'; + + # Get DNS 'A' or 'AAAA' record ID + $url = "https://$config{$domain}{'server'}/records?zone_id=$zone_id"; + $reply = geturl(proxy => opt('proxy'), + url => $url, + headers => $headers + ); unless ($reply && header_ok($domain, $reply)) { - failed("updating %s: Could not connect to %s.", $domain, $config{$key}{'server'}); + failed("updating %s: Could not connect to %s.", $domain, $config{$domain}{'server'}); next; } - # Strip header $reply =~ qr/{(?:[^{}]*|(?R))*}/mp; - my $response = eval {decode_json(${^MATCH})}; - unless ($response && $response->{zones}) { + $response = eval {decode_json(${^MATCH})}; + unless ($response && $response->{records}) { failed("updating %s: invalid json or result.", $domain); next; } - # Pull the ID out of the json, messy - my ($zone_id) = map {$_->{name} eq $config{$key}{'zone'} ? $_->{id} : ()} @{$response->{zones}}; - unless ($zone_id) { - failed("updating %s: No zone ID found.", $config{$key}{'zone'}); + my ($dns_rec_id) = map { ($_->{name} eq $hostname && $_->{type} eq $type) ? $_->{id} : ()} @{$response->{records}}; + + # Set domain + my $http_method=""; + if ($dns_rec_id) + { + debug("updating %s: DNS '$type' record ID: $dns_rec_id", $domain); + $url = "https://$config{$domain}{'server'}/records/$dns_rec_id"; + $http_method = "PUT"; + } else { + debug("creating %s: DNS '$type'", $domain); + $url = "https://$config{$domain}{'server'}/records"; + $http_method = "POST"; + } + my $data = "{\"zone_id\":\"$zone_id\", \"name\": \"$hostname\", \"value\": \"$ip\", \"type\": \"$type\", \"ttl\": $config{$domain}{'ttl'}}"; + + $reply = geturl(proxy => opt('proxy'), + url => $url, + headers => $headers, + method => $http_method, + data => $data + ); + unless ($reply && header_ok($domain, $reply)) { + failed("updating %s: Could not connect to %s.", $domain, $config{$domain}{'server'}); next; } - info("Zone ID is %s", $zone_id); - - # IPv4 and IPv6 handling are similar enough to do in a loop... - for my $ip ($ipv4, $ipv6) { - next if (!$ip); - my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; - my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A'; - - info("updating %s: setting IPv$ipv address to %s", $domain, $ip); - $config{$domain}{"status-ipv$ipv"} = 'failed'; - - # Get DNS 'A' or 'AAAA' record ID - $url = "https://$config{$key}{'server'}/records?zone_id=$zone_id"; - $reply = geturl(proxy => opt('proxy'), - url => $url, - headers => $headers - ); - unless ($reply && header_ok($domain, $reply)) { - failed("updating %s: Could not connect to %s.", $domain, $config{$key}{'server'}); - next; - } - # Strip header - $reply =~ qr/{(?:[^{}]*|(?R))*}/mp; - $response = eval {decode_json(${^MATCH})}; - unless ($response && $response->{records}) { - failed("updating %s: invalid json or result.", $domain); - next; - } - # Pull the ID out of the json, messy - my ($dns_rec_id) = map { ($_->{name} eq $hostname && $_->{type} eq $type) ? $_->{id} : ()} @{$response->{records}}; - - # Set domain - my $http_method=""; - if ($dns_rec_id) - { - debug("updating %s: DNS '$type' record ID: $dns_rec_id", $domain); - $url = "https://$config{$key}{'server'}/records/$dns_rec_id"; - $http_method = "PUT"; - } else { - debug("creating %s: DNS '$type'", $domain); - $url = "https://$config{$key}{'server'}/records"; - $http_method = "POST"; - } - my $data = "{\"zone_id\":\"$zone_id\", \"name\": \"$hostname\", \"value\": \"$ip\", \"type\": \"$type\", \"ttl\": $config{$domain}{'ttl'}}"; - - $reply = geturl(proxy => opt('proxy'), - url => $url, - headers => $headers, - method => $http_method, - data => $data - ); - unless ($reply && header_ok($domain, $reply)) { - failed("updating %s: Could not connect to %s.", $domain, $config{$domain}{'server'}); - next; - } - # Strip header - $reply =~ qr/{(?:[^{}]*|(?R))*}/mp; - $response = eval {decode_json(${^MATCH})}; - if ($response && $response->{record}) { - success("updating %s: IPv$ipv address set to %s", $domain, $ip); - $config{$domain}{"ipv$ipv"} = $ip; - $config{$domain}{'mtime'} = $now; - $config{$domain}{"status-ipv$ipv"} = 'good'; - } else { - failed("updating %s: invalid json or result.", $domain); - } + # Strip header + $reply =~ qr/{(?:[^{}]*|(?R))*}/mp; + $response = eval {decode_json(${^MATCH})}; + if ($response && $response->{record}) { + success("updating %s: IPv$ipv address set to %s", $domain, $ip); + $config{$domain}{"ipv$ipv"} = $ip; + $config{$domain}{'mtime'} = $now; + $config{$domain}{"status-ipv$ipv"} = 'good'; + } else { + failed("updating %s: invalid json or result.", $domain); } } } From 5d2a1e864ac70061b9602d90794a31a27bd602c8 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 20:16:21 -0400 Subject: [PATCH 125/150] yandex: Remove unnecessary host groupings Each host is already updated individually so there's no point in grouping the hosts. --- ddclient.in | 135 +++++++++++++++++++++++++--------------------------- 1 file changed, 64 insertions(+), 71 deletions(-) diff --git a/ddclient.in b/ddclient.in index 6211064..edc2059 100755 --- a/ddclient.in +++ b/ddclient.in @@ -6398,80 +6398,73 @@ EoEXAMPLE ###################################################################### sub nic_yandex_update { debug("\nnic_yandex_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(server login pasword wantip)]); - for my $sig (keys %groups) { - my @hosts = @{$groups{$sig}}; - my $key = $hosts[0]; - my $ip = $config{$key}{'wantip'}; - my $headers = "PddToken: $config{$key}{'password'}\n"; + for my $host (@_) { + my $ip = delete $config{$host}{'wantip'}; + my $headers = "PddToken: $config{$host}{'password'}\n"; - for my $host (@hosts) { - delete $config{$host}{'wantip'}; + info("setting IP address to %s for %s", $ip, $host); + verbose("UPDATE:", "updating %s", $host); - info("setting IP address to %s for %s", $ip, $host); - verbose("UPDATE:", "updating %s", $host); - - # Get record ID for host - my $url = "https://$config{$host}{'server'}/api2/admin/dns/list?"; - $url .= "domain="; - $url .= $config{$key}{'login'}; - my $reply = geturl(proxy => opt('proxy'), url => $url, headers => $headers); - unless ($reply) { - failed("updating %s: Could not connect to %s.", $host, $config{$key}{'server'}); - next; - } - next if !header_ok($host, $reply); - - # Strip header - $reply =~ s/^.*?\n\n//s; - my $response = eval { decode_json($reply) }; - if ($response->{success} eq 'error') { - failed("%s", $response->{error}); - next; - } - - # Pull the ID out of the json - my ($id) = map { $_->{fqdn} eq $host ? $_->{record_id} : () } @{$response->{records}}; - unless ($id) { - failed("updating %s: DNS record ID not found.", $host); - next; - } - - # Update the DNS record - $url = "https://$config{$host}{'server'}/api2/admin/dns/edit"; - my $data = "domain="; - $data .= $config{$key}{'login'}; - $data .= "&record_id="; - $data .= $id; - $data .= "&content="; - $data .= $ip if $ip; - - $reply = geturl( - proxy => opt('proxy'), - url => $url, - headers => $headers, - method => 'POST', - data => $data, - ); - unless ($reply) { - failed("updating %s: Could not connect to %s.", $host, $config{$host}{'server'}); - next; - } - next if !header_ok($host, $reply); - - # Strip header - $reply =~ s/^.*?\n\n//s; - $response = eval { decode_json($reply) }; - if ($response->{success} eq 'error') { - failed("%s", $response->{error}); - } else { - success("%s -- Updated Successfully to %s", $host, $ip); - } - - $config{$host}{'ip'} = $ip; - $config{$host}{'mtime'} = $now; - $config{$host}{'status'} = 'good'; + # Get record ID for host + my $url = "https://$config{$host}{'server'}/api2/admin/dns/list?"; + $url .= "domain="; + $url .= $config{$host}{'login'}; + my $reply = geturl(proxy => opt('proxy'), url => $url, headers => $headers); + unless ($reply) { + failed("updating %s: Could not connect to %s.", $host, $config{$host}{'server'}); + next; } + next if !header_ok($host, $reply); + + # Strip header + $reply =~ s/^.*?\n\n//s; + my $response = eval { decode_json($reply) }; + if ($response->{success} eq 'error') { + failed("%s", $response->{error}); + next; + } + + # Pull the ID out of the json + my ($id) = map { $_->{fqdn} eq $host ? $_->{record_id} : () } @{$response->{records}}; + unless ($id) { + failed("updating %s: DNS record ID not found.", $host); + next; + } + + # Update the DNS record + $url = "https://$config{$host}{'server'}/api2/admin/dns/edit"; + my $data = "domain="; + $data .= $config{$host}{'login'}; + $data .= "&record_id="; + $data .= $id; + $data .= "&content="; + $data .= $ip if $ip; + + $reply = geturl( + proxy => opt('proxy'), + url => $url, + headers => $headers, + method => 'POST', + data => $data, + ); + unless ($reply) { + failed("updating %s: Could not connect to %s.", $host, $config{$host}{'server'}); + next; + } + next if !header_ok($host, $reply); + + # Strip header + $reply =~ s/^.*?\n\n//s; + $response = eval { decode_json($reply) }; + if ($response->{success} eq 'error') { + failed("%s", $response->{error}); + } else { + success("%s -- Updated Successfully to %s", $host, $ip); + } + + $config{$host}{'ip'} = $ip; + $config{$host}{'mtime'} = $now; + $config{$host}{'status'} = 'good'; } } From 64af205cfcdbac268b7a9e4fd12c456502e028dd Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 16:10:13 -0400 Subject: [PATCH 126/150] group_hosts_by: Readability improvements --- ddclient.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ddclient.in b/ddclient.in index edc2059..23e5f08 100755 --- a/ddclient.in +++ b/ddclient.in @@ -3459,14 +3459,14 @@ sub get_ipv6 { ###################################################################### sub group_hosts_by { my ($hosts, $attributes) = @_; - my %attrs = map({ ($_ => 1) } @$attributes); + my %attrs = map({ ($_ => undef); } @$attributes); my @attrs = sort(keys(%attrs)); - my %groups = (); + my %groups; my $d = Data::Dumper->new([])->Indent(0)->Sortkeys(1)->Terse(1)->Useqq(1); for my $h (@$hosts) { my %cfg = map({ ($_ => $config{$h}{$_}); } grep(exists($config{$h}{$_}), @attrs)); my $sig = $d->Reset()->Values([\%cfg])->Dump(); - push @{$groups{$sig}}, $h; + push(@{$groups{$sig}}, $h); } return %groups; } From 08ccc4165005b1c9b27837c7d5f6aee09e7cd444 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 16:16:03 -0400 Subject: [PATCH 127/150] group_hosts_by: Use arg list instead of arrayref This is more idiomatic. --- ddclient.in | 18 +++++++++--------- t/group_hosts_by.pl | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ddclient.in b/ddclient.in index 23e5f08..e6554a0 100755 --- a/ddclient.in +++ b/ddclient.in @@ -3458,9 +3458,9 @@ sub get_ipv6 { ## group_hosts_by ###################################################################### sub group_hosts_by { - my ($hosts, $attributes) = @_; - my %attrs = map({ ($_ => undef); } @$attributes); - my @attrs = sort(keys(%attrs)); + my ($hosts, @attrs) = @_; + my %attrs = map({ ($_ => undef); } @attrs); + @attrs = sort(keys(%attrs)); my %groups; my $d = Data::Dumper->new([])->Indent(0)->Sortkeys(1)->Terse(1)->Useqq(1); for my $h (@$hosts) { @@ -4011,7 +4011,7 @@ 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 %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', @@ -4289,7 +4289,7 @@ sub dnsexit2_update_host { ###################################################################### sub nic_noip_update { debug("\nnic_noip_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(login password server wantipv4 wantipv6)]); + my %groups = group_hosts_by(\@_, qw(login password server wantipv4 wantipv6)); my %errors = ( 'badauth' => 'Invalid username or password', 'badagent' => 'Invalid user agent', @@ -4632,7 +4632,7 @@ sub nic_zoneedit1_force_update { ###################################################################### sub nic_zoneedit1_update { debug("\nnic_zoneedit1_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(login password server zone wantip)]); + my %groups = group_hosts_by(\@_, qw(login password server zone wantip)); for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); @@ -5996,7 +5996,7 @@ EoEXAMPLE ###################################################################### sub nic_nsupdate_update { debug("\nnic_nsupdate_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(login password server tcp zone wantipv4 wantipv6)]); + my %groups = group_hosts_by(\@_, qw(login password server tcp zone wantipv4 wantipv6)); for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); @@ -6110,7 +6110,7 @@ EoEXAMPLE ###################################################################### sub nic_cloudflare_update { debug("\nnic_cloudflare_update -------------------"); - my %groups = group_hosts_by(\@_, [qw(login password)]); + my %groups = group_hosts_by(\@_, qw(login password)); for my $sig (keys %groups) { my @hosts = @{$groups{$sig}}; my $hosts = join(',', @hosts); @@ -7278,7 +7278,7 @@ EoEXAMPLE } sub nic_cloudns_update { - my %groups = group_hosts_by(\@_, [qw(dynurl wantip)]); + my %groups = group_hosts_by(\@_, qw(dynurl wantip)); for my $hr (values(%groups)) { my @hosts = @$hr; my $hosts = join(',', @hosts); diff --git a/t/group_hosts_by.pl b/t/group_hosts_by.pl index 61acd0f..9b3a7bf 100644 --- a/t/group_hosts_by.pl +++ b/t/group_hosts_by.pl @@ -74,7 +74,7 @@ my @test_cases = ( ); for my $tc (@test_cases) { - my %got = ddclient::group_hosts_by([$h1, $h2, $h3], $tc->{groupby}); + my %got = ddclient::group_hosts_by([$h1, $h2, $h3], @{$tc->{groupby}}); # %got is used as a set of sets. Sort everything to make comparison easier. my @got = sort({ for (my $i = 0; $i < @$a && $i < @$b; ++$i) { From b8df93febec63f89e6bdcdc933c45183a38b4ca0 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 16:35:41 -0400 Subject: [PATCH 128/150] group_hosts_by: Return a list, not a hashref The hash key value is an implementation detail that should not be leaked to the caller. --- ddclient.in | 33 ++++++++++++++------------------- t/group_hosts_by.pl | 8 ++++---- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/ddclient.in b/ddclient.in index e6554a0..3804987 100755 --- a/ddclient.in +++ b/ddclient.in @@ -3468,7 +3468,7 @@ sub group_hosts_by { my $sig = $d->Reset()->Values([\%cfg])->Dump(); push(@{$groups{$sig}}, $h); } - return %groups; + return values(%groups); } ###################################################################### @@ -4011,7 +4011,7 @@ 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 @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', @@ -4025,8 +4025,8 @@ 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 $sig (keys %groups) { - my @hosts = @{$groups{$sig}}; + for my $group (@groups) { + my @hosts = @$group; my $hosts = join(',', @hosts); my $h = $hosts[0]; my $ipv4 = $config{$h}{'wantipv4'}; @@ -4289,7 +4289,6 @@ sub dnsexit2_update_host { ###################################################################### sub nic_noip_update { debug("\nnic_noip_update -------------------"); - my %groups = group_hosts_by(\@_, qw(login password server wantipv4 wantipv6)); my %errors = ( 'badauth' => 'Invalid username or password', 'badagent' => 'Invalid user agent', @@ -4300,8 +4299,8 @@ sub nic_noip_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 $sig (keys %groups) { - my @hosts = @{$groups{$sig}}; + for my $group (group_hosts_by(\@_, qw(login password server wantipv4 wantipv6))) { + my @hosts = @$group; my $hosts = join(',', @hosts); my $h = $hosts[0]; my $ipv4 = $config{$h}{'wantipv4'}; @@ -4632,9 +4631,8 @@ sub nic_zoneedit1_force_update { ###################################################################### sub nic_zoneedit1_update { debug("\nnic_zoneedit1_update -------------------"); - my %groups = group_hosts_by(\@_, qw(login password server zone wantip)); - for my $sig (keys %groups) { - my @hosts = @{$groups{$sig}}; + for my $group (group_hosts_by(\@_, qw(login password server zone wantip))) { + my @hosts = @$group; my $hosts = join(',', @hosts); my $h = $hosts[0]; my $ip = $config{$h}{'wantip'}; @@ -5996,9 +5994,8 @@ EoEXAMPLE ###################################################################### sub nic_nsupdate_update { debug("\nnic_nsupdate_update -------------------"); - my %groups = group_hosts_by(\@_, qw(login password server tcp zone wantipv4 wantipv6)); - for my $sig (keys %groups) { - my @hosts = @{$groups{$sig}}; + for my $group (group_hosts_by(\@_, qw(login password server tcp zone wantipv4 wantipv6))) { + my @hosts = @$group; my $hosts = join(',', @hosts); my $h = $hosts[0]; my $binary = $config{$h}{'login'}; @@ -6110,9 +6107,8 @@ EoEXAMPLE ###################################################################### sub nic_cloudflare_update { debug("\nnic_cloudflare_update -------------------"); - my %groups = group_hosts_by(\@_, qw(login password)); - for my $sig (keys %groups) { - my @hosts = @{$groups{$sig}}; + for my $group (group_hosts_by(\@_, qw(login password))) { + my @hosts = @$group; my $hosts = join(',', @hosts); my $key = $hosts[0]; my $headers = "Content-Type: application/json\n"; @@ -7278,9 +7274,8 @@ EoEXAMPLE } sub nic_cloudns_update { - my %groups = group_hosts_by(\@_, qw(dynurl wantip)); - for my $hr (values(%groups)) { - my @hosts = @$hr; + for my $group (group_hosts_by(\@_, qw(dynurl wantip))) { + my @hosts = @$group; my $hosts = join(',', @hosts); my $ip = $config{$hosts[0]}{'wantip'}; my $dynurl = $config{$hosts[0]}{'dynurl'}; diff --git a/t/group_hosts_by.pl b/t/group_hosts_by.pl index 9b3a7bf..ae92110 100644 --- a/t/group_hosts_by.pl +++ b/t/group_hosts_by.pl @@ -74,15 +74,15 @@ my @test_cases = ( ); for my $tc (@test_cases) { - my %got = ddclient::group_hosts_by([$h1, $h2, $h3], @{$tc->{groupby}}); - # %got is used as a set of sets. Sort everything to make comparison easier. - my @got = sort({ + my @got = ddclient::group_hosts_by([$h1, $h2, $h3], @{$tc->{groupby}}); + # @got is used as a set of sets. Sort everything to make comparison easier. + @got = sort({ for (my $i = 0; $i < @$a && $i < @$b; ++$i) { my $x = $a->[$i] cmp $b->[$i]; return $x if $x != 0; } return @$a <=> @$b; - } map({ [sort(@$_)]; } values(%got))); + } map({ [sort(@$_)]; } @got)); is_deeply(\@got, $tc->{want}, $tc->{desc}) or diag(Data::Dumper->Dump([\@got, $tc->{want}], [qw(got want)])); } From 8a65264841326dcf90e52e85adb464758aabad3a Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 17:37:07 -0400 Subject: [PATCH 129/150] group_hosts_by: Return the common group configuration This should make it easier to detect missing attribute names passed to `group_hosts_by`. --- ddclient.in | 110 +++++++++++++++++++++++--------------------- t/group_hosts_by.pl | 46 ++++++++++++------ 2 files changed, 90 insertions(+), 66 deletions(-) diff --git a/ddclient.in b/ddclient.in index 3804987..a26099d 100755 --- a/ddclient.in +++ b/ddclient.in @@ -3462,13 +3462,15 @@ sub group_hosts_by { my %attrs = map({ ($_ => undef); } @attrs); @attrs = sort(keys(%attrs)); my %groups; + my %cfgs; my $d = Data::Dumper->new([])->Indent(0)->Sortkeys(1)->Terse(1)->Useqq(1); for my $h (@$hosts) { my %cfg = map({ ($_ => $config{$h}{$_}); } grep(exists($config{$h}{$_}), @attrs)); my $sig = $d->Reset()->Values([\%cfg])->Dump(); push(@{$groups{$sig}}, $h); + $cfgs{$sig} = \%cfg; } - return values(%groups); + return map({ {cfg => $cfgs{$_}, hosts => $groups{$_}}; } keys(%groups)); } ###################################################################### @@ -4026,23 +4028,23 @@ sub nic_dyndns2_update { 'nochg' => 'No update required; unnecessary attempts to change to the current address are considered abusive', ); for my $group (@groups) { - my @hosts = @$group; + my @hosts = @{$group->{hosts}}; + my %groupcfg = %{$group->{cfg}}; my $hosts = join(',', @hosts); - my $h = $hosts[0]; - my $ipv4 = $config{$h}{'wantipv4'}; - my $ipv6 = $config{$h}{'wantipv6'}; + my $ipv4 = $groupcfg{'wantipv4'}; + 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); ## Select the DynDNS system to update - my $url = "http://$config{$h}{'server'}$config{$h}{'script'}?system="; - if ($config{$h}{'custom'}) { + my $url = "http://$groupcfg{'server'}$groupcfg{'script'}?system="; + if ($groupcfg{'custom'}) { warning("updating %s: 'custom' and 'static' may not be used together. ('static' ignored)", $hosts) - if $config{$h}{'static'}; + if $groupcfg{'static'}; $url .= 'custom'; - } elsif ($config{$h}{'static'}) { + } elsif ($groupcfg{'static'}) { $url .= 'statdns'; } else { $url .= 'dyndns'; @@ -4055,19 +4057,19 @@ sub nic_dyndns2_update { $url .= $ipv6; } ## some args are not valid for a custom domain. - $url .= "&wildcard=ON" if ynu($config{$h}{'wildcard'}, 1, 0, 0); - if ($config{$h}{'mx'}) { - $url .= "&mx=$config{$h}{'mx'}"; - $url .= "&backmx=" . ynu($config{$h}{'backupmx'}, 'YES', 'NO'); + $url .= "&wildcard=ON" if ynu($groupcfg{'wildcard'}, 1, 0, 0); + if ($groupcfg{'mx'}) { + $url .= "&mx=$groupcfg{'mx'}"; + $url .= "&backmx=" . ynu($groupcfg{'backupmx'}, 'YES', 'NO'); } my $reply = geturl( proxy => opt('proxy'), url => $url, - login => $config{$h}{'login'}, - password => $config{$h}{'password'}, + login => $groupcfg{'login'}, + password => $groupcfg{'password'}, ) // ''; if ($reply eq '') { - failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'}); + failed("updating %s: Could not connect to %s.", $hosts, $groupcfg{'server'}); next; } next if !header_ok($hosts, $reply); @@ -4124,7 +4126,7 @@ sub nic_dyndns2_update { } } } - failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'}) + failed("updating %s: Could not connect to %s.", $hosts, $groupcfg{'server'}) if $state ne 'results2'; } } @@ -4300,11 +4302,11 @@ sub nic_noip_update { 'nochg' => 'No update required; unnecessary attempts to change to the current address are considered abusive', ); for my $group (group_hosts_by(\@_, qw(login password server wantipv4 wantipv6))) { - my @hosts = @$group; + my @hosts = @{$group->{hosts}}; + my %groupcfg = %{$group->{cfg}}; my $hosts = join(',', @hosts); - my $h = $hosts[0]; - my $ipv4 = $config{$h}{'wantipv4'}; - my $ipv6 = $config{$h}{'wantipv6'}; + my $ipv4 = $groupcfg{'wantipv4'}; + my $ipv6 = $groupcfg{'wantipv6'}; delete $config{$_}{'wantipv4'} for @hosts; delete $config{$_}{'wantipv6'} for @hosts; @@ -4312,7 +4314,7 @@ sub nic_noip_update { info("setting IPv6 address to %s for %s", $ipv6, $hosts) if $ipv6; verbose("UPDATE:", "updating %s", $hosts); - my $url = "https://$config{$h}{'server'}/nic/update?system=noip&hostname=$hosts&myip="; + my $url = "https://$groupcfg{'server'}/nic/update?system=noip&hostname=$hosts&myip="; $url .= $ipv4 if $ipv4; if ($ipv6) { $url .= "," if $ipv4; @@ -4322,11 +4324,11 @@ sub nic_noip_update { my $reply = geturl( proxy => opt('proxy'), url => $url, - login => $config{$h}{'login'}, - password => $config{$h}{'password'}, + login => $groupcfg{'login'}, + password => $groupcfg{'password'}, ) // ''; if ($reply eq '') { - failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'}); + failed("updating %s: Could not connect to %s.", $hosts, $groupcfg{'server'}); next; } next if !header_ok($hosts, $reply); @@ -4392,7 +4394,7 @@ sub nic_noip_update { } } } - failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'}) + failed("updating %s: Could not connect to %s.", $hosts, $groupcfg{'server'}) if $state ne 'results2'; } } @@ -4632,29 +4634,29 @@ sub nic_zoneedit1_force_update { sub nic_zoneedit1_update { debug("\nnic_zoneedit1_update -------------------"); for my $group (group_hosts_by(\@_, qw(login password server zone wantip))) { - my @hosts = @$group; + my @hosts = @{$group->{hosts}}; + my %groupcfg = %{$group->{cfg}}; my $hosts = join(',', @hosts); - my $h = $hosts[0]; - my $ip = $config{$h}{'wantip'}; + my $ip = $groupcfg{'wantip'}; delete $config{$_}{'wantip'} for @hosts; info("setting IP address to %s for %s", $ip, $hosts); verbose("UPDATE:", "updating %s", $hosts); my $url = ''; - $url .= "https://$config{$h}{'server'}/auth/dynamic.html"; + $url .= "https://$groupcfg{'server'}/auth/dynamic.html"; $url .= "?host=$hosts"; $url .= "&dnsto=$ip" if $ip; - $url .= "&zone=$config{$h}{'zone'}" if defined $config{$h}{'zone'}; + $url .= "&zone=$groupcfg{'zone'}" if defined $groupcfg{'zone'}; my $reply = geturl( proxy => opt('proxy'), url => $url, - login => $config{$h}{'login'}, - password => $config{$h}{'password'}, + login => $groupcfg{'login'}, + password => $groupcfg{'password'}, ) // ''; if ($reply eq '') { - failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'}); + failed("updating %s: Could not connect to %s.", $hosts, $groupcfg{'server'}); next; } next if !header_ok($hosts, $reply); @@ -4662,6 +4664,7 @@ sub nic_zoneedit1_update { my @reply = split /\n/, $reply; # TODO: This is awkward and fragile -- it assumes that each line in the response body # corresponds with each host in @hosts (and in the same order). + my $h = $hosts[0]; for my $line (@reply) { if ($h && $line =~ /^[^<]*<(SUCCESS|ERROR)\s+([^>]+)>(.*)/) { my ($status, $assignments, $rest) = ($1, $2, $3); @@ -4693,7 +4696,7 @@ sub nic_zoneedit1_update { } } # TODO: Shouldn't this log join(',' @hosts) instead of $hosts? - failed("updating %s: no response from %s", $hosts, $config{$h}{'server'}) + failed("updating %s: no response from %s", $hosts, $groupcfg{'server'}) if @hosts; } } @@ -5995,17 +5998,17 @@ EoEXAMPLE sub nic_nsupdate_update { debug("\nnic_nsupdate_update -------------------"); for my $group (group_hosts_by(\@_, qw(login password server tcp zone wantipv4 wantipv6))) { - my @hosts = @$group; + my @hosts = @{$group->{hosts}}; + my %groupcfg = %{$group->{cfg}}; my $hosts = join(',', @hosts); - my $h = $hosts[0]; - my $binary = $config{$h}{'login'}; - my $keyfile = $config{$h}{'password'}; - my $server = $config{$h}{'server'}; + my $binary = $groupcfg{'login'}; + my $keyfile = $groupcfg{'password'}; + my $server = $groupcfg{'server'}; ## nsupdate requires a port number to be separated by whitepace, not colon $server =~ s/:/ /; - my $zone = $config{$h}{'zone'}; - my $ipv4 = $config{$h}{'wantipv4'}; - my $ipv6 = $config{$h}{'wantipv6'}; + my $zone = $groupcfg{'zone'}; + my $ipv4 = $groupcfg{'wantipv4'}; + my $ipv6 = $groupcfg{'wantipv6'}; delete $config{$_}{'wantipv4'} for @hosts; delete $config{$_}{'wantipv6'} for @hosts; @@ -6032,7 +6035,7 @@ EoINSTR2 send EoINSTR4 my $command = "$binary -k $keyfile"; - $command .= " -v" if ynu($config{$h}{'tcp'}, 1, 0, 0); + $command .= " -v" if ynu($groupcfg{'tcp'}, 1, 0, 0); $command .= " -d" if (opt('debug')); verbose("UPDATE:", "nsupdate command is: %s", $command); verbose("UPDATE:", "nsupdate instructions are:\n%s", $instructions); @@ -6108,15 +6111,15 @@ EoEXAMPLE sub nic_cloudflare_update { debug("\nnic_cloudflare_update -------------------"); for my $group (group_hosts_by(\@_, qw(login password))) { - my @hosts = @$group; + my @hosts = @{$group->{hosts}}; + my %groupcfg = %{$group->{cfg}}; my $hosts = join(',', @hosts); - my $key = $hosts[0]; my $headers = "Content-Type: application/json\n"; - if ($config{$key}{'login'} eq 'token') { - $headers .= "Authorization: Bearer $config{$key}{'password'}\n"; + if ($groupcfg{'login'} eq 'token') { + $headers .= "Authorization: Bearer $groupcfg{'password'}\n"; } else { - $headers .= "X-Auth-Email: $config{$key}{'login'}\n"; - $headers .= "X-Auth-Key: $config{$key}{'password'}\n"; + $headers .= "X-Auth-Email: $groupcfg{'login'}\n"; + $headers .= "X-Auth-Key: $groupcfg{'password'}\n"; } for my $domain (@hosts) { @@ -7275,10 +7278,11 @@ EoEXAMPLE sub nic_cloudns_update { for my $group (group_hosts_by(\@_, qw(dynurl wantip))) { - my @hosts = @$group; + my @hosts = @{$group->{hosts}}; + my %groupcfg = %{$group->{cfg}}; my $hosts = join(',', @hosts); - my $ip = $config{$hosts[0]}{'wantip'}; - my $dynurl = $config{$hosts[0]}{'dynurl'}; + my $ip = $groupcfg{'wantip'}; + my $dynurl = $groupcfg{'dynurl'}; delete $config{$_}{'wantip'} for @hosts; # https://www.cloudns.net/wiki/article/36/ says, "If you are behind a proxy and your real # IP is set in the header X-Forwarded-For you need to add &proxy=1 at the end of the diff --git a/t/group_hosts_by.pl b/t/group_hosts_by.pl index ae92110..4e2c29f 100644 --- a/t/group_hosts_by.pl +++ b/t/group_hosts_by.pl @@ -34,57 +34,77 @@ my @test_cases = ( { desc => 'empty attribute set yields single group with all hosts', groupby => [qw()], - want => [[$h1, $h2, $h3]], + want => [{cfg => {}, hosts => [$h1, $h2, $h3]}], }, { desc => 'common attribute yields single group with all hosts', groupby => [qw(common)], - want => [[$h1, $h2, $h3]], + want => [{cfg => {common => 'common'}, hosts => [$h1, $h2, $h3]}], }, { desc => 'subset share a value', groupby => [qw(h1h2)], - want => [[$h1, $h2], [$h3]], + want => [ + {cfg => {h1h2 => 'h1 and h2'}, hosts => [$h1, $h2]}, + {cfg => {h1h2 => 'unique'}, hosts => [$h3]}, + ], }, { desc => 'all unique', groupby => [qw(unique)], - want => [[$h1], [$h2], [$h3]], + want => [ + {cfg => {unique => 'h1'}, hosts => [$h1]}, + {cfg => {unique => 'h2'}, hosts => [$h2]}, + {cfg => {unique => 'h3'}, hosts => [$h3]}, + ], }, { desc => 'combination', groupby => [qw(common h1h2)], - want => [[$h1, $h2], [$h3]], + want => [ + {cfg => {common => 'common', h1h2 => 'h1 and h2'}, hosts => [$h1, $h2]}, + {cfg => {common => 'common', h1h2 => 'unique'}, hosts => [$h3]}, + ], }, { desc => 'falsy values', groupby => [qw(falsy)], - want => [[$h1], [$h2], [$h3]], + want => [ + {cfg => {falsy => 0}, hosts => [$h1]}, + {cfg => {falsy => ''}, hosts => [$h2]}, + {cfg => {falsy => undef}, hosts => [$h3]}, + ], }, { desc => 'set, unset, undef', groupby => [qw(maybeunset)], - want => [[$h1], [$h2], [$h3]], + want => [ + {cfg => {maybeunset => 'unique'}, hosts => [$h1]}, + {cfg => {maybeunset => undef}, hosts => [$h2]}, + {cfg => {}, hosts => [$h3]}, + ], }, { desc => 'missing attribute', groupby => [qw(thisdoesnotexist)], - want => [[$h1, $h2, $h3]], + want => [{cfg => {}, hosts => [$h1, $h2, $h3]}], }, ); for my $tc (@test_cases) { my @got = ddclient::group_hosts_by([$h1, $h2, $h3], @{$tc->{groupby}}); # @got is used as a set of sets. Sort everything to make comparison easier. + $_->{hosts} = [sort(@{$_->{hosts}})] for @got; @got = sort({ - for (my $i = 0; $i < @$a && $i < @$b; ++$i) { - my $x = $a->[$i] cmp $b->[$i]; + for (my $i = 0; $i < @{$a->{hosts}} && $i < @{$b->{hosts}}; ++$i) { + my $x = $a->{hosts}[$i] cmp $b->{hosts}[$i]; return $x if $x != 0; } - return @$a <=> @$b; - } map({ [sort(@$_)]; } @got)); + return @{$a->{hosts}} <=> @{$b->{hosts}}; + } @got); is_deeply(\@got, $tc->{want}, $tc->{desc}) - or diag(Data::Dumper->Dump([\@got, $tc->{want}], [qw(got want)])); + or diag(Data::Dumper->new([\@got, $tc->{want}], + [qw(got want)])->Sortkeys(1)->Useqq(1)->Dump()); } done_testing(); From 3ffcdf8317f003404b62b348d8164cf40902440d Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 13 Jul 2024 04:42:06 -0400 Subject: [PATCH 130/150] ci: Pass `--skip-broken` after `install`, not before Apparently dnf was changed in Fedora Rawhide: https://bugzilla.redhat.com/show_bug.cgi?id=2216055 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ad8ff9..0b5139c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,10 +80,10 @@ jobs: dnf --refresh install -y 'dnf-command(config-manager)' epel-release && dnf config-manager --set-enabled crb - name: install dependencies - # The --skip-broken argument works around RedHat UBI's missing packages. - # (They're only used for testing, so it's OK to not install them.) + # The --skip-broken argument works around missing packages. (They're + # only used for testing, so it's OK to not install them.) run: | - dnf --refresh --skip-broken install -y \ + dnf --refresh install --skip-broken -y \ automake \ findutils \ iproute \ From 45e36039180ae118e316117b4b10dfcecaa9beb3 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 13 Jul 2024 04:01:22 -0400 Subject: [PATCH 131/150] dyndns2: Simplify response parsing --- ddclient.in | 103 +++++++++++++++++++++++++--------------------------- 1 file changed, 49 insertions(+), 54 deletions(-) diff --git a/ddclient.in b/ddclient.in index a26099d..4098313 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4073,61 +4073,56 @@ sub nic_dyndns2_update { next; } next if !header_ok($hosts, $reply); - my @reply = split /\n/, $reply; - my $state = 'header'; - 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; - for my $h (@hosts) { - $config{$h}{'status-ipv4'} = $status if $ipv4; - $config{$h}{'status-ipv6'} = $status if $ipv6; - } - if ($status eq 'good') { - 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; - } - } 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); - } - } + # 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 ($line) = grep(qr/^results/, split(qr/\n/, $body)); + if (!$line) { + failed("updating %s: Could not connect to %s.", $hosts, $groupcfg{'server'}); + next; + } + # bug #10: some dyndns providers does not return the IP so + # we can't use the returned IP + my ($status, $returnedips) = split / /, lc $line; + for my $h (@hosts) { + $config{$h}{'status-ipv4'} = $status if $ipv4; + $config{$h}{'status-ipv6'} = $status if $ipv6; + } + if ($status eq 'good') { + 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; + } + } 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'; } } From 0882712ec2f79f7ac074902669b31a072aa5ce07 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 13 Jul 2024 04:23:27 -0400 Subject: [PATCH 132/150] dyndns2: Treat `nochg` as `good` to eliminate duplicate code --- ddclient.in | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/ddclient.in b/ddclient.in index 4098313..56897a2 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4084,6 +4084,10 @@ sub nic_dyndns2_update { # bug #10: some dyndns providers does not return the IP so # we can't use the returned IP my ($status, $returnedips) = split / /, lc $line; + if ($status eq 'nochg') { + warning("updating %s: %s: %s", $hosts, $status, $errors{$status}); + $status = 'good'; + } for my $h (@hosts) { $config{$h}{'status-ipv4'} = $status if $ipv4; $config{$h}{'status-ipv6'} = $status if $ipv6; @@ -4097,18 +4101,7 @@ sub nic_dyndns2_update { 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; - } - } else { - failed("updating %s: %s: %s", $hosts, $status, $errors{$status}); - } + failed("updating %s: %s: %s", $hosts, $status, $errors{$status}); } elsif ($status =~ /w(\d+)(.)/) { my ($wait, $units) = ($1, lc $2); my ($sec, $scale) = ($wait, 1); From ad4e3769eb673600aa484e7b1eaa0e1645d0bb2d Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 13 Jul 2024 04:24:55 -0400 Subject: [PATCH 133/150] dyndns2: Invert condition to improve readability --- ddclient.in | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/ddclient.in b/ddclient.in index 56897a2..c981b37 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4092,30 +4092,32 @@ sub nic_dyndns2_update { $config{$h}{'status-ipv4'} = $status if $ipv4; $config{$h}{'status-ipv6'} = $status if $ipv6; } - if ($status eq 'good') { - for my $h (@hosts) { - $config{$h}{'ipv4'} = $ipv4 if $ipv4; - $config{$h}{'ipv6'} = $ipv6 if $ipv6; - $config{$h}{'mtime'} = $now; + if ($status ne 'good') { + if (exists($errors{$status})) { + failed("updating %s: %s: %s", $hosts, $status, $errors{$status}); + } elsif ($status =~ qr/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); } - 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}) { - 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); + 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; } } From 203bf12245d689e20d0a6b0cd838eaaf2af3ead6 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 13 Jul 2024 04:33:44 -0400 Subject: [PATCH 134/150] dyndns2: Improve readability of status parsing --- ddclient.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ddclient.in b/ddclient.in index c981b37..291b532 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4081,9 +4081,9 @@ sub nic_dyndns2_update { failed("updating %s: Could not connect to %s.", $hosts, $groupcfg{'server'}); next; } - # 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. (Some services do not + # return the IP so we can't rely on it anyway.) + (my $status = $line) =~ s/ .*$//; if ($status eq 'nochg') { warning("updating %s: %s: %s", $hosts, $status, $errors{$status}); $status = 'good'; From d489cea344668a85961e35bb176bed5d6185e488 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 13 Jul 2024 04:52:05 -0400 Subject: [PATCH 135/150] dyndns2: Wrap long list of group by attributes --- ddclient.in | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index 291b532..b8428e1 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4013,7 +4013,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', @@ -4027,7 +4026,20 @@ 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 + custom + login + mx + password + script + server + static + wantipv4 + wantipv6 + wildcard + ); + for my $group (group_hosts_by(\@_, @group_by_attrs)) { my @hosts = @{$group->{hosts}}; my %groupcfg = %{$group->{cfg}}; my $hosts = join(',', @hosts); From ecf935a4e2f022f3175b9329b165f4cd39631852 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Mon, 3 Jun 2024 02:07:50 -0500 Subject: [PATCH 136/150] he.net: Add support for Hurricane Electric provider The implementation is based on the existing dyndns2 protocol with a few differences: - The IPv4 and IPv6 addresses must be updated in separate calls. This is different from most of the other providers where both IPv4 and IPv6 addresses can be updated in a single call. Thus the existing dyndns2 protocol implementation cannot be reused for dns.he.net. - Multiple hosts are not supported by the provider. See: https://dns.he.net/docs.html --- ChangeLog.md | 4 ++- README.md | 1 + ddclient.conf.in | 7 ++++ ddclient.in | 89 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index b6e7622..37cf416 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -47,6 +47,8 @@ repository history](https://github.com/ddclient/ddclient/commits/master). [#676](https://github.com/ddclient/ddclient/pull/676) * `emailonly`: New `protocol` option that simply emails you when your IP address changes. [#654](https://github.com/ddclient/ddclient/pull/654) + * `he.net`: Added support for updating Hurricane Electric records. + [#682](https://github.com/ddclient/ddclient/pull/682) ### Bug fixes @@ -150,7 +152,7 @@ Refer to [v3.11 release plan discussions](https://github.com/ddclient/ddclient/i * Added support for domaindiscount24.com * Added support for njal.la - + ## 2022-05-15 v3.10.0_2 ### Bug fixes diff --git a/README.md b/README.md index c55db17..9309fce 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Dynamic DNS services currently supported include: * [Gandi](https://gandi.net) * [GoDaddy](https://www.godaddy.com) * [Google](https://domains.google) +* [Hurricane Electric](https://dns.he.net) * [Infomaniak](https://faq.infomaniak.com/2376) * [Loopia](https://www.loopia.se) * [Mythic Beasts](https://www.mythic-beasts.com/support/api/dnsv2/dynamic-dns) diff --git a/ddclient.conf.in b/ddclient.conf.in index e916958..12d8dcb 100644 --- a/ddclient.conf.in +++ b/ddclient.conf.in @@ -222,6 +222,13 @@ ssl=yes # use ssl-support. Works with # password=my-auto-generated-password # my.domain.tld, otherhost.domain.tld +## +## Hurricane Electric (dns.he.net) +## +# protocol=he.net, \ +# password=my-genereated-password \ +# myhost.example.com + ## ## Duckdns (http://www.duckdns.org/) ## diff --git a/ddclient.in b/ddclient.in index b8428e1..8de28e4 100755 --- a/ddclient.in +++ b/ddclient.in @@ -915,6 +915,17 @@ our %protocols = ( 'server' => setv(T_FQDNP, 0, 0, 'domains.google.com', undef), }, }, + 'he.net' => { + 'updateable' => undef, + 'update' => \&nic_henet_update, + 'examples' => \&nic_henet_examples, + 'variables' => { + %{$variables{'protocol-common-defaults'}}, + 'login' => undef, + 'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), + 'server' => setv(T_FQDNP, 0, 0, 'dyn.dns.he.net', undef), + }, + }, 'hetzner' => { 'force_update' => undef, 'update' => \&nic_hetzner_update, @@ -5862,6 +5873,84 @@ sub nic_googledomains_update { } } +###################################################################### +## nic_henet_examples +## +## written by Indrajit Raychaudhuri +## +###################################################################### +sub nic_henet_examples { + return <<"EoEXAMPLE"; +o 'he.net' + +The 'he.net' protocol is used by DNS service offered by dns.he.net. + +Configuration variables applicable to the 'he.net' protocol are: + protocol=he.net ## + password=service-password ## the password provided by the admin interface + fully.qualified.host ## the host registered with the service. + +Example ${program}.conf file entries: + ## single host update + protocol=he.net, \\ + password=my-genereated-password \\ + myhost.example.com +EoEXAMPLE +} + +###################################################################### +## nic_henet_update +###################################################################### +sub nic_henet_update { + debug("\nnic_henet_update -------------------"); + + my %errors = ( + 'badauth' => 'Bad authorization (username or password)', + 'badsys' => 'The system parameter given was not valid', + 'nohost' => 'The hostname specified does not exist in the database', + 'abuse' => 'The hostname specified is blocked for abuse', + 'nochg' => 'No update required; unnecessary attempts to change to the current address are considered abusive', + ); + + for my $h (@_) { + # The IPv4 and IPv6 addresses must be updated in separate API call. + for my $ipv ('4', '6') { + my $ip = delete($config{$h}{"wantipv$ipv"}) or next; + info("Setting IPv%s address to %s for %s", $ipv, $ip, $h); + verbose("UPDATE:", "updating %s", $h); + my $reply = geturl( + proxy => opt('proxy'), + url => "https://$config{$h}{'server'}/nic/update?hostname=$h&myip=$ip", + login => $h, + password => $config{$h}{'password'}, + ) // ''; + if ($reply eq '') { + failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'}); + next; + } + next if !header_ok($h, $reply); + # dyn.dns.he.net 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 ($line) = split(/\n/, $body, 2); + my ($status, $returnedip) = split(/ /, lc($line)); + $status = 'good' if $status eq 'nochg'; + $config{$h}{"status-ipv$ipv"} = $status; + if ($status ne 'good') { + if (exists($errors{$status})) { + failed("updating %s: %s: %s", $h, $status, $errors{$status}); + } else { + failed("updating %s: unexpected status: %s", $h, $line); + } + next; + } + success("updating %s: %s: IPv%s address set to %s", $h, $status, $ipv, $returnedip); + $config{$h}{"ipv$ipv"} = $returnedip; + $config{$h}{'mtime'} = $now; + } + } +} + ###################################################################### ## nic_mythicdyn_examples ## From 0973e9d83c1d12bdd9c1b181be51d1442c982c74 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Fri, 12 Jul 2024 16:48:40 -0500 Subject: [PATCH 137/150] Deprecate 'builtinweb' 'he' for 'he.net' for consistency with protocol --- ChangeLog.md | 5 +++++ ddclient.in | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 37cf416..ddf7074 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -19,6 +19,11 @@ repository history](https://github.com/ddclient/ddclient/commits/master). `Digest::SHA` is now required. Previously, `Digest::SHA1` was used (if available) as an alternative to `Digest::SHA`. [#685](https://github.com/ddclient/ddclient/pull/685) + * The `he` built-in web IP discovery service (`--webv4=he`, `--webv6=he`, and + `--web=he`) was renamed to `he.net` for consistency with the new `he.net` + protocol. The old name is still accepted but is deprecated and will be + removed in a future version of ddclient. + [#682](https://github.com/ddclient/ddclient/pull/682) ### New features diff --git a/ddclient.in b/ddclient.in index 8de28e4..2ba70ad 100755 --- a/ddclient.in +++ b/ddclient.in @@ -195,6 +195,7 @@ our %builtinweb = ( 'freedns' => {'url' => 'https://freedns.afraid.org/dynamic/check.php'}, 'googledomains' => {'url' => 'https://domains.google.com/checkip'}, # Deprecated! See https://github.com/ddclient/ddclient/issues/622 for more details 'he' => {'url' => 'https://checkip.dns.he.net/'}, + 'he.net' => {'url' => 'https://checkip.dns.he.net/'}, 'ip4only.me' => {'url' => 'https://ip4only.me/api/'}, 'ip6only.me' => {'url' => 'https://ip6only.me/api/'}, 'ipify-ipv4' => {'url' => 'https://api.ipify.org/'}, @@ -2913,6 +2914,7 @@ sub get_ip { $skip = opt('web-skip', $h); if (exists $builtinweb{$url}) { warning("googledomains is deprecated! See https://github.com/ddclient/ddclient/issues/622 for more info.") if ($url eq 'googledomains'); + warning("'he' is deprecated; use 'he.net' instead.") if ($url eq 'he'); $skip //= $builtinweb{$url}->{'skip'}; $url = $builtinweb{$url}->{'url'}; } @@ -3308,6 +3310,7 @@ sub get_ipv4 { $skip = opt('webv4-skip', $h); if (exists $builtinweb{$url}) { warning("googledomains is deprecated! See https://github.com/ddclient/ddclient/issues/622 for more info.") if ($url eq 'googledomains'); + warning("'he' is deprecated; use 'he.net' instead.") if ($url eq 'he'); $skip //= $builtinweb{$url}->{'skip'}; $url = $builtinweb{$url}->{'url'}; $arg = $url; @@ -3425,6 +3428,7 @@ sub get_ipv6 { $skip = opt('webv6-skip', $h); if (exists $builtinweb{$url}) { warning("googledomains is deprecated! See https://github.com/ddclient/ddclient/issues/622 for more info.") if ($url eq 'googledomains'); + warning("'he' is deprecated; use 'he.net' instead.") if ($url eq 'he'); $skip //= $builtinweb{$url}->{'skip'}; $url = $builtinweb{$url}->{'url'}; $arg = $url; From efa487bfb3cdd7fe9b82d6745bb7d0d562316452 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 13 Jul 2024 17:25:46 -0400 Subject: [PATCH 138/150] Note that `--web=googledomains` is deprecated in changelog --- ChangeLog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index ddf7074..90ee101 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -10,6 +10,11 @@ repository history](https://github.com/ddclient/ddclient/commits/master). * Unencrypted (plain) HTTP is now used instead of encrypted (TLS) HTTP if the URL uses `http://` instead of `https://`, even if the `--ssl` option is enabled. [#608](https://github.com/ddclient/ddclient/pull/608) + * The `googledomains` built-in web IP discovery service + (`--webv4=googledomains`, `--webv6=googledomains`, and + `--web=googledomains`) is deprecated due to the service shutting down. It + will be removed in a future version of ddclient. + [5b104ad1](https://github.com/ddclient/ddclient/commit/5b104ad116c023c3760129cab6e141f04f72b406) * The default web service for `--webv4` and `--webv6` has changed from Google Domains (which is shutting down) to ipify. [5b104ad1](https://github.com/ddclient/ddclient/commit/5b104ad116c023c3760129cab6e141f04f72b406) From 4d5a41672556b55f0b2ef1b50151629364f9f7c7 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 13 Jul 2024 17:01:39 -0400 Subject: [PATCH 139/150] Omit deprecated services from `--list-web-services` This also makes the handling of deprecated services a bit more general. --- ChangeLog.md | 3 +++ ddclient.in | 43 +++++++++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 90ee101..4f4c23e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -29,6 +29,9 @@ repository history](https://github.com/ddclient/ddclient/commits/master). protocol. The old name is still accepted but is deprecated and will be removed in a future version of ddclient. [#682](https://github.com/ddclient/ddclient/pull/682) + * 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) ### New features diff --git a/ddclient.in b/ddclient.in index 2ba70ad..1d11b79 100755 --- a/ddclient.in +++ b/ddclient.in @@ -193,8 +193,14 @@ sub T_POSTS { 'postscript' } our %builtinweb = ( 'dyndns' => {'url' => 'http://checkip.dyndns.org/', 'skip' => 'Current IP Address:'}, 'freedns' => {'url' => 'https://freedns.afraid.org/dynamic/check.php'}, - 'googledomains' => {'url' => 'https://domains.google.com/checkip'}, # Deprecated! See https://github.com/ddclient/ddclient/issues/622 for more details - 'he' => {'url' => 'https://checkip.dns.he.net/'}, + 'googledomains' => { + url => 'https://domains.google.com/checkip', + deprecated => 'See https://github.com/ddclient/ddclient/issues/622 for more info.', + }, + 'he' => { + url => 'https://checkip.dns.he.net/', + deprecated => "Use 'he.net' instead.", + }, 'he.net' => {'url' => 'https://checkip.dns.he.net/'}, 'ip4only.me' => {'url' => 'https://ip4only.me/api/'}, 'ip6only.me' => {'url' => 'https://ip6only.me/api/'}, @@ -1166,7 +1172,11 @@ $opt{'list-protocols'} = sub { exit(0); }; $opt{'list-web-services'} = sub { - printf("%s %s\n", $_, $builtinweb{$_}{url}) for sort(keys(%builtinweb)); + # This intentionally does not list deprecated services, although they are still accepted. + # Excluding deprecated services from the output discourages their selection by configuration + # wizards (e.g., Debian's debconf) that present this list to users. + printf("%s %s\n", $_, $builtinweb{$_}{url}) + for sort(grep(!$builtinweb{$_}{deprecated}, keys(%builtinweb))); exit(0); }; $opt{'version'} = sub { @@ -2912,11 +2922,10 @@ sub get_ip { } elsif ($use eq 'web') { $url = opt('web', $h) // ''; $skip = opt('web-skip', $h); - if (exists $builtinweb{$url}) { - warning("googledomains is deprecated! See https://github.com/ddclient/ddclient/issues/622 for more info.") if ($url eq 'googledomains'); - warning("'he' is deprecated; use 'he.net' instead.") if ($url eq 'he'); - $skip //= $builtinweb{$url}->{'skip'}; - $url = $builtinweb{$url}->{'url'}; + if (my $biw = $builtinweb{$url}) { + warning("'--web=$url' is deprecated! $biw->{deprecated}") if $biw->{deprecated}; + $skip //= $biw->{skip}; + $url = $biw->{url}; } $arg = $url; if ($url) { @@ -3308,11 +3317,10 @@ sub get_ipv4 { ## Obtain IPv4 address by accessing website at url in "webv4=" $url = $arg; $skip = opt('webv4-skip', $h); - if (exists $builtinweb{$url}) { - warning("googledomains is deprecated! See https://github.com/ddclient/ddclient/issues/622 for more info.") if ($url eq 'googledomains'); - warning("'he' is deprecated; use 'he.net' instead.") if ($url eq 'he'); - $skip //= $builtinweb{$url}->{'skip'}; - $url = $builtinweb{$url}->{'url'}; + if (my $biw = $builtinweb{$url}) { + warning("'--webv4=$url' is deprecated! $biw->{deprecated}") if $biw->{deprecated}; + $skip //= $biw->{skip}; + $url = $biw->{url}; $arg = $url; } if ($url) { @@ -3426,11 +3434,10 @@ sub get_ipv6 { if (!defined(opt('webv6-skip', $h)) && defined(opt('web-skip', $h))); $url = $arg; $skip = opt('webv6-skip', $h); - if (exists $builtinweb{$url}) { - warning("googledomains is deprecated! See https://github.com/ddclient/ddclient/issues/622 for more info.") if ($url eq 'googledomains'); - warning("'he' is deprecated; use 'he.net' instead.") if ($url eq 'he'); - $skip //= $builtinweb{$url}->{'skip'}; - $url = $builtinweb{$url}->{'url'}; + if (my $biw = $builtinweb{$url}) { + warning("'--webv6=$url' is deprecated! $biw->{deprecated}") if $biw->{deprecated}; + $skip //= $biw->{skip}; + $url = $biw->{url}; $arg = $url; } if ($url) { From c31668b41379a39e7d7f86d9e1c52ce86b7775cd Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 13 Jul 2024 18:08:53 -0400 Subject: [PATCH 140/150] dyndns2: Honor `http:` or `https:` scheme in `server` variable or fall back to the value of the `ssl` variable if no `http:` or `https:` scheme is present. --- ChangeLog.md | 4 ++++ ddclient.in | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 4f4c23e..fe98389 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -62,6 +62,10 @@ repository history](https://github.com/ddclient/ddclient/commits/master). address changes. [#654](https://github.com/ddclient/ddclient/pull/654) * `he.net`: Added support for updating Hurricane Electric records. [#682](https://github.com/ddclient/ddclient/pull/682) + * `dyndns2`: The `server` option can now include `http://` or `https://` to + control the use of TLS. If omitted, the value of the `ssl` option is used + to determine the scheme. + [#703](https://github.com/ddclient/ddclient/pull/703) ### Bug fixes diff --git a/ddclient.in b/ddclient.in index 1d11b79..89f40f7 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4072,8 +4072,7 @@ sub nic_dyndns2_update { 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); - ## Select the DynDNS system to update - my $url = "http://$groupcfg{'server'}$groupcfg{'script'}?system="; + 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'}; From 6fbb7eb3dc55608e38dacf2181e21f85ab9fae19 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 13 Jul 2024 18:10:59 -0400 Subject: [PATCH 141/150] domeneshop: Honor `http:` or `https:` scheme in `server` variable --- ChangeLog.md | 6 +++--- ddclient.in | 9 +-------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index fe98389..cc2e604 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -62,9 +62,9 @@ repository history](https://github.com/ddclient/ddclient/commits/master). address changes. [#654](https://github.com/ddclient/ddclient/pull/654) * `he.net`: Added support for updating Hurricane Electric records. [#682](https://github.com/ddclient/ddclient/pull/682) - * `dyndns2`: The `server` option can now include `http://` or `https://` to - control the use of TLS. If omitted, the value of the `ssl` option is used - to determine the scheme. + * `dyndns2`, `domeneshop`: The `server` option can now include `http://` or + `https://` to control the use of TLS. If omitted, the value of the `ssl` + option is used to determine the scheme. [#703](https://github.com/ddclient/ddclient/pull/703) ### Bug fixes diff --git a/ddclient.in b/ddclient.in index 89f40f7..d2f149b 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4570,16 +4570,9 @@ sub nic_domeneshop_update { my $ip = delete $config{$h}{'wantip'}; info("Setting IP address to %s for %s", $ip, $h); verbose("UPDATE:", "Updating %s", $h); - - # Set the URL that we're going to to update - my $url; - $url = $globals{'ssl'} ? "https://" : "http://"; - $url .= "$config{$h}{'server'}$endpointPath?hostname=$h&myip=$ip"; - - # Try to get URL my $reply = geturl( proxy => opt('proxy'), - url => $url, + url => "$config{$h}{'server'}$endpointPath?hostname=$h&myip=$ip", login => $config{$h}{'login'}, password => $config{$h}{'password'}, ); From 469c5a072e9ee475d33d5acc857d44277ec1df3f Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 13 Jul 2024 18:12:49 -0400 Subject: [PATCH 142/150] dnsmadeeasy: Honor `http:` or `https:` scheme in `server` variable --- ChangeLog.md | 6 +++--- ddclient.in | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index cc2e604..ca430c7 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -62,9 +62,9 @@ repository history](https://github.com/ddclient/ddclient/commits/master). address changes. [#654](https://github.com/ddclient/ddclient/pull/654) * `he.net`: Added support for updating Hurricane Electric records. [#682](https://github.com/ddclient/ddclient/pull/682) - * `dyndns2`, `domeneshop`: The `server` option can now include `http://` or - `https://` to control the use of TLS. If omitted, the value of the `ssl` - option is used to determine the scheme. + * `dyndns2`, `domeneshop`, `dnsmadeeasy`: The `server` option can now include + `http://` or `https://` to control the use of TLS. If omitted, the value of + the `ssl` option is used to determine the scheme. [#703](https://github.com/ddclient/ddclient/pull/703) ### Bug fixes diff --git a/ddclient.in b/ddclient.in index d2f149b..3963c0d 100755 --- a/ddclient.in +++ b/ddclient.in @@ -7025,11 +7025,7 @@ sub nic_dnsmadeeasy_update { my $ip = delete $config{$h}{'wantip'}; info("Setting IP address to %s for %s", $ip, $h); verbose("UPDATE:", "Updating %s", $h); - - # Set the URL that we're going to to update - my $url; - $url = $globals{'ssl'} ? "https://" : "http://"; - $url .= $config{$h}{'server'} . $config{$h}{'script'}; + my $url = $config{$h}{'server'} . $config{$h}{'script'}; $url .= "?username=$config{$h}{'login'}"; $url .= "&password=$config{$h}{'password'}"; $url .= "&ip=$ip"; From 0ed297085240d9738d04bc32ac9b0324c3a44347 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 13 Jul 2024 18:13:45 -0400 Subject: [PATCH 143/150] keysystems: Honor `http:` or `https:` scheme in `server` variable or fall back to the value of the `ssl` variable if neither `http:` nor `https:` is present. --- ChangeLog.md | 6 +++--- ddclient.in | 5 +---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index ca430c7..30deeb5 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -62,9 +62,9 @@ repository history](https://github.com/ddclient/ddclient/commits/master). address changes. [#654](https://github.com/ddclient/ddclient/pull/654) * `he.net`: Added support for updating Hurricane Electric records. [#682](https://github.com/ddclient/ddclient/pull/682) - * `dyndns2`, `domeneshop`, `dnsmadeeasy`: The `server` option can now include - `http://` or `https://` to control the use of TLS. If omitted, the value of - the `ssl` option is used to determine the scheme. + * `dyndns2`, `domeneshop`, `dnsmadeeasy`, `keysystems`: The `server` option + can now include `http://` or `https://` to control the use of TLS. If + omitted, the value of the `ssl` option is used to determine the scheme. [#703](https://github.com/ddclient/ddclient/pull/703) ### Bug fixes diff --git a/ddclient.in b/ddclient.in index 3963c0d..1e2cd18 100755 --- a/ddclient.in +++ b/ddclient.in @@ -7654,10 +7654,7 @@ sub nic_keysystems_update { for my $h (@_) { my $ip = delete $config{$h}{'wantip'}; info("KEYSYSTEMS setting IP address to %s for %s", $ip, $h); - - my $url = "http://$config{$h}{'server'}/update.php?hostname=$h&password=$config{$h}{'password'}&ip=$ip"; - - # Try to get URL + my $url = "$config{$h}{'server'}/update.php?hostname=$h&password=$config{$h}{'password'}&ip=$ip"; my $reply = geturl(proxy => opt('proxy'), url => $url) // ''; # No response, declare as failed From 7754c6510343aedaa7537f5c56c28db2c25781a9 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 13 Jul 2024 18:23:01 -0400 Subject: [PATCH 144/150] woima: Honor `http:` or `https:` scheme in `server` variable --- ChangeLog.md | 6 +++--- ddclient.in | 12 +++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 30deeb5..d60effc 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -62,9 +62,9 @@ repository history](https://github.com/ddclient/ddclient/commits/master). address changes. [#654](https://github.com/ddclient/ddclient/pull/654) * `he.net`: Added support for updating Hurricane Electric records. [#682](https://github.com/ddclient/ddclient/pull/682) - * `dyndns2`, `domeneshop`, `dnsmadeeasy`, `keysystems`: The `server` option - can now include `http://` or `https://` to control the use of TLS. If - omitted, the value of the `ssl` option is used to determine the scheme. + * `dyndns2`, `domeneshop`, `dnsmadeeasy`, `keysystems`, `woima`: The `server` + option can now include `http://` or `https://` to control the use of TLS. + If omitted, the value of the `ssl` option is used to determine the scheme. [#703](https://github.com/ddclient/ddclient/pull/703) ### Bug fixes diff --git a/ddclient.in b/ddclient.in index 1e2cd18..111516a 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1054,7 +1054,12 @@ our %protocols = ( 'custom' => setv(T_BOOL, 0, 1, 0, undef), 'mx' => setv(T_OFQDN, 0, 1, undef, undef), 'script' => setv(T_STRING, 0, 1, '/nic/update', undef), - 'server' => setv(T_FQDNP, 0, 0, 'dyn.woima.fi', undef), + # As of 2024-07-13, dyn.woima.fi does not have a valid TLS certificate so the `http:` + # scheme is explicitly specified here. Once a proper certificate is deployed, or if + # the user overrides certificate validation, the user can manually set the `server` + # option and either change http: to https: or omit the scheme (in which case ddclient + # will use the value of the `ssl` option to determine the scheme). + 'server' => setv(T_FQDNP, 0, 0, 'http://dyn.woima.fi', undef), 'static' => setv(T_BOOL, 0, 1, 0, undef), 'wildcard' => setv(T_BOOL, 0, 1, 0, undef), }, @@ -6802,10 +6807,7 @@ sub nic_woima_update { info("setting IP address to %s for %s", $ip, $h); verbose("UPDATE:", "updating %s", $h); - - ## Select the DynDNS system to update - ## TODO: endpoint does not support https with functioning certificate. Remove? - my $url = "http://$config{$h}{'server'}$config{$h}{'script'}?system="; + my $url = "$config{$h}{'server'}$config{$h}{'script'}?system="; if ($config{$h}{'custom'}) { warning("updating %s: 'custom' and 'static' may not be used together. ('static' ignored)", $h) if $config{$h}{'static'}; From 5931a7150c6ac132dce7bb77a0d1f5153b8df3a1 Mon Sep 17 00:00:00 2001 From: Bodenhaltung Date: Sun, 14 Jul 2024 21:31:51 +0200 Subject: [PATCH 145/150] Add dnsHome.de --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9309fce..b6d60a8 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Dynamic DNS services currently supported include: * [DonDominio](https://www.dondominio.com) * [DNS Made Easy](https://dnsmadeeasy.com) * [DNSExit](https://dnsexit.com/dns/dns-api) +* [dnsHome.de](https://www.dnshome.de) * [Domeneshop](https://api.domeneshop.no/docs/#tag/ddns/paths/~1dyndns~1update/get) * [DslReports](https://www.dslreports.com) * [Duck DNS](https://duckdns.org) From 6284133b1c51794a8bace7ada6d09b6fd476702e Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sun, 14 Jul 2024 18:18:03 -0400 Subject: [PATCH 146/150] Add dnsHome.de example config --- ddclient.conf.in | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ddclient.conf.in b/ddclient.conf.in index 12d8dcb..22b5d0e 100644 --- a/ddclient.conf.in +++ b/ddclient.conf.in @@ -404,3 +404,12 @@ ssl=yes # use ssl-support. Works with ## # protocol=emailonly # host.example.com + +## +## dnsHome.de +## +# protocol=dyndns2 \ +# server=www.dnshome.de \ +# login=subdomain.domain.tld \ +# password=your_password \ +# subdomain.domain.tld From 13369804a0b8d7a24ff6a6e3655a47ebe7491010 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sun, 14 Jul 2024 18:49:06 -0400 Subject: [PATCH 147/150] Improve documentation for `--ssl` option Also move it to the top of the sample config file due to its importance. --- ddclient.conf.in | 8 ++++++-- ddclient.in | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ddclient.conf.in b/ddclient.conf.in index 22b5d0e..65a9650 100644 --- a/ddclient.conf.in +++ b/ddclient.conf.in @@ -16,13 +16,17 @@ ## are mentioned here. ## ###################################################################### + +## Use encryption (TLS) when the scheme (either "http://" or "https://") is +## missing from a URL. Defaults to "no" for compatibility reasons, but you are +## strongly encouraged to set this to "yes". +ssl=yes + daemon=300 # check every 300 seconds syslog=yes # log update msgs to syslog mail=root # mail all msgs to root mail-failure=root # mail failed update msgs to root pid=@runstatedir@/ddclient.pid # record PID in file. -ssl=yes # use ssl-support. Works with - # ssl-library # postscript=script # run script after updating. The # new IP is added as argument. # diff --git a/ddclient.in b/ddclient.in index 111516a..82ace95 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1259,7 +1259,7 @@ my @opt = ( "", ["options", "=s", "--options==[,=,...]\n : optional per-service arguments (see below)"], "", - ["ssl", "!", "--{no}ssl : do updates over encrypted SSL connection"], + ["ssl", "!", '--{no}ssl : use encryption (TLS) when the scheme (either "http://" or "https://") is missing from a URL'], ["ssl_ca_dir", "=s", "--ssl_ca_dir= : look in for certificates of trusted certificate authorities (default: auto-detect)"], ["ssl_ca_file", "=s", "--ssl_ca_file= : look at for certificates of trusted certificate authorities (default: auto-detect)"], ["fw-ssl-validate", "!", "--{no}fw-ssl-validate : Validate SSL certificate when retrieving IP address from firewall"], From f0de73e8c468b18015a7807203e2e3351d159a83 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sun, 14 Jul 2024 18:36:35 -0400 Subject: [PATCH 148/150] Enable `--ssl` by default In this day and age there's no good reason to prefer plain HTTP over HTTPS, and security is more important than potential compatibility concerns. --- ChangeLog.md | 2 ++ ddclient.conf.in | 5 ++--- ddclient.in | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index d60effc..36db792 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -7,6 +7,8 @@ repository history](https://github.com/ddclient/ddclient/commits/master). ### Breaking changes + * The `--ssl` option is now enabled by default. + [#705](https://github.com/ddclient/ddclient/pull/705) * Unencrypted (plain) HTTP is now used instead of encrypted (TLS) HTTP if the URL uses `http://` instead of `https://`, even if the `--ssl` option is enabled. [#608](https://github.com/ddclient/ddclient/pull/608) diff --git a/ddclient.conf.in b/ddclient.conf.in index 65a9650..19df639 100644 --- a/ddclient.conf.in +++ b/ddclient.conf.in @@ -18,9 +18,8 @@ ###################################################################### ## Use encryption (TLS) when the scheme (either "http://" or "https://") is -## missing from a URL. Defaults to "no" for compatibility reasons, but you are -## strongly encouraged to set this to "yes". -ssl=yes +## missing from a URL. Defaults to "yes". +#ssl=yes daemon=300 # check every 300 seconds syslog=yes # log update msgs to syslog diff --git a/ddclient.in b/ddclient.in index 82ace95..7c06024 100755 --- a/ddclient.in +++ b/ddclient.in @@ -604,7 +604,7 @@ our %variables = ( 'timeout' => setv(T_DELAY, 0, 0, interval('120s'), interval('120s')), 'retry' => setv(T_BOOL, 0, 0, 0, undef), 'force' => setv(T_BOOL, 0, 0, 0, undef), - 'ssl' => setv(T_BOOL, 0, 0, 0, undef), + 'ssl' => setv(T_BOOL, 0, 0, 1, undef), 'syslog' => setv(T_BOOL, 0, 0, 0, undef), 'facility' => setv(T_STRING,0, 0, 'daemon', undef), 'priority' => setv(T_STRING,0, 0, 'notice', undef), From c6581b03f2fcc7841b74dc9d5a126c4177dfc2ed Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sun, 14 Jul 2024 19:07:55 -0400 Subject: [PATCH 149/150] Bump version to v4.0.0~alpha The list of breaking changes has become significant enough to warrant bumping the major version number. --- ChangeLog.md | 2 +- README.md | 4 ++-- ddclient.in | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 36db792..ec8bc1a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,7 +3,7 @@ This document describes notable changes. For details, see the [source code repository history](https://github.com/ddclient/ddclient/commits/master). -## v3.12.0~alpha (unreleased work-in-progress) +## v4.0.0~alpha (unreleased work-in-progress) ### Breaking changes diff --git a/README.md b/README.md index b6d60a8..2c989aa 100644 --- a/README.md +++ b/README.md @@ -136,11 +136,11 @@ If a fix is committed but not yet part of any tagged release, the notes here wil The `ssl` parameter forces all connections to use HTTPS. While technically working as expected, this behavior keeps coming up as a pain point when using HTTP-only IP querying sites such as http://checkip.dyndns.org. Starting with -v3.12.0, the behavior is changed to respect `http://` in a URL. A separate +v4.0.0, the behavior is changed to respect `http://` in a URL. A separate parameter to disallow all HTTP connections or warn about them may be added later. -**Fix**: v3.12.0 uses HTTP to connect to URLs starting with `http://`. See +**Fix**: v4.0.0 uses HTTP to connect to URLs starting with `http://`. See [here](https://github.com/ddclient/ddclient/pull/608) for more info. **Workaround**: Disable the SSL parameter diff --git a/ddclient.in b/ddclient.in index 7c06024..2080379 100755 --- a/ddclient.in +++ b/ddclient.in @@ -63,7 +63,7 @@ use Sys::Hostname; # # For consistency and to match user expectations, the release part of the version is always three # components: MAJOR.MINOR.PATCH. -use version 0.77; our $VERSION = version->declare('v3.12.0.0_0'); +use version 0.77; our $VERSION = version->declare('v4.0.0.0_0'); sub parse_version { my ($v) = @_; From 2f4b0859bd7a30ad9905fe67a92c3f23ffa995a8 Mon Sep 17 00:00:00 2001 From: woolflare <167610549+woolflare@users.noreply.github.com> Date: Sat, 6 Jul 2024 04:19:23 +0800 Subject: [PATCH 150/150] Add DDNS.FM support --- ChangeLog.md | 2 ++ README.md | 1 + ddclient.conf.in | 8 ++++++ ddclient.in | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index ec8bc1a..19633f3 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -68,6 +68,8 @@ repository history](https://github.com/ddclient/ddclient/commits/master). option can now include `http://` or `https://` to control the use of TLS. If omitted, the value of the `ssl` option is used to determine the scheme. [#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) ### Bug fixes diff --git a/README.md b/README.md index 2c989aa..5b3275a 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Dynamic DNS services currently supported include: * [ChangeIP](https://www.changeip.com) * [CloudFlare](https://www.cloudflare.com) * [ClouDNS](https://www.cloudns.net) +* [DDNS.fm](https://www.ddns.fm/) * [DigitalOcean](https://www.digitalocean.com/) * [dinahosting](https://dinahosting.com) * [DonDominio](https://www.dondominio.com) diff --git a/ddclient.conf.in b/ddclient.conf.in index 19df639..8a336ac 100644 --- a/ddclient.conf.in +++ b/ddclient.conf.in @@ -248,6 +248,14 @@ pid=@runstatedir@/ddclient.pid # record PID in file. # password=my-token # myhost +## +## DDNS.FM (https://ddns.fm/) +## +# +# protocol=ddns.fm, +# password=my-token +# myhost.example.com + ## ## MyOnlinePortal (http://myonlineportal.net) ## diff --git a/ddclient.in b/ddclient.in index 2080379..409bd3b 100755 --- a/ddclient.in +++ b/ddclient.in @@ -762,6 +762,16 @@ our %protocols = ( 'dynurl' => setv(T_STRING, 1, 0, undef, undef), }, }, + 'ddns.fm' => { + 'force_update' => undef, + 'update' => \&nic_ddnsfm_update, + 'examples' => \&nic_ddnsfm_examples, + 'variables' => { + %{$variables{'protocol-common-defaults'}}, + 'login' => undef, + 'server' => setv(T_FQDNP, 0, 0, 'https://api.ddns.fm', undef), + }, + }, 'digitalocean' => { 'force_update' => undef, 'update' => \&nic_digitalocean_update, @@ -6730,6 +6740,62 @@ sub nic_freemyip_update { } } +###################################################################### +## nic_ddnsfm_examples +###################################################################### +sub nic_ddnsfm_examples { + return <<"EoEXAMPLE"; +o 'ddns.fm' + +The 'ddns.fm' protocol is used by the free +dynamic DNS service available at ddns.fm. +API is documented here: https://ddns.fm/docs + +Configuration variables applicable to the 'ddns.fm' protocol are: + protocol=ddns.fm ## + password=service-key ## key for your domain + non-fully.qualified.host ## the host registered with the service. + +Example ${program}.conf file entries: + ## single host update + protocol=ddns.fm, \\ + password=your_ddns_key, \\ + myhost.example.com + +EoEXAMPLE +} + +###################################################################### +## nic_ddnsfm_update +###################################################################### +sub nic_ddnsfm_update { + debug("\nnic_ddnsfm_update -------------------"); + for my $h (@_) { + # ddns.fm behavior as of 2024-07-14: + # - IPv4 and IPv6 addresses cannot be updated simultaneously. + # - IPv4 updates do not affect the IPv6 AAAA record (if present). + # - IPv6 updates do not affect the IPv4 A record (if present). + for my $ipv ('4', '6') { + my $ip = delete $config{$h}{"wantipv$ipv"} or next; + info("setting IPv$ipv address to $ip for $h"); + verbose("UPDATE:", "updating %s", $h); + my $reply = geturl( + proxy => opt('proxy'), + url => "$config{$h}{server}/update?key=$config{$h}{password}&domain=$h&myip=$ip", + ); + if (!$reply) { + failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'}); + next; + } + next if !header_ok($h, $reply); + $config{$h}{"ipv$ipv"} = $ip; + $config{$h}{'mtime'} = $now; + $config{$h}{"status-ipv$ipv"} = 'good'; + success("updating $h: good: IPv$ipv address set to $ip"); + } + } +} + ###################################################################### ## nic_woima_examples ######################################################################