diff --git a/ChangeLog.md b/ChangeLog.md index 1a24aad..ee8434f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,11 +3,17 @@ This document describes notable changes. For details, see the [source code repository history](https://github.com/ddclient/ddclient/commits/master). -## 2023-XX-XX v3.11.0_1 +## 20XX-XX-XX v3.11.1_0 (WIP) + +## 2023-10-21 v3.11.0 +This version is the same as v3.11.0_1 (except for the updated version number in the code). +Refer to [v3.11 release plan discussions](https://github.com/ddclient/ddclient/issues/552) for the reasons. + +## 2023-10-15 v3.11.0_1 ### Breaking changes - * ddclient now requires curl. + * ddclient now requires curl. The Perl modules IO::Socket::IP and IO::Socket::SSL are no longer used. * ddclient no longer ships any example files for init systems that use `/etc/init.d`. This was done because those files where effectively unmaintained, untested by the developers and only updated by downstream distros. If you where relying on those files, please copy them into your packaging. diff --git a/README.md b/README.md index c58f7b4..1c0adfa 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Dynamic DNS services currently supported include: * [ChangeIP](https://www.changeip.com) * [CloudFlare](https://www.cloudflare.com) * [ClouDNS](https://www.cloudns.net) +* [DigitalOcean](https://www.digitalocean.com/) * [dinahosting](https://dinahosting.com) * [DonDominio](https://www.dondominio.com) * [DNS Made Easy](https://dnsmadeeasy.com) diff --git a/configure.ac b/configure.ac index 53dc3b8..d4a4152 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_PREREQ([2.63]) -AC_INIT([ddclient], [3.11.0]) +AC_INIT([ddclient], [3.11.1_0]) AC_CONFIG_SRCDIR([ddclient.in]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/ddclient.in b/ddclient.in index 0403558..477009b 100755 --- a/ddclient.in +++ b/ddclient.in @@ -535,9 +535,9 @@ my %variables = ( 'dnsexit2-common-defaults' => { 'ssl' => setv(T_BOOL, 0, 0, 1, undef), 'server' => setv(T_FQDNP, 1, 0, 'api.dnsexit.com', undef), - 'path' => setv(T_STRING, 0, 1, '/dns/', undef), - 'record-type' => setv(T_STRING, 1, 0, 'A', undef), + 'path' => setv(T_STRING, 0, 0, '/dns/', undef), 'ttl' => setv(T_NUMBER, 1, 0, 5, 0), + 'zone' => setv(T_STRING, 0, 0, undef, undef) }, 'regfishde-common-defaults' => { 'server' => setv(T_FQDNP, 1, 0, 'dyndns.regfish.de', undef), @@ -4060,24 +4060,29 @@ sub nic_dnsexit2_examples { return <<"EoEXAMPLE"; o 'dnsexit2' -The 'dnsexit2' protocol is the new API protocol used by the dynamic hostname services -of the 'DNSExit' dns services. This is currently used by the free -dynamic DNS service offered by www.dnsexit.com. +The 'dnsexit2' protocol is the updated protocol for the (free) dynamic hostname services +of 'DNSExit' (www.dnsexit.com). Their API is accepting JSON payload. Configuration variables applicable to the 'dnsexit2' protocol are: protocol=dnsexit2 ## password=YourAPIKey ## API Key of your account. server=api.dnsexit.com ## defaults to api.dnsexit.com. path=/dns/ ## defaults to /dns/. - record-type=A ## defaults to A record. ttl=5 ## defaults to 5 minutes. + zone='' ## defaults to empty, which assumes the zone is equal to the fully.qualified.host (is root of your DNSExit domain). fully.qualified.host ## the host registered with the service. Example ${program}.conf file entries: ## single host update protocol=dnsexit2 password=YourAPIKey - fully.qualified.host + yourown.publicvm.com + + ## two hosts (which must be) on the same zone + protocol=dnsexit2 + password=YourAPIKey + zone=yourown.publicvm.com + host1.yourown.publicvm.com,host2.yourown.publicvm.com EoEXAMPLE } @@ -4091,7 +4096,7 @@ EoEXAMPLE sub nic_dnsexit2_update { debug("\nnic_dnsexit2_update -------------------"); - ## Update each configured host + ## Update each configured host (hosts cannot be grouped on this API) foreach my $h (@_) { # All the known status my %status = ( @@ -4104,25 +4109,45 @@ sub nic_dnsexit2_update { '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 $ip = delete $config{$h}{'wantip'}; - info("Going to update IP address to %s for %s.", $ip, $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 + my %total_payload; + + foreach my $ip ($ipv4, $ipv6){ + next if (!$ip); + 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); + $config{$h}{'status-ipv$ipv'} = 'failed'; + + # One key per ipv (4 or 6) + my %payload = (name => $h, type => $type, content => $ip, ttl => $config{$h}{'ttl'}); + @total_payload{$ipv} = \%payload; + }; # Set the URL of the API endpoint my $url = "https://$config{$h}{'server'}$config{$h}{'path'}"; - # Set JSON payload - my $data = encode_json({ - apikey => $config{$h}{'password'}, - domain => $h, - update => { - type => $config{$h}{'record-type'}, - name => $h, - content => $ip, - ttl => $config{$h}{'ttl'}}, - }); - # Set additional headers - my $header = "Content-Type: application/json\n"; - $header .= "Accept: application/json"; + 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( @@ -4130,32 +4155,27 @@ sub nic_dnsexit2_update { url => $url, headers => $header, method => 'POST', - data => $data, + data => $data ); # No reply, declare as failed - if (!defined($reply) || !$reply) { + unless ($reply && header_ok($h, $reply)){ failed("updating %s: Could not connect to %s%s.", $h, $config{$h}{'server'}, $config{$h}{'path'}); - $config{$h}{'status'} = 'failed'; last; }; # Reply found debug("%s", $reply); - # $ok is mandatory? - my $ok = header_ok($h, $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 != "200"){ - failed("Failed to update Host\n%s to IP:%s", $h, $ip); + 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'); - $config{$h}{'status'} = 'failed'; - last; + next; } # Strip HTTP response headers @@ -4192,31 +4212,30 @@ sub nic_dnsexit2_update { # Handle statuses if ($status eq 'good') { - $config{$h}{'ip'} = $ip; $config{$h}{'mtime'} = $now; - $config{$h}{'status'} = 'good'; - success("%s", $message); - success("Updated %s successfully to IP address %s at time %s", $h, $ip, prettytime($config{$h}{'mtime'})); + my $tracked_ipv; + foreach $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); + success("Updated %s successfully to IPv$tracked_ipv address %s at time %s", $h, $total_payload{$tracked_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); - $config{$h}{'status'} = 'failed'; } else { failed("This should not be possible"); - $config{$h}{'status'} = 'failed'; } } else { failed("Status code %s is unknown!", $response->{'code'}); - $config{$h}{'status'} = 'failed'; } } else { failed("Did not receive expected \"code\" and \"message\" keys in server response."); failed("Response:"); failed("%s", $response); - $config{$h}{'status'} = 'failed'; } } }