Merge branch 'ddclient:master' into develop
This commit is contained in:
commit
74bd0c2fa0
7 changed files with 185 additions and 60 deletions
13
README.md
13
README.md
|
|
@ -1,3 +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 <https://github.com/troglobit/inadyn> or <https://github.com/lopsided98/dnsupdate>.
|
||||
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
|
||||
|
|
@ -40,6 +51,7 @@ Dynamic DNS services currently supported include:
|
|||
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
|
||||
|
||||
`ddclient` now supports many cable and DSL broadband routers.
|
||||
|
||||
|
|
@ -54,7 +66,6 @@ through github.com. Please check out http://ddclient.net
|
|||
* Perl v5.10.1 or later
|
||||
* `IO::Socket::SSL` perl library for ssl-support
|
||||
* `JSON::PP` perl library for JSON support
|
||||
* `IO::Socket::INET6` perl library for ipv6-support
|
||||
* Linux, macOS, or any other Unix-ish system
|
||||
* An implementation of `make` (such as [GNU
|
||||
Make](https://www.gnu.org/software/make/))
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ m4_foreach_w([_m], [
|
|||
File::Path
|
||||
File::Temp
|
||||
Getopt::Long
|
||||
IO::Socket::INET
|
||||
IO::Socket::IP
|
||||
Socket
|
||||
Sys::Hostname
|
||||
version=0.77
|
||||
|
|
@ -71,8 +71,6 @@ m4_foreach_w([_m], [
|
|||
HTTP::Message::PSGI
|
||||
HTTP::Request
|
||||
HTTP::Response
|
||||
IO::Socket::INET6
|
||||
IO::Socket::IP
|
||||
IO::Socket::SSL
|
||||
Scalar::Util
|
||||
Test::MockModule
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ ssl=yes # use ssl-support. Works with
|
|||
#protocol=cloudflare, \
|
||||
#zone=domain.tld, \
|
||||
#ttl=1, \
|
||||
#login=your-login-email, \ # Only needed if you are using your global API key. If you are using an API token, set it to "token" (wihtout double quotes).
|
||||
#login=your-login-email, \ # Only needed if you are using your global API key. If you are using an API token, set it to "token" (without double quotes).
|
||||
#password=APIKey \ # This is either your global API key, or an API token. If you are using an API token, it must have the permissions "Zone - DNS - Edit" and "Zone - Zone - Read". The Zone resources must be "Include - All zones".
|
||||
#domain.tld,my.domain.tld
|
||||
|
||||
|
|
@ -365,3 +365,11 @@ ssl=yes # use ssl-support. Works with
|
|||
#zone=example.com, \
|
||||
#password=api-token \
|
||||
#example.com,sub.example.com
|
||||
|
||||
##
|
||||
## Infomaniak (www.infomaniak.com)
|
||||
##
|
||||
# protocol=infomaniak,
|
||||
# login=ddns_username,
|
||||
# password=ddns_password
|
||||
# example.com
|
||||
|
|
|
|||
205
ddclient.in
205
ddclient.in
|
|
@ -26,7 +26,7 @@ use File::Basename;
|
|||
use File::Path qw(make_path);
|
||||
use File::Temp;
|
||||
use Getopt::Long;
|
||||
use IO::Socket::INET;
|
||||
use IO::Socket::IP;
|
||||
use Socket qw(AF_INET AF_INET6 PF_INET PF_INET6);
|
||||
use Sys::Hostname;
|
||||
|
||||
|
|
@ -540,7 +540,7 @@ my %variables = (
|
|||
'wildcard' => setv(T_BOOL, 0, 1, 0, undef),
|
||||
},
|
||||
'keysystems-common-defaults' => {
|
||||
'server' => setv(T_FQDNP, 1, 0, 1, 'dynamicdns.key-systems.net', undef),
|
||||
'server' => setv(T_FQDNP, 1, 0, 'dynamicdns.key-systems.net', undef),
|
||||
'login' => setv(T_LOGIN, 0, 0, 0, 'unused', undef),
|
||||
},
|
||||
'dnsexit-common-defaults' => {
|
||||
|
|
@ -937,7 +937,7 @@ my %services = (
|
|||
'examples' => \&nic_zoneedit1_examples,
|
||||
'variables' => {
|
||||
%{$variables{'service-common-defaults'}},
|
||||
'min-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0),
|
||||
'min-interval' => setv(T_DELAY, 0, 0, interval('10m'), 0),
|
||||
'server' => setv(T_FQDNP, 1, 0, 'dynamic.zoneedit.com', undef),
|
||||
'zone' => setv(T_OFQDN, 0, 0, undef, undef),
|
||||
},
|
||||
|
|
@ -979,6 +979,14 @@ my %services = (
|
|||
'min-interval' => setv(T_DELAY, 0, 0, 0, interval('5m')),
|
||||
},
|
||||
},
|
||||
'infomaniak' => {
|
||||
'updateable' => undef,
|
||||
'update' => \&nic_infomaniak_update,
|
||||
'examples' => \&nic_infomaniak_examples,
|
||||
'variables' => {
|
||||
%{$variables{'service-common-defaults'}},
|
||||
},
|
||||
},
|
||||
);
|
||||
$variables{'merged'} = {
|
||||
map({ %{$services{$_}{'variables'}} } keys(%services)),
|
||||
|
|
@ -1212,7 +1220,8 @@ sub runpostscript {
|
|||
my ($ip) = @_;
|
||||
|
||||
if (defined $globals{postscript}) {
|
||||
if (-x $globals{postscript}) {
|
||||
my @postscript = split(/\s+/, $globals{postscript});
|
||||
if (-x $postscript[0]) {
|
||||
system("$globals{postscript} $ip &");
|
||||
} else {
|
||||
warning("Can not execute post script: %s", $globals{postscript});
|
||||
|
|
@ -2439,23 +2448,6 @@ EOM
|
|||
{ no warnings; $IO::Socket::SSL::DEBUG = 0; }
|
||||
}
|
||||
|
||||
######################################################################
|
||||
## load_ipv6_support
|
||||
######################################################################
|
||||
sub load_ipv6_support {
|
||||
my $ipv6_loaded = eval { require IO::Socket::INET6 };
|
||||
unless ($ipv6_loaded) {
|
||||
fatal("%s", <<"EOM");
|
||||
Error loading the Perl module IO::Socket::INET6 needed for ipv6 connect.
|
||||
On Debian, the package libio-socket-inet6-perl must be installed.
|
||||
On Red Hat, the package perl-IO-Socket-INET6 must be installed.
|
||||
On Alpine, the package perl-io-socket-inet6 must be installed.
|
||||
EOM
|
||||
}
|
||||
import IO::Socket::INET6;
|
||||
{ no warnings; $IO::Socket::INET6::DEBUG = 0; }
|
||||
}
|
||||
|
||||
######################################################################
|
||||
## load_sha1_support
|
||||
######################################################################
|
||||
|
|
@ -2574,10 +2566,9 @@ sub fetch_via_socket_io {
|
|||
PeerAddr => $peer,
|
||||
PeerPort => $port,
|
||||
Proto => 'tcp',
|
||||
MultiHomed => 1,
|
||||
Timeout => opt('timeout'),
|
||||
);
|
||||
my $socket_class = 'IO::Socket::INET';
|
||||
my $socket_class = 'IO::Socket::IP';
|
||||
if ($use_ssl) {
|
||||
# IO::Socket::SSL will load IPv6 support if available on the system.
|
||||
load_ssl_support;
|
||||
|
|
@ -2587,9 +2578,6 @@ sub fetch_via_socket_io {
|
|||
$socket_args{SSL_verify_mode} = ($params{ssl_validate} // 1)
|
||||
? IO::Socket::SSL->SSL_VERIFY_PEER
|
||||
: IO::Socket::SSL->SSL_VERIFY_NONE;
|
||||
} elsif ($globals{'ipv6'} || $ipversion eq '6') {
|
||||
load_ipv6_support;
|
||||
$socket_class = 'IO::Socket::INET6';
|
||||
}
|
||||
if (defined($params{_testonly_socket_class})) {
|
||||
$socket_args{original_socket_class} = $socket_class;
|
||||
|
|
@ -4194,30 +4182,38 @@ sub nic_dyndns2_update {
|
|||
# bug #10: some dyndns providers does not return the IP so
|
||||
# we can't use the returned IP
|
||||
my ($status, $returnedips) = split / /, lc $line;
|
||||
my $h = shift @hosts;
|
||||
|
||||
$config{$h}{'status'} = $status;
|
||||
$config{$h}{'status-ipv4'} = $status if $ipv4;
|
||||
$config{$h}{'status-ipv6'} = $status if $ipv6;
|
||||
foreach my $h (@hosts) {
|
||||
$config{$h}{'status'} = $status;
|
||||
$config{$h}{'status-ipv4'} = $status if $ipv4;
|
||||
$config{$h}{'status-ipv6'} = $status if $ipv6;
|
||||
}
|
||||
|
||||
if ($status eq 'good') {
|
||||
$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", $h, $status, $ipv4) if $ipv4;
|
||||
success("updating %s: %s: IPv6 address set to %s", $h, $status, $ipv6) if $ipv6;
|
||||
foreach 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", $h, $status, $errors{$status});
|
||||
$config{$h}{'ipv4'} = $ipv4 if $ipv4;
|
||||
$config{$h}{'ipv6'} = $ipv6 if $ipv6;
|
||||
$config{$h}{'mtime'} = $now;
|
||||
$config{$h}{'status'} = 'good';
|
||||
$config{$h}{'status-ipv4'} = 'good' if $ipv4;
|
||||
$config{$h}{'status-ipv6'} = 'good' if $ipv6;
|
||||
warning("updating %s: %s: %s", $hosts, $status, $errors{$status});
|
||||
|
||||
foreach my $h (@hosts) {
|
||||
$config{$h}{'ipv4'} = $ipv4 if $ipv4;
|
||||
$config{$h}{'ipv6'} = $ipv6 if $ipv6;
|
||||
$config{$h}{'mtime'} = $now;
|
||||
$config{$h}{'status'} = 'good';
|
||||
$config{$h}{'status-ipv4'} = 'good' if $ipv4;
|
||||
$config{$h}{'status-ipv6'} = 'good' if $ipv6;
|
||||
}
|
||||
|
||||
} else {
|
||||
failed("updating %s: %s: %s", $h, $status, $errors{$status});
|
||||
failed("updating %s: %s: %s", $hosts, $status, $errors{$status});
|
||||
}
|
||||
|
||||
} elsif ($status =~ /w(\d+)(.)/) {
|
||||
|
|
@ -4229,11 +4225,14 @@ sub nic_dyndns2_update {
|
|||
($scale, $units) = (60*60, 'hours') if $units eq 'h';
|
||||
|
||||
$sec = $wait * $scale;
|
||||
$config{$h}{'wtime'} = $now + $sec;
|
||||
warning("updating %s: %s: wait %s %s before further updates", $h, $status, $wait, $units);
|
||||
foreach 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)", $h, $line);
|
||||
failed("updating %s: unexpected status (%s)", $hosts, $line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4722,7 +4721,7 @@ sub nic_zoneedit1_update {
|
|||
|
||||
my @reply = split /\n/, $reply;
|
||||
foreach my $line (@reply) {
|
||||
if ($line =~ /^[^<]*<(SUCCESS|ERROR)\s+([^>]+)>(.*)/) {
|
||||
if ($h && $line =~ /^[^<]*<(SUCCESS|ERROR)\s+([^>]+)>(.*)/) {
|
||||
my ($status, $assignments, $rest) = ($1, $2, $3);
|
||||
my ($left, %var) = parse_assignments($assignments);
|
||||
|
||||
|
|
@ -7974,6 +7973,118 @@ sub nic_digitalocean_update {
|
|||
}
|
||||
}
|
||||
|
||||
######################################################################
|
||||
## nic_infomaniak_examples
|
||||
######################################################################
|
||||
sub nic_infomaniak_examples {
|
||||
return <<"EoEXAMPLE";
|
||||
|
||||
o 'infomaniak'
|
||||
|
||||
The 'infomaniak' protocol is used by DNS services offered by www.infomaniak.com.
|
||||
|
||||
Configuration variables applicable to the 'infomaniak' protocol are:
|
||||
protocol=infomaniak
|
||||
login=ddns_username ## the DDNS username set up in Infomaniak
|
||||
password=ddns_password ## the DDNS username set up in Infomaniak
|
||||
example.com ## domain name to update
|
||||
|
||||
Example ${program}.conf file entries:
|
||||
protocol=infomaniak, \\
|
||||
login=my-username, \\
|
||||
password=my-password \\
|
||||
my.address.com
|
||||
|
||||
For more information about how to create a dynamic DNS, see https://faq.infomaniak.com/2357.
|
||||
|
||||
EoEXAMPLE
|
||||
}
|
||||
|
||||
######################################################################
|
||||
## nic_infomaniak_update
|
||||
##
|
||||
## written by Timothée Andres
|
||||
##
|
||||
## based on https://faq.infomaniak.com/2376
|
||||
##
|
||||
## needs one of the following urls to update:
|
||||
## https://username:password@infomaniak.com/nic/update?hostname=subdomain.yourdomain.com&myip=1.2.3.4
|
||||
## https://infomaniak.com/nic/update?hostname=subdomain.yourdomain.com&myip=1.2.3.4&username=XXX&password=XXX
|
||||
######################################################################
|
||||
sub nic_infomaniak_update {
|
||||
debug("\nnic_infomaniak_update -------------------");
|
||||
|
||||
foreach my $h (@_) {
|
||||
INFOMANIAK_IP_LOOP:
|
||||
foreach my $v (4, 6) {
|
||||
my $ip = delete $config{$h}{"wantipv$v"};
|
||||
|
||||
if (!defined $ip) {
|
||||
debug("ipv%d not wanted, skipping", $v);
|
||||
next;
|
||||
}
|
||||
|
||||
verbose("setting IP address to %s for %s", $ip, $h);
|
||||
info("updating %s", $h);
|
||||
|
||||
# No change in IP => nochg <w.x.y.z>
|
||||
# Bad auth => badauth
|
||||
# Bad domain name => nohost
|
||||
# Bad IP => nohost
|
||||
# IP changed => good <xxxx::yyyy>
|
||||
# No domain name => Validation failed
|
||||
my %statuses = (
|
||||
'good' => (1, sprintf("IP set to %s for %s", $ip, $h)),
|
||||
'nochg' => (1, sprintf("IP already set to %s for %s", $ip, $h)),
|
||||
'nohost' => (0, sprintf("Bad domain name %s or bad IP %s", $h, $ip)),
|
||||
'badauth' => (0, sprintf("Bad authentication for %s", $h)),
|
||||
);
|
||||
|
||||
my $url1 = "https://$config{$h}{'login'}:$config{$h}{'password'}";
|
||||
$url1 .= "\@infomaniak.com/nic/update";
|
||||
$url1 .= "?hostname=$h";
|
||||
$url1 .= "&myip=$ip";
|
||||
|
||||
my $url2 = "https://infomaniak.com/nic/update";
|
||||
$url2 .= "?hostname=$h";
|
||||
$url2 .= "&myip=$ip";
|
||||
$url2 .= "&username=$config{$h}{'login'}";
|
||||
$url2 .= "&password=$config{$h}{'password'}";
|
||||
|
||||
my $reply;
|
||||
|
||||
foreach my $url ($url1, $url2) {
|
||||
verbose("trying update with %s", $url);
|
||||
$reply = geturl(proxy => opt('proxy'), url => $url);
|
||||
if (!defined($reply) || !$reply) {
|
||||
info("could not update %s using url %s, trying next one", $h, $url);
|
||||
next;
|
||||
}
|
||||
|
||||
my ($status) = split / /, $reply, 1;
|
||||
my ($updated, $msg) =
|
||||
$statuses{$status} // (0, sprintf("Unknown reply from Infomaniak: %s", $reply));
|
||||
|
||||
if (defined $updated && $updated) {
|
||||
info($msg);
|
||||
$config{$h}{"ipv$v"} = $ip;
|
||||
$config{$h}{'mtime'} = $config{$h}{'mtime'} // $now;
|
||||
$config{$h}{'status'} = 'good';
|
||||
$config{$h}{"status-ipv$v"} = 'good';
|
||||
next INFOMANIAK_IP_LOOP;
|
||||
}
|
||||
else {
|
||||
warning($msg);
|
||||
}
|
||||
}
|
||||
|
||||
$config{$h}{'status'} = $config{$h}{'status'} // 'failed';
|
||||
$config{$h}{"status-ipv$v"} = 'failed';
|
||||
failed("updating %s: could not update IP on Infomaniak", $h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Execute main() if this file is run as a script or run via PAR (https://metacpan.org/pod/PAR),
|
||||
# otherwise do nothing. This "modulino" pattern makes it possible to import this file as a module
|
||||
# and test its functions directly; there's no need for test-only command-line arguments or stdout
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
[Unit]
|
||||
Description=Dynamic DNS Update Client
|
||||
After=network.target network-online.target
|
||||
Wants=network-online.target
|
||||
After=network-online.target nss-lookup.target
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ eval { require ddclient::Test::Fake::HTTPD; } or plan(skip_all => $@);
|
|||
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
|
||||
eval { require 'ddclient'; } or BAIL_OUT($@);
|
||||
my $has_http_daemon_ssl = eval { require HTTP::Daemon::SSL; };
|
||||
my $has_io_socket_inet6 = eval { require IO::Socket::INET6; };
|
||||
my $ipv6_supported = eval {
|
||||
require IO::Socket::IP;
|
||||
my $ipv6_socket = IO::Socket::IP->new(
|
||||
|
|
@ -56,13 +55,13 @@ my %httpd = (
|
|||
);
|
||||
|
||||
my @test_cases = (
|
||||
# Fetch via IO::Socket::INET
|
||||
# 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::INET6
|
||||
# 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 => ''},
|
||||
|
|
@ -92,8 +91,6 @@ for my $tc (@test_cases) {
|
|||
$tc->{ssl} //= 0;
|
||||
$tc->{curl} //= 0;
|
||||
SKIP: {
|
||||
skip("IO::Socket::INET6 not available", 1)
|
||||
if ($tc->{ipv6_opt} || $tc->{client_ipv} eq '6') && !$tc->{curl} && !$has_io_socket_inet6;
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ my $args;
|
|||
# * 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 MultiHomed, Proto,
|
||||
# * 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.
|
||||
|
|
@ -244,7 +244,6 @@ for my $tc (@test_cases) {
|
|||
local $TODO = $tc->{todo};
|
||||
subtest $tc->{name} => sub {
|
||||
my %want_args = (
|
||||
MultiHomed => 1,
|
||||
Proto => 'tcp',
|
||||
Timeout => ddclient::opt('timeout'),
|
||||
original_socket_class => 'IO::Socket::SSL',
|
||||
|
|
|
|||
Loading…
Reference in a new issue