diff --git a/Makefile.am b/Makefile.am index 2b5eebc..7f77c79 100644 --- a/Makefile.am +++ b/Makefile.am @@ -68,6 +68,7 @@ handwritten_tests = \ t/is-and-extract-ipv6.pl \ t/is-and-extract-ipv6-global.pl \ t/parse_assignments.pl \ + t/ssl-validate.pl \ t/write_cache.pl generated_tests = \ t/version.pl diff --git a/ddclient.in b/ddclient.in index 07c55af..58f47e7 100755 --- a/ddclient.in +++ b/ddclient.in @@ -62,7 +62,8 @@ local $lineno = ''; $ENV{'PATH'} = (exists($ENV{PATH}) ? "$ENV{PATH}:" : "") . "/sbin:/usr/sbin:/bin:/usr/bin:/etc:/usr/lib:"; our %globals; -my ($result, %config, %cache); +our %config; +my ($result, %cache); my $saved_cache; my %saved_opt; my $daemon; @@ -3191,7 +3192,7 @@ sub get_ipv4 { $reply = geturl( proxy => opt('proxy', $h), url => $url, ipversion => 4, # when using a URL to find IPv4 address we should force use of IPv4 - ssl_validate => opt('ssl-validate', $h), + ssl_validate => opt('web-ssl-validate', $h), ) // ''; } @@ -3223,7 +3224,7 @@ sub get_ipv4 { password => opt('fw-password', $h), ipversion => 4, # when using a URL to find IPv4 address we should force use of IPv4 ignore_ssl_option => 1, - ssl_validate => opt('ssl-validate', $h), + ssl_validate => opt('fw-ssl-validate', $h), ) // ''; } elsif ($usev4 eq 'disabled') { @@ -3251,7 +3252,7 @@ sub get_ipv4 { password => opt('fw-password', $h), ipversion => 4, # when using a URL to find IPv4 address we should force use of IPv4 ignore_ssl_option => 1, - ssl_validate => opt('ssl-validate', $h), + ssl_validate => opt('fw-ssl-validate', $h), ) // ''; } } @@ -3340,7 +3341,7 @@ sub get_ipv6 { proxy => opt('proxy'), url => $url, ipversion => 6, # when using a URL to find IPv6 address we should force use of IPv6 - ssl_validate => opt('ssl-validate', $h), + ssl_validate => opt('web-ssl-validate', $h), ) // ''; } diff --git a/t/lib/ddclient/Test/Fake/HTTPD.pm b/t/lib/ddclient/Test/Fake/HTTPD.pm index 0a7ba85..308fb99 100644 --- a/t/lib/ddclient/Test/Fake/HTTPD.pm +++ b/t/lib/ddclient/Test/Fake/HTTPD.pm @@ -1,8 +1,11 @@ -# Copied from https://metacpan.org/pod/release/MASAKI/Test-Fake-HTTPD-0.08/lib/Test/Fake/HTTPD.pm +# Copied from https://metacpan.org/release/MASAKI/Test-Fake-HTTPD-0.09/source/lib/Test/Fake/HTTPD.pm # and modified as follows: -# * Patched with https://github.com/masaki/Test-Fake-HTTPD/pull/4 to add IPv6 support. +# * Added this comment block. +# * Patched with https://github.com/masaki/Test-Fake-HTTPD/pull/6 to fix server exit if TLS +# negotiation fails. # * Changed package name to ddclient::Test::Fake::HTTPD. # +# Copyright: 2011-2020 NAKAGAWA Masaki # License: This library is free software; you can redistribute it and/or modify it under the same # terms as Perl itself. @@ -20,7 +23,7 @@ use Scalar::Util qw(blessed weaken); use Carp qw(croak); use Exporter qw(import); -our $VERSION = '0.08'; +our $VERSION = '0.09'; $VERSION = eval $VERSION; our @EXPORT = qw( @@ -101,9 +104,10 @@ sub run { $self->port || '', $@ eq '' ? '' : ": $@")) unless $d; - $d->accept; # wait for port check from parent process - - while (my $c = $d->accept) { + while (1) { + # accept can return undef if TLS handshake fails (e.g., port test or client rejects + # cert). + my $c = $d->accept or next; while (my $req = $c->get_request) { my $res = $self->_to_http_res($app->($req)); $c->send_response($res); @@ -143,7 +147,7 @@ sub endpoint { my $self = shift; my $uri = URI->new($self->scheme . ':'); my $host = $self->host; - $host = 'localhost' if !defined($host) || $host eq '0.0.0.0' || $host eq '::'; + $host = 'localhost' if !defined($host) || $host eq '' || $host eq '0.0.0.0' || $host eq '::'; $uri->host($host); $uri->port($self->port); return $uri; diff --git a/t/ssl-validate.pl b/t/ssl-validate.pl new file mode 100644 index 0000000..bf2265d --- /dev/null +++ b/t/ssl-validate.pl @@ -0,0 +1,118 @@ +use Test::More; +eval { + require ddclient::Test::Fake::HTTPD; + require HTTP::Daemon::SSL; +} or plan(skip_all => $@); +SKIP: { eval { require Test::Warnings; } or skip($@, 1); } +eval { require 'ddclient'; } or BAIL_OUT($@); +my $ipv6_supported = eval { + require IO::Socket::IP; + my $ipv6_socket = IO::Socket::IP->new( + Domain => 'PF_INET6', + LocalHost => '::1', + Listen => 1, + ); + defined($ipv6_socket); +}; +my $http_daemon_supports_ipv6 = eval { + require HTTP::Daemon; + HTTP::Daemon->VERSION(6.12); +}; + +# Note: $ddclient::globals{'ssl_ca_file'} is intentionally NOT set to "$certdir/dummy-ca-cert.pem" +# so that we can test what happens when certificate validation fails. +my $certdir = "$ENV{abs_top_srcdir}/t/lib/ddclient/Test/Fake/HTTPD"; + +sub run_httpd { + my ($ipv6) = @_; + return undef if $ipv6 && (!$ipv6_supported || !$http_daemon_supports_ipv6); + my $addr = $ipv6 ? '::1' : '127.0.0.1'; + my $httpd = ddclient::Test::Fake::HTTPD->new( + host => $addr, + scheme => 'https', + daemon_args => { + SSL_cert_file => "$certdir/dummy-server-cert.pem", + SSL_key_file => "$certdir/dummy-server-key.pem", + V6Only => 1, + }, + ); + $httpd->run(sub { + return [200, ['Content-Type' => 'text/plain'], [$addr]]; + }); + diag(sprintf("started IPv%s SSL server running at %s", $ipv6 ? '6' : '4', $httpd->endpoint())); + return $httpd; +} +my $h = 't/ssl-validate.pl'; +my %httpd = ( + '4' => run_httpd(0), + '6' => run_httpd(1), +); +my %ep = ( + '4' => $httpd{'4'}->endpoint(), + '6' => $httpd{'6'} ? $httpd{'6'}->endpoint() : undef, +); + +my @test_cases = ( + { + desc => 'usev4=webv4 web-ssl-validate=no', + cfg => {'usev4' => 'webv4', 'web-ssl-validate' => 0, 'webv4' => $ep{'4'}}, + want => '127.0.0.1', + }, + { + desc => 'usev4=webv4 web-ssl-validate=yes', + cfg => {'usev4' => 'webv4', 'web-ssl-validate' => 1, 'webv4' => $ep{'4'}}, + want => undef, + }, + { + desc => 'usev6=webv6 web-ssl-validate=no', + cfg => {'usev6' => 'webv6', 'web-ssl-validate' => 0, 'webv6' => $ep{'6'}}, + ipv6 => 1, + want => '::1', + }, + { + desc => 'usev6=webv6 web-ssl-validate=yes', + cfg => {'usev6' => 'webv6', 'web-ssl-validate' => 1, 'webv6' => $ep{'6'}}, + ipv6 => 1, + want => undef, + }, + { + desc => 'usev4=cisco-asa fw-ssl-validate=no', + cfg => {'usev4' => 'cisco-asa', 'fw-ssl-validate' => 0, + # cisco-asa adds https:// to the URL. :-/ + 'fwv4' => substr($ep{'4'}, length('https://'))}, + want => '127.0.0.1', + }, + { + desc => 'usev4=cisco-asa fw-ssl-validate=yes', + cfg => {'usev4' => 'cisco-asa', 'fw-ssl-validate' => 1, + # cisco-asa adds https:// to the URL. :-/ + 'fwv4' => substr($ep{'4'}, length('https://'))}, + want => undef, + }, + { + desc => 'usev4=fwv4 fw-ssl-validate=no', + cfg => {'usev4' => 'fwv4', 'fw-ssl-validate' => 0, 'fwv4' => $ep{'4'}}, + want => '127.0.0.1', + }, + { + desc => 'usev4=fwv4 fw-ssl-validate=yes', + cfg => {'usev4' => 'fwv4', 'fw-ssl-validate' => 1, 'fwv4' => $ep{'4'}}, + want => undef, + }, +); + +for my $tc (@test_cases) { + SKIP: { + skip("IPv6 not supported on this system", 1) if $tc->{ipv6} && !$ipv6_supported; + skip("HTTP::Daemon too old for IPv6 support", 1) + if $tc->{ipv6} && !$http_daemon_supports_ipv6; + $ddclient::config{$h} = $tc->{cfg}; + %ddclient::config if 0; # suppress spurious warning "Name used only once: possible typo" + is(ddclient::get_ipv4($tc->{cfg}{usev4}, $h), $tc->{want}, $tc->{desc}) + if ($tc->{cfg}{usev4}); + is(ddclient::get_ipv6($tc->{cfg}{usev6}, $h), $tc->{want}, $tc->{desc}) + if ($tc->{cfg}{usev6}); + } +} + +done_testing();