From 4d9d0646cfa89eb3b6f78605480ba3587fcf868b Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 12 Jul 2024 15:54:26 -0400 Subject: [PATCH 01/20] 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 02/20] 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 03/20] 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 04/20] 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 05/20] 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 06/20] 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 07/20] 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 08/20] 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 09/20] 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 10/20] 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 11/20] 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 12/20] 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 13/20] 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 14/20] 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 15/20] 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 16/20] 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 17/20] 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 18/20] 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 19/20] 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 20/20] 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 \