Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
6fa3c98496
18 changed files with 290 additions and 69 deletions
18
.github/workflows/pr.yml
vendored
18
.github/workflows/pr.yml
vendored
|
@ -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
|
||||
|
|
45
ChangeLog.md
45
ChangeLog.md
|
@ -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
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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
|
||||
|
|
15
configure.ac
15
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])])])
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
##
|
||||
|
|
19
ddclient.in
19
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=<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') {
|
||||
|
|
|
@ -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') {
|
||||
|
|
80
t/lib/ddclient/Test/Fake/HTTPD/other-ca-cert.pem
Normal file
80
t/lib/ddclient/Test/Fake/HTTPD/other-ca-cert.pem
Normal 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-----
|
|
@ -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})) {
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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) = @_;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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
41
t/use_cmd.pl
Normal 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();
|
18
t/use_web.pl
18
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};
|
||||
|
|
Loading…
Reference in a new issue