From ec4d83bc3f7a30127ba6fe7441a15ae1f1f0ee63 Mon Sep 17 00:00:00 2001 From: jortkoopmans Date: Sat, 5 Nov 2022 22:39:38 +0100 Subject: [PATCH 01/19] Add support for new DNSExit API (adding protocol dnsexit2). --- README.md | 1 + ddclient.conf.in | 7 ++ ddclient.in | 189 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 196 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fc9c368..faca1a5 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ Dynamic DNS services currently supported include: dinahosting - See https://dinahosting.com Gandi - See https://gandi.net dnsexit - See https://dnsexit.com/ for details + dnsexit2 - See https://dnsexit.com/dns/dns-api/ for details 1984.is - See https://www.1984.is/product/freedns/ for details Njal.la - See https://njal.la/docs/ddns/ regfish.de - See https://www.regfish.de/domains/dyndns/ for details diff --git a/ddclient.conf.in b/ddclient.conf.in index 3c1811e..fb50ab3 100644 --- a/ddclient.conf.in +++ b/ddclient.conf.in @@ -317,6 +317,13 @@ ssl=yes # use ssl-support. Works with #password=mypassword, \ #subdomain-1.domain.com,subdomain-2.domain.com +## +## dnsexit2 (API method www.dnsexit.com) +## +#protocol=dnsexit2 +#password=MyAPIKey +#subdomain-1.domain.com,subdomain-2.domain.com + ## ## domeneshop (www.domeneshop.no) ## diff --git a/ddclient.in b/ddclient.in index 8dde8e5..f6487e6 100755 --- a/ddclient.in +++ b/ddclient.in @@ -548,6 +548,13 @@ my %variables = ( 'script' => setv(T_STRING, 0, 1, '/RemoteUpdate.sv', undef), 'min-error-interval' => setv(T_DELAY, 0, 0, interval('8m'), 0), }, + '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), + 'ttl' => setv(T_NUMBER, 1, 0, 5, 0), + }, 'regfishde-common-defaults' => { 'server' => setv(T_FQDNP, 1, 0, 'dyndns.regfish.de', undef), 'login' => setv(T_LOGIN, 0, 0, 0, 'unused', undef), @@ -959,6 +966,19 @@ my %services = ( $variables{'service-common-defaults'}, ), }, + 'dnsexit2' => { + 'updateable' => undef, + 'update' => \&nic_dnsexit2_update, + 'examples' => \&nic_dnsexit2_examples, + 'variables' => { + %{$variables{'service-common-defaults'}}, + %{$variables{'dnsexit2-common-defaults'}}, + # nic_updateable() assumes that every service uses a username/login but that is + # not true for the DNSExit API. Silence warnings by redefining the username variable + # as non-required with value unused. + 'login' => setv(T_STRING, 0, 0, 'unused', undef), + }, + }, 'regfishde' => { 'updateable' => undef, 'update' => \&nic_regfishde_update, @@ -1815,7 +1835,7 @@ sub init_config { $proto = opt('protocol') if !defined($proto); load_sha1_support($proto) if (grep (/^$proto$/, ("freedns", "nfsn"))); - load_json_support($proto) if (grep (/^$proto$/, ("1984", "cloudflare", "digitalocean", "gandi", "godaddy", "hetzner", "yandex", "nfsn", "njalla", "porkbun"))); + load_json_support($proto) if (grep (/^$proto$/, ("1984", "cloudflare", "digitalocean", "gandi", "godaddy", "hetzner", "yandex", "nfsn", "njalla", "porkbun", "dnsexit2"))); if (!exists($services{$proto})) { warning("skipping host: %s: unrecognized protocol '%s'", $h, $proto); @@ -4352,6 +4372,173 @@ sub nic_dnsexit_update { } } ###################################################################### +## nic_dnsexit2_examples +###################################################################### +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. + +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. + fully.qualified.host ## the host registered with the service. + +Example ${program}.conf file entries: + ## single host update + protocol=dnsexit2 + password=YourAPIKey + fully.qualified.host + +EoEXAMPLE +} +###################################################################### +## nic_dnsexit2_update +## +## by @jortkoopmans +## based on https://dnsexit.com/dns/dns-api/ +## +###################################################################### +sub nic_dnsexit2_update { + debug("\nnic_dnsexit2_update -------------------"); + + ## Update each configured host + foreach my $h (@_) { + # All the known status + my %status = ( + '0' => [ 'good', 'Success! Actions got executed successfully.' ], + '1' => [ 'warning', 'Some execution problems. May not indicate actions failures. Some action may got executed fine and some may have problems.' ], + '2' => [ 'badauth', 'API Key Authentication Error. The API Key is missing or wrong.' ], + '3' => [ 'error', 'Missing Required Definitions. Your JSON file may missing some required definitions.' ], + '4' => [ 'error', 'JSON Data Syntax Error. Your JSON file has syntax error.' ], + '5' => [ 'error', 'JSON Defined Record Type not Supported. Your JSON may try to update some record type not supported by our system.' ], + '6' => [ 'error', 'System Error. Our system problem. May not be your problem. Contact our support if you got such error.' ], + '7' => [ 'error', 'Error getting post data. Our server has problem to receive your JSON posting.' ], + ); + my $ip = delete $config{$h}{'wantip'}; + info("Going to update IP address to %s for %s.", $ip, $h); + # 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"; + + # Make the call + my $reply = geturl( + proxy => opt('proxy'), + url => $url, + headers => $header, + method => 'POST', + data => $data, + ); + + # No reply, declare as failed + if (!defined($reply) || !$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); + failed("HTTP response code\n%s", $http_status); + failed("Full reply\n%s", $reply) unless opt('verbose'); + $config{$h}{'status'} = 'failed'; + last; + } + + # Strip HTTP response headers + (my $strip_status) = ($reply =~ s/^[\s\S]*?(?=\{"code":)//); + debug("strip_status"); + debug("%s", $strip_status); + if ($strip_status) { + debug("HTTP headers are stripped."); + } + else { + warning("Unexpected: no HTTP headers stripped!"); + } + + # Decode the remaining reply, it should be JSON. + my $response = decode_json($reply); + + # It should at least have a 'code' and 'message'. + if (defined($response->{'code'}) and defined($response->{'message'})) { + if (exists $status{$response->{'code'}}) { + # Add the server response data to the applicable array + push( @{ $status {$response->{'code'} } }, $response->{'message'}); + if (defined($response->{'details'})) { + push ( @{ $status {$response->{'code'} } }, $response->{'details'}[0]); + } else { + # Keep it symmetrical for simplicity + push ( @{ $status {$response->{'code'} } }, "no details received"); + } + + # Set data from array + my ($status, $message, $srv_message, $srv_details) = @{ $status {$response->{'code'} } }; + info("Status: %s -- Message: %s", $status, $message); + info("Server Message: %s -- Server Details: %s", $srv_message, $srv_details); + $config{$h}{'status'} = $status; + + # 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'})); + } 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'; + } + } +} +###################################################################### ## nic_noip_update ## Note: uses same features as nic_dyndns2_update, less return codes ###################################################################### From ae905b3baff156b3d3b5e73a9a29a60429e78782 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Wed, 5 Jul 2023 22:43:20 +0300 Subject: [PATCH 02/19] README.md: update to show this project is live --- README.md | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index faca1a5..891727f 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,14 @@ -# Unmaintained - -ddclient is unmaintained and no further changes will be done nor will issues or pull requests of any kind be accepted. - -As alternatives consider or . -There will be no support for migrating of ddclient and your current provider might not be supported by those alternatives. - -See https://github.com/ddclient/ddclient/issues/528 and https://github.com/ddclient/ddclient/issues/380 for more details. - ---- - # DDCLIENT `ddclient` is a Perl client used to update dynamic DNS entries for accounts on many dynamic DNS services. +This is a friendly fork/continuation of https://github.com/ddclient/ddclient + +## Alternatives + +You might also want to consider using one of the following, if they support your dynamic DNS provider(s): or . + ## Supported services Dynamic DNS services currently supported include: From 2af841acdb8b6fd86d83e7ba926800df5ffc0092 Mon Sep 17 00:00:00 2001 From: Lenard Hess Date: Sun, 26 Feb 2023 18:54:19 +0100 Subject: [PATCH 03/19] Changed password config regex The password regex searches for password assignments, extracts the password and replaces it with a dummy value to prevent it being logged. This change adjusts the password regex to no longer accept trailing characters behind the password string --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index f6487e6..776e5d0 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1614,7 +1614,7 @@ sub _read_config { $content .= "$_\n" unless /^#/; ## parsing passwords is special - if (/^([^#]*\s)?([^#]*?password\S*?)\s*=\s*('.*'|[^']\S*)(.*)/) { + if (/^([^#]*\s)?([^#]*?password)\s*=\s*('.*'|[^']\S*)(.*)/) { my ($head, $key, $value, $tail) = ($1 // '', $2, $3, $4); $value = $1 if $value =~ /^'(.*)'$/; $passwords{$key} = $value; From 52b5eea6f493dfffb078b0949e170da732deb14d Mon Sep 17 00:00:00 2001 From: Lenard Hess Date: Tue, 21 Mar 2023 22:59:25 +0100 Subject: [PATCH 04/19] Implemented _env suffix for configuration With this change, any config value may be set through an environment variable by appending '_env' to the keyword (i.e. 'password_env' instead of 'password') and setting the value to the name of the environment variable that contains the actual configuration value. This allows keeping sensitive info (i.e. login and password) out of the configuration file. Example configuration snippet: protocol=namecheap, \ server=dynamicdns.park-your-domain.com, \ login_env=DD_LOGIN, \ password_env=DD_PASSWORD \ @ With this configuration snippet, ddclient will use the contents of DD_LOGIN as the login value and the contents of DD_PASSWORD as the password value. These can in turn be supplied via the command line, .env files or any other mechanism to safeguard sensitive information. --- ddclient.in | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/ddclient.in b/ddclient.in index 776e5d0..899b674 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1645,6 +1645,25 @@ sub _read_config { ## verify that keywords are valid...and check the value foreach my $k (keys %locals) { + # Handle '_env' keyword suffix + if ($k =~ /(.*)_env$/) + { + debug("Loading value for $1 from environment variable $locals{$k}."); + if (exists($ENV{$locals{$k}})) + { + # Set the value to the value of the environment variable + $locals{$1} = $ENV{$locals{$k}}; + # Remove the '_env' suffix from the key + $k = $1; + } + else + { + warning("Environment variable '$locals{$k}' not set for keyword '$k' (ignored)"); + delete $locals{$k}; + next; + } + } + $locals{$k} = $passwords{$k} if defined $passwords{$k}; if (!exists $variables{'merged'}{$k}) { warning("unrecognized keyword '%s' (ignored)", $k); From 211404b5d122acc9f4e40a0aff66ce9a0d47dc4b Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 10 Jul 2023 00:42:05 +0300 Subject: [PATCH 05/19] ddclient.in: update maintainer details --- ddclient.in | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/ddclient.in b/ddclient.in index 899b674..d7ae065 100755 --- a/ddclient.in +++ b/ddclient.in @@ -3,19 +3,10 @@ # # DDCLIENT - a Perl client for updating DynDNS information # -# Author: Paul Burry (paul+ddclient@burry.ca) -# ddclient developers: see https://github.com/orgs/ddclient/people -# -# website: https://ddclient.net -# -# Support for multiple IP numbers added by -# Astaro AG, Ingo Schwarze September 16, 2008 -# -# Support for multiple domain support for Namecheap by Robert Ian Hawdon 2010-09-03: https://robertianhawdon.me.uk/ -# -# Initial Cloudflare support by Ian Pye, updated by Robert Ian Hawdon 2012-07-16 -# Further updates by Peter Roberts to support the new API 2013-09-26, 2014-06-22: http://blog.peter-r.co.uk/ +# Original Author: Paul Burry (paul+ddclient@burry.ca) +# Current maintainer: Reuben Thomas # +# website: https://github.com/rrthomas/ddclient # ###################################################################### package ddclient; From a9c1e545fb0190a59952e8d448927bae49fd0508 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 10 Jul 2023 00:53:54 +0300 Subject: [PATCH 06/19] Require curl Use command-line curl, and remove alternative Perl and Curl-via-Perl implementations of network code. --- .github/workflows/ci.yml | 26 +--- ChangeLog.md | 3 +- Makefile.am | 1 - README.md | 2 +- configure.ac | 1 + ddclient.in | 303 ++++-------------------------------- t/geturl_connectivity.pl.in | 42 ++--- t/geturl_ssl.pl | 263 ------------------------------- 8 files changed, 45 insertions(+), 596 deletions(-) delete mode 100644 t/geturl_ssl.pl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bce2ecc..2deef1b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: matrix: image: - ubuntu:latest - - ubuntu:16.04 + - ubuntu:20.04 - debian:testing - debian:stable - debian:oldstable @@ -24,6 +24,7 @@ jobs: automake \ ca-certificates \ git \ + curl \ libhttp-daemon-perl \ libhttp-daemon-ssl-perl \ libio-socket-inet6-perl \ @@ -48,28 +49,6 @@ jobs: - name: distribution tarball is complete run: ./.github/workflows/scripts/dist-tarball-check - #test-centos6: - # runs-on: ubuntu-latest - # container: centos:6 - # steps: - # - uses: actions/checkout@v1 - # - name: install dependencies - # run: | - # yum install -y \ - # automake \ - # perl-IO-Socket-INET6 \ - # perl-core \ - # perl-libwww-perl \ - # ; - # - name: autogen - # run: ./autogen - # - name: configure - # run: ./configure - # - name: check - # run: make VERBOSE=1 AM_COLOR_TESTS=always check - # - name: distcheck - # run: make VERBOSE=1 AM_COLOR_TESTS=always distcheck - #test-centos8: # runs-on: ubuntu-latest # container: centos:8 @@ -105,6 +84,7 @@ jobs: automake \ findutils \ make \ + curl \ perl \ perl-HTTP-Daemon \ perl-HTTP-Daemon-SSL \ diff --git a/ChangeLog.md b/ChangeLog.md index 2496130..8dd2d0b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -7,6 +7,7 @@ repository history](https://github.com/ddclient/ddclient/commits/master). ### Breaking changes + * ddclient now requires curl. * 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. @@ -23,7 +24,7 @@ repository history](https://github.com/ddclient/ddclient/commits/master). ### Bug fixes - * DynDNS2 now uses the newer ipv4/ipv6 syntax's + * DynDNS2 now uses the newer ipv4/ipv6 syntaxes * The OVH provider now ignores extra data returned * Allow to define usev4 and usev6 options per hostname * Merge multiple configs for the same hostname instead of use the last diff --git a/Makefile.am b/Makefile.am index 99ab507..af5bf06 100644 --- a/Makefile.am +++ b/Makefile.am @@ -63,7 +63,6 @@ AM_PL_LOG_FLAGS = -Mstrict -w \ -MDevel::Autoflush handwritten_tests = \ t/get_ip_from_if.pl \ - t/geturl_ssl.pl \ t/is-and-extract-ipv4.pl \ t/is-and-extract-ipv6.pl \ t/is-and-extract-ipv6-global.pl \ diff --git a/README.md b/README.md index 891727f..45f8d41 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # DDCLIENT `ddclient` is a Perl client used to update dynamic DNS entries for accounts -on many dynamic DNS services. +on many dynamic DNS services. It uses `curl` for internet access. This is a friendly fork/continuation of https://github.com/ddclient/ddclient diff --git a/configure.ac b/configure.ac index 8750122..945eba0 100644 --- a/configure.ac +++ b/configure.ac @@ -28,6 +28,7 @@ AC_PATH_PROG([FIND], [find]) AS_IF([test -z "${FIND}"], [AC_MSG_ERROR(['find' utility not found])]) AC_PATH_PROG([CURL], [curl]) +AS_IF([test -z "${CURL}"], [AC_MSG_ERROR([curl not found])]) AX_WITH_PROG([PERL], perl) AX_PROG_PERL_VERSION([5.10.1], [], diff --git a/ddclient.in b/ddclient.in index d7ae065..a31b914 100755 --- a/ddclient.in +++ b/ddclient.in @@ -17,8 +17,6 @@ use File::Basename; use File::Path qw(make_path); use File::Temp; use Getopt::Long; -use IO::Socket::IP; -use Socket qw(AF_INET AF_INET6 PF_INET PF_INET6); use Sys::Hostname; use version 0.77; our $VERSION = version->declare('@PACKAGE_VERSION@'); @@ -454,7 +452,6 @@ my %variables = ( 'retry' => setv(T_BOOL, 0, 0, 0, undef), 'force' => setv(T_BOOL, 0, 0, 0, undef), 'ssl' => setv(T_BOOL, 0, 0, 0, undef), - 'curl' => setv(T_BOOL, 0, 0, 0, undef), 'syslog' => setv(T_BOOL, 0, 0, 0, undef), 'facility' => setv(T_STRING,0, 0, 'daemon', undef), 'priority' => setv(T_STRING,0, 0, 'notice', undef), @@ -1090,7 +1087,6 @@ my @opt = ( ["ssl_ca_file", "=s", "-ssl_ca_file : look at for certificates of trusted certificate authorities (default: auto-detect)"], ["fw-ssl-validate", "!", "-{no}fw-ssl-validate : Validate SSL certificate when retrieving IP address from firewall"], ["web-ssl-validate", "!","-{no}web-ssl-validate : Validate SSL certificate when retrieving IP address from web"], - ["curl", "!", "-{no}curl : use curl for network connections"], ["retry", "!", "-{no}retry : retry failed updates"], ["force", "!", "-{no}force : force an update even if the update may be unnecessary"], ["timeout", "=i", "-timeout : when fetching a URL, wait at most seconds for a response"], @@ -2460,22 +2456,6 @@ sub encode_base64 ($;$) { $res =~ s/.{$padding}$/'=' x $padding/e if $padding; $res; } -###################################################################### -## load_ssl_support -###################################################################### -sub load_ssl_support { - my $ssl_loaded = eval { require IO::Socket::SSL }; - unless ($ssl_loaded) { - fatal("%s", <<"EOM"); -Error loading the Perl module IO::Socket::SSL needed for SSL connect. -On Debian, the package libio-socket-ssl-perl must be installed. -On Red Hat, the package perl-IO-Socket-SSL must be installed. -On Alpine, the package perl-io-socket-ssl must be installed. -EOM - } - import IO::Socket::SSL; - { no warnings; $IO::Socket::SSL::DEBUG = 0; } -} ###################################################################### ## load_sha1_support @@ -2510,180 +2490,6 @@ EOM import JSON::PP (qw/decode_json encode_json/); } -###################################################################### -## geturl -###################################################################### -sub geturl { - return opt('curl') ? fetch_via_curl(@_) : fetch_via_socket_io(@_); -} - -sub fetch_via_socket_io { - my %params = @_; - my $proxy = $params{proxy}; - my $url = $params{url}; - my $login = $params{login}; - my $password = $params{password}; - my $ipversion = $params{ipversion} // ''; - my $headers = $params{headers} // ''; - my $method = $params{method} // 'GET'; - my $data = $params{data} // ''; - my ($peer, $server, $port, $default_port, $use_ssl); - my ($sd, $request, $reply); - - ## canonify proxy and url - my $force_ssl; - $force_ssl = 1 if ($url =~ /^https:/); - $proxy =~ s%^https?://%%i if defined($proxy); - $url =~ s%^https?://%%i; - $server = $url; - $server =~ s%[?/].*%%; - $url =~ s%^[^?/]*/?%%; - - if ($force_ssl || ($globals{'ssl'} && !($params{ignore_ssl_option} // 0))) { - $use_ssl = 1; - $default_port = '443'; - } else { - $use_ssl = 0; - $default_port = '80'; - } - debug("proxy = %s", $proxy // ''); - debug("protocol = %s", $use_ssl ? "https" : "http"); - debug("server = %s", $server); - (my $_url = $url) =~ s%\?.*%?%; #redact ALL parameters passed on URL, including possible passwords - debug("url = %s", $_url); - debug("ip ver = %s", $ipversion); - - ## determine peer and port to use. - $peer = $proxy // $server; - $peer =~ s%[?/].*%%; - if ($peer =~ /^\[([^]]+)\](?::(\d+))?$/ || $peer =~ /^([^:]+)(?::(\d+))?/) { - $peer = $1; - $port = $2 // $default_port; - } else { - failed("unable to extract host and port from %s", $peer); - return undef; - } - - $request = "$method "; - if (!$use_ssl) { - $request .= "http://$server" if defined($proxy); - } else { - $request .= "https://$server" if defined($proxy); - } - $request .= "/$url HTTP/1.1\n"; - $request .= "Host: $server\n"; - - if (defined($login) || defined($password)) { - my $auth = encode_base64(($login // '') . ':' . ($password // ''), ''); - $request .= "Authorization: Basic $auth\n"; - } - $request .= "User-Agent: ${program}/${version}\n"; - if ($data) { - $request .= "Content-Type: application/x-www-form-urlencoded\n" if $headers !~ /^Content-Type:/mi; - $request .= "Content-Length: " . length($data) . "\n"; - } - $request .= "Connection: close\n"; - $headers .= "\n" if $headers ne '' && substr($headers, -1) ne "\n"; - $request .= $headers; - $request .= "\n"; - # RFC 7230 says that all lines before the body must end with . - (my $rq = $request) =~ s/(? $peer, - PeerPort => $port, - Proto => 'tcp', - Timeout => opt('timeout'), - ); - my $socket_class = 'IO::Socket::IP'; - if ($use_ssl) { - # IO::Socket::SSL will load IPv6 support if available on the system. - load_ssl_support; - $socket_class = 'IO::Socket::SSL'; - $socket_args{SSL_ca_file} = opt('ssl_ca_file') if defined(opt('ssl_ca_file')); - $socket_args{SSL_ca_path} = opt('ssl_ca_dir') if defined(opt('ssl_ca_dir')); - $socket_args{SSL_verify_mode} = ($params{ssl_validate} // 1) - ? IO::Socket::SSL->SSL_VERIFY_PEER - : IO::Socket::SSL->SSL_VERIFY_NONE; - } - if (defined($params{_testonly_socket_class})) { - $socket_args{original_socket_class} = $socket_class; - $socket_class = $params{_testonly_socket_class}; - } - if ($ipversion eq '4') { - $socket_args{Domain} = PF_INET; - $socket_args{Family} = AF_INET; - } elsif ($ipversion eq '6') { - $socket_args{Domain} = PF_INET6; - $socket_args{Family} = AF_INET6; - } elsif ($ipversion ne '') { - fatal("geturl passed unsupported 'ipversion' value %s", $ipversion); - } - - my $ipv = $ipversion eq '' ? '' : sprintf(" (IPv%s)", $ipversion); - my $peer_port_ipv = sprintf("%s:%s%s", $peer, $port, $ipv); - my $to = sprintf("%s%s%s", $server, defined($proxy) ? " via proxy $peer:$port" : "", $ipv); - verbose("CONNECT:", "%s", $to); - $0 = sprintf("%s - connecting to %s", $program, $peer_port_ipv); - if (opt('exec')) { - $sd = $socket_class->new(%socket_args); - defined($sd) or warning("cannot connect to %s socket: %s%s", $peer_port_ipv, $@, - $use_ssl ? ' ' . IO::Socket::SSL::errstr() : ''); - } else { - debug("skipped network connection"); - verbose("SENDING:", "%s", $request); - } - if (defined $sd) { - ## send the request to the http server - verbose("CONNECTED: ", $use_ssl ? 'using SSL' : 'using HTTP'); - verbose("SENDING:", "%s", $request); - - $0 = sprintf("%s - sending to %s", $program, $peer_port_ipv); - my $result = syswrite $sd, $rq; - if ($result != length($rq)) { - warning("cannot send to %s (%s).", $peer_port_ipv, $!); - } else { - $0 = sprintf("%s - reading from %s", $program, $peer_port_ipv); - eval { - local $SIG{'ALRM'} = sub { die "timeout"; }; - alarm(opt('timeout')) if opt('timeout') > 0; - while ($_ = <$sd>) { - $0 = sprintf("%s - read from %s", $program, $peer_port_ipv); - verbose("RECEIVE:", "%s", $_ // ""); - $reply .= $_ // ''; - } - if (opt('timeout') > 0) { - alarm(0); - } - }; - close($sd); - - if ($@ and $@ =~ /timeout/) { - warning("TIMEOUT: %s after %s seconds", $to, opt('timeout')); - $reply = ''; - } - $reply //= ''; - } - } - $0 = sprintf("%s - closed %s", $program, $peer_port_ipv); - - ## during testing simulate reading the URL - if (opt('test')) { - my $filename = "$server/$url"; - $filename =~ s|/|%2F|g; - if (opt('exec')) { - $reply = save_file("$savedir/$filename", $reply, 'unique'); - } else { - $reply = load_file("$savedir/$filename"); - } - } - - $reply =~ s/\r//g if defined $reply; - return $reply; -} - ###################################################################### ## curl_cmd() function to execute system curl command ###################################################################### @@ -2753,10 +2559,7 @@ sub escape_curl_param { return $str; } -###################################################################### -## fetch_via_curl() is used for geturl() when global curl option set -###################################################################### -sub fetch_via_curl { +sub geturl { my %params = @_; my $proxy = $params{proxy}; my $url = $params{url}; @@ -2799,89 +2602,35 @@ sub fetch_via_curl { debug("skipped network connection"); verbose("SENDING:", "%s", "${server}/${url}"); } else { - my $curl_loaded = eval { require WWW::Curl::Easy }; - if ($curl_loaded) { - # System has the WWW::Curl::Easy module so use that - import WWW::Curl::Easy; - my $curl = WWW::Curl::Easy->new; + push(@curlopt, "silent"); + push(@curlopt, "include"); ## Include HTTP response for compatibility + push(@curlopt, "insecure") if ($use_ssl && !($params{ssl_validate} // 1)); + push(@curlopt, "cacert=\"".escape_curl_param(opt('ssl_ca_file')).'"') if defined(opt('ssl_ca_file')); + push(@curlopt, "capath=\"".escape_curl_param(opt('ssl_ca_dir')).'"') if defined(opt('ssl_ca_dir')); + push(@curlopt, "ipv4") if ($ipversion == 4); + push(@curlopt, "ipv6") if ($ipversion == 6); + push(@curlopt, "user-agent=\"".escape_curl_param("${program}/${version}").'"'); + push(@curlopt, "connect-timeout=$timeout"); + push(@curlopt, "max-time=$timeout"); + push(@curlopt, "request=$method"); + push(@curlopt, "user=\"".escape_curl_param("${login}:${password}").'"') if (defined($login) && defined($password)); + push(@curlopt, "proxy=\"".escape_curl_param("${protocol}://${proxy}").'"') if defined($proxy); + push(@curlopt, "url=\"".escape_curl_param("${protocol}://${server}/${url}").'"'); - $curl->setopt(WWW::Curl::Easy->CURLOPT_HEADER, 1); ## Include HTTP response for compatibility - $curl->setopt(WWW::Curl::Easy->CURLOPT_SSL_VERIFYPEER, ($params{ssl_validate} // 1) ? 1 : 0 ); - $curl->setopt(WWW::Curl::Easy->CURLOPT_SSL_VERIFYHOST, ($params{ssl_validate} // 1) ? 1 : 0 ); - $curl->setopt(WWW::Curl::Easy->CURLOPT_CAINFO, opt('ssl_ca_file')) if defined(opt('ssl_ca_file')); - $curl->setopt(WWW::Curl::Easy->CURLOPT_CAPATH, opt('ssl_ca_dir')) if defined(opt('ssl_ca_dir')); - $curl->setopt(WWW::Curl::Easy->CURLOPT_IPRESOLVE, - ($ipversion == 4) ? WWW::Curl::Easy->CURL_IPRESOLVE_V4 : - ($ipversion == 6) ? WWW::Curl::Easy->CURL_IPRESOLVE_V6 : - WWW::Curl::Easy->CURL_IPRESOLVE_WHATEVER); - $curl->setopt(WWW::Curl::Easy->CURLOPT_USERAGENT, "${program}/${version}"); - $curl->setopt(WWW::Curl::Easy->CURLOPT_CONNECTTIMEOUT, $timeout); - $curl->setopt(WWW::Curl::Easy->CURLOPT_TIMEOUT, $timeout); + # Each header line is added individually + @header_lines = split('\n', $headers); + $_ = "header=\"".escape_curl_param($_).'"' foreach (@header_lines); + push(@curlopt, @header_lines); - $curl->setopt(WWW::Curl::Easy->CURLOPT_POST, 1) if ($method eq 'POST'); - $curl->setopt(WWW::Curl::Easy->CURLOPT_PUT, 1) if ($method eq 'PUT'); - $curl->setopt(WWW::Curl::Easy->CURLOPT_CUSTOMREQUEST, $method) if ($method ne 'GET'); ## for PATCH + # Add in the data if any was provided (for POST/PATCH) + push(@curlopt, "data=\"".escape_curl_param(${data}).'"') if ($data); - $curl->setopt(WWW::Curl::Easy->CURLOPT_USERPWD, "${login}:${password}") if (defined($login) && defined($password)); - $curl->setopt(WWW::Curl::Easy->CURLOPT_PROXY, "${protocol}://${proxy}") if defined($proxy); - $curl->setopt(WWW::Curl::Easy->CURLOPT_URL, "${protocol}://${server}/${url}"); + # don't include ${url} as that might expose login credentials + $0 = sprintf("%s - Curl system cmd sending to %s", $program, "${protocol}://${server}"); + verbose("SENDING:", "Curl system cmd to %s", "${protocol}://${server}"); + verbose("SENDING:", "%s", $_) foreach (@curlopt); - # Add header lines if any was provided - if ($headers) { - @header_lines = split('\n', $headers); - $curl->setopt(WWW::Curl::Easy->CURLOPT_HTTPHEADER, \@header_lines); - } - # Add in the data if any was provided (for POST/PATCH) - if (my $datalen = length($data)) { - $curl->setopt(WWW::Curl::Easy->CURLOPT_POSTFIELDS, ${data}); - $curl->setopt(WWW::Curl::Easy->CURLOPT_POSTFIELDSIZE, $datalen); - } - $curl->setopt(WWW::Curl::Easy->CURLOPT_WRITEDATA,\$reply); - - # don't include ${url} as that might expose login credentials - $0 = sprintf("%s - WWW::Curl::Easy sending to %s", $program, "${protocol}://${server}"); - verbose("SENDING:", "WWW::Curl::Easy to %s", "${protocol}://${server}"); - verbose("SENDING:", "%s", $headers) if ($headers); - verbose("SENDING:", "%s", $data) if ($data); - - my $rc = $curl->perform; - - if ($rc != 0) { - warning("CURL error (%d) %s", $rc, $curl->strerror($rc)); - debug($curl->errbuf); - } - } else { - # System does not have the WWW::Curl::Easy module so attempt with system Curl command - push(@curlopt, "silent"); - push(@curlopt, "include"); ## Include HTTP response for compatibility - push(@curlopt, "insecure") if ($use_ssl && !($params{ssl_validate} // 1)); - push(@curlopt, "cacert=\"".escape_curl_param(opt('ssl_ca_file')).'"') if defined(opt('ssl_ca_file')); - push(@curlopt, "capath=\"".escape_curl_param(opt('ssl_ca_dir')).'"') if defined(opt('ssl_ca_dir')); - push(@curlopt, "ipv4") if ($ipversion == 4); - push(@curlopt, "ipv6") if ($ipversion == 6); - push(@curlopt, "user-agent=\"".escape_curl_param("${program}/${version}").'"'); - push(@curlopt, "connect-timeout=$timeout"); - push(@curlopt, "max-time=$timeout"); - push(@curlopt, "request=$method"); - push(@curlopt, "user=\"".escape_curl_param("${login}:${password}").'"') if (defined($login) && defined($password)); - push(@curlopt, "proxy=\"".escape_curl_param("${protocol}://${proxy}").'"') if defined($proxy); - push(@curlopt, "url=\"".escape_curl_param("${protocol}://${server}/${url}").'"'); - - # Each header line is added individually - @header_lines = split('\n', $headers); - $_ = "header=\"".escape_curl_param($_).'"' foreach (@header_lines); - push(@curlopt, @header_lines); - - # Add in the data if any was provided (for POST/PATCH) - push(@curlopt, "data=\"".escape_curl_param(${data}).'"') if ($data); - - # don't include ${url} as that might expose login credentials - $0 = sprintf("%s - Curl system cmd sending to %s", $program, "${protocol}://${server}"); - verbose("SENDING:", "Curl system cmd to %s", "${protocol}://${server}"); - verbose("SENDING:", "%s", $_) foreach (@curlopt); - - $reply = curl_cmd(@curlopt); - } + $reply = curl_cmd(@curlopt); verbose("RECEIVE:", "%s", $reply // ""); if (!$reply) { # don't include ${url} as that might expose login credentials diff --git a/t/geturl_connectivity.pl.in b/t/geturl_connectivity.pl.in index 5127b30..8c1cb99 100644 --- a/t/geturl_connectivity.pl.in +++ b/t/geturl_connectivity.pl.in @@ -12,7 +12,6 @@ my $ipv6_supported = eval { ); defined($ipv6_socket); }; -my $has_curl = qx{ @CURL@ --version 2>/dev/null; } && $? == 0; my $http_daemon_supports_ipv6 = eval { require HTTP::Daemon; @@ -55,54 +54,37 @@ my %httpd = ( ); my @test_cases = ( - # Fetch via IO::Socket::IP {ipv6_opt => 0, server_ipv => '4', client_ipv => ''}, {ipv6_opt => 0, server_ipv => '4', client_ipv => '4'}, # IPv* client to a non-SSL IPv6 server is not expected to work unless opt('ipv6') is true {ipv6_opt => 0, server_ipv => '6', client_ipv => '6'}, - # Fetch via IO::Socket::IP - {ipv6_opt => 1, server_ipv => '4', client_ipv => ''}, - {ipv6_opt => 1, server_ipv => '4', client_ipv => '4'}, - {ipv6_opt => 1, server_ipv => '6', client_ipv => ''}, - {ipv6_opt => 1, server_ipv => '6', client_ipv => '6'}, + # Fetch without ssl + { server_ipv => '4', client_ipv => '' }, + { server_ipv => '4', client_ipv => '4' }, + { server_ipv => '6', client_ipv => '' }, + { server_ipv => '6', client_ipv => '6' }, - # Fetch via IO::Socket::SSL - {ssl => 1, server_ipv => '4', client_ipv => ''}, - {ssl => 1, server_ipv => '4', client_ipv => '4'}, - {ssl => 1, server_ipv => '6', client_ipv => ''}, - {ssl => 1, server_ipv => '6', client_ipv => '6'}, - - # Fetch with curl - { curl => 1, server_ipv => '4', client_ipv => '' }, - { curl => 1, server_ipv => '4', client_ipv => '4' }, - { curl => 1, server_ipv => '6', client_ipv => '' }, - { curl => 1, server_ipv => '6', client_ipv => '6' }, - - # Fetch with curl and ssl - { curl => 1, ssl => 1, server_ipv => '4', client_ipv => '' }, - { curl => 1, ssl => 1, server_ipv => '4', client_ipv => '4' }, - { curl => 1, ssl => 1, server_ipv => '6', client_ipv => '' }, - { curl => 1, ssl => 1, server_ipv => '6', client_ipv => '6' }, + # Fetch with ssl + { ssl => 1, server_ipv => '4', client_ipv => '' }, + { ssl => 1, server_ipv => '4', client_ipv => '4' }, + { ssl => 1, server_ipv => '6', client_ipv => '' }, + { ssl => 1, server_ipv => '6', client_ipv => '6' }, ); for my $tc (@test_cases) { $tc->{ipv6_opt} //= 0; $tc->{ssl} //= 0; - $tc->{curl} //= 0; SKIP: { skip("IPv6 not supported on this system", 1) if $tc->{server_ipv} eq '6' && !$ipv6_supported; skip("HTTP::Daemon too old for IPv6 support", 1) if $tc->{server_ipv} eq '6' && !$http_daemon_supports_ipv6; skip("HTTP::Daemon::SSL not available", 1) if $tc->{ssl} && !$has_http_daemon_ssl; - skip("Curl not available on this system", 1) if $tc->{curl} && !$has_curl; my $uri = $httpd{$tc->{server_ipv}}{$tc->{ssl} ? 'https' : 'http'}->endpoint(); - my $name = sprintf("IPv%s client to %s%s%s", - $tc->{client_ipv} || '*', $uri, $tc->{ipv6_opt} ? ' (-ipv6)' : '', - $tc->{curl} ? ' (curl)' : ''); + my $name = sprintf("IPv%s client to %s%s", + $tc->{client_ipv} || '*', $uri, $tc->{ipv6_opt} ? ' (-ipv6)' : ''); $ddclient::globals{'ipv6'} = $tc->{ipv6_opt}; - $ddclient::globals{'curl'} = $tc->{curl}; my $got = ddclient::geturl(url => $uri, ipversion => $tc->{client_ipv}); isnt($got // '', '', $name); } diff --git a/t/geturl_ssl.pl b/t/geturl_ssl.pl deleted file mode 100644 index 2b45f0b..0000000 --- a/t/geturl_ssl.pl +++ /dev/null @@ -1,263 +0,0 @@ -use Test::More; -use Data::Dumper; -eval { - require HTTP::Request; - require HTTP::Response; - require IO::Socket::IP; - require IO::Socket::SSL; - require ddclient::Test::Fake::HTTPD; -} or plan(skip_all => $@); -SKIP: { eval { require Test::Warnings; } or skip($@, 1); } -eval { require 'ddclient'; } or BAIL_OUT($@); - -$Data::Dumper::Sortkeys = 1; - -my $httpd = ddclient::Test::Fake::HTTPD->new(); -$httpd->run(sub { - my $req = shift; - # Echo back the full request. - my $resp = [ 200, [ 'Content-Type' => 'application/octet-stream' ], [ $req->as_string() ] ]; - if ($req->method() ne 'GET') { - # TODO: Add support for CONNECT to test https via proxy. - $resp->[0] = 501; # 501 == Not Implemented - } - return $resp; -}); - -my $args; - -{ - package InterceptSocket; - require base; - base->import(qw(IO::Socket::IP)); - - sub new { - my ($class, %args) = @_; - $args = \%args; - return $class->SUPER::new(%args, PeerAddr => $httpd->host(), PeerPort => $httpd->port()); - } -} - -# Keys: -# * name: Display name. -# * params: Parameters to pass to geturl. -# * opt_ssl: Value to return from opt('ssl'). Defaults to 0. -# * opt_ssl_ca_dir: Value to return from opt('ssl_ca_dir'). Defaults to undef. -# * opt_ssl_ca_file: Value to return from opt('ssl_ca_file'). Defaults to undef. -# * want_args: Args that should be passed to the socket constructor minus Proto, -# Timeout, and original_socket_class. -# * want_req_method: The HTTP method geturl is expected to use. Defaults to 'GET'. -# * want_req_uri: URI that geturl is expected to request. -# * todo: If defined, mark this test as expected to fail. -my @test_cases = ( - { - name => 'https', - params => { - url => 'https://hostname', - }, - want_args => { - PeerAddr => 'hostname', - PeerPort => '443', - SSL_verify_mode => IO::Socket::SSL->SSL_VERIFY_PEER, - }, - want_req_uri => '/', - }, - { - name => 'http with ssl=true', - params => { - url => 'http://hostname', - }, - opt_ssl => 1, - want_args => { - PeerAddr => 'hostname', - PeerPort => '443', - SSL_verify_mode => IO::Socket::SSL->SSL_VERIFY_PEER, - }, - want_req_uri => '/', - }, - { - name => 'https with port', - params => { - url => 'https://hostname:123', - }, - want_args => { - PeerAddr => 'hostname', - PeerPort => '123', - SSL_verify_mode => IO::Socket::SSL->SSL_VERIFY_PEER, - }, - want_req_uri => '/', - }, - { - name => 'http with port and ssl=true', - params => { - url => 'https://hostname:123', - }, - opt_ssl => 1, - want_args => { - PeerAddr => 'hostname', - PeerPort => '123', - SSL_verify_mode => IO::Socket::SSL->SSL_VERIFY_PEER, - }, - want_req_uri => '/', - }, - { - name => 'https proxy, http URL', - params => { - proxy => 'https://proxy', - url => 'http://hostname', - }, - want_args => { - PeerAddr => 'proxy', - PeerPort => '443', - SSL_verify_mode => IO::Socket::SSL->SSL_VERIFY_PEER, - }, - want_req_uri => 'http://hostname/', - todo => "broken", - }, - { - name => 'http proxy, https URL', - params => { - proxy => 'http://proxy', - url => 'https://hostname', - }, - want_args => { - PeerAddr => 'proxy', - PeerPort => '80', - SSL_startHandshake => 0, - }, - want_req_method => 'CONNECT', - want_req_uri => 'hostname:443', - todo => "not yet supported; silently fails", - }, - { - name => 'https proxy, https URL', - params => { - proxy => 'https://proxy', - url => 'https://hostname', - }, - want_args => { - PeerAddr => 'proxy', - PeerPort => '443', - SSL_verify_mode => IO::Socket::SSL->SSL_VERIFY_PEER, - }, - want_req_method => 'CONNECT', - want_req_uri => 'hostname:443', - todo => "not yet supported; silently fails", - }, - { - name => 'http proxy, http URL, ssl=true', - params => { - proxy => 'http://proxy', - url => 'http://hostname', - }, - opt_ssl => 1, - want_args => { - PeerAddr => 'proxy', - PeerPort => '443', - SSL_verify_mode => IO::Socket::SSL->SSL_VERIFY_PEER, - }, - want_req_method => 'CONNECT', - want_req_uri => 'hostname:443', - todo => "not yet supported; silently fails", - }, - { - name => 'https proxy with port, http URL with port', - params => { - proxy => 'https://proxy:123', - url => 'http://hostname:456', - }, - want_args => { - PeerAddr => 'proxy', - PeerPort => '123', - }, - want_req_uri => 'http://hostname:456/', - todo => "broken", - }, - { - name => 'http proxy with port, https URL with port', - params => { - proxy => 'http://proxy:123', - url => 'https://hostname:456', - }, - want_args => { - PeerAddr => 'proxy', - PeerPort => '123', - SSL_startHandshake => 0, - }, - want_req_method => 'CONNECT', - want_req_uri => 'hostname:456', - todo => "not yet supported; silently fails", - }, - { - name => 'CA dir', - params => { - url => 'https://hostname', - }, - opt_ssl_ca_dir => '/ca/dir', - want_args => { - PeerAddr => 'hostname', - PeerPort => '443', - SSL_ca_path => '/ca/dir', - SSL_verify_mode => IO::Socket::SSL->SSL_VERIFY_PEER, - }, - want_req_uri => '/', - }, - { - name => 'CA file', - params => { - url => 'https://hostname', - }, - opt_ssl_ca_file => '/ca/file', - want_args => { - PeerAddr => 'hostname', - PeerPort => '443', - SSL_ca_file => '/ca/file', - SSL_verify_mode => IO::Socket::SSL->SSL_VERIFY_PEER, - }, - want_req_uri => '/', - }, - { - name => 'CA dir and file', - params => { - url => 'https://hostname', - }, - opt_ssl_ca_dir => '/ca/dir', - opt_ssl_ca_file => '/ca/file', - want_args => { - PeerAddr => 'hostname', - PeerPort => '443', - SSL_ca_file => '/ca/file', - SSL_ca_path => '/ca/dir', - SSL_verify_mode => IO::Socket::SSL->SSL_VERIFY_PEER, - }, - want_req_uri => '/', - }, -); - -for my $tc (@test_cases) { - $args = undef; - $ddclient::globals{'ssl'} = $tc->{opt_ssl} // 0; - $ddclient::globals{'ssl_ca_dir'} = $tc->{opt_ssl_ca_dir}; - $ddclient::globals{'ssl_ca_file'} = $tc->{opt_ssl_ca_file}; - my $resp_str = ddclient::geturl(_testonly_socket_class => 'InterceptSocket', %{$tc->{params}}); - TODO: { - local $TODO = $tc->{todo}; - subtest $tc->{name} => sub { - my %want_args = ( - Proto => 'tcp', - Timeout => ddclient::opt('timeout'), - original_socket_class => 'IO::Socket::SSL', - %{$tc->{want_args}}, - ); - is(Dumper($args), Dumper(\%want_args), "socket constructor args"); - ok(defined($resp_str), "response is defined") or return; - ok(my $resp = HTTP::Response->parse($resp_str), "parse response") or return; - ok(my $req_str = $resp->decoded_content(), "decode request from response") or return; - ok(my $req = HTTP::Request->parse($req_str), "parse request") or return; - is($req->method(), $tc->{want_req_method} // 'GET', "request method"); - is($req->uri(), $tc->{want_req_uri}, "request URI"); - }; - } -} - -done_testing(); From fbc64243f3515c4ae658507f28b2702195eca578 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 10 Jul 2023 02:26:29 +0300 Subject: [PATCH 07/19] Note that we've added support for DNSExit API v2 --- ChangeLog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog.md b/ChangeLog.md index 8dd2d0b..0fc7963 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -18,6 +18,7 @@ repository history](https://github.com/ddclient/ddclient/commits/master). * Added support for domeneshop.no * Added support for Enom * Added support for Mythic Beasts Dynamic DNS + * Added support for DNSExit API v2 * Added support for njal.la * Added support for Porkbun * Added support for IPv6 to the EasyDNS and DuckDNS provider From b8bed7112fa59acf80c69baf209f14f50341445b Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 10 Jul 2023 15:08:39 +0300 Subject: [PATCH 08/19] Remove defunct dnsexit protocol --- ChangeLog.md | 3 +- README.md | 1 - ddclient.in | 126 --------------------------------------------------- 3 files changed, 2 insertions(+), 128 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 0fc7963..533e7d0 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -11,6 +11,7 @@ repository history](https://github.com/ddclient/ddclient/commits/master). * 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. + * The defunct `dnsexit` protocol is removed (replaced by `dnsexit2`). ### New features @@ -18,7 +19,7 @@ repository history](https://github.com/ddclient/ddclient/commits/master). * Added support for domeneshop.no * Added support for Enom * Added support for Mythic Beasts Dynamic DNS - * Added support for DNSExit API v2 + * Added support for DNSExit API v2 (replaces old DNSExit). * Added support for njal.la * Added support for Porkbun * Added support for IPv6 to the EasyDNS and DuckDNS provider diff --git a/README.md b/README.md index 45f8d41..10c4821 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,6 @@ Dynamic DNS services currently supported include: ClouDNS - See https://www.cloudns.net dinahosting - See https://dinahosting.com Gandi - See https://gandi.net - dnsexit - See https://dnsexit.com/ for details dnsexit2 - See https://dnsexit.com/dns/dns-api/ for details 1984.is - See https://www.1984.is/product/freedns/ for details Njal.la - See https://njal.la/docs/ddns/ diff --git a/ddclient.in b/ddclient.in index a31b914..41b62c4 100755 --- a/ddclient.in +++ b/ddclient.in @@ -530,12 +530,6 @@ my %variables = ( 'server' => setv(T_FQDNP, 1, 0, 'dynamicdns.key-systems.net', undef), 'login' => setv(T_LOGIN, 0, 0, 0, 'unused', undef), }, - 'dnsexit-common-defaults' => { - 'ssl' => setv(T_BOOL, 0, 0, 1, undef), - 'server' => setv(T_FQDNP, 1, 0, 'update.dnsexit.com', undef), - 'script' => setv(T_STRING, 0, 1, '/RemoteUpdate.sv', undef), - 'min-error-interval' => setv(T_DELAY, 0, 0, interval('8m'), 0), - }, 'dnsexit2-common-defaults' => { 'ssl' => setv(T_BOOL, 0, 0, 1, undef), 'server' => setv(T_FQDNP, 1, 0, 'api.dnsexit.com', undef), @@ -945,15 +939,6 @@ my %services = ( $variables{'service-common-defaults'}, ), }, - 'dnsexit' => { - 'updateable' => undef, - 'update' => \&nic_dnsexit_update, - 'examples' => \&nic_dnsexit_examples, - 'variables' => merge( - $variables{'dnsexit-common-defaults'}, - $variables{'service-common-defaults'}, - ), - }, 'dnsexit2' => { 'updateable' => undef, 'update' => \&nic_dnsexit2_update, @@ -4019,117 +4004,6 @@ sub nic_dyndns2_update { } } -###################################################################### -## nic_dnsexit_examples -###################################################################### -sub nic_dnsexit_examples { - return <<"EoEXAMPLE"; -o 'dnsexit' - -The 'dnsexit' protocol is the 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. - -Configuration variables applicable to the 'dnsexit' protocol are: - ssl=no ## turn off ssl - protocol=dnsexit ## - server=update.dnsexit.com ## defaults to update.dnsexit.com - use=web ## defaults to web - web=update.dnsexit.com ## defaults to update.dnsexit.com - script=/RemoteUpdate.sv ## defaults to /RemoteUpdate.sv - login=service-userid ## userid registered with the service - password=service-password ## password registered with the service - fully.qualified.host ## the host registered with the service. - -Example ${program}.conf file entries: - ## single host update - protocol=dnsexit \\ - login=service-userid \\ - password=service-password \\ - fully.qualified.host - -EoEXAMPLE -} -###################################################################### -## nic_dnsexit_update -## -## written by Gonzalo Pérez de Olaguer Córdoba -## -## based on https://www.dnsexit.com/Direct.sv?cmd=ipClients -## fetches this URL to update: -## https://update.dnsexit.com/RemoteUpdate.sv?login=yourlogin&password=yourpassword& -## host=yourhost.yourdomain.com&myip=xxx.xx.xx.xxx -## -###################################################################### -sub nic_dnsexit_update { - debug("\nnic_dnsexit_update -------------------"); - - my %status = ( - '0' => [ 'good', 'Success' ], - '1' => [ 'nochg', 'IP is the same as the IP on the system' ], - '2' => [ 'badauth', 'Invalid password' ], - '3' => [ 'badauth', 'User not found' ], - '4' => [ 'nochg', 'IP not changed. To save our system resources, please don\'t post updates unless the IP got changed.' ], - '10' => [ 'error', 'Hostname is not specified' ], - '11' => [ 'nohost', 'fail to find the domain' ], - '13' => [ 'error', 'parameter validation error' ], - ); - - ## update each configured host - foreach my $h (@_) { - my $ip = delete $config{$h}{'wantip'}; - info("setting IP address to %s for %s", $ip, $h); - verbose("UPDATE:","updating %s", $h); - - # Set the URL that we're going to update - my $url; - $url = "https://$config{$h}{'server'}$config{$h}{'script'}"; - $url .= "?login=$config{$h}{'login'}"; - $url .= "&password=$config{$h}{'password'}"; - $url .= "&host=$h"; - $url .= "&myip="; - $url .= $ip if $ip; - - # Try to get URL - my $reply = geturl( - proxy => opt('proxy'), - url => $url - ); - - # No response, declare as failed - if (!defined($reply) || !$reply) { - failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'}); - last; - } - last if !header_ok($h, $reply); - - # Response found - if ($reply =~ /(\d+)=(.+)/) { - my ($statuscode, $statusmsg) = ($1, $2); - if (exists $status{$statuscode}) { - my ($status, $message) = @{ $status{$statuscode} }; - if ($status =~ m'^(good|nochg)$') { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; - } - $config{$h}{'status'} = $status; - if ($status eq 'good') { - success("updating %s: good: IP address set to %s", $h, $ip); - } else { - warning("updating %s: %s: %s", $h, $status, $message); - } - } else { - $config{$h}{'status'} = 'failed'; - failed("updating %s: failed: unrecognized status code (%s)", $h, $statuscode); - } - } else { - $config{$h}{'status'} = 'failed'; - warning("SENT: %s", $url) unless opt('verbose'); - warning("REPLIED: %s", $reply); - failed("updating %s: unrecognized reply.", $h); - } - } -} ###################################################################### ## nic_dnsexit2_examples ###################################################################### From 3678befe76ca77e3177635d4aecc0ed214099b13 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 10 Jul 2023 18:23:25 +0300 Subject: [PATCH 09/19] README.md: update and prettify list of dyn DNS providers --- README.md | 82 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 10c4821..2823d8b 100644 --- a/README.md +++ b/README.md @@ -7,53 +7,57 @@ This is a friendly fork/continuation of https://github.com/ddclient/ddclient ## Alternatives -You might also want to consider using one of the following, if they support your dynamic DNS provider(s): or . +You might also want to consider using one of the following, if they support +your dynamic DNS provider(s): or +. ## Supported services Dynamic DNS services currently supported include: - DynDNS.com - See http://www.dyndns.com for details on obtaining a free account. - Zoneedit - See http://www.zoneedit.com for details. - EasyDNS - See http://www.easydns.com for details. - NameCheap - See http://www.namecheap.com for details - DslReports - See http://www.dslreports.com for details - Sitelutions - See http://www.sitelutions.com for details - Loopia - See http://www.loopia.se for details - Noip - See http://www.noip.com/ for details - Freedns - See http://freedns.afraid.org/ for details - ChangeIP - See http://www.changeip.com/ for details - nsupdate - See nsupdate(1) and ddns-confgen(8) for details - CloudFlare - See https://www.cloudflare.com/ for details - GoDaddy - See https://www.godaddy.com/ for details - Google - See http://www.google.com/domains for details - Duckdns - See https://duckdns.org/ for details - Freemyip - See https://freemyip.com for details - woima.fi - See https://woima.fi/ for details - Yandex - See https://domain.yandex.com/ for details - DNS Made Easy - See https://dnsmadeeasy.com/ for details - DonDominio - See https://www.dondominio.com for details - NearlyFreeSpeech.net - See https://www.nearlyfreespeech.net/services/dns for details - OVH - See https://www.ovh.com for details - Porkbun - See https://porkbun.com/ - ClouDNS - See https://www.cloudns.net - dinahosting - See https://dinahosting.com - Gandi - See https://gandi.net - dnsexit2 - See https://dnsexit.com/dns/dns-api/ for details - 1984.is - See https://www.1984.is/product/freedns/ for details - Njal.la - See https://njal.la/docs/ddns/ - regfish.de - See https://www.regfish.de/domains/dyndns/ for details - domenehsop - See https://api.domeneshop.no/docs/#tag/ddns/paths/~1dyndns~1update/get - Mythic Beasts - See https://www.mythic-beasts.com/support/api/dnsv2/dynamic-dns for details - Enom - See https://www.enom.com for details - Infomaniak - See https://faq.infomaniak.com/2376 for details +* [1984.is](https://www.1984.is/product/freedns) +* [ChangeIP](https://www.changeip.com) +* [CloudFlare](https://www.cloudflare.com) +* [ClouDNS](https://www.cloudns.net) +* [dinahosting](https://dinahosting.com) +* [DonDominio](https://www.dondominio.com) +* [DNS Made Easy](https://dnsmadeeasy.com) +* [DNSExit](https://dnsexit.com/dns/dns-api) +* [domenehsop](https://api.domeneshop.no/docs/#tag/ddns/paths/~1dyndns~1update/get) +* [DslReports](https://www.dslreports.com) +* [Duck DNS](https://duckdns.org) +* [DynDNS.com](https://account.dyn.com) +* [EasyDNS](https://www.easydns.com ) +* [Enom](https://www.enom.com) +* [Freedns](https://freedns.afraid.org) +* [Freemyip](https://freemyip.com) +* [Gandi](https://gandi.net) +* [GoDaddy](https://www.godaddy.com) +* [Google](https://domains.google) +* [Infomaniak](https://faq.infomaniak.com/2376) +* [Loopia](https://www.loopia.se) +* [Mythic Beasts](https://www.mythic-beasts.com/support/api/dnsv2/dynamic-dns) +* [NameCheap](https://www.namecheap.com) +* [NearlyFreeSpeech.net](https://www.nearlyfreespeech.net/services/dns) +* [Njalla](https://njal.la/docs/ddns) +* [Noip](https://www.noip.com) +* nsupdate - see nsupdate(1) and ddns-confgen(8) +* [OVH](https://www.ovhcloud.com) +* [Porkbun](https://porkbun.com) +* [regfish.de](https://www.regfish.de/domains/dyndns) +* [Sitelutions](https://www.sitelutions.com) +* [woima.fi](https://woima.fi) +* [Yandex](https://dns.yandex.com) +* [Zoneedit](https://www.zoneedit.com) -`ddclient` now supports many cable and DSL broadband routers. +`ddclient` supports finding your IP address from many cable and DSL +broadband routers. -Comments, suggestions and requests: use the issues on https://github.com/ddclient/ddclient/issues/new +Comments, suggestions and requests: please file an issue at +https://github.com/ddclient/ddclient/issues/new -The code was originally written by Paul Burry and is now hosted and maintained -through github.com. Please check out http://ddclient.net +The code was originally written by Paul Burry and is now hosted and +maintained through github.com. Please check out https://ddclient.net ## REQUIREMENTS From 379b1832de86e5027adcf9636f6a9a5fa4606dad Mon Sep 17 00:00:00 2001 From: Lenard Hess Date: Mon, 10 Jul 2023 17:31:25 +0200 Subject: [PATCH 10/19] ddclient.in: Update Maintainers --- ddclient.in | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index 41b62c4..d6a1a7d 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4,7 +4,9 @@ # DDCLIENT - a Perl client for updating DynDNS information # # Original Author: Paul Burry (paul+ddclient@burry.ca) -# Current maintainer: Reuben Thomas +# Current maintainers: +# Reuben Thomas +# Lenard Heß # # website: https://github.com/rrthomas/ddclient # From e37cf1935039ebc17a79bc598aaccdd48092d616 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 10 Jul 2023 09:55:27 +0300 Subject: [PATCH 11/19] configure.ac: no longer require IO::Socket::IP or IO::Socket::SSL --- .github/workflows/ci.yml | 2 -- README.md | 1 - configure.ac | 2 -- 3 files changed, 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2deef1b..10dbc91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,8 +27,6 @@ jobs: curl \ libhttp-daemon-perl \ libhttp-daemon-ssl-perl \ - libio-socket-inet6-perl \ - libio-socket-ip-perl \ libplack-perl \ libtest-mockmodule-perl \ libtest-tcp-perl \ diff --git a/README.md b/README.md index 2823d8b..7f77d8f 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,6 @@ maintained through github.com. Please check out https://ddclient.net * An account from a supported dynamic DNS service provider * Perl v5.10.1 or later - * `IO::Socket::SSL` perl library for ssl-support * `JSON::PP` perl library for JSON support * Linux, macOS, or any other Unix-ish system * An implementation of `make` (such as [GNU diff --git a/configure.ac b/configure.ac index 945eba0..53dc3b8 100644 --- a/configure.ac +++ b/configure.ac @@ -44,7 +44,6 @@ m4_foreach_w([_m], [ File::Path File::Temp Getopt::Long - IO::Socket::IP Socket Sys::Hostname version=0.77 @@ -72,7 +71,6 @@ m4_foreach_w([_m], [ HTTP::Message::PSGI HTTP::Request HTTP::Response - IO::Socket::SSL Scalar::Util Test::MockModule Test::TCP From 5af1550e62979b1a322590fd1ac0f58caa24c23c Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 10 Jul 2023 10:02:29 +0300 Subject: [PATCH 12/19] README.md: update URLs to point to my fork --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7f77d8f..4b07511 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Dynamic DNS services currently supported include: broadband routers. Comments, suggestions and requests: please file an issue at -https://github.com/ddclient/ddclient/issues/new +https://github.com/rrthomas/ddclient/issues/new The code was originally written by Paul Burry and is now hosted and maintained through github.com. Please check out https://ddclient.net @@ -73,7 +73,7 @@ maintained through github.com. Please check out https://ddclient.net ## DOWNLOAD -See https://github.com/ddclient/ddclient/releases +See https://github.com/rrthomas/ddclient/releases ## INSTALLATION @@ -83,8 +83,7 @@ See https://github.com/ddclient/ddclient/releases Packaging status The easiest way to install ddclient is to install a package offered by your -operating system. See the image to the right for a list of distributions with a -ddclient package. +operating system. See the image to the right for a list of distributions with a ddclient package. ### Manual Installation From 541e7f350cd1f525aefc2cb0206b25022e541de6 Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Mon, 10 Jul 2023 22:05:03 +0300 Subject: [PATCH 13/19] Update error message to reflect that curl is always used --- ddclient.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient.in b/ddclient.in index d6a1a7d..9221a1c 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2502,7 +2502,7 @@ sub curl_cmd { 67 => "The user name, password, or similar was not accepted and curl failed to log in.", 77 => "Problem with reading the SSL CA cert (path? access rights?).", 78 => "The resource referenced in the URL does not exist.", - 127 => "You requested network access with curl but $system_curl was not found", + 127 => "$system_curl was not found", ); debug("CURL: %s", $system_curl); From 6c1f9632fac0782ebc039d511154686e0e1cacb3 Mon Sep 17 00:00:00 2001 From: Lenard Hess Date: Mon, 10 Jul 2023 18:57:02 +0200 Subject: [PATCH 14/19] Fixed caching behaviour for new providers with legacy 'use' logic --- ddclient.in | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ddclient.in b/ddclient.in index 9221a1c..c871b5b 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1337,6 +1337,18 @@ sub update_nics { if (@hosts) { $0 = sprintf("%s - updating %s", $program, join(',', @hosts)); &$update(@hosts); + + # Backwards compatibility: + # If we only have 'use', we set 'wantipv4' or 'wantipv6' depending on the IP type of + # 'wantip'. Newer provider implementations such as cloudflare only check 'wantipv*' + # and set 'status-ipv*' accordingly, ignoring 'wantip' and 'status'. + # For these we then load back the 'status' from 'status-ipv*' to ensure correct + # caching and updating behaviour. + foreach my $h (@hosts) { + $config{$h}{'status'} //= $config{$h}{'status-ipv4'}; + $config{$h}{'status'} //= $config{$h}{'status-ipv6'}; + } + runpostscript(join ' ', keys %ipsv4, keys %ipsv6); } } From 17fd6ec083b217bc1857801c8a3503a223888c07 Mon Sep 17 00:00:00 2001 From: Lenard Hess Date: Mon, 10 Jul 2023 21:48:27 +0200 Subject: [PATCH 15/19] Added preliminary explanation for provider functions --- ddclient.in | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/ddclient.in b/ddclient.in index c871b5b..3a4a078 100755 --- a/ddclient.in +++ b/ddclient.in @@ -3696,6 +3696,25 @@ sub header_ok { } return $ok; } + +###################################################################### +## DDNS providers +# A DDNS provider consists of an example function, the update +# function, and an optional updateable function. +# +# The example function simply returns a string for the help message, +# explaining how to configure the provider +# +# The update function performs the actual record update. +# It receives an array of hosts as its argument. +# +# The updateable function allows a provider implementation to force +# an update even if ddclient has itself determined no update is +# necessary. The function shall return 1 if an update should be +# performed, else 0. +###################################################################### + + ###################################################################### ## nic_dyndns1_examples ###################################################################### From 7869acb266ab4d6f3b48df9af1c0fccd903a67ee Mon Sep 17 00:00:00 2001 From: Lenard Hess Date: Thu, 13 Jul 2023 14:10:44 +0200 Subject: [PATCH 16/19] easydns, porkbun: Set status-ipv4 and status-ipv6 instead of status This fixes caching issues when using the 'usev4' or 'usev6' parameters. Without this, the "min-interval" and "warned-min-interval" limits will not work. For the legacy 'use' parameter, the wrapping code takes care of translating 'status-ipv*' to 'status'. --- ddclient.in | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/ddclient.in b/ddclient.in index 3a4a078..65076d2 100755 --- a/ddclient.in +++ b/ddclient.in @@ -4747,7 +4747,8 @@ sub nic_easydns_update { my ($status) = $line =~ /^(\S*)\b.*/; my $h = shift @hosts; - $config{$h}{'status'} = $status; + $config{$h}{'status-ipv4'} = $status if $ipv4; + $config{$h}{'status-ipv6'} = $status if $ipv6; if ($status eq 'NOERROR') { $config{$h}{'ipv4'} = $ipv4; $config{$h}{'ipv6'} = $ipv6; @@ -7081,12 +7082,12 @@ sub nic_porkbun_update { ); # No response, declare as failed if (!defined($reply) || !$reply) { - $config{$host}{'status'} = "bad"; + $config{$host}{'status-ipv4'} = "bad"; failed("updating %s: Could not connect to porkbun.com.", $host); next; } if (!header_ok($host, $reply)) { - $config{$host}{'status'} = "bad"; + $config{$host}{'status-ipv4'} = "bad"; failed("updating %s: failed (%s)", $host, $reply); next; } @@ -7095,12 +7096,12 @@ sub nic_porkbun_update { $reply =~ qr/{(?:[^{}]*|(?R))*}/mp; my $response = eval { decode_json(${^MATCH}) }; if (!defined($response)) { - $config{$host}{'status'} = "bad"; + $config{$host}{'status-ipv4'} = "bad"; failed("%s -- Unexpected service response.", $host); next; } if ($response->{status} ne 'SUCCESS') { - $config{$host}{'status'} = "bad"; + $config{$host}{'status-ipv4'} = "bad"; failed("%s -- Unexpected status. (status = %s)", $host, $response->{status}); next; } @@ -7112,7 +7113,7 @@ sub nic_porkbun_update { } my $current_content = $records->[0]->{'content'}; if ($current_content eq $ipv4) { - $config{$host}{'status'} = "good"; + $config{$host}{'status-ipv4'} = "good"; success("updating %s: skipped: IPv4 address was already set to %s.", $host, $ipv4); next; } @@ -7144,11 +7145,11 @@ sub nic_porkbun_update { failed("updating %s: failed (%s)", $host, $reply); next; } - $config{$host}{'status'} = "good"; + $config{$host}{'status-ipv4'} = "good"; success("updating %s: good: IPv4 address set to %s", $host, $ipv4); next; } else { - $config{$host}{'status'} = "bad"; + $config{$host}{'status-ipv4'} = "bad"; failed("updating %s: No applicable existing records.", $host); next; } @@ -7174,12 +7175,12 @@ sub nic_porkbun_update { ); # No response, declare as failed if (!defined($reply) || !$reply) { - $config{$host}{'status'} = "bad"; + $config{$host}{'status-ipv6'} = "bad"; failed("updating %s: Could not connect to porkbun.com.", $host); next; } if (!header_ok($host, $reply)) { - $config{$host}{'status'} = "bad"; + $config{$host}{'status-ipv6'} = "bad"; failed("updating %s: failed (%s)", $host, $reply); next; } @@ -7188,12 +7189,12 @@ sub nic_porkbun_update { $reply =~ qr/{(?:[^{}]*|(?R))*}/mp; my $response = eval { decode_json(${^MATCH}) }; if (!defined($response)) { - $config{$host}{'status'} = "bad"; + $config{$host}{'status-ipv6'} = "bad"; failed("%s -- Unexpected service response.", $host); next; } if ($response->{status} ne 'SUCCESS') { - $config{$host}{'status'} = "bad"; + $config{$host}{'status-ipv6'} = "bad"; failed("%s -- Unexpected status. (status = %s)", $host, $response->{status}); next; } @@ -7205,7 +7206,7 @@ sub nic_porkbun_update { } my $current_content = $records->[0]->{'content'}; if ($current_content eq $ipv6) { - $config{$host}{'status'} = "good"; + $config{$host}{'status-ipv6'} = "good"; success("updating %s: skipped: IPv6 address was already set to %s.", $host, $ipv6); next; } @@ -7237,11 +7238,11 @@ sub nic_porkbun_update { failed("updating %s: failed (%s)", $host, $reply); next; } - $config{$host}{'status'} = "good"; + $config{$host}{'status-ipv6'} = "good"; success("updating %s: good: IPv6 address set to %s", $host, $ipv4); next; } else { - $config{$host}{'status'} = "bad"; + $config{$host}{'status-ipv6'} = "bad"; failed("updating %s: No applicable existing records.", $host); next; } From d4db5cbc90b229ea29fc9f3071ca0c9b25353775 Mon Sep 17 00:00:00 2001 From: Lenard Hess Date: Tue, 18 Jul 2023 13:13:55 +0200 Subject: [PATCH 17/19] Updated changelog for v3.11.0_rc1 Note: I dropped the "DynDNS2 now uses the newer ipv4/ipv6 syntaxes", as its not visible to users. --- ChangeLog.md | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 533e7d0..39f6d33 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,7 +3,7 @@ This document describes notable changes. For details, see the [source code repository history](https://github.com/ddclient/ddclient/commits/master). -## 2023-XX-XX v3.11.0 +## 2023-XX-XX v3.11.0_1 ### Breaking changes @@ -15,20 +15,29 @@ repository history](https://github.com/ddclient/ddclient/commits/master). ### New features - * Added support for domaindiscount24.com - * Added support for domeneshop.no - * Added support for Enom - * Added support for Mythic Beasts Dynamic DNS - * Added support for DNSExit API v2 (replaces old DNSExit). - * Added support for njal.la - * Added support for Porkbun - * Added support for IPv6 to the EasyDNS and DuckDNS provider + * Introduced `usev4` and `usev6` for separate IPv4/IPv6 configuration. These will replace the legacy `use` eventually. + * Added support for moving secrets out of the configuration through environment variables + * Extended postscript mechanism + * sample-get-ip-from-fritzbox: Added environment variable to override hostname + +### Provider updates: + * Added regfish + * Added domeneshop.no + * Added Mythic Beasts + * Added Porkbun + * Added Enom + * Added DigitalOcean + * Added Infomaniak + * Added DNSExit API v2 + * Removed old DNSExit API + * Extended EasyDNS to support IPv6 + * Extended duckdns to support IPv6 ### Bug fixes - * DynDNS2 now uses the newer ipv4/ipv6 syntaxes + * Fixed various issues with caching + * Fixed issues with Hetzner zones * The OVH provider now ignores extra data returned - * Allow to define usev4 and usev6 options per hostname * Merge multiple configs for the same hostname instead of use the last ## 2022-10-20 v3.10.0 From 1715bd3a177b831ba21eab57b4d24e344d1d3976 Mon Sep 17 00:00:00 2001 From: Lenard Hess Date: Tue, 18 Jul 2023 20:45:15 +0200 Subject: [PATCH 18/19] Fixed hosts trying to update if IP acquisition failed --- ChangeLog.md | 1 + ddclient.in | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 39f6d33..1a24aad 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -19,6 +19,7 @@ repository history](https://github.com/ddclient/ddclient/commits/master). * Added support for moving secrets out of the configuration through environment variables * Extended postscript mechanism * sample-get-ip-from-fritzbox: Added environment variable to override hostname + * Warn about hosts where no IP could be determined - and skip the (bogus) update. ### Provider updates: * Added regfish diff --git a/ddclient.in b/ddclient.in index 65076d2..2cb24c2 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1328,6 +1328,12 @@ sub update_nics { # But we will set 'wantip' to the IPv4 so old functions continue to work until we update them all $config{$h}{'wantip'} = $ipv4 if (!$ip && $ipv4); + if (!$ip && !$ipv4 && !$ipv6) + { + warning("Could not determine an IP for %s", $h); + next; + } + next if !nic_updateable($h, $updateable); push @hosts, $h; From ef496d108fea294ef83d7be6a6f9eb40ae3fc25c Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Wed, 19 Jul 2023 22:27:44 +0300 Subject: [PATCH 19/19] Revert URLs to canonical repo --- README.md | 4 ++-- ddclient.in | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4b07511..c58f7b4 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Dynamic DNS services currently supported include: broadband routers. Comments, suggestions and requests: please file an issue at -https://github.com/rrthomas/ddclient/issues/new +https://github.com/ddclient/ddclient/issues/new The code was originally written by Paul Burry and is now hosted and maintained through github.com. Please check out https://ddclient.net @@ -73,7 +73,7 @@ maintained through github.com. Please check out https://ddclient.net ## DOWNLOAD -See https://github.com/rrthomas/ddclient/releases +See https://github.com/ddclient/ddclient/releases ## INSTALLATION diff --git a/ddclient.in b/ddclient.in index 2cb24c2..f852c5e 100755 --- a/ddclient.in +++ b/ddclient.in @@ -8,7 +8,7 @@ # Reuben Thomas # Lenard Heß # -# website: https://github.com/rrthomas/ddclient +# website: https://github.com/ddclient/ddclient # ###################################################################### package ddclient;