Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Brian 2025-01-09 23:14:01 -05:00
commit 6fa3c98496
18 changed files with 290 additions and 69 deletions

View file

@ -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)!' <<EOF || fatal "--autosquash commits not allowed without the 'pr-permit-autosquash' label"
${out}
EOF

View file

@ -3,6 +3,51 @@
This document describes notable changes. For details, see the [source code
repository history](https://github.com/ddclient/ddclient/commits/main).
## v4.0.0-rc.3 (unreleased work-in-progress)
### Breaking changes
* The string argument to `--cmdv4` or `--cmdv6` is now executed as-is by the
system's shell, matching the behavior of the deprecated `--cmd` option.
This makes it possible to pass command-line arguments, which reduces the
need for a custom wrapper script. Beware that the string is also subject to
the shell's command substitution, quote handling, variable expansion, field
splitting, etc., so you may need to add extra escaping to ensure that any
special characters are preserved literally.
[#766](https://github.com/ddclient/ddclient/pull/766)
## 2025-01-07 v4.0.0-rc.2
### Breaking changes
* ddclient now looks for `ddclient.conf` in `${sysconfdir}/ddclient` by
default instead of `${sysconfdir}`.
[#789](https://github.com/ddclient/ddclient/pull/789)
To retain the previous behavior, pass `'--with-confdir=${sysconfdir}'` to
`configure`. For example:
```shell
# Before v4.0.0:
./configure --sysconfdir=/etc
# Equivalent with v4.0.0 and later (the single quotes are intentional):
./configure --sysconfdir=/etc --with-confdir='${sysconfdir}'
```
or:
```shell
# Before v4.0.0:
./configure --sysconfdir=/etc/ddclient
# Equivalent with v4.0.0 and later:
./configure --sysconfdir=/etc
```
### New features
* New `--mail-from` option to control the "From:" header of email messages.
[#565](https://github.com/ddclient/ddclient/pull/565)
## 2024-12-25 v4.0.0-rc.1
### Breaking changes

View file

@ -28,8 +28,8 @@ $(subst_files): Makefile
-e 's|@PACKAGE_VERSION[@]|$(PACKAGE_VERSION)|g' \
-e '1 s|^#\!.*perl$$|#\!$(PERL)|g' \
-e 's|@localstatedir[@]|$(localstatedir)|g' \
-e 's|@confdir[@]|$(confdir)|g' \
-e 's|@runstatedir[@]|$(runstatedir)|g' \
-e 's|@sysconfdir[@]|$(sysconfdir)|g' \
-e 's|@CURL[@]|$(CURL)|g' \
"$${in}" >'$@'.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 \

View file

@ -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

View file

@ -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])])])

View file

@ -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
##

View file

@ -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=<max> : log at most <max> warning messages for undefined IP address"],
["mail", "=s", "--mail=<address> : e-mail messages to <address>"],
["mail-failure", "=s", "--mail-failure=<addr> : e-mail messages for failed updates to <addr>"],
["mail-from", "=s", '--mail-from=<addr> : set the "From:" header in e-mail messages to <addr> 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=<command>"
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=<command>"
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') {

View file

@ -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') {

View file

@ -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-----

View file

@ -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})) {

View file

@ -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');

View file

@ -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');

View file

@ -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) = @_;

View file

@ -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(

View file

@ -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});

View file

@ -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 = (

41
t/use_cmd.pl Normal file
View file

@ -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();

View file

@ -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};