diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index b36bfa6..b4a7729 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -29,3 +29,21 @@ jobs: git show "${out}" >&2 exit 1 } + no-autosquash: + if: ${{ !contains(github.event.pull_request.labels.*.name, 'pr-permit-autosquash') }} + name: No --autosquash commits + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: 'No commits with messages starting with "fixup!", "squash!", or "amend!"' + run: | + log() { printf %s\\n "$*" >&2; } + error() { log "ERROR: $@"; } + fatal() { error "$@"; exit 1; } + try() { log "Running command $@"; "$@" || fatal "'$@' failed"; } + out=$(try git log --oneline '${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}') || exit 1 + ! grep -E '^[^ ]* (fixup|squash|amend)!' <'$@'.tmp && \ { ! test -x "$${in}" || chmod +x '$@'.tmp; } @@ -40,7 +40,7 @@ ddclient.conf: $(srcdir)/ddclient.conf.in bin_SCRIPTS = ddclient -sysconf_DATA = ddclient.conf +conf_DATA = ddclient.conf install-data-local: $(MKDIR_P) '$(DESTDIR)$(localstatedir)'/cache/ddclient @@ -77,6 +77,7 @@ handwritten_tests = \ t/skip.pl \ t/ssl-validate.pl \ t/update_nics.pl \ + t/use_cmd.pl \ t/use_web.pl \ t/variable_defaults.pl \ t/write_recap.pl @@ -156,6 +157,7 @@ EXTRA_DIST += $(handwritten_tests) \ t/lib/ddclient/Test/Fake/HTTPD/dummy-ca-cert.pem \ t/lib/ddclient/Test/Fake/HTTPD/dummy-server-cert.pem \ t/lib/ddclient/Test/Fake/HTTPD/dummy-server-key.pem \ + t/lib/ddclient/Test/Fake/HTTPD/other-ca-cert.pem \ t/lib/ddclient/t.pm \ t/lib/ddclient/t/HTTPD.pm \ t/lib/ddclient/t/ip.pm \ diff --git a/README.md b/README.md index f517737..40e0303 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ operating system. See the image to the right for a list of distributions with a ```shell ./configure \ --prefix=/usr \ - --sysconfdir=/etc/ddclient \ + --sysconfdir=/etc \ --localstatedir=/var make make VERBOSE=1 check diff --git a/configure.ac b/configure.ac index 827f8b7..4ccecc8 100644 --- a/configure.ac +++ b/configure.ac @@ -23,6 +23,18 @@ AC_REQUIRE_AUX_FILE([tap-driver.sh]) AM_INIT_AUTOMAKE([1.11 -Wall -Werror foreign subdir-objects parallel-tests]) AM_SILENT_RULES +m4_define([CONFDIR_DEFAULT], [${sysconfdir}/AC_PACKAGE_NAME]) +AC_ARG_WITH( + [confdir], + [AS_HELP_STRING( + [--with-confdir=DIR], + m4_expand([[look for ddclient.conf in DIR @<:@default: ]CONFDIR_DEFAULT[@:>@]]))], + [], + # The single quotes are intentional; see: + # https://www.gnu.org/software/automake/manual/html_node/Uniform.html + [with_confdir='CONFDIR_DEFAULT']) +AC_SUBST([confdir], [${with_confdir}]) + AC_PROG_MKDIR_P # The Fedora Docker image doesn't come with the 'findutils' package. @@ -75,6 +87,7 @@ m4_foreach_w([_m], [ # then some tests will fail. Only prints a warning if not installed. m4_foreach_w([_m], [ B + Exporter File::Spec::Functions File::Temp List::Util @@ -88,7 +101,6 @@ m4_foreach_w([_m], [ # prints a warning if not installed. m4_foreach_w([_m], [ Carp - Exporter HTTP::Daemon=6.12 HTTP::Daemon::SSL HTTP::Message::PSGI @@ -100,6 +112,7 @@ m4_foreach_w([_m], [ Test::Warnings Time::HiRes URI + parent ], [AX_PROG_PERL_MODULES([_m], [], [AC_MSG_WARN([some tests may be skipped due to missing module _m])])]) diff --git a/ddclient.conf.in b/ddclient.conf.in index d760826..02c4158 100644 --- a/ddclient.conf.in +++ b/ddclient.conf.in @@ -25,6 +25,9 @@ daemon=300 # check every 300 seconds syslog=yes # log update msgs to syslog mail=root # mail all msgs to root mail-failure=root # mail failed update msgs to root +# mail-from=root # set the email "From:" header to "root". If + # unset (the default) or empty, the from address + # depends on your system's default behavior. pid=@runstatedir@/ddclient.pid # record PID in file. # postscript=script # run script after updating. The new IP is # added as argument. @@ -130,10 +133,10 @@ pid=@runstatedir@/ddclient.pid # record PID in file. ## ## NearlyFreeSpeech.NET (nearlyfreespeech.net) ## -# protocol = nfsn, \ +# protocol=nfsn, \ +# zone=example.com, \ # login=member-login, \ -# password=api-key, \ -# zone=example.com \ +# password=api-key \ # example.com,subdomain.example.com ## diff --git a/ddclient.in b/ddclient.in index 776d885..e6662a7 100755 --- a/ddclient.in +++ b/ddclient.in @@ -78,7 +78,7 @@ use Sys::Hostname; # # For consistency and to match user expectations, the release part of the version is always three # components: MAJOR.MINOR.PATCH. -use version 0.77; our $VERSION = version->declare('v4.0.0.0_901'); +use version 0.77; our $VERSION = version->declare('v4.0.0.0_903'); sub parse_version { my ($v) = @_; @@ -132,7 +132,7 @@ sub subst_var { return $subst; } -my $etc = subst_var('@sysconfdir@', '/etc/ddclient'); +my $etc = subst_var('@confdir@', '/etc/ddclient'); my $cachedir = subst_var('@localstatedir@', '/var') . '/cache/ddclient'; our @curl = (subst_var('@CURL@', 'curl')); @@ -704,6 +704,7 @@ our %cfgvars = ( 'priority' => setv(T_STRING,0, 'notice', undef), 'mail' => setv(T_EMAIL, 0, undef, undef), 'mail-failure' => setv(T_EMAIL, 0, undef, undef), + 'mail-from' => setv(T_EMAIL, 0, undef, undef), 'max-warn' => setv(T_NUMBER,0, 1, undef), 'exec' => setv(T_BOOL, 0, 1, undef), @@ -1436,6 +1437,7 @@ my @opt = ( ["max-warn", "=i", "--max-warn= : log at most warning messages for undefined IP address"], ["mail", "=s", "--mail=
: e-mail messages to
"], ["mail-failure", "=s", "--mail-failure= : e-mail messages for failed updates to "], + ["mail-from", "=s", '--mail-from= : set the "From:" header in e-mail messages to if non-empty'], ["exec", "!", "--{no}exec : do {not} execute; just show what would be done"], ["debug", "!", "--{no}debug : print {no} debugging information"], ["verbose", "!", "--{no}verbose : print {no} verbose information"], @@ -2413,8 +2415,10 @@ sub sendmail { $recipients = opt('mail-failure'); } if ($emailbody && $recipients && $emailbody ne $last_emailbody) { + my $sender = opt('mail-from') // ''; pipecmd("sendmail -oi $recipients", "To: $recipients", + $sender ne '' ? ("From: $sender") : (), "Subject: status report from $program\@$hostname", "\r\n", $emailbody, @@ -3346,10 +3350,9 @@ sub get_ipv4 { } elsif ($p{'usev4'} eq 'cmdv4') { ## Obtain IPv4 address by executing the command in "cmdv4=" warning("'--cmd-skip' ignored for '--usev4=$p{'usev4'}'") - if (opt('verbose') && $p{'cmd-skip'}); + if opt('verbose') && defined($p{'cmd-skip'}); if ($arg) { - my $sys_cmd = quotemeta($arg); - $reply = qx{$sys_cmd}; + $reply = qx{$arg}; $reply = '' if $?; } } elsif ($p{'usev4'} eq 'webv4') { @@ -3459,10 +3462,10 @@ sub get_ipv6 { $ipv6 = get_ip_from_interface($arg, 6); } elsif ($p{'usev6'} eq 'cmdv6' || $p{'usev6'} eq 'cmd') { ## Obtain IPv6 address by executing the command in "cmdv6=" - warning("'--cmd-skip' ignored") if opt('verbose') && p{'cmd-skip'}; + warning("'--cmd-skip' ignored for '--usev6=$p{'usev6'}'") + if opt('verbose') && defined($p{'cmd-skip'}); if ($arg) { - my $sys_cmd = quotemeta($arg); - $reply = qx{$sys_cmd}; + $reply = qx{$arg}; $reply = '' if $?; } } elsif ($p{'usev6'} eq 'webv6' || $p{'usev6'} eq 'web') { diff --git a/t/geturl_connectivity.pl b/t/geturl_connectivity.pl index b0dd94d..d3f2033 100644 --- a/t/geturl_connectivity.pl +++ b/t/geturl_connectivity.pl @@ -1,12 +1,11 @@ use Test::More; BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } } BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); } -BEGIN { - eval { require ddclient::t::HTTPD; 1; } or plan(skip_all => $@); - ddclient::t::HTTPD->import(); -} +use ddclient::t::HTTPD; use ddclient::t::ip; +httpd_required(); + $ddclient::globals{'ssl_ca_file'} = $ca_file; for my $ipv ('4', '6') { diff --git a/t/lib/ddclient/Test/Fake/HTTPD/other-ca-cert.pem b/t/lib/ddclient/Test/Fake/HTTPD/other-ca-cert.pem new file mode 100644 index 0000000..c15b26c --- /dev/null +++ b/t/lib/ddclient/Test/Fake/HTTPD/other-ca-cert.pem @@ -0,0 +1,80 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 6c:bf:34:52:19:4d:c9:29:2b:a6:8b:41:59:aa:c6:c5:1f:a2:bb:10 + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=Root Certification Authority + Validity + Not Before: Jan 8 08:24:32 2025 GMT + Not After : Jan 9 08:24:32 2125 GMT + Subject: CN=Root Certification Authority + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:c3:3d:19:6b:72:0a:9e:87:c0:28:a1:ff:d0:08: + 21:55:52:71:92:f2:98:36:75:fc:95:b4:0c:5e:c9: + 98:b3:3c:a1:ee:cf:91:6f:07:bf:82:c9:d5:51:c0: + eb:f8:46:17:41:52:1d:c6:89:ec:63:dd:5c:30:87: + a7:b5:0d:dd:ae:bf:46:fd:de:1a:be:1d:69:83:0d: + fb:d9:5a:33:0b:8d:5f:63:76:fc:a8:b1:54:37:1e: + 0b:12:44:93:90:39:1c:48:ee:f0:f2:12:fe:dc:fb: + 58:a5:76:3b:e8:e8:94:44:1e:9d:03:22:5f:21:6a: + 17:66:d1:4a:bf:12:d7:3c:15:76:11:76:09:ab:bf: + 21:ef:0c:a5:a9:e0:08:99:63:19:26:e4:d8:5d:c2: + 40:8b:98:e6:5d:df:b3:8c:63:e2:01:7c:5e:fb:55: + 39:a8:67:78:80:d2:6b:61:b2:e2:2e:93:c0:9d:91: + 0e:a1:79:4f:fc:38:94:ff:6f:65:18:8f:3e:0b:8c: + 1f:cd:48:d7:46:5a:a2:76:d6:e0:bd:3c:aa:3d:44: + 9e:50:e6:fd:e1:12:1a:ee:a1:9a:69:48:60:63:da: + 41:ae:a7:3d:36:1b:95:fb:b7:f1:0d:60:cd:2f:e3: + b1:1f:b1:db:b4:98:a6:62:87:de:54:80:d1:45:43: + 5b:25 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + E1:7C:D3:C3:9E:C7:F5:2C:DA:7C:D7:85:78:91:BA:26:88:61:F9:D4 + X509v3 Authority Key Identifier: + E1:7C:D3:C3:9E:C7:F5:2C:DA:7C:D7:85:78:91:BA:26:88:61:F9:D4 + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 9d:dc:49:c6:14:13:19:38:d9:14:b5:70:f0:3b:01:8e:d7:32: + a7:69:f0:21:68:ec:ad:8c:ee:53:7d:16:64:7d:3e:c2:d2:ac: + 5a:54:17:55:84:43:1e:46:1d:42:01:fb:89:e0:db:ec:e8:f0: + 3c:22:82:54:1d:38:12:21:45:3c:37:44:3b:2e:c9:4d:ed:8d: + 6e:46:f5:a5:cc:ba:39:61:ab:df:cf:1f:d2:c9:40:e2:db:3f: + 05:ea:83:14:93:5f:0e:3d:33:be:98:04:80:87:25:3a:6c:ff: + 8e:87:6a:32:ed:1e:ec:54:90:9b:2a:6e:12:05:6a:9d:15:48: + 3c:ea:c6:9e:ab:71:58:1e:34:95:3f:9b:9e:e3:e5:4b:fb:9e: + 32:f2:d6:59:bf:8d:09:d6:e4:9e:9e:47:b9:d6:78:5f:f3:0c: + 98:ab:56:f0:18:5d:63:8e:83:ee:c1:f2:84:da:0e:64:af:1c: + 18:ff:b3:f9:15:0b:02:50:77:d1:0b:6e:ba:61:bc:9e:c3:37: + 63:91:26:e8:ce:77:9a:47:8f:ef:38:8f:9c:7f:f1:ab:7b:65: + a5:96:b6:92:2e:c7:d3:c3:7a:54:0d:d6:76:f5:d6:88:13:3b: + 17:e2:02:4e:3b:4d:10:95:0a:bb:47:e9:48:25:76:1d:7b:19: + 5c:6f:b8:a1 +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgIUbL80UhlNySkrpotBWarGxR+iuxAwDQYJKoZIhvcNAQEL +BQAwJzElMCMGA1UEAwwcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAgFw0y +NTAxMDgwODI0MzJaGA8yMTI1MDEwOTA4MjQzMlowJzElMCMGA1UEAwwcUm9vdCBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAMM9GWtyCp6HwCih/9AIIVVScZLymDZ1/JW0DF7JmLM8oe7PkW8Hv4LJ +1VHA6/hGF0FSHcaJ7GPdXDCHp7UN3a6/Rv3eGr4daYMN+9laMwuNX2N2/KixVDce +CxJEk5A5HEju8PIS/tz7WKV2O+jolEQenQMiXyFqF2bRSr8S1zwVdhF2Cau/Ie8M +pangCJljGSbk2F3CQIuY5l3fs4xj4gF8XvtVOahneIDSa2Gy4i6TwJ2RDqF5T/w4 +lP9vZRiPPguMH81I10ZaonbW4L08qj1EnlDm/eESGu6hmmlIYGPaQa6nPTYblfu3 +8Q1gzS/jsR+x27SYpmKH3lSA0UVDWyUCAwEAAaNjMGEwHQYDVR0OBBYEFOF808Oe +x/Us2nzXhXiRuiaIYfnUMB8GA1UdIwQYMBaAFOF808Oex/Us2nzXhXiRuiaIYfnU +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUA +A4IBAQCd3EnGFBMZONkUtXDwOwGO1zKnafAhaOytjO5TfRZkfT7C0qxaVBdVhEMe +Rh1CAfuJ4Nvs6PA8IoJUHTgSIUU8N0Q7LslN7Y1uRvWlzLo5Yavfzx/SyUDi2z8F +6oMUk18OPTO+mASAhyU6bP+Oh2oy7R7sVJCbKm4SBWqdFUg86saeq3FYHjSVP5ue +4+VL+54y8tZZv40J1uSenke51nhf8wyYq1bwGF1jjoPuwfKE2g5krxwY/7P5FQsC +UHfRC266YbyewzdjkSbozneaR4/vOI+cf/Gre2WllraSLsfTw3pUDdZ29daIEzsX +4gJOO00QlQq7R+lIJXYdexlcb7ih +-----END CERTIFICATE----- diff --git a/t/lib/ddclient/t/HTTPD.pm b/t/lib/ddclient/t/HTTPD.pm index f9a5f1a..997e451 100644 --- a/t/lib/ddclient/t/HTTPD.pm +++ b/t/lib/ddclient/t/HTTPD.pm @@ -7,21 +7,42 @@ use warnings; use parent qw(ddclient::Test::Fake::HTTPD); use Exporter qw(import); -use JSON::PP; use Test::More; BEGIN { require 'ddclient'; } use ddclient::t::ip; our @EXPORT = qw( httpd + httpd_ok httpd_required $httpd_supported $httpd_support_error httpd_ipv6_ok httpd_ipv6_required $httpd_ipv6_supported $httpd_ipv6_support_error httpd_ssl_ok httpd_ssl_required $httpd_ssl_supported $httpd_ssl_support_error - $ca_file $certdir + $ca_file $certdir $other_ca_file $textplain ); -our $httpd_ssl_support_error; -our $httpd_ssl_supported = eval { require HTTP::Daemon::SSL; 1; } or $httpd_ssl_support_error = $@; +our $httpd_supported; +our $httpd_support_error; +BEGIN { + $httpd_supported = eval { + require parent; parent->import(qw(ddclient::Test::Fake::HTTPD)); + require JSON::PP; JSON::PP->import(); + 1; + } or $httpd_support_error = $@; +} + +sub httpd_ok { + ok($httpd_supported, "HTTPD is supported") or diag($httpd_support_error); +} + +sub httpd_required { + plan(skip_all => $httpd_support_error) if !$httpd_supported; +} + +our $httpd_ssl_supported = $httpd_supported; +our $httpd_ssl_support_error = $httpd_support_error; +$httpd_ssl_supported = eval { require HTTP::Daemon::SSL; 1; } + or $httpd_ssl_support_error = $@ + if $httpd_ssl_supported; sub httpd_ssl_ok { ok($httpd_ssl_supported, "SSL is supported") or diag($httpd_ssl_support_error); @@ -31,8 +52,11 @@ sub httpd_ssl_required { plan(skip_all => $httpd_ssl_support_error) if !$httpd_ssl_supported; } -our $httpd_ipv6_support_error; -our $httpd_ipv6_supported = $ipv6_supported or $httpd_ipv6_support_error = $ipv6_support_error; +our $httpd_ipv6_supported = $httpd_supported; +our $httpd_ipv6_support_error = $httpd_support_error; +$httpd_ipv6_supported = $ipv6_supported + or $httpd_ipv6_support_error = $ipv6_support_error + if $httpd_ipv6_supported; $httpd_ipv6_supported = eval { require HTTP::Daemon; HTTP::Daemon->VERSION(6.12); } or $httpd_ipv6_support_error = $@ if $httpd_ipv6_supported; @@ -104,6 +128,7 @@ sub reset { our $certdir = "$ENV{abs_top_srcdir}/t/lib/ddclient/Test/Fake/HTTPD"; our $ca_file = "$certdir/dummy-ca-cert.pem"; +our $other_ca_file = "$certdir/other-ca-cert.pem"; my %daemons; @@ -111,6 +136,7 @@ sub httpd { my ($ipv, $ssl) = @_; $ipv //= ''; $ssl = !!$ssl; + return undef if !$httpd_supported; return undef if $ipv eq '6' && !$httpd_ipv6_supported; return undef if $ssl && !$httpd_ssl_supported; if (!defined($daemons{$ipv}{$ssl})) { diff --git a/t/protocol_directnic.pl b/t/protocol_directnic.pl index 30be5d5..92b245a 100644 --- a/t/protocol_directnic.pl +++ b/t/protocol_directnic.pl @@ -2,10 +2,9 @@ use Test::More; BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } } BEGIN { eval { require JSON::PP; 1; } or plan(skip_all => $@); JSON::PP->import(); } BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); } -BEGIN { - eval { require ddclient::t::HTTPD; 1; } or plan(skip_all => $@); - ddclient::t::HTTPD->import(); -} +use ddclient::t::HTTPD; + +httpd_required(); ddclient::load_json_support('directnic'); diff --git a/t/protocol_dnsexit2.pl b/t/protocol_dnsexit2.pl index 0586276..7b78fac 100644 --- a/t/protocol_dnsexit2.pl +++ b/t/protocol_dnsexit2.pl @@ -2,10 +2,9 @@ use Test::More; BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } } BEGIN { eval { require JSON::PP; 1; } or plan(skip_all => $@); JSON::PP->import(); } BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); } -BEGIN { - eval { require ddclient::t::HTTPD; 1; } or plan(skip_all => $@); - ddclient::t::HTTPD->import(); -} +use ddclient::t::HTTPD; + +httpd_required(); ddclient::load_json_support('dnsexit2'); diff --git a/t/protocol_dyndns2.pl b/t/protocol_dyndns2.pl index 682be57..b3130e0 100644 --- a/t/protocol_dyndns2.pl +++ b/t/protocol_dyndns2.pl @@ -1,12 +1,10 @@ use Test::More; BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } } use MIME::Base64; -use Scalar::Util qw(blessed); BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); } -BEGIN { - eval { require ddclient::t::HTTPD; 1; } or plan(skip_all => $@); - ddclient::t::HTTPD->import(); -} +use ddclient::t::HTTPD; + +httpd_required(); httpd()->run(sub { my ($req) = @_; diff --git a/t/skip.pl b/t/skip.pl index 1ee68a3..3f0ba3a 100644 --- a/t/skip.pl +++ b/t/skip.pl @@ -1,12 +1,11 @@ use Test::More; BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } } BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); } -BEGIN { - eval { require ddclient::t::HTTPD; 1; } or plan(skip_all => $@); - ddclient::t::HTTPD->import(); -} +use ddclient::t::HTTPD; use ddclient::t::ip; +httpd_required(); + httpd('4')->run( sub { return [200, ['Content-Type' => 'text/plain'], ['127.0.0.1 skip 127.0.0.2']]; }); httpd('6')->run( diff --git a/t/ssl-validate.pl b/t/ssl-validate.pl index 5939dd9..6bea9a3 100644 --- a/t/ssl-validate.pl +++ b/t/ssl-validate.pl @@ -1,16 +1,14 @@ use Test::More; BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } } BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); } -BEGIN { - eval { require ddclient::t::HTTPD; 1; } or plan(skip_all => $@); - ddclient::t::HTTPD->import(); -} +use ddclient::t::HTTPD; use ddclient::t::ip; -httpd_ssl_required(); +local $ddclient::globals{debug} = 1; +local $ddclient::globals{verbose} = 1; -# 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. +httpd_required(); +httpd_ssl_required(); httpd('4', 1)->run(sub { return [200, $textplain, ['127.0.0.1']]; }); httpd('6', 1)->run(sub { return [200, $textplain, ['::1']]; }) if httpd('6', 1); @@ -70,10 +68,21 @@ my @test_cases = ( ); for my $tc (@test_cases) { + local $ddclient::_l = ddclient::pushlogctx($tc->{desc}); 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} && !$httpd_ipv6_supported; - $ddclient::config{$h} = $tc->{cfg}; + # $ddclient::globals{'ssl_ca_file'} is intentionally NOT set to $ca_file so that we can + # test what happens when certificate validation fails. However, if curl can't find any CA + # certificates (which may be the case in some minimal test environments, such as Docker + # images and Debian package builder chroots), it will immediately close the connection + # after it sends the TLS client hello and before it receives the server hello (in Debian + # sid as of 2025-01-08, anyway). This confuses IO::Socket::SSL (used by + # Test::Fake::HTTPD), causing it to hang in the middle of the TLS handshake waiting for + # input that will never arrive. To work around this, the CA certificate file is explicitly + # set to an unrelated certificate so that curl has something to read. + local $ddclient::globals{'ssl_ca_file'} = $other_ca_file; + local $ddclient::config{$h} = $tc->{cfg}; %ddclient::config if 0; # suppress spurious warning "Name used only once: possible typo" is(ddclient::get_ipv4(ddclient::strategy_inputs('usev4', $h)), $tc->{want}, $tc->{desc}) if ($tc->{cfg}{usev4}); diff --git a/t/update_nics.pl b/t/update_nics.pl index d656c0f..e0fe679 100644 --- a/t/update_nics.pl +++ b/t/update_nics.pl @@ -6,12 +6,11 @@ BEGIN { eval { require JSON::PP; 1; } or plan(skip_all => $@); JSON::PP->import( use List::Util qw(max); use Scalar::Util qw(refaddr); BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); } -BEGIN { - eval { require ddclient::t::HTTPD; 1; } or plan(skip_all => $@); - ddclient::t::HTTPD->import(); -} +use ddclient::t::HTTPD; use ddclient::t::ip; +httpd_required(); + httpd('4')->run(); httpd('6')->run() if httpd('6'); local %ddclient::builtinweb = ( diff --git a/t/use_cmd.pl b/t/use_cmd.pl new file mode 100644 index 0000000..1391da9 --- /dev/null +++ b/t/use_cmd.pl @@ -0,0 +1,41 @@ +use Test::More; +BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } } +BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); } + +local $ddclient::globals{debug} = 1; +local $ddclient::globals{verbose} = 1; + +my @test_cases; +for my $ipv ('4', '6') { + my $ip = $ipv eq '4' ? '192.0.2.1' : '2001:db8::1'; + for my $use ('use', "usev$ipv") { + my @cmds = (); + push(@cmds, 'cmd') if $use eq 'use' || $ipv eq '6'; + push(@cmds, "cmdv$ipv") if $use ne 'use'; + for my $cmd (@cmds) { + my $cmdarg = "echo '$ip'"; + push( + @test_cases, + { + desc => "$use=$cmd $cmd=\"$cmdarg\"", + cfg => {$use => $cmd, $cmd => $cmdarg}, + want => $ip, + }, + ); + } + } +} + +for my $tc (@test_cases) { + local $ddclient::_l = ddclient::pushlogctx($tc->{desc}); + my $h = 'test-host'; + local $ddclient::config{$h} = $tc->{cfg}; + is(ddclient::get_ip(ddclient::strategy_inputs('use', $h)), $tc->{want}, $tc->{desc}) + if $tc->{cfg}{use}; + is(ddclient::get_ipv4(ddclient::strategy_inputs('usev4', $h)), $tc->{want}, $tc->{desc}) + if $tc->{cfg}{usev4}; + is(ddclient::get_ipv6(ddclient::strategy_inputs('usev6', $h)), $tc->{want}, $tc->{desc}) + if $tc->{cfg}{usev6}; +} + +done_testing(); diff --git a/t/use_web.pl b/t/use_web.pl index b129ecf..130034a 100644 --- a/t/use_web.pl +++ b/t/use_web.pl @@ -1,13 +1,11 @@ use Test::More; BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } } -use Scalar::Util qw(blessed); BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); } -BEGIN { - eval { require ddclient::t::HTTPD; 1; } or plan(skip_all => $@); - ddclient::t::HTTPD->import(); -} +use ddclient::t::HTTPD; use ddclient::t::ip; +httpd_required(); + my $builtinweb = 't/use_web.pl builtinweb'; my $h = 't/use_web.pl hostname'; @@ -70,16 +68,6 @@ for my $ipv ('4', '6') { } for my $tc (@test_cases) { - my $subst = sub { - return map({ - my $class = blessed($_); - (defined($class) && $class->isa('EndpointPlaceholder')) ? do { - my $uri = ${$_}->clone(); - $uri->query_param(tc => $tc->{desc}); - $uri; - } : $_; - } @_); - }; local $ddclient::builtinweb{$builtinweb} = $tc->{biw}; $ddclient::builtinweb if 0; local $ddclient::config{$h} = $tc->{cfg};