diff --git a/README.md b/README.md index 521297b..fc9c368 100644 --- a/README.md +++ b/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 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 @@ -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/)) diff --git a/configure.ac b/configure.ac index 8a550d0..8750122 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/ddclient.conf.in b/ddclient.conf.in index 23613ee..6f50a1c 100644 --- a/ddclient.conf.in +++ b/ddclient.conf.in @@ -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 diff --git a/ddclient.in b/ddclient.in index 4abc7ed..e4de740 100755 --- a/ddclient.in +++ b/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 + # Bad auth => badauth + # Bad domain name => nohost + # Bad IP => nohost + # IP changed => good + # 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 diff --git a/sample-etc_systemd.service b/sample-etc_systemd.service index 5fcc1ef..cd70712 100644 --- a/sample-etc_systemd.service +++ b/sample-etc_systemd.service @@ -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 diff --git a/t/geturl_connectivity.pl.in b/t/geturl_connectivity.pl.in index 2e825d0..5127b30 100644 --- a/t/geturl_connectivity.pl.in +++ b/t/geturl_connectivity.pl.in @@ -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) diff --git a/t/geturl_ssl.pl b/t/geturl_ssl.pl index c070def..2b45f0b 100644 --- a/t/geturl_ssl.pl +++ b/t/geturl_ssl.pl @@ -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',