Compare commits

...

702 commits

Author SHA1 Message Date
25d162db91 Aggiorna README.md
Some checks failed
CI / test-debian-like (debian:oldstable) (push) Has been cancelled
CI / test-debian-like (debian:stable) (push) Has been cancelled
CI / test-debian-like (debian:testing) (push) Has been cancelled
CI / test-debian-like (ubuntu:20.04) (push) Has been cancelled
CI / test-debian-like (ubuntu:latest) (push) Has been cancelled
CI / test-fedora-like (almalinux:8) (push) Has been cancelled
CI / test-fedora-like (almalinux:latest) (push) Has been cancelled
CI / test-fedora-like (fedora:39) (push) Has been cancelled
CI / test-fedora-like (fedora:latest) (push) Has been cancelled
CI / test-fedora-like (fedora:rawhide) (push) Has been cancelled
2025-02-05 21:38:17 +08:00
Richard Hansen
50e8d2ed00 Post-release version bump 2025-01-19 14:46:10 -05:00
Richard Hansen
d6da6b878d Release v4.0.0 2025-01-19 14:29:22 -05:00
Richard Hansen
33a86eb556
Merge pull request #801 from insanolanbiri/upnp
Add UPnP example in configuration file
2025-01-16 04:10:59 -05:00
Eren Akgün
10d3561353
Add UPnP example in configuration file
This commit demonstrates a simple way to obtain the
IP address using UPnP in the ddclient configuration file.
2025-01-16 11:02:25 +03:00
Richard Hansen
803f77404d Post-release version bump 2025-01-11 03:30:56 -05:00
Richard Hansen
590d7d91fc Release v4.0.0-rc.3 2025-01-11 03:18:38 -05:00
Richard Hansen
41170b9c08
Merge pull request #684 from rhansen/dnsexit2
dnsexit2: Update multiple hosts at a time when possible
2025-01-11 03:09:48 -05:00
Richard Hansen
63bf3512a4 dnsexit2: Update multiple hosts at a time when possible 2025-01-10 19:20:25 -05:00
Richard Hansen
3b10e37607 tests: dnsexit2: Add test for host outside of zone 2025-01-10 19:20:25 -05:00
Richard Hansen
115f23dead tests: Option to select which log messages to capture 2025-01-10 19:20:25 -05:00
Richard Hansen
009033d476 tests: Factor out duplicate log capture code 2025-01-10 19:20:25 -05:00
Richard Hansen
d18b1cdb27 logging: Move filtering and reactions to Logger class
This increases overhead when the verbose or debug option is disabled,
but makes it possible for tests to intercept debug, info, and fatal
messages.
2025-01-10 19:20:25 -05:00
Richard Hansen
b31e5e2f91 tests: dnsexit2: Add test for two hosts in the same zone 2025-01-09 19:33:57 -05:00
Richard Hansen
3b73350541 tests: dnsexit2 Convert tests to table-driven
This makes the tests easier to read and extend.
2025-01-09 19:33:57 -05:00
Richard Hansen
6bb80cbdaa
Merge pull request #766 from greenius/feature/cmdargs
Enable arguments on cmdv4 and cmdv6
2025-01-09 19:33:11 -05:00
Richard Hansen
6fd9a6f106 tests: Add some tests for use=cmd, usev4=cmdv4, usev6=cmdv6 2025-01-09 19:29:00 -05:00
Richard Hansen
1c178d4c09 Add ChangeLog entry for --cmdv4, --cmdv6 change 2025-01-09 19:29:00 -05:00
steven
ae01ba26c1 Do not use quotemeta on cmdv4 and cmdv6 arguments
`quotemeta` prevents executing commands with arguments.  With this
change, it is now possible to get an IP address with:

    usev4=cmdv4
    cmdv4="dig +short myip.opendns.com @resolver1.opendns.com"
2025-01-09 19:29:00 -05:00
Richard Hansen
17fc4c0a35
Merge pull request #783 from indrajitr/eval-cmd-skip
Make 'cmd-skip' warning message consistent for IPv4 and IPv6
2025-01-08 19:13:17 -05:00
Indrajit Raychaudhuri
8dcea0d779 Make 'cmd-skip' warning message consistent for IPv4 and IPv6 2025-01-08 19:09:29 -05:00
Bodo Eggert
8883641d97 fix ddclient --verbose calling 'p' instead of using $p{foo} 2025-01-08 19:08:19 -05:00
Richard Hansen
741a2345ea
Merge pull request #795 from rhansen/missing-semicolon
tests: Add missing semicolon; prevent similar future bugs
2025-01-08 18:27:35 -05:00
Richard Hansen
8cf322e162 tests: Only skip HTTPD tests if dependencies are unavailable
This prevents the tests from passing due to syntax errors in
the ddclient::t::HTTPD module.
2025-01-08 18:24:04 -05:00
Richard Hansen
ddeaedc136 tests: Add missing semicolon
This should have been in commit
06c47695fc.  The tests that use this
module did not fail because an import failure is assumed to be caused
by a missing dependency, not a genuine bug.
2025-01-08 18:24:04 -05:00
Richard Hansen
9ab038412f
Merge pull request #796 from rhansen/vestigial
tests: Delete vestigial code
2025-01-08 18:23:41 -05:00
Richard Hansen
ecaa05abd3 tests: Delete vestigial code
This deleted code came from a previous unpublished prior draft of the
tests and was accidentally not deleted when the approach changed.
2025-01-08 18:20:39 -05:00
Richard Hansen
6408be6ccc
Merge pull request #792 from rhansen/ssl-validate-tests
tests: Fix t/ssl-validate.pl in minimal test environments
2025-01-08 14:43:14 -05:00
Richard Hansen
06c47695fc tests: Fix t/ssl-validate.pl in minimal test environments 2025-01-08 03:33:30 -05:00
Richard Hansen
c89a2d6186 tests: Enable debug logging in t/ssl-validate.pl 2025-01-08 03:33:30 -05:00
Richard Hansen
3f3b8cf825 tests: Localize config setting
This isn't strictly necessary, but is good practice because it
guarantees that the config is cleaned up after each test case.
2025-01-08 03:00:41 -05:00
Richard Hansen
8decfc4b77 Post-release version bump
We'll probably just release `v4.0.0` without an `-rc.3`, but I'm
setting it to `-rc.3` just in case (decreasing the version number can
break automatically built daily Git snapshots).
2025-01-07 14:37:32 -05:00
Richard Hansen
660bb11c02 Release v4.0.0-rc.2 2025-01-07 04:39:01 -05:00
Richard Hansen
fee71b46be
Merge pull request #789 from rhansen/confdir
Change default location of `ddclient.conf` to `${sysconfdir}/ddclient`
2025-01-07 04:36:37 -05:00
Richard Hansen
7248341ad6 Change default location of ddclient.conf to ${sysconfdir}/ddclient 2025-01-07 04:33:54 -05:00
Richard Hansen
60bedd0fab
Merge pull request #790 from rhansen/autosquash
New GitHub workflow to prohibit autosquash commits
2025-01-07 04:33:15 -05:00
Richard Hansen
56f88e3bab New GitHub workflow to prohibit autosquash commits
This prohibits commits whose commit message starts with "squash!",
"fixup!", or "amend!" unless the PR has the 'pr-permit-autosquash'
label.  Such commits are created via the `--fixup` or `--squash`
options to `git commit`, and cause `git rebase --autosquash` to
automatically adjust the todo list appropriately before performing the
rebase.  Their existence implies that the PR should be rebased with
`--autosquash` before merging.
2025-01-07 04:30:40 -05:00
Indrajit Raychaudhuri
8ffbedd436
Merge pull request #754 from Zorks/patch-1
Correct NearlyFreeSpeech.NET example
2025-01-06 20:38:34 -06:00
Indrajit Raychaudhuri
678b76f7e8 nfsn Rearrange zone property in example
Usually, the zone property is not the last property
in the configuration convention that is followed in
ddclient config examples.
2025-01-06 20:27:21 -06:00
Zorks
e4920373ee Correct NearlyFreeSpeech.NET example
extra spaces in the protocol and a trailing comma caused connection issues if not corrected.
2025-01-06 20:27:21 -06:00
Indrajit Raychaudhuri
4008ccfa2d
Merge pull request #565 from TinfoilSubmarine/feature/mail-from
add mail-from option
2025-01-06 20:24:49 -06:00
Indrajit Raychaudhuri
cf4bad127d fixup! add main-from option
move changelog entry to v4.0.0-rc.2
2025-01-06 20:21:56 -06:00
Richard Hansen
76fccba151 fixup! add mail-from option
add changelog entry
2025-01-06 20:20:26 -06:00
Richard Hansen
d2b1a4dfa6 fixup! add mail-from option
move variable declaration closer to usage
2025-01-06 20:20:26 -06:00
Richard Hansen
d1f81dc9e4 fixup! add mail-from option
factor out duplicate code
2025-01-06 20:20:26 -06:00
Richard Hansen
2de77f17f7 fixup! add mail-from option
default to undef
2025-01-06 20:20:26 -06:00
Richard Hansen
a2e818d6d3 fixup! add mail-from option
refine usage wording
2025-01-06 20:20:26 -06:00
Joel Beckmeyer
8030a46ca3 add mail-from option 2025-01-06 20:20:26 -06:00
Richard Hansen
59f6c2959a Prepare for v4.0.0-rc.2 2025-01-06 21:03:47 -05:00
Richard Hansen
0a687d505b
Merge pull request #782 from indrajitr/typo-fix
Fix small typo in 'nochg' message grammar
2024-12-27 14:47:50 -05:00
Indrajit Raychaudhuri
3da4259a41 Fix small typo in 'nochg' message grammar 2024-12-26 18:26:26 -06:00
Richard Hansen
87a919a715 Release v4.0.0-rc.1 2024-12-25 02:22:24 -05:00
Richard Hansen
59495e99d2
Merge pull request #779 from rhansen/linear-pr
New GitHub workflow to enforce linear pull requests
2024-12-25 02:11:28 -05:00
Richard Hansen
07289d5c48 New GitHub workflow to enforce linear pull requests
When combined with GitHub's "Require branches to be up to date before
merging" setting, this forces semi-linear merging.  This check can be
disabled by adding the "pr-permit-nonlinear" label to the PR.
2024-12-25 02:09:00 -05:00
Richard Hansen
4d7d6ae48e
Merge pull request #777 from rhansen/semver
Use semver 2.0.0 as the human-readable version string format
2024-12-24 19:45:34 -05:00
Richard Hansen
54b6d0cb0d Use semver 2.0.0 as the human-readable version string format
This avoids the need to escape tilde in tag names.
2024-12-23 21:40:07 -05:00
Richard Hansen
9f2d6279d2 Rename master branch to main 2024-12-21 22:00:56 -05:00
Richard Hansen
af4ea14fda
Merge pull request #771 from rhansen/build
Build system improvements
2024-12-19 05:08:06 -05:00
Richard Hansen
a12398c315 Makefile.am: Fix Automake portability warning
This silences:

    Makefile.am:20: warning: escaping \# comment markers is not portable
2024-12-19 05:00:56 -05:00
Richard Hansen
6dfcede81a Ignore build-aux/config.guess, build-aux/config.sub
These currently aren't installed by `autoreconf --install`, but ignore
them anyway as a defensive measure.
2024-12-19 04:49:57 -05:00
Richard Hansen
ceced7e094 Delete checked-in copy of tap-driver.sh
It is not needed now that RHEL 6 and CentOS 6 have reached EOL.
2024-12-19 04:49:57 -05:00
Richard Hansen
8fbf9ed4c8 autogen: Force regeneration of --install files 2024-12-19 04:49:29 -05:00
Richard Hansen
8aedcf47db autogen: Add a comment explaining the mkdir 2024-12-19 04:41:53 -05:00
Richard Hansen
d3e793bf21 Move m4 to build-aux/m4 to reduce clutter 2024-12-19 04:21:25 -05:00
Richard Hansen
b200e0c4e3 Mention #752 in ChangeLog.md 2024-12-19 03:59:25 -05:00
Richard Hansen
aba1df3e6b
Merge pull request #752 from cristian-aldea/master
update porkbun api endpoint domain
2024-12-19 03:51:43 -05:00
Joel Beckmeyer
eb48bb55ae make porkbun endpoint configurable 2024-12-19 03:50:03 -05:00
Cristian Aldea
d9365359bd update porkbun api endpoint domain 2024-12-19 03:50:03 -05:00
Richard Hansen
1c0ba9a126
Merge pull request #742 from rhansen/strategy-dedup
Improve deduplication of redundant `use*` queries
2024-09-06 18:58:09 -04:00
Richard Hansen
ad3cd11446 Improve deduplication of redundant use* queries 2024-09-06 18:53:14 -04:00
Richard Hansen
c71f6f6eae Prefetch the data relevant to the use* strategies
This is a preparatory step to improving the deduplication of queries.
It also makes possible future improvements to config validation and
help usage output.
2024-09-06 18:53:14 -04:00
Richard Hansen
f3678ce119 Don't get host-specific values of global options 2024-09-06 18:53:14 -04:00
Richard Hansen
5d545aae5c Simplify arg assignment for readability 2024-09-06 18:53:14 -04:00
Richard Hansen
490dc16d33
Merge pull request #741 from rhansen/tests
Unit test improvements
2024-09-06 18:51:44 -04:00
Richard Hansen
5ed43a2e4c tests: Factor out duplicate HTTP server code 2024-09-06 18:44:19 -04:00
Richard Hansen
62f3759c54 tests: Factor out duplicate IPv6 support detection code 2024-09-06 18:41:37 -04:00
Richard Hansen
9c7c0e55c1 tests: Refine module loads
* Wrap all conditional loads in `BEGIN {}` to be closer to the
    behavior of `use`.
  * Add missing `Test::Warnings`, `HTTP::Request` loads.
  * Sort by module name, except load `Test::More` first and
    `Test::Warnings` immediately after to maximize checking
    effectiveness.
  * Return truthy from `eval` block to prevent the `or` case from
    executing if the loaded module does not have a final truthy
    statement.  (Except for `ddclient` because we want to test that it
    does have a final truthy statement.)
2024-09-06 18:41:33 -04:00
Richard Hansen
dd7ad1ccf4 tests: dnsexit2: Use reserved IP addresses and domain names 2024-09-06 15:47:05 -04:00
Richard Hansen
d38fcbddb8 tests: dnsexit2: Rename variables to follow got, want pattern 2024-09-06 15:47:05 -04:00
Richard Hansen
d0eb899fc8 tests: dnsexit2: Localize changes to %config 2024-09-06 15:47:05 -04:00
Richard Hansen
e8d79d842c tests: dnsexit2: Inline unnecessary helper function
for readability
2024-09-06 15:47:05 -04:00
Richard Hansen
7653f60058 tests: dnsexit2: Move request to a variable
for readability
2024-09-06 15:46:59 -04:00
Richard Hansen
c768f1350b tests: dnsexit2: Check number of requests 2024-09-06 15:24:16 -04:00
Richard Hansen
e9029b85d5 tests: dnsexit2: Simplify request check 2024-09-06 15:22:55 -04:00
Richard Hansen
bd1e42ac6c tests: dnsexit2: Declare @requests where used
for readability
2024-09-06 15:22:55 -04:00
Richard Hansen
d7861b6d61 tests: Do use parent -norequire instead of modifying ISA 2024-09-06 15:22:55 -04:00
Richard Hansen
6c33ccaa25 tests: Always have a truthy final expression in modules
This ensures that `eval { require Module; } or ...` doesn't execute
the `or` case when loading is successful.
2024-09-06 15:22:55 -04:00
Richard Hansen
2ccdd3b19e tests: Delete obsolete debugging comments
All log output goes to stderr, so turning on `debug` or `verbose` will
not interfere with TAP.  A better way to debug is to add the following
to whatever scope you think is appropriate:

    local $ddclient::globals{debug} = 1;
    local $ddclient::globals{verbose} = 1;
2024-09-06 15:22:55 -04:00
Richard Hansen
8b7581287c tests: update_nics: Test number of web* queries
This will be used in a future commit to test deduplication of of
`use*` strategies.
2024-09-06 15:22:46 -04:00
Richard Hansen
b6ac0e6d05 tests: update_nics: Support multiple hosts
This will be used in a future commit to test deduplication of `use*`
strategies.
2024-09-06 15:22:37 -04:00
Richard Hansen
f32f7fc29a tests: update_nics: Fix tracking of update calls
The tests weren't failing because there was only one host being
updated at a time, but this will change in a future commit.
2024-09-06 15:22:18 -04:00
Richard Hansen
a7abfcb715
Merge pull request #740 from rhansen/recap
Overhaul recap handling
2024-09-02 03:59:15 -04:00
Richard Hansen
695c3c4be8 Separate recap variables from configuration variables 2024-09-02 03:55:09 -04:00
Richard Hansen
76afbb6673 _read_config: Add infrastructure for host-dependent validation
This is a preparatory step for separating recap variables from config
variables.
2024-09-02 03:55:09 -04:00
Richard Hansen
0f1ea65fd7 _read_config: Minor refactor for readability and maintainability 2024-09-02 03:55:09 -04:00
Richard Hansen
ac67c04f13 _read_config: Check host definedness, not existence 2024-09-02 03:55:09 -04:00
Richard Hansen
a18efcbe32 Force an update if a host's protocol changes 2024-09-02 03:55:09 -04:00
Richard Hansen
1e3bebc60d Object-oriented protocol definitions
This improves readability and will make it easier to refactor to fix
issues or add features.
2024-09-02 03:55:09 -04:00
Richard Hansen
2da08cceb9 Convert static list of config change detection vars to per-protocol 2024-09-02 03:55:09 -04:00
Richard Hansen
273af1c821 nic_updateable: Ignore non-recap vars when detecting config change 2024-09-02 03:55:09 -04:00
Richard Hansen
803621a9ee Switch "magic constant" list of change detection vars to a var 2024-09-02 03:55:09 -04:00
Richard Hansen
268369a05e Write update status to %recap, not %config
Status is not configuration so it doesn't belong in `%config`.

`wantip`, `wantipv4`, and `wantipv6` are still passed along in
`%config` because `group_hosts_by` needs access to them like other
settings.
2024-09-02 03:55:09 -04:00
Richard Hansen
0348ded46b write_recap: Move update-specific %recap sync to update_nics
This is a step toward separating `%recap` from `%config`.
2024-09-01 20:10:42 -04:00
Richard Hansen
e478117d4e write_recap: Move warned-min-* recap sync to where they are set
This is a step toward separating `%recap` from `%config`.
2024-09-01 20:10:26 -04:00
Richard Hansen
1a748e7a86 write_recap: Only update variables that could have changed
This is a step toward improving readability of `%config`/`%recap`
synchronization.
2024-09-01 20:10:09 -04:00
Richard Hansen
7660ca52bf write_recap: Remove unnecessary recap existence check 2024-09-01 20:09:52 -04:00
Richard Hansen
2927f205ea update_nics: Move non-config recap var reset to update call
for readability
2024-09-01 20:09:35 -04:00
Richard Hansen
974bba4d93 update_nics: Don't set wantip* if they're all undef
This helps keep `%config` "clean", which helps with testing and
debugging.
2024-09-01 20:09:18 -04:00
Richard Hansen
75552f80f7 nic_updateable: Don't mutate status-ipv* vars if not updating
This simplifies the logic a bit and improves readability.
2024-09-01 20:09:01 -04:00
Richard Hansen
25fac765a0 nic_updateable: Move clearing of update to write_recap
for readability (the logic that uses the `update` boolean should be
responsible for clearing it).
2024-09-01 20:08:44 -04:00
Richard Hansen
5256a1d02c update_nics: Move legacy protocol support to an adapter function 2024-09-01 20:08:27 -04:00
Richard Hansen
a178d40633 update_nics: Combine post-update host loops 2024-09-01 20:08:10 -04:00
Richard Hansen
bf83ba032c update_nics: Move legacy wantip assignment to update call
This consolidates the legacy support with other legacy support logic,
which will make it easier to refactor in a future commit.
2024-09-01 20:07:53 -04:00
Richard Hansen
c5df774b7e update_nics: Change || next to or next (for readability) 2024-09-01 20:07:36 -04:00
Richard Hansen
20439bc130 update_nics: Refine comment 2024-09-01 20:07:19 -04:00
Richard Hansen
cb66870019 update_nics: Refine debug message for consistency/readability 2024-09-01 20:07:02 -04:00
Richard Hansen
78be40fe2c update_nics: Remove unnecessary assertions
These just add cold code paths and impair readability and
maintainability.
2024-09-01 20:06:45 -04:00
Richard Hansen
499318fbe0 update_nics: Always overwrite status-ipv* with value from status 2024-09-01 20:06:28 -04:00
Richard Hansen
94ce6367ec write_recap: Also clear out non-recap and stale values
Before, if a non-`undef` value was in `%recap` and the corresponding
value in `%config` became `undef`, the `%recap` value would remain
untouched.  Now it is deleted to match `%config`.

Also, any `%recap` values without a corresponding recap variable
declaration are deleted.
2024-09-01 20:06:11 -04:00
Richard Hansen
c64e432bf1 write_recap: Update all status recap vars when writing recap 2024-09-01 20:05:54 -04:00
Richard Hansen
f2c9ef6641 read_recap: Scrub recap values without var declarations 2024-09-01 20:05:40 -04:00
Richard Hansen
70e2b51377 read_recap: Don't copy non-recap values to %config 2024-09-01 20:04:45 -04:00
Richard Hansen
8359eff6ea read_recap: Check variable definedness, not existence 2024-09-01 20:03:13 -04:00
Richard Hansen
989f8be8c3 read_recap: Delete from %config any status values missing from recap 2024-09-01 20:02:58 -04:00
Richard Hansen
c9cdb96086 read_recap: Fix copying of recap values into %config 2024-09-01 20:02:34 -04:00
Richard Hansen
fbd7167b94 read_recap: Fix iteration over hosts 2024-09-01 18:08:10 -04:00
Richard Hansen
35cbc8d200 read_recap: Reference %recap directly (for readability)
There's no need to pass a reference to `%recap` as an argument when
that is the only way `read_recap` is ever used.
2024-09-01 18:08:10 -04:00
Richard Hansen
31740006d0 read_recap: Use a named loop variable (for readability) 2024-09-01 18:08:10 -04:00
Richard Hansen
65d2473213 read_recap: Don't load ip from recap
This should have been part of commit
de5d894c91 but I forgot.
2024-09-01 18:08:10 -04:00
Richard Hansen
ce1bcaa68b nic_updateable: Set warned-min-* in %config, not %recap
Currently the semantics of recap variables are that values are updated
in `%config` and propagate to `%recap`.  Before this commit,
`warned-min-interval` and `warned-min-error-interval` were set in
`%recap` instead of `%config`, meaning if they followed the semantics
they would be overwritten or deleted when synced with `%config`.  Now
the values are set in `%config` to match the behavior of other recap
variables.
2024-09-01 18:08:10 -04:00
Richard Hansen
e8b3d9168b Remove unnecessary variables from the recap
The logic does not use the persisted values so they do not need to be
persisted.
2024-09-01 18:08:10 -04:00
Richard Hansen
c943d7c0d9 tests: Add some unit tests for read_recap 2024-09-01 18:05:53 -04:00
Richard Hansen
0a9ee106e4 tests: Debug log when in protocol update callback 2024-09-01 02:44:05 -04:00
Richard Hansen
7181152c78 cloudflare: Delete unused variable declarations 2024-09-01 01:01:33 -04:00
Richard Hansen
4b5f28b2f0 Add/update TODO comments for problematic bits of code 2024-09-01 00:59:55 -04:00
Richard Hansen
cf54da50e4 read_recap: Invert condition (for readability) 2024-09-01 00:54:41 -04:00
Richard Hansen
c2db690efb Whitespace fixes 2024-09-01 00:54:41 -04:00
Richard Hansen
3dafdbf604
Merge pull request #732 from rhansen/legacy-status
Fix handling of legacy `status` value
2024-08-22 04:20:37 -04:00
Richard Hansen
de5d894c91 Fix handling of legacy status value
When a legacy protocol implementation returns, move its `status` and
`ip` results to the new `status-ipv4` and `ipv4` (or `status-ipv6` and
`ipv6`) properties.

Also remove the now-unused `status` variable definition, and remove
`ip` from the recap.
2024-08-22 02:08:39 -04:00
Richard Hansen
4f89492dc0 nic_updateable: Use wantip definedness, not use enabledness
Rather than check whether `use`, `usev4`, or `usev6` is a non-disabled
value, check that `wantip`, `wantipv4`, or `wantipv6` is a defined
value.  This is preparation for removing the `status` variable in a
future commit.
2024-08-22 02:08:39 -04:00
Richard Hansen
a21e215ada Reduce unnecessary values in %config and %recap
* Delete values from `$config{$h}` and `$recap{$h}` when resetting
    values (as opposed to setting a falsy value).
  * Delete values from `$config{$h}` and `$recap{$h}` when they are no
    longer needed.

This is mostly done to improve the tests in `t/update_nics.pl`.
2024-08-22 02:08:39 -04:00
Richard Hansen
acd8dfe47f Don't force use to disabled if usev4 or usev6 is enabled
Now that the default changes depending on `usev4` and `usev6`, this is
no longer necessary.  Removing it simplifies the code a bit and makes
the behavior of unit tests match the overall behavior a bit better.
2024-08-22 02:08:39 -04:00
Richard Hansen
f024bcce34 Dynamically compute default for use based on usev4, usev6
This is mostly to simplify tests, but it also improves readability.

The infrastructure changes in this commit also make it possible to
introduce a new `url` variable that defaults to `opt('server', $h)`
concatenated with `opt('script', $h)` so that we can start migrating
away from those user-unfriendly variables.
2024-08-22 02:08:39 -04:00
Richard Hansen
46bd2f1771 tests: Also test default() return value 2024-08-22 02:08:39 -04:00
Richard Hansen
f23070a114 Change defaults for warned-min{,-error}-interval from 0 to undef
The code already treats `undef` and 0 the same, and `undef` omits them
from the recap which simplifies testing.
2024-08-22 02:08:39 -04:00
Richard Hansen
603a59ffe3 Delete redundant variable declarations from global-defaults 2024-08-22 02:08:39 -04:00
Richard Hansen
533e4735cd init_config: Support any variable as a command-line arg
This doesn't add any new command-line arguments, but it does mean that
a new command-line argument can be added for any variable, not just
those in `$variables{'global-defaults'}`, and its value will be copied
to `%globals`.

My main motivation for this commit is to make it possible to remove
the redundant variable declarations between
`$variables{'global-defaults'}` and
`$variables{'protocol-common-defaults'}`.
2024-08-22 02:08:39 -04:00
Richard Hansen
b9ec2d42a3 Remove the (broken and unused?) --retry option 2024-08-22 02:08:39 -04:00
Richard Hansen
555359dc98 tests: Add unit tests for legacy protocol handling in nic_updateable 2024-08-22 02:08:39 -04:00
Richard Hansen
80bbf1dc43
Merge pull request #734 from rhansen/cache
Don't write undefined recap values to the cache file
2024-08-19 17:35:45 -04:00
Richard Hansen
1631b465d5 Don't write undefined recap values to the cache file
There might be a semantic difference between `undef` and the empty
string, so it is incorrect to write an empty string when the value is
`undef`.
2024-08-19 17:29:59 -04:00
Richard Hansen
ed1d480617 Update PR TODOs in ChangeLog.md
I forgot to update these before merging PR #733.
2024-08-18 01:29:37 -04:00
Richard Hansen
12ff5bfbdc
Merge pull request #733 from rhansen/config
Option processing improvements
2024-08-18 01:27:40 -04:00
Richard Hansen
442eac96c7 Update changelog for recent config fixes 2024-08-18 01:22:53 -04:00
Richard Hansen
3be5b91601 Improve documentation for --host and --options command-line args 2024-08-18 01:22:53 -04:00
Richard Hansen
a06c532394 Call load_sha1_support, load_json_support once 2024-08-18 01:22:53 -04:00
Richard Hansen
42f720df86 Move --options validation to where it is processed 2024-08-18 01:22:53 -04:00
Richard Hansen
967bf2f6e8 Error out if given an unknown per-host option 2024-08-18 01:22:53 -04:00
Richard Hansen
564b315bfa Convert command-line argument warnings into fatal errors 2024-08-18 01:22:53 -04:00
Richard Hansen
18bd312216 Don't initialize $config{$h} entries to undef
There's no need -- if the key doesn't exist the value returned is
already `undef`.  This prevents debug output from being littered with
`undef` lines.
2024-08-18 01:22:53 -04:00
Richard Hansen
e8f0358bbb Rely on opt() fallback if a value is invalid 2024-08-18 01:22:53 -04:00
Richard Hansen
2b65aff56b Use opt() instead of accessing %config directly
This makes it possible to leave `$config{$h}{$var}` undefined to have
it fall back to `%opt`, `%globals`, or the variable's default value.
2024-08-18 01:22:52 -04:00
Richard Hansen
912bc6291a group_hosts_by: Treat undef as unset for consistency with opt
The `opt` function falls back to global/default if the value is
undefined, even if it is explicitly set to `undef`.  `group_hosts_by`
should behave the same.
2024-08-18 01:21:40 -04:00
Richard Hansen
5a66efe79e Delete unnecessary defined() check 2024-08-18 00:45:04 -04:00
Richard Hansen
478f517d53 Delete no-effect option normalization
The alternative is to "fix" the code to match the original intention,
but users haven't been complaining so it's better to avoid the risk of
introducing a new bug in the fix.
2024-08-18 00:45:04 -04:00
Richard Hansen
7fde55c188 Don't initialize %opt entries to undef
There's no need -- if the key doesn't exist the value returned is
already `undef`.  This prevents debug output from being littered with
`undef` lines.
2024-08-18 00:45:04 -04:00
Richard Hansen
fe1768316a Use protocol-specific default when known
Different protocols can have different default values for a particular
variable.  Grab the protocol-specific variable definition if given a
hostname when looking up the variable's default.
2024-08-18 00:45:04 -04:00
Richard Hansen
775b7fcbfe Validate and normalize all command-line arguments 2024-08-18 00:45:04 -04:00
Richard Hansen
bbf98dd031 Use parse_assignments to process --options
This simplifies the code and enables quoting of special characters.
2024-08-18 00:41:28 -04:00
Richard Hansen
270a82dd58 parse_assignments: Support newlines
Allow newlines to be in values, but stop searching for assignments
once an unescaped/unquoted newline is discovered.  This is preparation
to using `parse_assignments` to process the `--options` command-line
argument, which might have embedded newlines.
2024-08-18 00:41:28 -04:00
Richard Hansen
4c7634855b Move *_env processing to parse_assignment
This makes the behavior transparent to the rest of ddclient, which
will make it possible for a future commit to simplify option
processing.
2024-08-18 00:41:28 -04:00
Richard Hansen
19848852a4 check_value: Mention supported values if given an invalid value 2024-08-18 00:41:28 -04:00
Richard Hansen
ed2afde72d check_value: die if the value is invalid
This makes it possible to convey details about why the value was
deemed invalid.  It also allows `undef` to be treated as a valid
value.
2024-08-18 00:41:28 -04:00
Richard Hansen
2f8a4ba00a Use opt() instead of accessing %opt or %globals directly
This is for consistency, and to ensure that all possible ways of
configuring are respected.
2024-08-18 00:41:28 -04:00
Richard Hansen
4d3dcdc7de Move option normalization from usage to load
This avoids bugs if a usage forgets to normalize the value.
2024-08-18 00:41:28 -04:00
Richard Hansen
70858e659f Also warn about non-required values that are invalid
Before, only required values that were invalid produced a warning.
Non-required values were quietly ignored.
2024-08-18 00:41:28 -04:00
Richard Hansen
05dbe7a984 Delete confusing and unnecessary T_OFQDN type
Variable declarations already have a `required` flag, which makes the
type confusing.  What would it mean for a variable to be a required
`T_OFQDN`?  And how would an optional `T_FQDN` differ from an optional
`T_OFQDN`?
2024-08-18 00:41:28 -04:00
Richard Hansen
9e659a18eb Move --help processing to %opt 2024-08-18 00:41:28 -04:00
Richard Hansen
c83dc67039 Remove pointless help setting
This does not affect the `--help` command-line argument.

The `help` setting didn't do anything useful, and it didn't make sense
to set `help=1` in the config file (or pass `--options=help=1`), so
this removal is unlikely to affect anyone.  If the setting does exist,
the user will get a warning and the setting will be ignored.
2024-08-18 00:41:28 -04:00
Richard Hansen
b4c4b5dc54 Move usage generation to a separate function 2024-08-18 00:41:28 -04:00
Richard Hansen
bd688e9750 Add TODO comments for problematic bits of code 2024-08-18 00:41:28 -04:00
Richard Hansen
ab2e0d7999 hetzner: Quote interpolated value in regex
to prevent metacharacter issues.
2024-08-18 00:41:28 -04:00
Richard Hansen
0b79e3bc95 godaddy: Delete redundant condition 2024-08-18 00:41:27 -04:00
Richard Hansen
0c094f6ee8 tests: Fix verbose option for dnsexit2 protocol tests
The `verbose` option is a global option, not a per-host option.
2024-08-18 00:35:44 -04:00
Richard Hansen
a136ba4cdc tests: Fix ssl option for dnsexit2 protocol tests
The `ssl` option is a global option, not a per-host option.  This
commit could instead do:

    local $ddclient::globals{ssl} = 0;

but it's more straightforward to include `http://` in the `server`
option, and it tests that `server` supports the inclusion of the
scheme.
2024-08-18 00:35:44 -04:00
Richard Hansen
598dee50ca
Merge pull request #726 from jeffrego/feature_provider_directnic
Added support for provider Directnic
2024-08-07 00:06:46 -04:00
Jeff Rego
959b5ddc37 Add support for Directnic provider 2024-08-07 00:06:25 -04:00
Jeff Rego
d497422bf9 Add T_URL type for config properties 2024-08-07 00:06:25 -04:00
Richard Hansen
2330543cc8 dyndns2: Add comment explaining why keys are listed in test 2024-08-05 19:57:18 -04:00
Richard Hansen
13a66c79bb
Merge pull request #729 from tamasbakos/master
Removed 5m min-interval from changeip protocol
2024-08-03 18:15:24 -04:00
Tamás
e4d43f0292
Removed min-interval from changeip protocol
Removed the min-interval set to 5 minutes in changeip, because according to my tests, changeip has no problem updating every 30 seconds, which is the default min-interval value in ddclient
2024-08-03 11:11:16 +03:00
Richard Hansen
ab27df6f79
Merge pull request #728 from rhansen/dyndns2
dyndns2: Fix handling of multi-host response, add tests
2024-08-03 03:36:45 -04:00
Richard Hansen
eb281ea47b dnsexit2: Rename test file for consistency 2024-08-03 03:32:35 -04:00
Richard Hansen
3d345ff08b dyndns2: Add tests 2024-08-03 03:32:35 -04:00
Richard Hansen
622abfca2c repr: New utility function to make it easier to dump values
I find Data::Dumper to be awkward to use.  This function wraps it so
that I don't have to keep looking up the perldoc.
2024-08-03 03:25:11 -04:00
Richard Hansen
4f369a3b0b geturl: Simplify headers logic 2024-08-03 03:25:11 -04:00
Richard Hansen
2239b57101 dyndns2: Fix handling of multi-host response 2024-08-03 03:25:11 -04:00
Richard Hansen
7bee2d7c82 dyndns2: Log message improvements 2024-08-03 03:17:52 -04:00
Richard Hansen
143630c7fd dyndns2: Delete rogue comma 2024-08-03 03:17:52 -04:00
Richard Hansen
a99d093eca dyndns2: Whitespace fixes 2024-08-03 03:17:52 -04:00
Richard Hansen
60d1c53a36
Merge pull request #727 from rhansen/logger
Improvements to the new Logger class
2024-08-03 02:44:54 -04:00
Richard Hansen
43ea691e0c Logger: Move log output to parentmost Logger
This makes it possible for tests to redirect log output so that they
can ensure that certain log messages are generated.
2024-08-02 21:37:14 -04:00
Richard Hansen
f4248d0617 Logger: Separate implementation from interface
This makes it easier to override the implementation for testing
purposes.
2024-08-02 21:14:37 -04:00
Richard Hansen
56f8c83d3a Logger: Check label for emptiness, not truthiness 2024-08-02 21:05:42 -04:00
Richard Hansen
0f094ac121 Logger: Check msg and label for definedness 2024-08-02 21:05:41 -04:00
Richard Hansen
15db76f739 Logger: Accept an arrayref of contexts for ctx parameter 2024-08-02 20:42:36 -04:00
Richard Hansen
f36c2f45aa Logger: Always use STDERR as output filehandle
There's no good reason for the caller of the `log` method to control
the output filehandle.
2024-08-02 17:03:56 -04:00
Richard Hansen
439b0fd0e1 Logger: Minimize STDERR override in tests 2024-08-02 17:00:00 -04:00
Richard Hansen
1bdd65e46e Delete unused encode_base64 function 2024-08-02 16:13:56 -04:00
Richard Hansen
dff4cd4854 Logger: Localize override in test 2024-08-02 16:13:56 -04:00
Richard Hansen
37504fe6f2 Logger: Document the log method 2024-08-02 16:13:56 -04:00
Richard Hansen
2e59e86df6
Merge pull request #725 from rhansen/logging
Add context to log messages
2024-07-31 01:03:25 -04:00
Richard Hansen
e036fd0cf6 logging: Use Logger contexts to improve log message readability
This also makes it easier to write useful log messages.
2024-07-31 01:01:00 -04:00
Richard Hansen
9e45aecf20 logging: New Logger class to generally handle context prefixes 2024-07-31 00:39:48 -04:00
Richard Hansen
23bc8cdac3 logging: Move colon from the label to logmsg 2024-07-31 00:39:48 -04:00
Richard Hansen
3262dd0952 logging: Rename pfx to label
This is to prepare for a general log prefix mechanism to improve log
readability.
2024-07-31 00:39:48 -04:00
Richard Hansen
015600d72f logging: Delete unused debug2 function 2024-07-31 00:39:47 -04:00
Richard Hansen
42d635c2df dinahosting: Fix missing argument for log message format specifier
Also use string interpolation for readability and to reduce the
chances of reintroducing a bug like this in the future.
2024-07-31 00:39:47 -04:00
Richard Hansen
706ba713e0 porkbun: Fix IP version in success log message
Fixes a bug introduced in commit:
d8a23ff9a4
2024-07-31 00:39:47 -04:00
Richard Hansen
f5c59c2024 nsupdate: Log success/failure once for all hosts 2024-07-31 00:39:47 -04:00
Richard Hansen
0c2c97123f namecheap: Log message improvements 2024-07-31 00:39:47 -04:00
Richard Hansen
71dc1f92e4 zoneedit1: Fix logged host names 2024-07-31 00:39:47 -04:00
Richard Hansen
9dce53ea4a
Merge pull request #724 from vladmovchan/master
Correct NoIP example
2024-07-31 00:37:41 -04:00
Vladyslav Movchan
af65dd86cf Correct NoIP example
An extra comma in the example causes `401 Unauthorized` / `badauth`
2024-07-31 00:37:04 -04:00
Richard Hansen
9c5160a514 Whitespace fixes 2024-07-30 02:25:05 -04:00
Richard Hansen
5620127c71 Delete unnecessary comments 2024-07-30 02:24:27 -04:00
Richard Hansen
96ada0c79e inwx: Add comment explaining why hostnames are not in update URL 2024-07-29 23:45:55 -04:00
Richard Hansen
32f95526f9
Merge pull request #723 from rhansen/infomaniak
infomaniak: Fix response status processing
2024-07-29 04:09:41 -04:00
Richard Hansen
d380e17aba infomaniak: Fix response status processing
Previously, `nochg` responses were treated as failures and the logged
message for all responses was incorrect (either `undef` or "Unknown
reply from Infomaniak").

Background: Hash values are always scalars, so lists of values can
only be stored in hashes in arrayref form.

The following is legal but does not do what one might expect:

    my %h = (
        a => (1, 2),
        b => (3, 4),
    );

The `=>` operator is just a variant of the comma operator, and lists
in list context are flattened, so the above is equivalent to:

    my %h = ('a', 1, 2, 'b', 3, 4);

which is equivalent to:

    my %h = (
        a => 1,
        2 => 'b',
        3 => 4,
    );

which is obviously not what was intended.
2024-07-29 04:07:15 -04:00
Richard Hansen
3f0fd0f37b infomaniak: Use variable interpolation instead of sprintf
for readability.
2024-07-29 04:02:42 -04:00
Richard Hansen
bb65b64e39 infomaniak: Whitespace fixes 2024-07-29 04:02:42 -04:00
Richard Hansen
2715743ee3
Merge pull request #721 from rhansen/gandi
`gandi` cleanups
2024-07-28 19:15:29 -04:00
Richard Hansen
0b30df4b69 gandi: Fix processing of PUT error responses
Before, the returned JSON wasn't even parsed -- the error handling
code was reusing the parsed response from the earlier `GET`.  Also, it
was reading object properties that were not documented in the Gandi
API documentation.
2024-07-28 19:04:30 -04:00
Richard Hansen
06c3dd5825 gandi: Invert condition to improve readability 2024-07-28 18:24:19 -04:00
Richard Hansen
6f505e6538 gandi: Inline an unnecessary variable 2024-07-28 18:24:19 -04:00
Richard Hansen
5e52f728ad gandi: Use an array for headers for readability 2024-07-28 18:24:19 -04:00
Richard Hansen
a890b08935 gandi: Check for JSON object, not just definedness 2024-07-28 18:24:19 -04:00
Richard Hansen
b1ddaa0ce8 gandi: Log message improvements 2024-07-28 18:24:19 -04:00
Richard Hansen
12d5539abc gandi: Don't ignore HTTP response code 2024-07-28 18:24:19 -04:00
Richard Hansen
325eb10536 gandi: Style fixes for readability 2024-07-28 18:24:19 -04:00
Richard Hansen
2ccdefff93 gandi: Whitespace fixes 2024-07-28 18:24:19 -04:00
Richard Hansen
15595d01ac gandi: Delete unnecessary comment 2024-07-28 18:24:19 -04:00
Richard Hansen
26d7aa500a
Merge pull request #720 from rhansen/delete
Delete some unnecessary code
2024-07-28 18:22:19 -04:00
Richard Hansen
0fa7e132b1 logging: Delete now-unused verbose function 2024-07-28 17:52:42 -04:00
Richard Hansen
54d381a18e nsupdate: Convert some verbase log messages to debug 2024-07-28 17:52:42 -04:00
Richard Hansen
61cc5d66ae logging: Delete unnecessary verbose calls
The same information (or more) is logged in the previous line.
2024-07-28 17:48:58 -04:00
Richard Hansen
ee0940175e Delete redundant calls to failed
`header_ok` already calls `failed` if there's an error.
2024-07-28 03:45:10 -04:00
Richard Hansen
f410b915ce Delete redundant checks
`header_ok` already asserts that the reply is defined and non-empty.
2024-07-28 03:45:10 -04:00
Richard Hansen
228efa7927
Merge pull request #719 from rhansen/cleanups
Various minor cleanups and improvements
2024-07-27 04:46:46 -04:00
Richard Hansen
fc453a0de3 porkbun: Check for JSON object, not just non-null 2024-07-27 04:40:34 -04:00
Richard Hansen
536c7c87a2 porkbun: Invert condition to improve readability 2024-07-27 04:40:34 -04:00
Richard Hansen
d8a23ff9a4 porkbun: Improve log messages 2024-07-27 04:40:34 -04:00
Richard Hansen
e1c8b26f7b porkbun: Simplify string equality check 2024-07-27 04:40:34 -04:00
Richard Hansen
0c31681d35 porkbun: Simplify array length check 2024-07-27 04:40:34 -04:00
Richard Hansen
16b15ea089 porkbun: Remove headers from HTTP response before processing 2024-07-27 04:40:34 -04:00
Richard Hansen
c65d5c1254 porkbun: Don't bother setting status on failure
It's already initialized to a non-success value.
2024-07-27 04:40:34 -04:00
Richard Hansen
bafd5a8715 porkbun: Inline unnecessary variables 2024-07-27 04:40:34 -04:00
Richard Hansen
45d832145f porkbun: Simplify RR set type 2024-07-27 04:40:34 -04:00
Richard Hansen
038b31cf77 porkbun: Simplify zone removal logic 2024-07-27 04:40:34 -04:00
Richard Hansen
630a2d5d49 porkbun: Move "ipv" out of $ipv for consistency
This also makes it possible to change "ipv4" and "ipv6" in log
messages to "IPv4" and "IPv6".
2024-07-27 04:40:34 -04:00
Richard Hansen
4273580bdf porkbun: Consolidate lines to improve readability 2024-07-27 04:40:34 -04:00
Richard Hansen
c6bcfd4644 porkbun: Quote interpolated variable in regex 2024-07-27 04:40:34 -04:00
Richard Hansen
1020145fdf porkbun: Rename variables for brevity and consistency 2024-07-27 04:40:34 -04:00
Richard Hansen
3f740c3e19 porkbun: Whitespace fixes 2024-07-27 04:40:34 -04:00
Richard Hansen
4b5b8ab62d porkbun: Delete unnecessary comments 2024-07-27 04:40:34 -04:00
Richard Hansen
3dae16457a dnsmadeeasy: Consolidate lines for readability 2024-07-27 04:40:34 -04:00
Richard Hansen
2eb0398cf2 dnsmadeeasy: Invert condition to improve readability 2024-07-27 04:40:34 -04:00
Richard Hansen
b07aa91ed5 dnsmadeeasy: Don't bother setting status on failure
It's already initialized to a non-success value.
2024-07-27 04:40:34 -04:00
Richard Hansen
971fe438a3 dnsmadeeasy: Convert string literal to compiled regex
for readability.
2024-07-27 04:40:34 -04:00
Richard Hansen
ca28694dd7 dnsmadeeasy: Don't assume the result code is known 2024-07-27 04:40:34 -04:00
Richard Hansen
11876498d5 dnsmadeeasy: Improve log messages 2024-07-27 04:40:34 -04:00
Richard Hansen
c79d12263e dnsmadeeasy: Whitespace fixes 2024-07-27 04:40:34 -04:00
Richard Hansen
07800a4586 dnsmadeeasy: Delete unnecessary comments 2024-07-27 04:40:34 -04:00
Richard Hansen
f42583c0cf dondominio: Quote interpolated variable in regex 2024-07-27 04:40:34 -04:00
Richard Hansen
6ac5b41a20 dondominio: Invert condition to improve readability 2024-07-27 04:40:34 -04:00
Richard Hansen
d79ef268bd dondominio: Combine regular expressions to improve readability 2024-07-27 04:40:34 -04:00
Richard Hansen
8b58f7bd99 dondominio: Don't bother setting status on error
It's already initialized to a non-success value.
2024-07-27 04:40:34 -04:00
Richard Hansen
2823e47c58 dondominio: The IP address is always provided 2024-07-27 04:40:34 -04:00
Richard Hansen
fe502abcd8 dondominio: Consolidate lines for readability 2024-07-27 04:40:34 -04:00
Richard Hansen
7a43920b99 dondominio: Improve log messages 2024-07-27 04:40:34 -04:00
Richard Hansen
17a002cbd6 dondominio: Whitespace fixes 2024-07-27 04:40:34 -04:00
Richard Hansen
e155e1bf2c dondominio: Delete unnecessary comments 2024-07-27 04:40:34 -04:00
Richard Hansen
d62495c41e ddns.fm: Improve log messages 2024-07-27 04:40:34 -04:00
Richard Hansen
1195a40c45 freemyip: Check entire result body, not just first line
This is simpler, and should be more resilient to bugs.
2024-07-27 04:40:34 -04:00
Richard Hansen
3ece2017e9 freemyip: Skip headers before processing response
This isn't strictly necessary but improves readability and
consistency.
2024-07-27 04:40:34 -04:00
Richard Hansen
a252ff5ebe freemyip: Invert condition to improve readability 2024-07-27 04:40:34 -04:00
Richard Hansen
9343ebec89 freemyip: Don't bother setting status on error
It's not necessary because it's already initialized to a non-success
value.
2024-07-27 04:40:34 -04:00
Richard Hansen
c53f40d205 freemyip: Improve log messages 2024-07-27 04:40:34 -04:00
Richard Hansen
b3006dd6c6 freemyip: Consolidate lines for readability 2024-07-27 04:40:34 -04:00
Richard Hansen
62154f9869 freemyip: Whitespace fixes 2024-07-27 04:40:34 -04:00
Richard Hansen
f8bdc48e42 freemyip: Delete unnecessary comments 2024-07-27 04:40:34 -04:00
Richard Hansen
7bdb554e36 duckdns: Update multiple hosts simultaneously 2024-07-27 04:40:34 -04:00
Richard Hansen
1eccfb8c77 duckdns: Invert condition to improve readability 2024-07-27 04:40:34 -04:00
Richard Hansen
91fd9e3842 duckdns: Simplify response processing 2024-07-27 04:40:34 -04:00
Richard Hansen
971e88452d duckdns: Improve log messages 2024-07-27 04:40:34 -04:00
Richard Hansen
8a334fd9cf duckdns: Consolidate lines to improve readability 2024-07-27 04:40:34 -04:00
Richard Hansen
98ed129b20 duckdns: Whitespace fixes 2024-07-27 04:40:34 -04:00
Richard Hansen
b80fe1b505 duckdns: Delete unnecessary comments 2024-07-27 04:40:34 -04:00
Richard Hansen
459970c5e3 yandex: Check for presence of success, not lack of error
This is more resilient to bugs.
2024-07-27 04:40:34 -04:00
Richard Hansen
f807ba58ac yandex: Don't treat an error as success 2024-07-27 04:40:34 -04:00
Richard Hansen
58c6caa5ff nfsn: Include host in failure message 2024-07-27 04:40:34 -04:00
Richard Hansen
ef8bf634fe domeneshop: Add IPv6 support 2024-07-27 04:40:34 -04:00
Richard Hansen
61fff1c344 domeneshop: Inline an unnecessary variable 2024-07-27 04:40:34 -04:00
Richard Hansen
d391f41074 domeneshop: Treat all 2xx as success 2024-07-27 04:40:34 -04:00
Richard Hansen
b4e08ae3ae domeneshop: Improve log messages 2024-07-27 04:40:34 -04:00
Richard Hansen
231601ae54 domeneshop: Whitespace fixes 2024-07-27 04:40:34 -04:00
Richard Hansen
f0edd7f781 domeneshop: Delete unnecessary comments 2024-07-27 04:40:34 -04:00
Richard Hansen
2534375cfd dyndns1: Move else case up a level for readability 2024-07-27 04:40:34 -04:00
Richard Hansen
02c80fdf09 dslreports1: Move out else case to improve readability 2024-07-27 04:40:34 -04:00
Richard Hansen
95a10e2595 dslreports1: Log message improvements 2024-07-27 04:40:34 -04:00
Richard Hansen
2d60183e93 noip: Log message improvements 2024-07-27 04:40:34 -04:00
Richard Hansen
c0b28f344f noip: Simplify response processing 2024-07-27 04:40:34 -04:00
Richard Hansen
5b433c3cd5 noip: Delete redundant response check 2024-07-27 04:40:34 -04:00
Richard Hansen
27143db56e dnsexit2: Pass an arrayref of headers for readability 2024-07-27 04:40:34 -04:00
Richard Hansen
1c94ed6063 dnsexit2: Check for JSON object, not just truthiness 2024-07-27 04:40:34 -04:00
Richard Hansen
4a394f4562 dnsexit2: Delete rogue semicolon 2024-07-27 04:40:34 -04:00
Richard Hansen
56f0d931a4 dnsexit2: Delete redundant check
`header_ok` already checks whether the response is `undef`.
2024-07-27 04:40:34 -04:00
Richard Hansen
e5b00216ec dnsexit2: Log message improvements 2024-07-27 04:40:34 -04:00
Richard Hansen
073fe5a51d dnsexit2: Delete redundant HTTP status code check
`header_ok` already checks for non-2xx codes.
2024-07-27 04:40:34 -04:00
Richard Hansen
3d894364bf dnsexit2: Delete redundant debug message
`geturl` already debug logs the response.
2024-07-27 04:40:34 -04:00
Richard Hansen
2ac61250e5 dnsexit2: Fix compatibility with old versions of Perl
The non-destructive substitution modifier wasn't added until Perl
v5.14.0.
2024-07-27 04:40:34 -04:00
Richard Hansen
962abfbbc3 use=web, use=<fw>: Don't treat non-2xx results as successes 2024-07-27 04:40:34 -04:00
Richard Hansen
e272caa385 use=web, use=<fw>: Strip HTTP headers before searching for IP 2024-07-27 04:40:34 -04:00
Richard Hansen
b563e9c2fd use=web: Add tests 2024-07-27 04:40:34 -04:00
Richard Hansen
08626482c3 header_ok: Don't assume that it is only used for host updates 2024-07-27 03:59:52 -04:00
Richard Hansen
d48b482269 geturl: Log message improvements 2024-07-27 03:59:52 -04:00
Richard Hansen
b1752c2622 logging: Delete unused msg function 2024-07-27 03:59:29 -04:00
Richard Hansen
6aa68f72a7 logging: Change multi-line log message designation style
Before, the first line of a multi-line log message was prefixed with a
space while all subsequent messages were prefixed with `|`.  Now the
first line is prefixed with `>` and all subsequent lines with a space.
This makes it easier to quickly discern message boundaries.
2024-07-27 03:47:47 -04:00
Richard Hansen
bd437a0abf nic_updateable: Log message improvements 2024-07-26 23:58:20 -04:00
Richard Hansen
8262f112ea nic_updateable: Read option value after loading config 2024-07-26 23:58:20 -04:00
Richard Hansen
1ad9b565bd nic_updateable: Don't warn about success
Why issue a warning that things have suddenly started going well?
It's bizarre.
2024-07-26 23:58:20 -04:00
Richard Hansen
1054e162fa query_cisco: Use host-specific option value 2024-07-26 23:50:14 -04:00
Richard Hansen
0392c5e725 query_cisco: Delete redundant warning
The same message is already logged in `get_ipv4`.
2024-07-26 23:50:14 -04:00
Richard Hansen
e891f53345 update_nics: Consistently use --opt instead of opt 2024-07-26 23:50:04 -04:00
Richard Hansen
5d68b11d78 get_ipv6: Factor out check for deprecated value 2024-07-26 23:50:04 -04:00
Richard Hansen
2530adb39e get_ip: Log message improvements 2024-07-26 23:50:04 -04:00
Richard Hansen
b9d372c12d get_ip: Allow $arg to be undefined
This is simpler, and makes it possible to distinguish unset from set
to an empty string.
2024-07-26 23:49:48 -04:00
Richard Hansen
0ea2f06513 get_ip: Don't mutate $arg
This makes log messages easier to understand.
2024-07-26 19:05:47 -04:00
Richard Hansen
ccc205301a Delete old test code
It's better to start a test HTTP server or override `@curl` from tests
in `t/*.pl`.
2024-07-26 19:05:47 -04:00
Richard Hansen
6ddecb4ecc Replace use vars with our
From the documentation for `vars`:

> NOTE: For use with variables in the current package for a single
> scope, the functionality provided by this pragma has been superseded
> by "our" declarations, available in Perl v5.6.0 or later, and use of
> this pragma is discouraged.
2024-07-26 19:05:47 -04:00
Richard Hansen
356c3354bd
Merge pull request #718 from rhansen/force_update
Cleanups to forced update logic
2024-07-26 19:04:36 -04:00
Richard Hansen
89d7193f69 nic_updateable: Simplify option changed detection logic 2024-07-26 18:15:21 -04:00
Richard Hansen
38a4e9eeef nic_updateable: Remove unnecessary force_update parameter
`nic_updateable` can look up the host's `force_update` function itself
so there's no need to pass it as an argument.
2024-07-26 17:24:49 -04:00
Richard Hansen
0ffffb1400 Delete unnecessary force_update => undef lines 2024-07-26 17:21:11 -04:00
Richard Hansen
5a8bee1e4d Delete unnecessary nic_dyndns2_force_update
The `nic_updateable` function already checks the variables to see if
they have changed, so this function is redundant.
2024-07-26 17:06:09 -04:00
Richard Hansen
2a9abc9d4c Delete unused nic_easydns_force_update function 2024-07-26 17:04:12 -04:00
Richard Hansen
59ff497c1b
Merge pull request #717 from jameskimmel/patch-1
delete unused zoneedit force update
2024-07-24 13:53:42 -04:00
jameskimmel
bcfdf70c34
delete unused zoneedit force update
I think this sub is never used.
2024-07-23 18:53:27 +02:00
Richard Hansen
482be5ce46
Merge pull request #716 from rhansen/defunct
Delete defunct services
2024-07-23 00:12:49 -04:00
Richard Hansen
9996c1b7d4 googledomains: Remove support for defunct service 2024-07-22 23:28:33 -04:00
Richard Hansen
ddfa8663ad woima: Remove support for defunct service 2024-07-22 23:26:41 -04:00
Richard Hansen
cf078017c2
Merge pull request #713 from rhansen/easydns
easydns: Multiple fixes and cleanups
2024-07-21 23:18:49 -04:00
Richard Hansen
3c68abe551 easydns: Fix incorrect status value on success 2024-07-20 03:44:05 -04:00
Richard Hansen
a7feb95091 easydns: Add missing OK and NO_AUTH result codes 2024-07-20 03:44:05 -04:00
Richard Hansen
d8c74169ee easydns: Include the full ILLEGAL INPUT result code in log messages 2024-07-20 03:44:05 -04:00
Richard Hansen
a724084114 easydns: Fix extraction of result code from response body
The server returns a full HTML document, not just the result code.
Change equality check to a regex match that is resilient to
server-side changes.
2024-07-20 03:44:05 -04:00
Richard Hansen
435357ac50 easydns: Invert condition to improve readability 2024-07-20 03:44:04 -04:00
Richard Hansen
da26fe76e0 easydns: Update IPv4 and IPv6 separately
https://kb.easydns.com/knowledge/dynamic-dns/ doesn't say anything
about repeating the `myip` parameter, or that both IPv4 and IPv6
addresses can be included in the same `myip` parameter.

Unfortunately it also doesn't say whether updating the IPv4 address
alone will nuke the IPv6 AAAA record, or whether updating the IPv6
address alone will nuke the IPv4 A record (like Google Domains used to
do).  Here's hoping that the A and AAAA records are truly independent.
2024-07-20 03:44:04 -04:00
Richard Hansen
7a4b96e04e easydns: Delete incorrect handling of TOOSOON error
Before:
  * `$scale` was ignored causing it to set `wtime` to 5s in the
    future, which is too brief.  Fortunately, `min-error-interval`
    applied which prevented overly aggressive retries.
  * The lack of call to `failed` meant that `$result` was not set to
    "FAILED" and thus `mail-failure` recipients were not emailed.

Now `TOOSOON` is treated like any other error and will result in a
retry after `min-error-interval` (default: 5m).
2024-07-20 03:35:56 -04:00
Richard Hansen
fb990208b3 easydns: Consolidate lines for readability 2024-07-20 03:35:56 -04:00
Richard Hansen
6992a34028 easydns: Simplify response processing 2024-07-20 03:35:56 -04:00
Richard Hansen
7a2625b7a7 easydns: Refine log messages
* Consistently use just the hostname as the log message prefix.
  * Delete redundant verbose log message.
  * Log IPv4 and IPv6 separately in case `$ipv4` or `$ipv6` is
    `undef`.
  * Don't log the full line when a known error result is returned.
  * For unknown results, don't wrap the line in parentheses.
  * Use string interpolation for readability.
2024-07-20 03:35:56 -04:00
Richard Hansen
94c304601e easydns: Increase default min-interval to 10m 2024-07-20 03:35:56 -04:00
Richard Hansen
e7ad0e8e6e easydns: Whitespace fixes 2024-07-20 03:35:46 -04:00
Richard Hansen
c57f4b0d56 ChangeLog.md: Fix PR URL 2024-07-20 03:35:45 -04:00
Richard Hansen
2792689c35 ChangeLog.md: Fix PR URLs 2024-07-20 03:06:52 -04:00
Richard Hansen
e142d6a5ac
Merge pull request #712 from rhansen/geturl
Delete `--geturl` command-line argument and `geturl` option
2024-07-19 18:10:19 -04:00
Richard Hansen
3a57ca1374 Delete --geturl command-line argument and geturl option
They are not used in any tests, and `curl` is a better choice anyway.
2024-07-19 18:06:28 -04:00
Richard Hansen
dab67eae69 README.md: Revise troubleshooting section
* Use a bulleted list instead of a numbered list
  * Fix indentation
  * Fix formatting
  * Replace `fw` with `fwv4`
  * Suggest `curl` instead of `ddclient --geturl`
  * etc.
2024-07-19 18:06:28 -04:00
Richard Hansen
dfef6b2e99
Merge pull request #711 from rhansen/godaddy
godaddy: readability improvements
2024-07-19 16:36:24 -04:00
Richard Hansen
12b2c0d03d godaddy: Inline some unnecessary variables 2024-07-19 16:21:37 -04:00
Richard Hansen
bdc69d879f geturl: Accept an arrayref for headers 2024-07-19 16:21:37 -04:00
Richard Hansen
b9144c01c2 godaddy: Use eq for string equality
`==` is for numeric equality.
2024-07-19 16:21:37 -04:00
Richard Hansen
4b2155a43c godaddy: Combine URL lines to improve readability
Also drop unnecessary curly braces for consistency.
2024-07-19 16:18:14 -04:00
Richard Hansen
6e98c0cdb2 godaddy: Invert conditional to improve readability 2024-07-19 16:17:50 -04:00
Richard Hansen
b1c0029604 godaddy: Simplify for loop 2024-07-19 16:17:49 -04:00
Richard Hansen
3258ea34c0 godaddy: Shorten variables for brevity and consistency 2024-07-19 16:17:19 -04:00
Richard Hansen
56f4a2afe2 godaddy: Don't bother setting status on error
It's initialized to a non-'good' value before the function is called,
and ddclient doesn't distinguish between different non-good values, so
it is sufficient to leave it alone.
2024-07-19 16:16:44 -04:00
Richard Hansen
e01ed55a58 godaddy: Simplify and improve error messages 2024-07-19 16:16:21 -04:00
Richard Hansen
c63eb0f060 godaddy: Fix dubious response body check
It doesn't make sense to continue processing if the response body
can't be parsed.  Maybe GoDaddy returned 200 and there's a bug in the
body parsing logic, in which case the `bad` result should actually be
`good`.  But it's better to assume that the update wasn't saved, in
case the server returns 200 with a JSON object that semantically means
"failed to update".
2024-07-19 16:15:37 -04:00
Richard Hansen
f82d2af0f2 godaddy: Whitespace fixes 2024-07-19 16:15:30 -04:00
Richard Hansen
53b373fc9e godaddy: Delete unnecessary comments 2024-07-19 16:08:41 -04:00
Richard Hansen
5ab15b1d53
Merge pull request #690 from Starkstromkonsument/add_INWX_support
Add new protocol inwx
2024-07-19 02:28:30 -04:00
Starkstromkonsument
83ef1fa99a Add new protocol inwx
Adoption of protocol dyndns2 to support their custom URL:

'https://dyndns.inwx.com/nic/update?myip=<ipaddr>&myipv6=<ip6addr>'
2024-07-19 02:26:25 -04:00
Richard Hansen
f6e13f8003
Merge pull request #709 from rhansen/dyndns2
`dyndns2` readability improvements, take 2
2024-07-19 02:25:08 -04:00
Richard Hansen
30a7c5ad78 dyndns2: Delete obsolete custom and static options
<https://help.dyn.com/remote-access-api/perform-update/> says:

> We will accept these parameters without generating error messages:
>
>   * `system`, previously used to identify update type
2024-07-19 02:12:53 -04:00
Richard Hansen
26f57bf36a dyndns2: Delete obsolete(?) "wait" response handling 2024-07-19 01:33:02 -04:00
Richard Hansen
adfd68d5e0 dyndns2: Refine log messages 2024-07-18 05:42:49 -04:00
Richard Hansen
1e73f4a51a dyndns2: Wrap long list of group by attributes 2024-07-18 05:42:49 -04:00
Richard Hansen
90de2f9606 dyndns2: Improve readability of status parsing 2024-07-18 05:42:49 -04:00
Richard Hansen
88f140d470 dyndns2: Invert condition to improve readability 2024-07-18 05:42:48 -04:00
Richard Hansen
8a667e3f57 dyndns2: Treat nochg as good to eliminate duplicate code 2024-07-18 05:42:30 -04:00
Richard Hansen
db3472a7ce dyndns2: Simplify response parsing 2024-07-18 05:42:29 -04:00
Richard Hansen
0892655fd6 dyndns2: Add response handling TODO comments 2024-07-18 05:41:59 -04:00
Richard Hansen
d88e6438ef Revert "Merge pull request #702 from rhansen/dyndns2"
I misread the original code and introduced a bug.  This reverts the
entire PR so that I can redo it.

This reverts commit 9eff7404e3, reversing
changes made to 60f931e7da.
2024-07-18 03:30:34 -04:00
Richard Hansen
23dad564be dyndns2: Expand rationale for not checking returned IP 2024-07-17 22:54:02 -04:00
Richard Hansen
9256096e64
Merge pull request #695 from woolflare/master
Add DDNS.FM support
2024-07-15 03:50:51 -04:00
woolflare
2f4b0859bd Add DDNS.FM support 2024-07-15 03:50:07 -04:00
Richard Hansen
af0035d266
Merge pull request #705 from rhansen/ssl
Enable `--ssl` by default
2024-07-14 19:12:43 -04:00
Richard Hansen
c6581b03f2 Bump version to v4.0.0~alpha
The list of breaking changes has become significant enough to warrant
bumping the major version number.
2024-07-14 19:07:55 -04:00
Richard Hansen
f0de73e8c4 Enable --ssl by default
In this day and age there's no good reason to prefer plain HTTP over
HTTPS, and security is more important than potential compatibility
concerns.
2024-07-14 19:00:53 -04:00
Richard Hansen
13369804a0 Improve documentation for --ssl option
Also move it to the top of the sample config file due to its
importance.
2024-07-14 19:00:24 -04:00
Richard Hansen
430d9026f8
Merge pull request #704 from Bodenhaltung/add-dnshome-de
Add dnsHome.de
2024-07-14 18:20:35 -04:00
Richard Hansen
6284133b1c Add dnsHome.de example config 2024-07-14 18:18:03 -04:00
Bodenhaltung
5931a7150c Add dnsHome.de 2024-07-14 17:11:18 -04:00
Richard Hansen
4c6842e569
Merge pull request #703 from rhansen/tls
Don't force plain HTTP
2024-07-14 16:44:15 -04:00
Richard Hansen
7754c65103 woima: Honor http: or https: scheme in server variable 2024-07-13 18:38:53 -04:00
Richard Hansen
0ed2970852 keysystems: Honor http: or https: scheme in server variable
or fall back to the value of the `ssl` variable if neither `http:` nor
`https:` is present.
2024-07-13 18:38:44 -04:00
Richard Hansen
469c5a072e dnsmadeeasy: Honor http: or https: scheme in server variable 2024-07-13 18:38:36 -04:00
Richard Hansen
6fbb7eb3dc domeneshop: Honor http: or https: scheme in server variable 2024-07-13 18:38:26 -04:00
Richard Hansen
c31668b413 dyndns2: Honor http: or https: scheme in server variable
or fall back to the value of the `ssl` variable if no `http:` or
`https:` scheme is present.
2024-07-13 18:38:18 -04:00
Richard Hansen
a7fef2e1eb
Merge pull request #682 from indrajitr/henet-provider
he.net: Add support for Hurricane Electric provider
2024-07-13 17:53:15 -04:00
Richard Hansen
4d5a416725 Omit deprecated services from --list-web-services
This also makes the handling of deprecated services a bit more
general.
2024-07-13 17:50:10 -04:00
Richard Hansen
efa487bfb3 Note that --web=googledomains is deprecated in changelog 2024-07-13 17:31:52 -04:00
Indrajit Raychaudhuri
0973e9d83c Deprecate 'builtinweb' 'he' for 'he.net' for consistency with protocol 2024-07-13 17:31:52 -04:00
Indrajit Raychaudhuri
ecf935a4e2 he.net: Add support for Hurricane Electric provider
The implementation is based on the existing
dyndns2 protocol with a few differences:
- The IPv4 and IPv6 addresses must be updated in
  separate calls. This is different from most of
  the other providers where both IPv4 and IPv6
  addresses can be updated in a single call. Thus
  the existing dyndns2 protocol implementation
  cannot be reused for dns.he.net.
- Multiple hosts are not supported by the provider.

See: https://dns.he.net/docs.html
2024-07-13 17:31:52 -04:00
Richard Hansen
9eff7404e3
Merge pull request #702 from rhansen/dyndns2
`dyndns2` readability improvements
2024-07-13 04:58:02 -04:00
Richard Hansen
d489cea344 dyndns2: Wrap long list of group by attributes 2024-07-13 04:52:05 -04:00
Richard Hansen
203bf12245 dyndns2: Improve readability of status parsing 2024-07-13 04:45:35 -04:00
Richard Hansen
ad4e3769eb dyndns2: Invert condition to improve readability 2024-07-13 04:45:35 -04:00
Richard Hansen
0882712ec2 dyndns2: Treat nochg as good to eliminate duplicate code 2024-07-13 04:45:35 -04:00
Richard Hansen
45e3603918 dyndns2: Simplify response parsing 2024-07-13 04:45:35 -04:00
Richard Hansen
60f931e7da
Merge pull request #701 from rhansen/group_hosts_by
`group_hosts_by` improvements
2024-07-13 04:45:16 -04:00
Richard Hansen
3ffcdf8317 ci: Pass --skip-broken after install, not before
Apparently dnf was changed in Fedora Rawhide:
https://bugzilla.redhat.com/show_bug.cgi?id=2216055
2024-07-13 04:42:06 -04:00
Richard Hansen
8a65264841 group_hosts_by: Return the common group configuration
This should make it easier to detect missing attribute names passed to
`group_hosts_by`.
2024-07-13 04:09:24 -04:00
Richard Hansen
b8df93febe group_hosts_by: Return a list, not a hashref
The hash key value is an implementation detail that should not be
leaked to the caller.
2024-07-13 04:08:35 -04:00
Richard Hansen
08ccc41650 group_hosts_by: Use arg list instead of arrayref
This is more idiomatic.
2024-07-13 04:08:35 -04:00
Richard Hansen
64af205cfc group_hosts_by: Readability improvements 2024-07-13 04:08:35 -04:00
Richard Hansen
5d2a1e864a yandex: Remove unnecessary host groupings
Each host is already updated individually so there's no point in
grouping the hosts.
2024-07-13 04:08:35 -04:00
Richard Hansen
216c9c6010 hetzner: Remove unnecessary host groupings
Each host is already updated individually so there's no point in
grouping the hosts.
2024-07-13 04:08:35 -04:00
Richard Hansen
5ae0fd3024 hetzner: Delete unused login variable 2024-07-13 04:08:35 -04:00
Richard Hansen
45677c0403 cloudflare: Remove unnecessary attributes from group_hosts_by args 2024-07-13 04:08:35 -04:00
Richard Hansen
8e20185323 googledomains: Remove unnecessary host groupings
Each host is already updated individually so there's no point in
grouping the hosts.
2024-07-13 04:08:35 -04:00
Richard Hansen
40e4aee74f nsupdate: Add missing tcp to group_hosts_by attributes 2024-07-13 04:08:35 -04:00
Richard Hansen
e8a6d1479f godaddy: Remove unnecessary host groupings
Each host is already updated individually so there's no point in
grouping the hosts.
2024-07-13 04:08:35 -04:00
Richard Hansen
5db77f7c31 zoneedit1: Add TODO comments for problematic bits of code 2024-07-13 04:08:35 -04:00
Richard Hansen
217bc998fc easydns: Remove unnecessary single host groupings 2024-07-13 04:08:35 -04:00
Richard Hansen
c8ee25ef82 noip: Remove unused attributes from group_hosts_by arguments 2024-07-13 04:08:35 -04:00
Richard Hansen
1faa315794 noip: Delete unused variables 2024-07-13 04:08:35 -04:00
Richard Hansen
39e3322fc0 dyndns2: Add missing script to group_hosts_by attributes 2024-07-13 04:08:35 -04:00
Richard Hansen
161c623557 Whitespace fixes 2024-07-13 04:08:33 -04:00
Richard Hansen
b488cb2235 Unwrap error message
Normally I prefer lines to be less than 100 characters long, but error
messages are an exception because it makes it easier to search the
source code for the error message.
2024-07-12 18:00:38 -04:00
Richard Hansen
4d9d0646cf Delete unnecessary comments 2024-07-12 18:00:38 -04:00
Richard Hansen
2e26a63c2f
Merge pull request #698 from rhansen/tests
t/get_ip_from_if.pl test improvements
2024-07-11 00:35:22 -04:00
Richard Hansen
fa0bfde3cb Don't assume the default interface has a globally routable IP 2024-07-11 00:18:27 -04:00
Richard Hansen
6af76afde9 Use skip_all if test precondition is not met
Subtests can't have zero checks.
2024-07-11 00:18:27 -04:00
Richard Hansen
01d2db06c1 Invert conditions for readability 2024-07-11 00:18:27 -04:00
Richard Hansen
6e7a4fb460 Split subtest into two subtests
This makes it easier to debug failures.
2024-07-11 00:18:27 -04:00
Richard Hansen
7b6f640c9b ci: Remove Red Hat UBI 7
UBI 7 is at end of maintenance and can't run newer versions of node
used by some workflows.
2024-07-11 00:15:34 -04:00
Richard Hansen
ab9ac65f46
Merge pull request #694 from rhansen/vars
More variable fixes
2024-06-29 04:18:57 -04:00
Richard Hansen
f5b369a7ef Fix undef warning when encountering an unset but required var
This fixes a bug probably introduced in commit
b8a0a26441
2024-06-29 04:14:57 -04:00
Richard Hansen
89c84f9f07 Ignore (with warning) unknown vars in --options 2024-06-29 04:14:35 -04:00
Richard Hansen
1f31b0e570 Prevent autovivification of empty definitions for unknown variables 2024-06-29 04:14:35 -04:00
Richard Hansen
d8317a730d Fix die that should be return undef
I got ahead of myself -- I intend to replace `return undef` with `die`
in a future commit, and somehow this one jumped the gun.

This fixes a bug introduced in commit
eab72ef6d7
2024-06-29 04:14:31 -04:00
Richard Hansen
04bdd68415 Always set the host variable
The `host` variable is required, so always set it to avoid error
messages when validating the host configuration.
2024-06-29 04:14:02 -04:00
Richard Hansen
be3c2060eb Fix undefined hash reference warning
This fixes a bug introduced in commit
5e3e10d32e

For some reason Perl is OK with:

    my $x = undef;
    my @k = keys(%$x);  # empty list, no warnings

but not:

    my $x = undef;
    my %h = %$x;
    my @k = keys(%h);
2024-06-29 04:13:57 -04:00
Richard Hansen
de39ac7bcc Fix undef warning when daemon is unset
This fixes a bug introduced in commit
88eb2ed4fe
2024-06-29 03:10:51 -04:00
Richard Hansen
ae7a9dce2a Fix variable name typo
This fixes a bug introduced in commit
b154d8ef98
2024-06-29 03:10:51 -04:00
Richard Hansen
76900c708c
Merge pull request #693 from rhansen/vars
Variable fixes: required vs. optional, default values
2024-06-28 16:18:26 -04:00
Richard Hansen
49f5551764 Add variable default value tests 2024-06-28 15:53:40 -04:00
Richard Hansen
eab72ef6d7 Require a defined value if the variable is required 2024-06-28 15:53:40 -04:00
Richard Hansen
399f8a8b32 Adjust variable defaults to pass validity checks
Change the default of every variable whose default (non-`undef`)
doesn't pass through `check_value` unmodified.
2024-06-28 15:53:40 -04:00
Richard Hansen
88eb2ed4fe Use undef as the default of truly optional variables 2024-06-28 15:53:39 -04:00
Richard Hansen
ba6a279186 Convert unnecessarily required variables to optional
Users are not required to provide values for these variables; either
the default is reasonable or the variable can be left unset.
2024-06-28 15:53:06 -04:00
Richard Hansen
b8a0a26441 Remove defaults from required variables without sensible defaults
Required variables with defaults don't make sense; remove the default
values on variables that don't have sensible defaults, such as login
and password.
2024-06-28 15:50:35 -04:00
Richard Hansen
be9e305e73 Fix definition of wtime variable 2024-06-28 15:28:44 -04:00
Richard Hansen
e32b9436fb nfsn: Fix type of min-interval variable 2024-06-28 15:28:44 -04:00
Richard Hansen
66bb07450f nfsn: Fix spelling of min-interval variable name 2024-06-28 15:28:44 -04:00
Richard Hansen
5757f7e07d Restore accidentally deleted --fw command-line argument
This was mistakenly deleted in commit
908b728503.
2024-06-27 00:10:35 -04:00
Richard Hansen
f4c4d974d2
Merge pull request #692 from rhansen/infomaniak
`infomaniak` fixes
2024-06-25 22:56:44 -04:00
Richard Hansen
948567c456 infomaniak: Unrequire server setting
The `infomaniak` protocol doesn't use `server`.
2024-06-25 22:53:23 -04:00
Richard Hansen
9ba583175a infomaniak: Fail if the HTTP status code is not 2xx 2024-06-25 22:53:23 -04:00
Richard Hansen
7d99da77cc header_ok: Fail if the reply is falsy 2024-06-25 22:53:23 -04:00
Richard Hansen
8e24c92b1e infomaniak: Fix response parsing 2024-06-25 22:53:23 -04:00
Richard Hansen
d2f0e042f4 infomaniak: Invert condition to improve readability 2024-06-25 22:47:26 -04:00
Richard Hansen
29e86d9a91 infomaniak: Rename variable for readability 2024-06-25 22:47:25 -04:00
Richard Hansen
a5dedeed3c infomaniak: Fix geturl call
* Pass login and password via `login` and `password` options to
    avoid issues with escaping special characters.
  * Don't attempt twice -- if the first attempt fails, the second will
    almost certainly fail as well.  (The two attempted URLs were
    equivalent, differing only in how the login and password were
    passed.)
2024-06-25 22:47:25 -04:00
Richard Hansen
ac9f937c88 infomaniak: Delete unnecessary defined checks
`undef` is falsy so there's no need to check whether the value is
defined.
2024-06-25 22:46:00 -04:00
Richard Hansen
bab9d9483e infomaniak: Move variable declaration to definition 2024-06-25 21:55:46 -04:00
Richard Hansen
134e47b61d infomaniak: Delete unnecessary newlines 2024-06-25 21:50:51 -04:00
Richard Hansen
e0d9bcc36d
Merge pull request #691 from rhansen/fixes
Fix `regfishde` IPv6 support, repeated `infomaniak` force updates, and other minor issues
2024-06-25 03:00:20 -04:00
Richard Hansen
9d49a33ac6 regfishde: Fix IPv6 support 2024-06-25 02:58:09 -04:00
Richard Hansen
0cde2e3f96 infomaniak: Fix mtime update
`mtime` should always be updated whenever the IP address is updated,
otherwise ddclient will keep force updating over and over.
2024-06-25 02:57:42 -04:00
Richard Hansen
61577d29ae njalla: Fix configuration change during update
If the user enabled `quietreply`, it should not become false after the
first update.

Users might not notice a problem because I think ddclient re-reads the
config file before every check.
2024-06-25 02:52:22 -04:00
Richard Hansen
32bf975bfa Fix call to wrong function name with bad --usev6 2024-06-25 02:52:22 -04:00
Richard Hansen
99dfd7f84d Don't assume that --use is defined 2024-06-25 02:51:58 -04:00
Richard Hansen
b154d8ef98 Fix missing v4, v6 variants in recap update
Fixes an oversight when IPv6 support was added.
2024-06-25 02:48:51 -04:00
Richard Hansen
dafde8becb Fix erroneous backupmx recap check for dyndns1, dyndns2 2024-06-25 01:38:43 -04:00
Richard Hansen
7ac6eda7cc Fix missing local use* override in --query 2024-06-25 01:37:47 -04:00
Richard Hansen
c7c8c5f097 Fix usev4, usev6 output for --query 2024-06-25 01:32:27 -04:00
Richard Hansen
27b50a3b93 Fix --usev4=cisco, --usev4=cisco-asa warning messages 2024-06-25 01:29:29 -04:00
Richard Hansen
e1e8d5711a Fix get_ip argument in --query when testing --fw 2024-06-25 01:20:30 -04:00
Richard Hansen
b363fb48a5 Fix string equality check
The `$proto` interpolation wasn't quoted with `\Q` and `\E`, so
metacharacters in `$proto` could break the matching.  Switch from a
regex to an expression to fix the equality check and improve
readability.
2024-06-25 00:50:22 -04:00
Richard Hansen
61539105bd
Merge pull request #689 from rhansen/readability
Readability improvements
2024-06-22 17:32:27 -04:00
Richard Hansen
1be8438c70 Rename updateable to force_update for readability 2024-06-22 03:07:49 -04:00
Richard Hansen
b426b370fd Rename %cache to %recap for readability 2024-06-22 02:48:03 -04:00
Richard Hansen
49bd1b7347 Rename %services to %protocols for consistency 2024-06-22 02:42:51 -04:00
Richard Hansen
1718ceab70 Add comments documenting the cached variables 2024-06-22 02:35:01 -04:00
Richard Hansen
3d73e7c231 Add TODO comments for confusing bits of code 2024-06-22 02:30:01 -04:00
Richard Hansen
9a5500a667 Improve readability via minor logic tweaks
Change some code that is unnecessarily complicated or otherwise not
the right tool for the job to improve readability.
2024-06-22 02:30:01 -04:00
Richard Hansen
ab60675660 Improve readability of cache var update logic 2024-06-22 02:30:01 -04:00
Richard Hansen
0d712f7bbc Improve readability of wantip, wantipv4, wantipv6 fallback 2024-06-22 02:30:01 -04:00
Richard Hansen
5c38af2ed5 Improve readability by moving code out of unnecessary blocks
Invert conditions and move out unnecessary blocks to reduce
indentation and make it easier to see control flow.
2024-06-22 02:28:12 -04:00
Richard Hansen
160344514f Improve readability by simplifying if conditions 2024-06-22 02:27:44 -04:00
Richard Hansen
288a30ab1e Whitespace fixes 2024-06-22 01:38:28 -04:00
Richard Hansen
afa6db8129
Merge pull request #687 from rhansen/ci
ci: Switch from RedHat UBI to AlmaLinux
2024-06-06 20:22:47 -04:00
Richard Hansen
0c42478ea7 ci: Switch from RedHat UBI to AlmaLinux 2024-06-06 20:16:07 -04:00
Richard Hansen
1ee64537df
Merge pull request #686 from rhansen/header_ok
Minor improvements to `header_ok` utility function
2024-06-06 19:40:44 -04:00
Richard Hansen
2d4a93d5e7 header_ok: Fix typo(?) in HTTP response regular expression 2024-06-06 19:34:06 -04:00
Richard Hansen
211d59fccc header_ok: Log all non-2xx HTTP status codes 2024-06-06 19:34:06 -04:00
Richard Hansen
7fe7fd0e18 header_ok: Refactor for readability 2024-06-06 19:34:06 -04:00
Richard Hansen
adbac91be7 header_ok: Only keep first line of argument
This allows callers to pass the entire response without generating
overly long error messages.
2024-06-06 19:34:06 -04:00
Richard Hansen
b58a10b3e3 header_ok: Add unit tests 2024-06-06 19:34:06 -04:00
Richard Hansen
a486d4f976
Merge pull request #685 from rhansen/module-load
Improve loading of optional SHA1 and JSON modules
2024-06-06 19:10:21 -04:00
Richard Hansen
bb658d763a Simplify loading of JSON::PP 2024-06-06 19:08:15 -04:00
Richard Hansen
1401ff4aea Only attempt to load Digest::SHA
`Digest::SHA` has been a core module for a long time, and
`Digest::SHA1` has not been updated in a long time.
2024-06-06 19:07:25 -04:00
Richard Hansen
1e1e100d7f Prefer Digest::SHA over Digest::SHA1
`Digest::SHA` is a core module; `Digest::SHA1` is not.
2024-06-04 18:44:25 -04:00
Richard Hansen
a0240345bf Use Module->import(...) instead of import(Module, ...)
This matches the documentation of the `use` statement.
2024-06-04 18:44:25 -04:00
Richard Hansen
11be757d54
Merge pull request #683 from rhansen/curl
curl execution improvements
2024-06-03 03:27:15 -04:00
Richard Hansen
1c1642acfd configure.ac: Allow users to specify path to curl 2024-06-03 03:13:05 -04:00
Richard Hansen
31dbd8e4ed geturl: Set raw (binary) mode when reading from curl 2024-06-03 03:13:05 -04:00
Richard Hansen
8e901c3db6 geturl: Avoid the shell when invoking curl 2024-06-02 17:00:05 -04:00
Richard Hansen
09d8d0426e geturl: Don't suppress curl's STDERR
This makes it easier for users to troubleshoot problems.
2024-06-02 17:00:05 -04:00
Richard Hansen
09ce262c82
Merge pull request #674 from jortkoopmans/bugfix/673_Fix_DnsExit_subdomain
Fix DNSExit provider when provided with a zone and a non-identical hostname
2024-06-02 16:58:43 -04:00
jortkoopmans
73a67b728d dnsexit2: Move body of for loop to a separate function
For improved readability.
2024-06-02 16:58:00 -04:00
Richard Hansen
11d0c84639 dnsexit2: Don't skip remaining hosts on connect error or non-2xx
A non-2xx status code might be host-specific, so ddclient should
continue with the next host.  We could skip the remaining hosts if
there is a connection failure, but it doesn't hurt to retry.
2024-06-02 16:58:00 -04:00
jortkoopmans
216741c9ce dnsexit2: Fix when provided with a zone and a non-identical hostname
Trim the zone from the hostname in the request to fix issue.
2024-06-02 16:58:00 -04:00
jortkoopmans
ec2d5f7f69 dnsexit2: Add tests
Needs LWP::UserAgent.
2024-06-02 16:58:00 -04:00
Richard Hansen
282bb01e17 Bump version to v3.12.0~alpha
Enough has changed to warrant a minor revision bump.
2024-06-01 03:49:16 -04:00
Richard Hansen
a0e119c2f2
Merge pull request #681 from rhansen/group_hosts_by
group_hosts_by: Add IPv6 and undef/unset support
2024-06-01 03:09:18 -04:00
Richard Hansen
e60e6e804b group_hosts_by: Use Data::Dumper to make the group signature
This is a bit more robust than manually making the group signature
because it gracefully handles corner cases such as `undef`.
2024-06-01 03:06:06 -04:00
Richard Hansen
f4802fc534 Fix group_hosts_by call for IPv6-enabled services 2024-06-01 03:05:36 -04:00
Richard Hansen
343fcff625 group_hosts_by: Add support for wantipv4, wantipv6 2024-06-01 03:05:36 -04:00
Richard Hansen
ce0a362fd0 group_hosts_by: Add tests 2024-06-01 03:05:26 -04:00
Richard Hansen
ddb04075be
Merge pull request #680 from rhansen/dnsexit2
dnsexit2: Fix parsing of JSON response
2024-05-31 03:27:34 -04:00
Richard Hansen
f976b771d4 dnsexit2: Fix logging of erroneous response body 2024-05-30 18:29:38 -04:00
Richard Hansen
f7f4856b93 dnsexit2: Combine related log messages 2024-05-30 18:28:57 -04:00
Richard Hansen
24a22092ca dnsexit2: Don't croak if JSON decoding fails 2024-05-30 18:22:59 -04:00
Richard Hansen
d28c8ea7ad dnsexit2: Delete unnecessary debug messages 2024-05-30 17:56:51 -04:00
Richard Hansen
7b95b379aa dnsexit2: Fix extraction and processing of JSON response body 2024-05-30 17:52:12 -04:00
Richard Hansen
86ec02a9b6
Merge pull request #679 from rhansen/dnsexit2
dnsexit2: Minor fixes and improvements
2024-05-30 17:22:52 -04:00
Richard Hansen
2a47b17541 dnsexit2: Include the unexpected status in the error message 2024-05-30 16:26:44 -04:00
Richard Hansen
d8a1449a19 dnsexit2: Fix error message format string
This fixes a bug introduced in commit
2bf6d348b0.
2024-05-30 16:25:25 -04:00
Richard Hansen
6e5e2ab63f dnsexit2: Remove https:// from update service URL
This allows the `ssl` setting to control TLS vs. plain HTTP, and makes
it possible to create a compatible service that doesn't use TLS (e.g.,
for testing).
2024-05-30 16:21:25 -04:00
Richard Hansen
eebb1b8a47 dnsexit2: Delete redundant status-ipv* assignments 2024-05-30 16:19:41 -04:00
Richard Hansen
b3951e407a
Merge pull request #678 from rhansen/dnsexit2
dnsexit2: Code health improvements
2024-05-30 05:02:11 -04:00
Richard Hansen
3c84f7a1b5 dnsexit2: Delete unnecessary debug messages 2024-05-30 04:49:46 -04:00
Richard Hansen
5b7400ae7d dnsexit2: Normalize the zone up front 2024-05-30 04:48:44 -04:00
Richard Hansen
46bca54393 dnsexit2: Fix string interpolation 2024-05-30 04:04:19 -04:00
Richard Hansen
da9f39917f dnsexit2: Inline some unnecessary variables 2024-05-29 18:29:09 -04:00
Richard Hansen
6c89eaf4ac dnsexit2: Build updates array directly, not hash to array
to improve readability, and to make it easier to use
`group_hosts_by()` in the future to update multiple hosts at the same
time.
2024-05-29 18:25:24 -04:00
Richard Hansen
2bf6d348b0 dnsexit2: Reuse the $url variable 2024-05-29 18:25:24 -04:00
Richard Hansen
4804e15c12 dnsexit2: Add final comma after last list item
for consistency, and to avoid bugs if additional items are added or
the items are reordered.
2024-05-29 18:25:24 -04:00
Richard Hansen
7c4fe28bab dnsexit2: Simplify IP version loop 2024-05-29 17:57:20 -04:00
Richard Hansen
40d1bc8e51 dnsexit2: Clarify comments 2024-05-29 17:51:54 -04:00
Richard Hansen
18007dda8a dnsexit2: Delete unnecessary comments
Comments should only be used if the code is doing something
subtle/tricky/non-obvious/unusual, otherwise they're distracting and
might get out of sync with the code (in which case it becomes
difficult to tell if it's the comment or the code that's wrong).  If
the code is difficult to understand without comments but is doing
something ordinary, then the code needs to be modified to improve
readability.
2024-05-29 17:51:54 -04:00
Richard Hansen
61d34a9157 dnsexit2: Move code meanings closer to where it is used 2024-05-29 17:42:35 -04:00
Richard Hansen
df81075e49 dnsexit2: Rename variable to avoid confusion 2024-05-29 17:41:34 -04:00
Richard Hansen
ed7f4a68a4 dnsexit2: Rewrite circuitous variable assignments 2024-05-29 17:34:51 -04:00
Richard Hansen
3e91fd02bf dnsexit2: Invert conditions to improve readability
Instead of:

    if ($success1) {
        if ($success2) {
            if ($success3) {
                # yay
            } else {
                # fail
            }
        } else {
            # fail
        }
    } else {
        # fail
    }

do:

    if (!$success1) {
        # fail
    }
    if (!$success2) {
        # fail
    }
    if (!$success3) {
        # fail
    }
    # yay
2024-05-29 17:21:39 -04:00
Richard Hansen
9b1a785c6d dnsexit2: Don't repeat the same success message 2024-05-29 17:15:24 -04:00
Richard Hansen
3a5e86b4d2 dnsexit2: Convert string interpolation to sprintf format specifier
Rationale:
  * For consistency.
  * It's generally not a good idea to mix interpolation with `sprintf`
    because the interpolated string itself might coincidentally (or
    maliciously) contain format specifiers.
2024-05-29 17:10:06 -04:00
Richard Hansen
6cdf5da9f4 dnsexit2: Rename variable for brevity 2024-05-29 17:05:54 -04:00
Richard Hansen
63989d96fb dnsexit2: Move variable declaration to loop scope 2024-05-29 17:02:46 -04:00
Richard Hansen
0040fc9608 dnsexit2: Whitespace fixes 2024-05-29 17:02:46 -04:00
Richard Hansen
a91ca7a199 s/foreach/for/g for consistency 2024-05-29 16:48:00 -04:00
Richard Hansen
d1068bede1
Merge pull request #654 from TV4Fun/emailonly
Add 'emailonly' client to send status emails without needing a DDNS service
2024-05-25 15:13:36 -04:00
Joel Croteau
61b979c49e New 'emailonly' protocol that simply sends an email on IP change
This adds a protocol to email IP address changes without needing a
dynamic DNS service.  This is useful if you don't use a DDNS service
but want to be notified when the IP of a machine changes.
2024-05-25 00:38:10 -04:00
Richard Hansen
d8a9d9d089 Add support for infinite duration 2024-05-25 00:38:10 -04:00
Richard Hansen
65fb4db6cd
Merge pull request #676 from rhansen/logging
Various minor logging improvements
2024-05-23 02:07:40 -04:00
Richard Hansen
9c6e5fdda4 Output a | character in log message continuation lines
This makes it easier to tell where multi-line log messages begin and
end.
2024-05-23 02:04:29 -04:00
Richard Hansen
ff39ce3874 Don't use sprintf if there is only one argument
This avoids problems when logging a string that might have
metacharacters.
2024-05-23 02:04:19 -04:00
Richard Hansen
bcd57b486b Always log to STDERR, even for debug, info, etc.
Rationale:
  * Logging to STDERR enables separation of processable output (e.g.,
    `--version` or `--help`) and ephemeral status/error messages.
  * A single file descriptor for all log messages makes it easier for
    users to capture all log messages.
  * Consistency: it's what most utilities do.
2024-05-23 02:04:19 -04:00
Richard Hansen
d6693e0175 Use the de facto standard signature separator instead of "regards" 2024-05-23 01:57:12 -04:00
Richard Hansen
065b227711 logmsg: New low-level logging interface 2024-05-23 01:57:12 -04:00
Richard Hansen
f9dafa35a1 Rename $msgs to $emailbody to improve readability 2024-05-23 01:54:09 -04:00
Richard Hansen
66ec57a902
Merge pull request #670 from rhansen/status
Fix handling of legacy `status` with IPv6-aware protocols
2024-05-23 01:53:46 -04:00
Richard Hansen
8ef7b13cb0 Don't set legacy status in protocols if IPv6-aware
ddclient infrastructure will update the legacy `status` variable if
necessary.
2024-05-20 01:35:21 -04:00
Richard Hansen
ba18535c51 Fix broken legacy status handling
`$config{$h}{'status'}` was always initialized to a non-`undef` value,
so the `//` fallbacks never did anything.  Instead, any protocol that
does not explicitly update the legacy `status` variable (such as
`godaddy`) would always appear to have failed even if it had
succeeded.

Change the `status*` variables to `undef` by default, and only set
them when an attempt is made so that the legacy `//` fallback works as
expected.
2024-05-20 01:33:15 -04:00
Richard Hansen
f212613526 godaddy: Fix status field name
This shouldn't matter in practice because the `status-ipv$ipversion`
field is initialized to a non-`good` value so failing to set it to
`bad` doesn't turn it `good`, but it improves readability.
2024-05-20 01:30:33 -04:00
Richard Hansen
c60aa225a1 godaddy: Rename $status to $code 2024-05-20 01:30:33 -04:00
Richard Hansen
dc92f16eb2 dnsexit2: Update new status-ipv* vars, not legacy status
The `dnsexit2` protocol reads the IP addresses from the new `ipv4` and
`ipv6` variables, so it should update the `status-ipv4` and
`status-ipv6` variables.
2024-05-20 01:30:33 -04:00
Richard Hansen
baec50d134 1984: Update cached status and IP on success 2024-05-20 01:30:33 -04:00
Richard Hansen
8b0c038d63 1984: Fix missing next on failure 2024-05-20 01:30:33 -04:00
Richard Hansen
9573051e3e njalla: Update cached status and IP on success 2024-05-20 01:30:33 -04:00
Richard Hansen
8269021d7b
Merge pull request #672 from rhansen/changelog
Update changelog
2024-05-20 01:29:25 -04:00
Richard Hansen
0d85dfd044 Update changelog 2024-05-20 01:21:21 -04:00
Richard Hansen
6320e6c395 Don't require login and password to be set
Not all services use them.

This change should have been included with commit
27b5c535bc.
2024-05-20 00:13:37 -04:00
Richard Hansen
7b18e4bce4
Merge pull request #675 from rhansen/ci
CI improvements
2024-05-19 20:31:34 -04:00
Richard Hansen
86031edd2d ci: Disable fail-fast 2024-05-19 20:19:23 -04:00
Richard Hansen
6d2dba3aee ci: Upload distribution tarball as artifact
This makes it easier for users to try a fix.
2024-05-19 20:19:23 -04:00
Richard Hansen
f2c9158da4 ci: Test all supported versions of RedHat UBI 2024-05-19 20:19:23 -04:00
Richard Hansen
dd7a8aeb10 ci: Bump actions/checkout to v4 2024-05-19 20:19:23 -04:00
Richard Hansen
08c914c660 ci: Combine RedHat UBI with Fedora 2024-05-19 20:19:23 -04:00
Richard Hansen
c0ba4b7d91 ci: Test all supported Fedora versions and rawhide 2024-05-19 19:50:31 -04:00
Richard Hansen
b32619892f ci: Delete commented-out centos 2024-05-19 18:18:47 -04:00
Richard Hansen
545d5e10d8
Merge pull request #671 from rhansen/vars
Clean up service variable definitions
2024-05-19 02:45:17 -04:00
Richard Hansen
23b368f5ff Add missing final comma 2024-05-19 02:18:55 -04:00
Richard Hansen
27b5c535bc Use undef to disable required variables 2024-05-19 02:18:55 -04:00
Richard Hansen
4d1b3439ea Use service-common-defaults variables
This avoids a lot of duplication and improves readability by making it
easier to see service-specific variables.
2024-05-19 01:51:06 -04:00
Richard Hansen
6da30367d0 Inline unnecessary *-common-defaults variable definitions 2024-05-19 01:49:48 -04:00
Richard Hansen
5e3e10d32e Replace unnecessary merge function with hash initializers 2024-05-19 01:49:48 -04:00
Richard Hansen
52864f2bb1 Delete redundant variable definitions 2024-05-19 01:49:45 -04:00
Richard Hansen
58152b03de
Merge pull request #667 from rhansen/scalar
Fix "Scalar value better written as" warning
2024-05-18 17:22:44 -04:00
Richard Hansen
03ad24829c Fix "Scalar value better written as" warning
Also some readability improvements.
2024-05-18 17:21:20 -04:00
Richard Hansen
af50f7f69d
Merge pull request #664 from rhansen/cisco
Move `--use=cisco` and `--use=cisco-asa` to `%builtinfw`
2024-05-18 17:20:48 -04:00
Richard Hansen
6b7bf29e56 Move --use=cisco and --use=cisco-asa to %builtinfw
This simplifies the code and will make it easier to remove support for
these devices in the future.
2024-05-14 22:18:59 -04:00
Richard Hansen
d02a9cf6db Add infrastructure for custom logic in a %builtinfw entry 2024-05-14 22:18:59 -04:00
Richard Hansen
ee5bb2de90 Check, don't assume, that --use* names a firewall 2024-05-14 22:18:59 -04:00
Richard Hansen
474cc76587 Enable --usev4=cisco and --usev4=cisco-asa
These were implemented, but accidentally(?) left out of
`%ipv4_strategies` which prevented their use.
2024-05-14 22:18:59 -04:00
Richard Hansen
e2919873ba
Merge pull request #669 from rhansen/systemd
systemd.service unit file improvements
2024-05-14 22:16:48 -04:00
Richard Hansen
21de3cbc96 systemd: Make it easier to override the daemon interval 2024-05-14 22:12:36 -04:00
Richard Hansen
509ea8745a systemd: Set Restart=on-failure 2024-05-14 22:12:36 -04:00
Richard Hansen
c0a1431f78 systemd: Use Type=exec instead of fork
When forking, ddclient redirects STDERR and STDOUT to `/dev/null`,
which prevents useful information from appearing in the systemd
journal (`journalctl`).
2024-05-14 22:12:36 -04:00
Richard Hansen
48daf8a5d7
Merge pull request #668 from rhansen/logging
Log message improvements
2024-05-14 22:11:59 -04:00
Richard Hansen
5cad38a047 Don't attempt to read file if open fails 2024-05-14 22:00:12 -04:00
Richard Hansen
2eacc71acc Logging improvements
* Consistently use logging functions instead of `print`
  * Wording/formatting fixes
  * Use `info(...)` instead of `verbose('VERBOSE:', ...)`
2024-05-14 22:00:12 -04:00
Richard Hansen
50b7e3d94b
Merge pull request #666 from rhansen/retry
`--retry` improvements
2024-05-14 21:55:54 -04:00
Richard Hansen
066b19af8f Error out if --daemon and --retry are both specified 2024-05-14 21:55:12 -04:00
Richard Hansen
2764cd8a10 Clarify what --retry does 2024-05-14 21:55:12 -04:00
Richard Hansen
498df75790 Fix "no hosts to update" warning condition 2024-05-14 21:55:03 -04:00
Richard Hansen
542bb28a13 Consistently use --arg instead of -arg 2024-05-14 18:00:14 -04:00
Richard Hansen
8ac575125b
Merge pull request #665 from rhansen/usage
Enable `--use=disabled`; more `--help` usage improvements
2024-05-13 19:24:50 -04:00
Richard Hansen
12222ff912 Clarify interaction between --use, --usev4, and --usev6 2024-05-13 18:53:19 -04:00
Richard Hansen
e35be25010 Reorder --help for --usev4 and --usev6 for readability
and for consistency with `--use`.
2024-05-13 18:53:19 -04:00
Richard Hansen
05304622ea Add context to --use deprecation notices in --help 2024-05-13 18:52:22 -04:00
Richard Hansen
a911f2bc0e Include --use=disabled and --use=no in --help usage 2024-05-13 18:40:05 -04:00
Richard Hansen
16fd4d948d Enable --use=disabled
It's unclear why this entry did not exist before now.
2024-05-13 18:37:33 -04:00
Richard Hansen
07c4e4ad4c
Merge pull request #639 from rhansen/version
User-friendly pre-release version strings
2024-05-12 16:51:49 -04:00
Richard Hansen
dfb2196499 Translate Perl version string to user-friendly version string
Perl version strings are flawed in a few ways.  Convert them to
user-friendly strings when printed so that Git tags and tarball names
are easier for downstream distributions to work with.
2024-05-12 16:43:43 -04:00
Richard Hansen
0806363b57 Tell Autoconf to get the version from ddclient.in
This avoids the need to maintain the same version string in two
different places.
2024-05-12 16:43:43 -04:00
Richard Hansen
c9cec591f0 Add short option to --version argument
Passing `--version=short` simply prints the version and exits.  This
will make it possible for a future commit to change `configure.ac` to
extract the version string from `ddclient.in` to avoid maintaining the
same version string in two places.
2024-05-12 16:43:43 -04:00
Richard Hansen
5eb6a6d755
Merge pull request #662 from rhansen/skip
Distinguish unset `--*-skip` settings from set to the empty string
2024-05-12 16:03:28 -04:00
Richard Hansen
58d7be4e83 Distinguish unset --*-skip settings from set to the empty string
This prevents the `%builtinweb` or `%builtinfw` skip defaults from
overriding a user's explicitly empty `--web-skip=` or `--fw-skip=`
setting.

This is technically a backwards-incompatible change: Any config that
explicitly sets `--web-skip` or `--fw-skip` to the empty string but
depends on the built-in skip behavior will fail.  This is unlikely to
affect many (if any) users; compatibility concerns are believed to be
far less significant than the potential need to turn off the built-in
skip.
2024-05-12 15:55:28 -04:00
Richard Hansen
fe06a19742
Merge pull request #661 from rhansen/ssl-validate
Fix misspelled `*-ssl-validate` option names
2024-05-12 03:37:13 -04:00
Richard Hansen
281b7307a8 Fix misspelled *-ssl-validate option names
There is no `ssl-validate` option, and there never has been.
2024-05-10 16:54:40 -04:00
Richard Hansen
fedf0cbf40 Update ddclient::Test::Fake::HTTPD 2024-05-09 22:32:28 -04:00
Richard Hansen
f6f19c962e
Merge pull request #660 from rhansen/logs
Improve log messages
2024-05-07 23:29:30 -04:00
Richard Hansen
d525e28c20 Show original user input in debug message
Also add comments explaining the purpose of the lines, because it's
not immediately clear.
2024-05-07 23:04:20 -04:00
Richard Hansen
b7dd590300 Fix misleading, unclear, redundant, and unnecessary warnings 2024-05-07 23:04:20 -04:00
Richard Hansen
57f15bcb97 Don't modify $usev6 to avoid misleading log messages 2024-05-07 23:04:20 -04:00
Richard Hansen
01d1d5e142 Change t/geturl_connectivity.pl to handwritten
Commit a9c1e545fb removed the
`configure.ac` substitution, so the test no longer needs to be
generated.
2024-05-07 22:39:17 -04:00
Richard Hansen
97397db294
Merge pull request #659 from rhansen/usage
Improvements to `--help` usage output
2024-05-06 23:58:01 -04:00
Richard Hansen
908b728503 Refine --help usage for --use* strategies 2024-05-06 00:21:13 -04:00
Richard Hansen
09b42a78bd Use --arg instead of -arg or arg in --help usage
`--arg` is preferred over `-arg` because the broader convention is to
use double hyphens for long option names.  Perl accepts either.

`--arg` is preferred over `arg` to avoid confusion between `--use=ip`
and `--ip` and similar option pairs.
2024-05-06 00:21:13 -04:00
Richard Hansen
6ac1ee45b0 Consistently use -arg=val in --help usage 2024-05-06 00:21:13 -04:00
Richard Hansen
fc4daae0cd Consistently use | in placeholders for --help usage 2024-05-06 00:21:12 -04:00
Richard Hansen
77c9cb5512 Consistently use < and > for placeholders in --help usage 2024-05-06 00:21:12 -04:00
Richard Hansen
dac72a344c Remove unimplemented --usev* strategies from --help usage
The `ciscov4`, `ciscov6`, `cisco-asav4`, and `cisco-asav6` strategies
were never implemented, and it doesn't make sense to implement them
because the `v4` and `v6` variants don't follow the pattern
established by the `%builtinfw` strategies.

The `%builtinfw` strategies were never implemented for `--usev6`.
2024-05-06 00:21:12 -04:00
Richard Hansen
640a6f08d7 Fix indentation in --help output 2024-05-06 00:21:12 -04:00
Lenard Hess
9885d55a37
Merge pull request #646 from toniwiki/fix-dondominio-response-treatment
Fix DonDominio service response treatment
2024-03-28 23:25:29 +01:00
Toni Wiki
e6e0072e8d dondominio: Fix service response treatment 2024-03-28 12:56:09 +01:00
Lenard Hess
981dd5f333 Fixed _env suffixed config variables causing errors in check_value() 2024-03-19 22:03:20 +01:00
Lenard Hess
09ee38f694
Merge pull request #638 from rhansen/testdep
Tell make that the tests depend on `ddclient`
2024-03-19 22:03:06 +01:00
Lenard Hess
b803b308fa
Merge pull request #636 from ddclient/feature_gandi_pat_support
Gandi: Personal access token support
2024-03-19 21:58:31 +01:00
Lenard Hess
12e9a7c47b gandi: Added support for personal access tokens 2024-03-19 21:48:30 +01:00
Lenard Hess
9b7714c39c Revert "gandi: Changed authorization to personal access token"
This reverts commit a57cb3b9ff.

See https://github.com/ddclient/ddclient/issues/602 for more info.
2024-03-19 21:48:30 +01:00
Richard Hansen
5aa6530c84 Tell make that the tests depend on ddclient
This ensures that `ddclient` is rebuilt before running tests if
`ddclient.in` changes.
2024-03-06 23:32:11 -08:00
Lenard Hess
9c3fc230ba
Merge pull request #634 from domdomegg/patch-1
docs: Remove fork notice in README
2024-02-28 08:31:34 +01:00
Adam Jones
9ba82e5472
docs: Remove fork notice in README
This no longer makes sense, given this fork then became the upstream repo.
2024-02-28 00:11:20 +00:00
Lenard Hess
0be0cc6953
Merge pull request #627 from etkal/Fix-porkbun-root-domain-update
porkbun code not handling root domain
2024-02-25 16:19:06 +01:00
Lenard Hess
28bb7d076a
Merge pull request #628 from ikruglov/fix-googledomains-warning
fix warning "Argument "googledomains" isnt numeric in numeric eq (==)
2024-02-25 16:10:15 +01:00
Lenard Hess
eb7e9c5e2e
Merge pull request #629 from code-chicken/patch-1
Fix for getting url for keysystems
2024-02-25 16:07:58 +01:00
Rüdiger Hahn
8246c65ba8
Fix for getting url for keysystems 2024-02-14 22:36:48 +01:00
Ivan Kruglov
e47e63d91e fix warning "Argument "googledomains" isnt numeric in numeric eq (==) at /usr/sbin/ddclient line 2736. 2024-02-13 18:16:38 +01:00
Lenard Hess
a57cb3b9ff gandi: Changed authorization to personal access token
The previous API key mechanism has been deprecated.
See https://github.com/ddclient/ddclient/issues/602 for more.
2024-02-10 12:30:46 +01:00
Erik Tkal
4e33dd754f porkbun code not handling root domain 2024-02-05 18:21:58 -05:00
Lenard Hess
5b104ad116 Switch the defaults for webv4 and webv6 to dyndns
The googledomains option (https://domains.google.com/checkip) is soon to be
deprecated by Google (see https://github.com/ddclient/ddclient/issues/622).
2024-02-04 11:26:51 +01:00
Lenard Hess
34cc8fc70c
Merge pull request #624 from ddclient/feature_porkbun_subdomains
Feature porkbun subdomains
2024-02-04 10:34:51 +01:00
Lenard Hess
330ddc6bd2 porkbun: Moved subdomain processing out of IPv4/6 loop
The domain/subdomain processing is the same for IPv4 and IPv6, no need to repeat it
2024-02-03 16:18:55 +01:00
Lenard Hess
ae7f92772b porkbun: Updated documentation and config example 2024-02-03 16:13:13 +01:00
Lenard Hess
5e7609ea2a porkbun: Added 'root-domain' config option
Porkbun API requires separation of the root domain and subdomains.
Previously ddclient only supported one layer of subdomain or the root domain
by selecting between the two with the boolean 'on-root-domain'.

This change now allows to specify the root domain via the 'root-domain' parameter
ddclient will then split the host domain into root and subdomain
2024-02-03 13:57:09 +01:00
Lenard Hess
f1c77a06fb Added missing exit to -version argument 2024-02-03 13:50:34 +01:00
Lenard Hess
bab66330ca Added -version argument 2024-02-03 13:31:26 +01:00
Awalon
f5a1a906d1 Pull request #560: Updated GoDaddy to new IPv4/IPv6 logic
Changes by Lenard Hess based on Awalon's pull request:
- Rebased to master
- Removed use=disabled addition from this commit
2024-01-28 16:40:18 +01:00
Lenard Hess
fc4f87b33e
Merge pull request #616 from retep/retep-patch-1-1
Update Mythic Beasts mythicdyn module in ddclient.in
2024-01-14 14:09:47 +01:00
Lenard Hess
95ac201b4b
Merge pull request #603 from indrajitr/noip-v4-v6
noip: Adjust script to support simultaneous IPv4 and IPv6 updates
2024-01-14 13:59:10 +01:00
Marco Emilio "sphakka" Poleggi
dc84a74c7e fix(curl, doc): enable HTTP 30x redirections in curl requests. Fixes Issue #589
- curl: enable a configurable number of redirections (-redirects=<max>) to
  follow when making HTTP(S) requests.
- docs: update Infomaniak example to prefer 'dyndns2' instead of obsolete
  protocol.

Signed-off-by: Marco Emilio "sphakka" Poleggi <7766137+sphakka@users.noreply.github.com>
2024-01-13 15:53:56 +01:00
Lenard Hess
203fe47aa1
Merge pull request #604 from indrajitr/nsupdate-v4-v6
nsupdate: Adjust script to support simultaneous IPv4 and IPv6 updates
2024-01-13 15:04:47 +01:00
PeterF
6994b05ab6
Update ddclient.in
The mythicdyn module is modified so that it will update either or both V4 and/or V6 addresses depending upon which specific address parameters have been defined in the config file. The module examines the wantipv4 and wantipv6 parameters.
If required, both addresses will be updated in a single invocation.
2024-01-11 16:01:11 +00:00
Lenard Hess
ad854ab716 Fixed legacy providers not supporting usev6 2024-01-06 18:03:26 +01:00
Lenard Hess
05c18fce67 README: Reformatted the known issues a bit 2024-01-06 17:03:14 +01:00
Lenard Hess
256cd89bb1 README: Added some known issues 2024-01-06 16:57:10 +01:00
Lenard Hess
3c522a7aa2 Config parsing: Allow trailing comments after a backslash in the config
The cloudflare documentation example had lines with a comment after a
backslash. This actually did not work in the parser until now.

The lines in question:
	#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" (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
2024-01-04 18:21:48 +01:00
Matthew Ogilvie
d195bcc4b8 If present, give URL protocol precedence over other SSL settings 2024-01-04 16:25:27 +01:00
Indrajit Raychaudhuri
bfe20e75f8 nsupdate: Adjust script to support simultaneous IPv4 and IPv6 updates
Adjust `nic_nsupdate_update` to use `wantipv4` and `wantipv6` and
support simultaneous IPv4 and IPv6 updates.

Also, set proper `status-ipv4` and `status-ipv6` values after successful
update.
2023-12-02 15:38:33 -06:00
Indrajit Raychaudhuri
8c8a193a70 noip: Adjust script to support simultaneous IPv4 and IPv6 updates
Adjust `nic_noip_update` to use `wantipv4` and `wantipv6` and support
simultaneous IPv4 and IPv6 updates.

Note: Unlike `nic_dyndns2_update`, `$returnedips` actually contains
valid IPv4 and IPv6 addresses, so we can use the response to update the
status.
2023-12-02 15:35:14 -06:00
Lenard Hess
e0611ab192 Merge pull request #558 from TinfoilSubmarine/fix/gandi 2023-11-25 13:55:09 +01:00
Stewart Whitman
2ceb4a9526 Resolves #585 - inheritance for noip service 2023-11-25 13:24:44 +01:00
Lenard Hess
eec72794fd Merge pull request #593 from TinfoilSubmarine/fix/ipversion
Note: This branch has been locally rebased before merging!
2023-11-25 13:18:17 +01:00
Joel Beckmeyer
c382af56a5 don't print erroneous IP version 2023-11-25 13:07:08 +01:00
Joel Beckmeyer
846ab59e68 gandi: improve documentation 2023-11-24 15:56:37 -05:00
Joel Beckmeyer
5ec8bfe141 gandi: update logic
- allow updating IPv6/AAAA
- allow updating A and AAAA records simultaneously
- skip updating if record already has same IP
2023-11-24 15:56:37 -05:00
Lenard Hess
baa7e440ed Updated version number for v3.11.3_0 development 2023-11-23 13:08:07 +01:00
Lenard Hess
4a1b06630b Updated version number for v3.11.2 release 2023-11-23 13:06:21 +01:00
Lenard Hess
58c5e4dc3a Merge pull request #595 from ddclient/fix_legacy_caching 2023-11-23 13:00:58 +01:00
Lenard Hess
30f68e4098 docs: Added initial description on provider implementation rules 2023-11-23 13:00:06 +01:00
Lenard Hess
5d022b0520 Fixed caching for new providers with legacy 'use' parameter
When the configuration used the legacy 'use' parameter, we already
populated the new internal 'wantipv*' field alongside the legacy
'wantip' field. This allows both old and new providers to work.

The legacy providers set 'status'/'ip'.
The new providers then set 'status-ipv*'/'ipv*'.

The caching logic would look for 'status' and 'ip' when encountering a
'use' parameter in the config. We previously already changed ddclient to
set 'status' when 'status-ipv*' was set. Now we also set 'ip' from
'ipv*'. This ensures caching correctly works.
2023-11-23 13:00:06 +01:00
Lenard Hess
9145dc1bfd Merge pull request #588 from indrajitr/duckdns-update
duckdns: Adjust script to support simultaneous IPv4 and IPv6 updates
2023-11-19 20:12:26 +01:00
Indrajit Raychaudhuri
3a224b66a4 duckdns: Adjust script to support simultaneous IPv4 and IPv6 updates 2023-11-02 19:34:45 -05:00
Birger J. Nordølum
afa1275253 docs: fix typo of domeneshop 2023-10-29 13:52:54 +01:00
Lenard Hess
23bfa31ea5 Updated version number for v3.11.2_0 development 2023-10-25 21:25:43 +02:00
50 changed files with 6994 additions and 4690 deletions

6
.autom4te.cfg Normal file
View file

@ -0,0 +1,6 @@
# Disable autom4te cache to ensure that any change to ddclient.in triggers a
# rebuild of the configure script (which gets the version of ddclient from
# ddclient.in). See <https://lists.gnu.org/r/automake/2019-10/msg00002.html>.
begin-language: "Autoconf-without-aclocal-m4"
args: --no-cache
end-language: "Autoconf-without-aclocal-m4"

View file

@ -6,6 +6,7 @@ on:
jobs:
test-debian-like:
strategy:
fail-fast: false
matrix:
image:
- ubuntu:latest
@ -32,10 +33,11 @@ jobs:
libtest-tcp-perl \
libtest-warnings-perl \
liburi-perl \
libwww-perl \
net-tools \
make \
;
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: autogen
run: ./autogen
- name: configure
@ -46,41 +48,45 @@ jobs:
run: make VERBOSE=1 AM_COLOR_TESTS=always distcheck
- name: distribution tarball is complete
run: ./.github/workflows/scripts/dist-tarball-check
- if: ${{ matrix.image == 'debian:testing' }}
uses: actions/upload-artifact@v4
with:
name: distribution-tarball
path: ddclient-*.tar.gz
#test-centos8:
# runs-on: ubuntu-latest
# container: centos:8
# steps:
# - uses: actions/checkout@v2
# - name: install dependencies
# run: |
# dnf --refresh --enablerepo=PowerTools install -y \
# automake \
# make \
# perl-HTTP-Daemon \
# perl-IO-Socket-INET6 \
# perl-Test-Warnings \
# perl-core \
# ;
# - name: autogen
# run: ./autogen
# - name: configure
# run: ./configure
# - name: check
# run: make VERBOSE=1 AM_COLOR_TESTS=always check
# - name: distcheck
# run: make VERBOSE=1 AM_COLOR_TESTS=always distcheck
test-fedora:
test-fedora-like:
strategy:
fail-fast: false
matrix:
image:
- fedora:39
- fedora:latest
- fedora:rawhide
- almalinux:8
- almalinux:latest
runs-on: ubuntu-latest
container: fedora
container:
image: ${{ matrix.image }}
steps:
- uses: actions/checkout@v2
- name: install dependencies
- uses: actions/checkout@v4
- name: enable repositories (AlmaLinux 8)
if: ${{ matrix.image == 'almalinux:8' }}
run: |
dnf --refresh install -y \
dnf --refresh install -y 'dnf-command(config-manager)' epel-release &&
dnf config-manager --set-enabled powertools
- name: enable repositories (AlmaLinux latest)
if: ${{ matrix.image == 'almalinux:latest' }}
run: |
dnf --refresh install -y 'dnf-command(config-manager)' epel-release &&
dnf config-manager --set-enabled crb
- name: install dependencies
# The --skip-broken argument works around missing packages. (They're
# only used for testing, so it's OK to not install them.)
run: |
dnf --refresh install --skip-broken -y \
automake \
findutils \
iproute \
make \
curl \
perl \
@ -91,6 +97,8 @@ jobs:
perl-Test-MockModule \
perl-Test-TCP \
perl-Test-Warnings \
perl-core \
perl-libwww-perl \
net-tools \
;
- name: autogen
@ -101,29 +109,3 @@ jobs:
run: make VERBOSE=1 AM_COLOR_TESTS=always check
- name: distcheck
run: make VERBOSE=1 AM_COLOR_TESTS=always distcheck
test-redhat-ubi7:
runs-on: ubuntu-latest
# we use redhats univeral base image which is not available on docker hub
# https://catalog.redhat.com/software/containers/ubi7/ubi/5c3592dcd70cc534b3a37814
container: registry.access.redhat.com/ubi7/ubi
steps:
- uses: actions/checkout@v2
- name: install dependencies
run: |
yum install -y \
automake \
make \
perl-HTTP-Daemon \
perl-IO-Socket-INET6 \
perl-core \
iproute \
;
- name: autogen
run: ./autogen
- name: configure
run: ./configure
- name: check
run: make VERBOSE=1 AM_COLOR_TESTS=always check
- name: distcheck
run: make VERBOSE=1 AM_COLOR_TESTS=always distcheck

49
.github/workflows/pr.yml vendored Normal file
View file

@ -0,0 +1,49 @@
name: Pull Request
on:
pull_request:
types:
- labeled
- opened
- reopened
- synchronize
- unlabeled
jobs:
linear-history:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'pr-permit-nonlinear') }}
name: Linear History
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: No new merge commits
run: |
log() { printf %s\\n "$*" >&2; }
error() { log "ERROR: $@"; }
fatal() { error "$@"; exit 1; }
try() { log "Running command $@"; "$@" || fatal "'$@' failed"; }
out=$(try git rev-list -n 1 --merges '${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}') || exit 1
[ -z "${out}" ] || {
error "pull request includes a merge commit and does not have the 'pr-permit-nonlinear' label"
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

@ -34,6 +34,7 @@ try git archive --format=tar --prefix=git-repo/ HEAD \
.github \
.gitignore \
docs/ipv6-design-doc.md \
docs/ProviderGuidelines.md \
shell.nix \
;
# TODO: Delete this next line once support for Automake 1.11 is dropped and

3
.gitignore vendored
View file

@ -7,8 +7,11 @@ release
/Makefile.in
/aclocal.m4
/autom4te.cache/
/build-aux/config.guess
/build-aux/config.sub
/build-aux/install-sh
/build-aux/missing
/build-aux/tap-driver.sh
/config.log
/config.status
/configure

View file

@ -80,7 +80,7 @@ perltidy -l=99 -conv -ci=4 -ola -ce -nbbc -kis -pt=2 -b ddclient
## Git Hygiene
* Please keep your pull request commits rebased on top of master.
* Please keep your pull request commits rebased on top of `main`.
* Please use `git rebase -i` to make your commits easy to review:
- Put unrelated changes in separate commits
- Squash your fixup commits
@ -190,11 +190,11 @@ better to revert the original change then redo it:
### Merging Pull Requests
To facilitate reviews and code archaeology, `master` should have a
To facilitate reviews and code archaeology, `main` should have a
semi-linear commit history like this:
```
* f4e6e90 sandro.jaeckel@gmail.com 2020-05-31 07:29:51 +0200 (master)
* f4e6e90 sandro.jaeckel@gmail.com 2020-05-31 07:29:51 +0200 (main)
|\ Merge pull request #142 from rhansen/config-line-format
| * 30180ed rhansen@rhansen.org 2020-05-30 13:09:38 -0400
|/ Expand comment documenting config line format
@ -231,7 +231,7 @@ has value:
change was made) and the merge timestamp (when it went live).
To achieve a history like the above, the pull request must be rebased
onto `master` before merging. Unfortunately, GitHub does not have a
onto `main` before merging. Unfortunately, GitHub does not have a
one-click way to do this (the "Rebase and merge" option does a
fast-forward merge, which is not what we want). See
[isaacs/github#1143](https://github.com/isaacs/github/issues/1143) and
@ -254,15 +254,15 @@ git remote set-url origin git@github.com:ddclient/ddclient.git
# Add a remote for the fork used in the PR
git remote add "${PR_USER:?}" git@github.com:"${PR_USER:?}"/ddclient
# Fetch the latest commits for the PR and ddclient master
# Fetch the latest commits for the PR and ddclient main
git remote update -p
# Switch to the pull request branch
git checkout -b "${PR_USER:?}-${PR_BRANCH:?}" "${PR_USER:?}/${PR_BRANCH:?}"
# Rebase the commits (optionally using -i to clean up history) onto
# the current ddclient master branch
git rebase origin/master
# the current ddclient main branch
git rebase origin/main
# Force update the contributor's fork. This will only work if the
# contributor has checked the "Allow edits by maintainers" box in the
@ -276,19 +276,19 @@ git push -f
# "Allow edits by maintainers", or if you prefer to merge manually,
# continue with the next steps.
# Switch to the local master branch
git checkout master
# Switch to the local main branch
git checkout main
# Make sure the local master branch is up to date
git merge --ff-only origin/master
# Make sure the local main branch is up to date
git merge --ff-only origin/main
# Merge in the rebased pull request branch **WITHOUT DOING A
# FAST-FORWARD MERGE**
git merge --no-ff "${PR_USER:?}-${PR_BRANCH:?}"
# Review the commits before pushing
git log --graph --oneline --decorate origin/master..
git log --graph --oneline --decorate origin/main..
# Push to ddclient master
git push origin master
# Push to ddclient main
git push origin main
```

View file

@ -1,7 +1,216 @@
# ChangeLog
This document describes notable changes. For details, see the [source code
repository history](https://github.com/ddclient/ddclient/commits/master).
repository history](https://github.com/ddclient/ddclient/commits/main).
## v4.0.1-alpha (unreleased work-in-progress)
## 2025-01-19 v4.0.0
### 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
```
* The `--ssl` option is now enabled by default.
[#705](https://github.com/ddclient/ddclient/pull/705)
* Unencrypted (plain) HTTP is now used instead of encrypted (TLS) HTTP if the
URL uses `http://` instead of `https://`, even if the `--ssl` option is
enabled. [#608](https://github.com/ddclient/ddclient/pull/608)
* 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)
* The default web service for `--webv4` and `--webv6` has changed from Google
Domains (which has shut down) to ipify.
[5b104ad1](https://github.com/ddclient/ddclient/commit/5b104ad116c023c3760129cab6e141f04f72b406)
* Invalid command-line options or values are now fatal errors (instead of
discarded with a warning).
[#733](https://github.com/ddclient/ddclient/pull/733)
* All log messages are now written to STDERR, not a mix of STDOUT and STDERR.
[#676](https://github.com/ddclient/ddclient/pull/676)
* For `--protocol=freedns` and `--protocol=nfsn`, the core module
`Digest::SHA` is now required. Previously, `Digest::SHA1` was used (if
available) as an alternative to `Digest::SHA`.
[#685](https://github.com/ddclient/ddclient/pull/685)
* The `he` built-in web IP discovery service (`--webv4=he`, `--webv6=he`, and
`--web=he`) was renamed to `he.net` for consistency with the new `he.net`
protocol. The old name is still accepted but is deprecated and will be
removed in a future version of ddclient.
[#682](https://github.com/ddclient/ddclient/pull/682)
* Deprecated built-in web IP discovery services are not listed in the output
of `--list-web-services`.
[#682](https://github.com/ddclient/ddclient/pull/682)
* `dyndns2`: Support for "wait" response lines has been removed. The Dyn
documentation does not mention such responses, and the code to handle them,
untouched since at least 2006, is believed to be obsolete.
[#709](https://github.com/ddclient/ddclient/pull/709)
* `dyndns2`: The obsolete `static` and `custom` options have been removed.
Setting the options may produce a warning.
[#709](https://github.com/ddclient/ddclient/pull/709)
* The diagnostic `--geturl` command-line argument was removed.
[#712](https://github.com/ddclient/ddclient/pull/712)
* `easydns`: The default value for `min-interval` was increased from 5m to 10m
to match easyDNS documentation.
[#713](https://github.com/ddclient/ddclient/pull/713)
* `woima`: The dyn.woima.fi service appears to be defunct so support was
removed. [#716](https://github.com/ddclient/ddclient/pull/716)
* `googledomains`: Support was removed because the service shut down.
[#716](https://github.com/ddclient/ddclient/pull/716)
* The `--retry` option was removed.
[#732](https://github.com/ddclient/ddclient/pull/732)
### New features
* New `--mail-from` option to control the "From:" header of email messages.
[#565](https://github.com/ddclient/ddclient/pull/565)
* Simultaneous/separate updating of IPv4 (A) records and IPv6 (AAAA) records
is now supported in the following services: `gandi`
([#558](https://github.com/ddclient/ddclient/pull/558)), `nsupdate`
([#604](https://github.com/ddclient/ddclient/pull/604)), `noip`
([#603](https://github.com/ddclient/ddclient/pull/603)), `mythicdyn`
([#616](https://github.com/ddclient/ddclient/pull/616)), `godaddy`
([#560](https://github.com/ddclient/ddclient/pull/560)).
* `porkbun`: Added support for subdomains.
[#624](https://github.com/ddclient/ddclient/pull/624)
* `gandi`: Added support for personal access tokens.
[#636](https://github.com/ddclient/ddclient/pull/636)
* Comments after the `\` line continuation character are now supported.
[3c522a7a](https://github.com/ddclient/ddclient/commit/3c522a7aa235f63ae0439e5674e7406e20c90956)
* Minor improvements to `--help` output.
[#659](https://github.com/ddclient/ddclient/pull/659),
[#665](https://github.com/ddclient/ddclient/pull/665)
* Improved formatting of ddclient's version number.
[#639](https://github.com/ddclient/ddclient/pull/639)
* Updated sample systemd service unit file to improve logging in the systemd
journal. [#669](https://github.com/ddclient/ddclient/pull/669)
* The second and subsequent lines in a multi-line log message now have a
different prefix to distinguish them from separate log messages.
[#676](https://github.com/ddclient/ddclient/pull/676)
[#719](https://github.com/ddclient/ddclient/pull/719)
* Log messages now include context, making it easier to troubleshoot issues.
[#725](https://github.com/ddclient/ddclient/pull/725)
* `emailonly`: New `protocol` option that simply emails you when your IP
address changes. [#654](https://github.com/ddclient/ddclient/pull/654)
* `he.net`: Added support for updating Hurricane Electric records.
[#682](https://github.com/ddclient/ddclient/pull/682)
* `dyndns2`, `domeneshop`, `dnsmadeeasy`, `keysystems`: The `server` option
can now include `http://` or `https://` to control the use of TLS. If
omitted, the value of the `ssl` option is used to determine the scheme.
[#703](https://github.com/ddclient/ddclient/pull/703)
* `ddns.fm`: New `protocol` option for updating [DDNS.FM](https://ddns.fm/)
records. [#695](https://github.com/ddclient/ddclient/pull/695)
* `inwx`: New `protocol` option for updating [INWX](https://www.inwx.com/)
records. [#690](https://github.com/ddclient/ddclient/pull/690)
* `domeneshop`: Add IPv6 support.
[#719](https://github.com/ddclient/ddclient/pull/719)
* `duckdns`: Multiple hosts with the same IP address are now updated together.
[#719](https://github.com/ddclient/ddclient/pull/719)
* `directnic`: Added support for updatng Directnic records.
[#726](https://github.com/ddclient/ddclient/pull/726)
* `porkbun`: The update URL hostname is now configurable via the `server`
option. [#752](https://github.com/ddclient/ddclient/pull/752)
* `dnsexit2`: Multiple hosts are updated in a single API call when possible.
[#684](https://github.com/ddclient/ddclient/pull/684)
### Bug fixes
* Fixed numerous bugs in cache file (recap) handling.
[#740](https://github.com/ddclient/ddclient/pull/740)
* Fixed numerous bugs in command-line option and configuration file
processing. [#733](https://github.com/ddclient/ddclient/pull/733)
* `noip`: Fixed failure to honor IP discovery settings in some circumstances.
[#591](https://github.com/ddclient/ddclient/pull/591)
* Fixed `--usev6` with providers that have not yet been updated to use the new
separate IPv4/IPv6 logic.
[ad854ab7](https://github.com/ddclient/ddclient/commit/ad854ab716922f5f25742421ebd4c27646b86619)
* HTTP redirects (301, 302) are now followed.
[#592](https://github.com/ddclient/ddclient/pull/592)
* `keysystems`: Fixed update URL.
[#629](https://github.com/ddclient/ddclient/pull/629)
* `dondominio`: Fixed response parsing.
[#646](https://github.com/ddclient/ddclient/pull/646)
* Fixed `--web-ssl-validate` and `--fw-ssl-validate` options, which were
ignored in some cases (defaulting to validate).
[#661](https://github.com/ddclient/ddclient/pull/661)
* Explicitly setting `--web-skip`, `--webv4-skip`, `--webv6-skip`,
`--fw-skip`, `--fwv4-skip`, and `--fwv6-skip` to the empty string now
disables any built-in default skip. Before, setting to the empty string had
no effect. [#662](https://github.com/ddclient/ddclient/pull/662)
* `--use=disabled` now works.
[#665](https://github.com/ddclient/ddclient/pull/665)
* `--retry` and `--daemon` are incompatible with each other; ddclient now
errors out if both are provided.
[#666](https://github.com/ddclient/ddclient/pull/666)
* `--usev4=cisco` and `--usev4=cisco-asa` now work.
[#664](https://github.com/ddclient/ddclient/pull/664)
* Fixed "Scalar value better written as" Perl warning.
[#667](https://github.com/ddclient/ddclient/pull/667)
* Fixed "Invalid Value for keyword 'wtime' = ''" warning.
[#734](https://github.com/ddclient/ddclient/pull/734)
* Fixed unnecessary repeated updates for some services.
[#670](https://github.com/ddclient/ddclient/pull/670)
[#732](https://github.com/ddclient/ddclient/pull/732)
* Fixed DNSExit provider when configured with a zone and non-identical
hostname. [#674](https://github.com/ddclient/ddclient/pull/674)
* `infomaniak`: Fixed frequent forced updates after 25 days (`max-interval`).
[#691](https://github.com/ddclient/ddclient/pull/691)
* `infomaniak`: Fixed incorrect parsing of server response.
[#692](https://github.com/ddclient/ddclient/pull/692)
* `infomaniak`: Fixed incorrect handling of `nochg` responses.
[#723](https://github.com/ddclient/ddclient/pull/723)
* `regfishde`: Fixed IPv6 support.
[#691](https://github.com/ddclient/ddclient/pull/691)
* `easydns`: IPv4 and IPv6 addresses are now updated separately to be
consistent with the easyDNS documentation.
[#713](https://github.com/ddclient/ddclient/pull/713)
* `easydns`: Fixed parsing of result code from server response.
[#713](https://github.com/ddclient/ddclient/pull/713)
* `easydns`: Fixed successful updates treated as failed updates.
[#713](https://github.com/ddclient/ddclient/pull/713)
* Any IP addresses in an HTTP response's headers or in an HTTP error
response's body are now ignored when obtaining the IP address from a
web-based IP discovery service (`--usev4=webv4`, `--usev6=webv6`) or from a
router/firewall device.
[#719](https://github.com/ddclient/ddclient/pull/719)
* `yandex`: Errors are now retried.
[#719](https://github.com/ddclient/ddclient/pull/719)
* `gandi`: Fixed handling of error responses.
[#721](https://github.com/ddclient/ddclient/pull/721)
* `dyndns2`: Fixed handling of responses for multi-host updates.
[#728](https://github.com/ddclient/ddclient/pull/728)
* `porkbun`: The default update URL was updated from `porkbun.com` to
`api.porkbun.com`. [#752](https://github.com/ddclient/ddclient/pull/752)
## 2023-11-23 v3.11.2
### Bug fixes
* Fixed simultaneous IPv4 and IPv6 updates for provider duckdns
* Fixed caching issues for new providers when using the old 'use' config parameter
## 2023-10-25 v3.11.1
@ -59,7 +268,7 @@ Refer to [v3.11 release plan discussions](https://github.com/ddclient/ddclient/i
* Added support for domaindiscount24.com
* Added support for njal.la
## 2022-05-15 v3.10.0_2
### Bug fixes

View file

@ -1,4 +1,4 @@
ACLOCAL_AMFLAGS = -I m4
ACLOCAL_AMFLAGS = -I build-aux/m4
EXTRA_DIST = \
CONTRIBUTING.md \
COPYING \
@ -16,19 +16,7 @@ EXTRA_DIST = \
sample-get-ip-from-fritzbox
CLEANFILES =
# Command that replaces substitution variables with their values.
subst = sed \
-e 's|@PACKAGE_VERSION[@]|$(PACKAGE_VERSION)|g' \
-e '1 s|^\#\!.*perl$$|\#\!$(PERL)|g' \
-e 's|@localstatedir[@]|$(localstatedir)|g' \
-e 's|@runstatedir[@]|$(runstatedir)|g' \
-e 's|@sysconfdir[@]|$(sysconfdir)|g' \
-e 's|@CURL[@]|$(CURL)|g'
# Files that will be generated by passing their *.in file through
# $(subst).
subst_files = ddclient ddclient.conf
EXTRA_DIST += $(subst_files:=.in)
CLEANFILES += $(subst_files)
@ -36,7 +24,14 @@ $(subst_files): Makefile
rm -f '$@' '$@'.tmp
in='$@'.in; \
test -f "$${in}" || in='$(srcdir)/'$${in}; \
$(subst) "$${in}" >'$@'.tmp && \
sed \
-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|@CURL[@]|$(CURL)|g' \
"$${in}" >'$@'.tmp && \
{ ! test -x "$${in}" || chmod +x '$@'.tmp; }
mv '$@'.tmp '$@'
@ -45,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
@ -62,17 +57,36 @@ AM_PL_LOG_FLAGS = -Mstrict -w \
-I'$(abs_top_srcdir)'/t/lib \
-MDevel::Autoflush
handwritten_tests = \
t/builtinfw_query.pl \
t/check_value.pl \
t/get_ip_from_if.pl \
t/geturl_connectivity.pl \
t/geturl_response.pl \
t/group_hosts_by.pl \
t/header_ok.pl \
t/interval_expired.pl \
t/is-and-extract-ipv4.pl \
t/is-and-extract-ipv6.pl \
t/is-and-extract-ipv6-global.pl \
t/logmsg.pl \
t/parse_assignments.pl \
t/write_cache.pl
t/protocol_directnic.pl \
t/protocol_dnsexit2.pl \
t/protocol_dyndns2.pl \
t/read_recap.pl \
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
generated_tests = \
t/geturl_connectivity.pl \
t/version.pl
TESTS = $(handwritten_tests) $(generated_tests)
$(TESTS): ddclient
EXTRA_DIST += $(handwritten_tests) \
.autom4te.cfg \
t/lib/Devel/Autoflush.pm \
t/lib/Test/Builder.pm \
t/lib/Test/Builder/Formatter.pm \
@ -143,5 +157,9 @@ 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/Logger.pm \
t/lib/ddclient/t/ip.pm \
t/lib/ok.pm

209
README.md
View file

@ -1,9 +1,37 @@
# DDCLIENT
`ddclient` is a Perl client used to update dynamic DNS entries for accounts
`ddclient` is a Perl client used to update dynamic DNS entries for accounts
on many dynamic DNS services. It uses `curl` for internet access.
This is a friendly fork/continuation of https://github.com/ddclient/ddclient
on docker compose
```docker-compose
services:
ddclient:
image: lscr.io/linuxserver/ddclient:latest
container_name: ddclient
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Rome
volumes:
- /home/orangepi/dockerfiles/ddclient/config:/config
restart: unless-stopped
```
file ddclient.conf per servizio DDNS di dynu.com da mettere nel folder config
```file
daemon=60 # 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
pid=/var/run/ddclient/ddclient.pid # record PID in file.
use=web, web=checkip.dynu.com/, web-skip='IP Address'
protocol=dyndns2 # default protocol
server=api.dynu.com
# default login
login=FabioMich66 # your default user
password=Master66! # your default password
wildcard=yes
patachina.casacam.net
```
## Alternatives
@ -15,41 +43,44 @@ your dynamic DNS provider(s): <https://github.com/troglobit/inadyn> or
Dynamic DNS services currently supported include:
* [1984.is](https://www.1984.is/product/freedns)
* [ChangeIP](https://www.changeip.com)
* [CloudFlare](https://www.cloudflare.com)
* [ClouDNS](https://www.cloudns.net)
* [DigitalOcean](https://www.digitalocean.com/)
* [dinahosting](https://dinahosting.com)
* [DonDominio](https://www.dondominio.com)
* [DNS Made Easy](https://dnsmadeeasy.com)
* [DNSExit](https://dnsexit.com/dns/dns-api)
* [domenehsop](https://api.domeneshop.no/docs/#tag/ddns/paths/~1dyndns~1update/get)
* [DslReports](https://www.dslreports.com)
* [Duck DNS](https://duckdns.org)
* [DynDNS.com](https://account.dyn.com)
* [EasyDNS](https://www.easydns.com )
* [Enom](https://www.enom.com)
* [Freedns](https://freedns.afraid.org)
* [Freemyip](https://freemyip.com)
* [Gandi](https://gandi.net)
* [GoDaddy](https://www.godaddy.com)
* [Google](https://domains.google)
* [Infomaniak](https://faq.infomaniak.com/2376)
* [Loopia](https://www.loopia.se)
* [Mythic Beasts](https://www.mythic-beasts.com/support/api/dnsv2/dynamic-dns)
* [NameCheap](https://www.namecheap.com)
* [NearlyFreeSpeech.net](https://www.nearlyfreespeech.net/services/dns)
* [Njalla](https://njal.la/docs/ddns)
* [Noip](https://www.noip.com)
* nsupdate - see nsupdate(1) and ddns-confgen(8)
* [OVH](https://www.ovhcloud.com)
* [Porkbun](https://porkbun.com)
* [regfish.de](https://www.regfish.de/domains/dyndns)
* [Sitelutions](https://www.sitelutions.com)
* [woima.fi](https://woima.fi)
* [Yandex](https://dns.yandex.com)
* [Zoneedit](https://www.zoneedit.com)
* [1984.is](https://www.1984.is/product/freedns)
* [ChangeIP](https://www.changeip.com)
* [CloudFlare](https://www.cloudflare.com)
* [ClouDNS](https://www.cloudns.net)
* [DDNS.fm](https://www.ddns.fm/)
* [DigitalOcean](https://www.digitalocean.com/)
* [dinahosting](https://dinahosting.com)
* [Directnic](https://directnic.com)
* [DonDominio](https://www.dondominio.com)
* [DNS Made Easy](https://dnsmadeeasy.com)
* [DNSExit](https://dnsexit.com/dns/dns-api)
* [dnsHome.de](https://www.dnshome.de)
* [Domeneshop](https://api.domeneshop.no/docs/#tag/ddns/paths/~1dyndns~1update/get)
* [DslReports](https://www.dslreports.com)
* [Duck DNS](https://duckdns.org)
* [DynDNS.com](https://account.dyn.com)
* [EasyDNS](https://www.easydns.com )
* [Enom](https://www.enom.com)
* [Freedns](https://freedns.afraid.org)
* [Freemyip](https://freemyip.com)
* [Gandi](https://gandi.net)
* [GoDaddy](https://www.godaddy.com)
* [Hurricane Electric](https://dns.he.net)
* [Infomaniak](https://faq.infomaniak.com/2376)
* [INWX](https://www.inwx.com/)
* [Loopia](https://www.loopia.se)
* [Mythic Beasts](https://www.mythic-beasts.com/support/api/dnsv2/dynamic-dns)
* [NameCheap](https://www.namecheap.com)
* [NearlyFreeSpeech.net](https://www.nearlyfreespeech.net/services/dns)
* [Njalla](https://njal.la/docs/ddns)
* [Noip](https://www.noip.com)
* nsupdate - see nsupdate(1) and ddns-confgen(8)
* [OVH](https://www.ovhcloud.com)
* [Porkbun](https://porkbun.com)
* [regfish.de](https://www.regfish.de/domains/dyndns)
* [Sitelutions](https://www.sitelutions.com)
* [Yandex](https://dns.yandex.com)
* [Zoneedit](https://www.zoneedit.com)
`ddclient` supports finding your IP address from many cable and DSL
broadband routers.
@ -104,7 +135,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
@ -125,43 +156,97 @@ start the first time by hand
systemctl start ddclient.service
## Known issues
This is a list for quick referencing of known issues. For further details check out the linked issues and the changelog.
Note that any issues prior to version v3.9.1 will not be listed here.
If a fix is committed but not yet part of any tagged release, the notes here will reference the not-yet-released version number.
### v3.11.2 - v3.9.1: SSL parameter breaks HTTP-only IP acquisition
The `ssl` parameter forces all connections to use HTTPS. While technically
working as expected, this behavior keeps coming up as a pain point when using
HTTP-only IP querying sites such as http://checkip.dyndns.org. Starting with
v4.0.0, the behavior is changed to respect `http://` in a URL. A separate
parameter to disallow all HTTP connections or warn about them may be added
later.
**Fix**: v4.0.0 uses HTTP to connect to URLs starting with `http://`. See
[here](https://github.com/ddclient/ddclient/pull/608) for more info.
**Workaround**: Disable the SSL parameter
### v3.10.0: Chunked encoding not corretly supported in IO::Socket HTTP code
Using the IO::Socket HTTP code will break in various ways whenever the server responds using HTTP 1.1 chunked encoding. Refer to [this issue](https://github.com/ddclient/ddclient/issues/548) for more info.
**Fix**: v3.11.0 - IO::Socket has been deprecated there and curl has been made the standard.
**Workaround**: Use curl for transfers by either setting `-curl` in the command line or by adding `curl=yes` in the config
### v3.10.0: Spammed updates to some providers
This issue arises when using the `use` parameter in the config and using one of these providers:
- Cloudflare
- Hetzner
- Digitalocean
- Infomaniak
**Fix**: v3.11.2
**Workaround**: Use the `usev4`/`usev6` parameters instead of `use`.
## TROUBLESHOOTING
1. enable debugging and verbose messages: ``$ ddclient -daemon=0 -debug -verbose -noquiet``
* Enable debugging and verbose messages: `ddclient --daemon=0 --debug --verbose`
2. Do you need to specify a proxy?
If so, just add a ``proxy=your.isp.proxy`` to the ddclient.conf file.
* Do you need to specify a proxy?
If so, just add a `proxy=your.isp.proxy` to the `ddclient.conf` file.
3. Define the IP address of your router with ``fw=xxx.xxx.xxx.xxx`` in
``/etc/ddclient/ddclient.conf`` and then try ``$ ddclient -daemon=0 -query`` to see if the router status web page can be understood.
* Define the IP address of your router with `fwv4=xxx.xxx.xxx.xxx` in
`/etc/ddclient/ddclient.conf` and then try `$ ddclient --daemon=0 --query`
to see if the router status web page can be understood.
4. Need support for another router/firewall?
Define the router status page yourself with: ``fw=url-to-your-router``'s-status-page ``fw-skip=any-string-preceding-your-IP-address``
* Need support for another router/firewall?
Define the router yourself with:
ddclient does something like this to provide builtin support for
common routers.
For example, the Linksys routers could have been added with:
```
usev4=fwv4
fwv4=url-to-your-router-status-page
fwv4-skip="regular expression matching any string preceding your IP address, if necessary"
```
fw=192.168.1.1/Status.htm
fw-skip=WAN.*?IP Address
ddclient does something like this to provide builtin support for common
routers.
For example, the Linksys routers could have been added with:
OR
Send me the output from:
``$ ddclient -geturl {fw-ip-status-url} [-login login [-password password]]``
and I'll add it to the next release!
```
usev4=fwv4
fwv4=192.168.1.1/Status.htm
fwv4-skip=WAN.*?IP Address
```
ie. for my fw/router I used: ``$ ddclient -geturl 192.168.1.254/status.htm``
OR [create a new issue](https://github.com/ddclient/ddclient/issues/new)
containing the output from:
5. Some broadband routers require the use of a password when ddclient
accesses its status page to determine the router's WAN IP address.
If this is the case for your router, add
```
curl --include --location http://url.of.your.firewall/ip-status-page
```
so that we can add a new firewall definition to a future release of
ddclient.
* Some broadband routers require the use of a password when ddclient accesses
its status page to determine the router's WAN IP address.
If this is the case for your router, add
```
fw-login=your-router-login
fw-password=your-router-password
```
to the beginning of your ddclient.conf file.
Note that some routers use either 'root' or 'admin' as their login
while some others accept anything.
to the beginning of your ddclient.conf file.
Note that some routers use either 'root' or 'admin' as their login while
some others accept anything.
## USING DDCLIENT WITH `ppp`
@ -199,7 +284,7 @@ In my case, it is named dhcpcd-eth0.exe and contains the lines:
#!/bin/sh
PATH=/usr/bin:/root/bin:${PATH}
logger -t dhcpcd IP address changed to $1
ddclient -proxy fasthttp.sympatico.ca -wildcard -ip $1 | logger -t ddclient
ddclient --proxy fasthttp.sympatico.ca --wildcard --ip $1 | logger -t ddclient
exit 0
```

26
autogen
View file

@ -7,18 +7,16 @@ fatal() { error "$@"; exit 1; }
try() { "$@" || fatal "'$@' failed"; }
try cd "${0%/*}"
try mkdir -p m4 build-aux
# aclocal complains if a directory passed to AC_CONFIG_MACRO_DIR doesn't exist.
try mkdir -p build-aux/m4
# autoreconf's '--force' option doesn't affect any of the files installed by the '--install' option.
# Remove the files to truly force them to be updated.
try rm -f \
aclocal.m4 \
build-aux/config.guess \
build-aux/config.sub \
build-aux/install-sh \
build-aux/missing \
build-aux/tap-driver.sh \
;
try autoreconf -fviW all
# Ignore changes to build-aux/tap-driver, but only if we're in a clone
# of the ddclient Git repository. Once CentOS 6 and RHEL 6 reach
# end-of-life we can delete build-aux/tap-driver.sh and this block of
# code. (tap-driver.sh is checked in to this Git repository only
# because we want to support all currently maintained CentOS and RHEL
# releases, and CentoOS 6 and RHEL 6 ship with Automake 1.11 which
# does not come with tap-driver.sh.)
command -v git >/dev/null || exit 0
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
cdup=$(try git rev-parse --show-cdup) || exit 1
[ -z "${cdup}" ] || exit 0
try git update-index --assume-unchanged -- build-aux/tap-driver.sh

View file

@ -1,651 +0,0 @@
#! /bin/sh
# Copyright (C) 2011-2020 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# As a special exception to the GNU General Public License, if you
# distribute this file as part of a program that contains a
# configuration script generated by Autoconf, you may include it under
# the same distribution terms that you use for the rest of that program.
# This file is maintained in Automake, please report
# bugs to <bug-automake@gnu.org> or send patches to
# <automake-patches@gnu.org>.
scriptversion=2013-12-23.17; # UTC
# Make unconditional expansion of undefined variables an error. This
# helps a lot in preventing typo-related bugs.
set -u
me=tap-driver.sh
fatal ()
{
echo "$me: fatal: $*" >&2
exit 1
}
usage_error ()
{
echo "$me: $*" >&2
print_usage >&2
exit 2
}
print_usage ()
{
cat <<END
Usage:
tap-driver.sh --test-name=NAME --log-file=PATH --trs-file=PATH
[--expect-failure={yes|no}] [--color-tests={yes|no}]
[--enable-hard-errors={yes|no}] [--ignore-exit]
[--diagnostic-string=STRING] [--merge|--no-merge]
[--comments|--no-comments] [--] TEST-COMMAND
The '--test-name', '-log-file' and '--trs-file' options are mandatory.
END
}
# TODO: better error handling in option parsing (in particular, ensure
# TODO: $log_file, $trs_file and $test_name are defined).
test_name= # Used for reporting.
log_file= # Where to save the result and output of the test script.
trs_file= # Where to save the metadata of the test run.
expect_failure=0
color_tests=0
merge=0
ignore_exit=0
comments=0
diag_string='#'
while test $# -gt 0; do
case $1 in
--help) print_usage; exit $?;;
--version) echo "$me $scriptversion"; exit $?;;
--test-name) test_name=$2; shift;;
--log-file) log_file=$2; shift;;
--trs-file) trs_file=$2; shift;;
--color-tests) color_tests=$2; shift;;
--expect-failure) expect_failure=$2; shift;;
--enable-hard-errors) shift;; # No-op.
--merge) merge=1;;
--no-merge) merge=0;;
--ignore-exit) ignore_exit=1;;
--comments) comments=1;;
--no-comments) comments=0;;
--diagnostic-string) diag_string=$2; shift;;
--) shift; break;;
-*) usage_error "invalid option: '$1'";;
esac
shift
done
test $# -gt 0 || usage_error "missing test command"
case $expect_failure in
yes) expect_failure=1;;
*) expect_failure=0;;
esac
if test $color_tests = yes; then
init_colors='
color_map["red"]="" # Red.
color_map["grn"]="" # Green.
color_map["lgn"]="" # Light green.
color_map["blu"]="" # Blue.
color_map["mgn"]="" # Magenta.
color_map["std"]="" # No color.
color_for_result["ERROR"] = "mgn"
color_for_result["PASS"] = "grn"
color_for_result["XPASS"] = "red"
color_for_result["FAIL"] = "red"
color_for_result["XFAIL"] = "lgn"
color_for_result["SKIP"] = "blu"'
else
init_colors=''
fi
# :; is there to work around a bug in bash 3.2 (and earlier) which
# does not always set '$?' properly on redirection failure.
# See the Autoconf manual for more details.
:;{
(
# Ignore common signals (in this subshell only!), to avoid potential
# problems with Korn shells. Some Korn shells are known to propagate
# to themselves signals that have killed a child process they were
# waiting for; this is done at least for SIGINT (and usually only for
# it, in truth). Without the `trap' below, such a behaviour could
# cause a premature exit in the current subshell, e.g., in case the
# test command it runs gets terminated by a SIGINT. Thus, the awk
# script we are piping into would never seen the exit status it
# expects on its last input line (which is displayed below by the
# last `echo $?' statement), and would thus die reporting an internal
# error.
# For more information, see the Autoconf manual and the threads:
# <https://lists.gnu.org/archive/html/bug-autoconf/2011-09/msg00004.html>
# <http://mail.opensolaris.org/pipermail/ksh93-integration-discuss/2009-February/004121.html>
trap : 1 3 2 13 15
if test $merge -gt 0; then
exec 2>&1
else
exec 2>&3
fi
"$@"
echo $?
) | LC_ALL=C ${AM_TAP_AWK-awk} \
-v me="$me" \
-v test_script_name="$test_name" \
-v log_file="$log_file" \
-v trs_file="$trs_file" \
-v expect_failure="$expect_failure" \
-v merge="$merge" \
-v ignore_exit="$ignore_exit" \
-v comments="$comments" \
-v diag_string="$diag_string" \
'
# TODO: the usages of "cat >&3" below could be optimized when using
# GNU awk, and/on on systems that supports /dev/fd/.
# Implementation note: in what follows, `result_obj` will be an
# associative array that (partly) simulates a TAP result object
# from the `TAP::Parser` perl module.
## ----------- ##
## FUNCTIONS ##
## ----------- ##
function fatal(msg)
{
print me ": " msg | "cat >&2"
exit 1
}
function abort(where)
{
fatal("internal error " where)
}
# Convert a boolean to a "yes"/"no" string.
function yn(bool)
{
return bool ? "yes" : "no";
}
function add_test_result(result)
{
if (!test_results_index)
test_results_index = 0
test_results_list[test_results_index] = result
test_results_index += 1
test_results_seen[result] = 1;
}
# Whether the test script should be re-run by "make recheck".
function must_recheck()
{
for (k in test_results_seen)
if (k != "XFAIL" && k != "PASS" && k != "SKIP")
return 1
return 0
}
# Whether the content of the log file associated to this test should
# be copied into the "global" test-suite.log.
function copy_in_global_log()
{
for (k in test_results_seen)
if (k != "PASS")
return 1
return 0
}
function get_global_test_result()
{
if ("ERROR" in test_results_seen)
return "ERROR"
if ("FAIL" in test_results_seen || "XPASS" in test_results_seen)
return "FAIL"
all_skipped = 1
for (k in test_results_seen)
if (k != "SKIP")
all_skipped = 0
if (all_skipped)
return "SKIP"
return "PASS";
}
function stringify_result_obj(result_obj)
{
if (result_obj["is_unplanned"] || result_obj["number"] != testno)
return "ERROR"
if (plan_seen == LATE_PLAN)
return "ERROR"
if (result_obj["directive"] == "TODO")
return result_obj["is_ok"] ? "XPASS" : "XFAIL"
if (result_obj["directive"] == "SKIP")
return result_obj["is_ok"] ? "SKIP" : COOKED_FAIL;
if (length(result_obj["directive"]))
abort("in function stringify_result_obj()")
return result_obj["is_ok"] ? COOKED_PASS : COOKED_FAIL
}
function decorate_result(result)
{
color_name = color_for_result[result]
if (color_name)
return color_map[color_name] "" result "" color_map["std"]
# If we are not using colorized output, or if we do not know how
# to colorize the given result, we should return it unchanged.
return result
}
function report(result, details)
{
if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/)
{
msg = ": " test_script_name
add_test_result(result)
}
else if (result == "#")
{
msg = " " test_script_name ":"
}
else
{
abort("in function report()")
}
if (length(details))
msg = msg " " details
# Output on console might be colorized.
print decorate_result(result) msg
# Log the result in the log file too, to help debugging (this is
# especially true when said result is a TAP error or "Bail out!").
print result msg | "cat >&3";
}
function testsuite_error(error_message)
{
report("ERROR", "- " error_message)
}
function handle_tap_result()
{
details = result_obj["number"];
if (length(result_obj["description"]))
details = details " " result_obj["description"]
if (plan_seen == LATE_PLAN)
{
details = details " # AFTER LATE PLAN";
}
else if (result_obj["is_unplanned"])
{
details = details " # UNPLANNED";
}
else if (result_obj["number"] != testno)
{
details = sprintf("%s # OUT-OF-ORDER (expecting %d)",
details, testno);
}
else if (result_obj["directive"])
{
details = details " # " result_obj["directive"];
if (length(result_obj["explanation"]))
details = details " " result_obj["explanation"]
}
report(stringify_result_obj(result_obj), details)
}
# `skip_reason` should be empty whenever planned > 0.
function handle_tap_plan(planned, skip_reason)
{
planned += 0 # Avoid getting confused if, say, `planned` is "00"
if (length(skip_reason) && planned > 0)
abort("in function handle_tap_plan()")
if (plan_seen)
{
# Error, only one plan per stream is acceptable.
testsuite_error("multiple test plans")
return;
}
planned_tests = planned
# The TAP plan can come before or after *all* the TAP results; we speak
# respectively of an "early" or a "late" plan. If we see the plan line
# after at least one TAP result has been seen, assume we have a late
# plan; in this case, any further test result seen after the plan will
# be flagged as an error.
plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN)
# If testno > 0, we have an error ("too many tests run") that will be
# automatically dealt with later, so do not worry about it here. If
# $plan_seen is true, we have an error due to a repeated plan, and that
# has already been dealt with above. Otherwise, we have a valid "plan
# with SKIP" specification, and should report it as a particular kind
# of SKIP result.
if (planned == 0 && testno == 0)
{
if (length(skip_reason))
skip_reason = "- " skip_reason;
report("SKIP", skip_reason);
}
}
function extract_tap_comment(line)
{
if (index(line, diag_string) == 1)
{
# Strip leading `diag_string` from `line`.
line = substr(line, length(diag_string) + 1)
# And strip any leading and trailing whitespace left.
sub("^[ \t]*", "", line)
sub("[ \t]*$", "", line)
# Return what is left (if any).
return line;
}
return "";
}
# When this function is called, we know that line is a TAP result line,
# so that it matches the (perl) RE "^(not )?ok\b".
function setup_result_obj(line)
{
# Get the result, and remove it from the line.
result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0)
sub("^(not )?ok[ \t]*", "", line)
# If the result has an explicit number, get it and strip it; otherwise,
# automatically assing the next progresive number to it.
if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/)
{
match(line, "^[0-9]+")
# The final `+ 0` is to normalize numbers with leading zeros.
result_obj["number"] = substr(line, 1, RLENGTH) + 0
line = substr(line, RLENGTH + 1)
}
else
{
result_obj["number"] = testno
}
if (plan_seen == LATE_PLAN)
# No further test results are acceptable after a "late" TAP plan
# has been seen.
result_obj["is_unplanned"] = 1
else if (plan_seen && testno > planned_tests)
result_obj["is_unplanned"] = 1
else
result_obj["is_unplanned"] = 0
# Strip trailing and leading whitespace.
sub("^[ \t]*", "", line)
sub("[ \t]*$", "", line)
# This will have to be corrected if we have a "TODO"/"SKIP" directive.
result_obj["description"] = line
result_obj["directive"] = ""
result_obj["explanation"] = ""
if (index(line, "#") == 0)
return # No possible directive, nothing more to do.
# Directives are case-insensitive.
rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*"
# See whether we have the directive, and if yes, where.
pos = match(line, rx "$")
if (!pos)
pos = match(line, rx "[^a-zA-Z0-9_]")
# If there was no TAP directive, we have nothing more to do.
if (!pos)
return
# Let`s now see if the TAP directive has been escaped. For example:
# escaped: ok \# SKIP
# not escaped: ok \\# SKIP
# escaped: ok \\\\\# SKIP
# not escaped: ok \ # SKIP
if (substr(line, pos, 1) == "#")
{
bslash_count = 0
for (i = pos; i > 1 && substr(line, i - 1, 1) == "\\"; i--)
bslash_count += 1
if (bslash_count % 2)
return # Directive was escaped.
}
# Strip the directive and its explanation (if any) from the test
# description.
result_obj["description"] = substr(line, 1, pos - 1)
# Now remove the test description from the line, that has been dealt
# with already.
line = substr(line, pos)
# Strip the directive, and save its value (normalized to upper case).
sub("^[ \t]*#[ \t]*", "", line)
result_obj["directive"] = toupper(substr(line, 1, 4))
line = substr(line, 5)
# Now get the explanation for the directive (if any), with leading
# and trailing whitespace removed.
sub("^[ \t]*", "", line)
sub("[ \t]*$", "", line)
result_obj["explanation"] = line
}
function get_test_exit_message(status)
{
if (status == 0)
return ""
if (status !~ /^[1-9][0-9]*$/)
abort("getting exit status")
if (status < 127)
exit_details = ""
else if (status == 127)
exit_details = " (command not found?)"
else if (status >= 128 && status <= 255)
exit_details = sprintf(" (terminated by signal %d?)", status - 128)
else if (status > 256 && status <= 384)
# We used to report an "abnormal termination" here, but some Korn
# shells, when a child process die due to signal number n, can leave
# in $? an exit status of 256+n instead of the more standard 128+n.
# Apparently, both behaviours are allowed by POSIX (2008), so be
# prepared to handle them both. See also Austing Group report ID
# 0000051 <http://www.austingroupbugs.net/view.php?id=51>
exit_details = sprintf(" (terminated by signal %d?)", status - 256)
else
# Never seen in practice.
exit_details = " (abnormal termination)"
return sprintf("exited with status %d%s", status, exit_details)
}
function write_test_results()
{
print ":global-test-result: " get_global_test_result() > trs_file
print ":recheck: " yn(must_recheck()) > trs_file
print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file
for (i = 0; i < test_results_index; i += 1)
print ":test-result: " test_results_list[i] > trs_file
close(trs_file);
}
BEGIN {
## ------- ##
## SETUP ##
## ------- ##
'"$init_colors"'
# Properly initialized once the TAP plan is seen.
planned_tests = 0
COOKED_PASS = expect_failure ? "XPASS": "PASS";
COOKED_FAIL = expect_failure ? "XFAIL": "FAIL";
# Enumeration-like constants to remember which kind of plan (if any)
# has been seen. It is important that NO_PLAN evaluates "false" as
# a boolean.
NO_PLAN = 0
EARLY_PLAN = 1
LATE_PLAN = 2
testno = 0 # Number of test results seen so far.
bailed_out = 0 # Whether a "Bail out!" directive has been seen.
# Whether the TAP plan has been seen or not, and if yes, which kind
# it is ("early" is seen before any test result, "late" otherwise).
plan_seen = NO_PLAN
## --------- ##
## PARSING ##
## --------- ##
is_first_read = 1
while (1)
{
# Involutions required so that we are able to read the exit status
# from the last input line.
st = getline
if (st < 0) # I/O error.
fatal("I/O error while reading from input stream")
else if (st == 0) # End-of-input
{
if (is_first_read)
abort("in input loop: only one input line")
break
}
if (is_first_read)
{
is_first_read = 0
nextline = $0
continue
}
else
{
curline = nextline
nextline = $0
$0 = curline
}
# Copy any input line verbatim into the log file.
print | "cat >&3"
# Parsing of TAP input should stop after a "Bail out!" directive.
if (bailed_out)
continue
# TAP test result.
if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/)
{
testno += 1
setup_result_obj($0)
handle_tap_result()
}
# TAP plan (normal or "SKIP" without explanation).
else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/)
{
# The next two lines will put the number of planned tests in $0.
sub("^1\\.\\.", "")
sub("[^0-9]*$", "")
handle_tap_plan($0, "")
continue
}
# TAP "SKIP" plan, with an explanation.
else if ($0 ~ /^1\.\.0+[ \t]*#/)
{
# The next lines will put the skip explanation in $0, stripping
# any leading and trailing whitespace. This is a little more
# tricky in truth, since we want to also strip a potential leading
# "SKIP" string from the message.
sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "")
sub("[ \t]*$", "");
handle_tap_plan(0, $0)
}
# "Bail out!" magic.
# Older versions of prove and TAP::Harness (e.g., 3.17) did not
# recognize a "Bail out!" directive when preceded by leading
# whitespace, but more modern versions (e.g., 3.23) do. So we
# emulate the latter, "more modern" behaviour.
else if ($0 ~ /^[ \t]*Bail out!/)
{
bailed_out = 1
# Get the bailout message (if any), with leading and trailing
# whitespace stripped. The message remains stored in `$0`.
sub("^[ \t]*Bail out![ \t]*", "");
sub("[ \t]*$", "");
# Format the error message for the
bailout_message = "Bail out!"
if (length($0))
bailout_message = bailout_message " " $0
testsuite_error(bailout_message)
}
# Maybe we have too look for dianogtic comments too.
else if (comments != 0)
{
comment = extract_tap_comment($0);
if (length(comment))
report("#", comment);
}
}
## -------- ##
## FINISH ##
## -------- ##
# A "Bail out!" directive should cause us to ignore any following TAP
# error, as well as a non-zero exit status from the TAP producer.
if (!bailed_out)
{
if (!plan_seen)
{
testsuite_error("missing test plan")
}
else if (planned_tests != testno)
{
bad_amount = testno > planned_tests ? "many" : "few"
testsuite_error(sprintf("too %s tests run (expected %d, got %d)",
bad_amount, planned_tests, testno))
}
if (!ignore_exit)
{
# Fetch exit status from the last line.
exit_message = get_test_exit_message(nextline)
if (exit_message)
testsuite_error(exit_message)
}
}
write_test_results()
exit 0
} # End of "BEGIN" block.
'
# TODO: document that we consume the file descriptor 3 :-(
} 3>"$log_file"
test $? -eq 0 || fatal "I/O or internal error"
# Local Variables:
# mode: shell-script
# sh-indentation: 2
# eval: (add-hook 'before-save-hook 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-time-zone: "UTC0"
# time-stamp-end: "; # UTC"
# End:

View file

@ -1,8 +1,17 @@
AC_PREREQ([2.63])
AC_INIT([ddclient], [3.11.1])
# Get the version from ddclient.in so that the same version string
# doesn't have to be maintained in two places. The m4_dquote macro is
# used instead of quote characters to ensure that the command is only
# run once. The command outputs quote characters to prevent
# incidental expansion (the m4_esyscmd macro does not quote the
# command output itself, so the command output is subject to
# expansion).
AC_INIT([ddclient], m4_dquote(m4_esyscmd([printf '[%s]' "$(./ddclient.in --version=short)"])))
# Needed because of the above invocation of ddclient.in.
AC_SUBST([CONFIGURE_DEPENDENCIES], ['$(top_srcdir)/ddclient.in'])
AC_CONFIG_SRCDIR([ddclient.in])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_MACRO_DIR([build-aux/m4])
AC_REQUIRE_AUX_FILE([tap-driver.sh])
# If the automake dependency is bumped to v1.12 or newer, remove
# build-aux/tap-driver.sh from the repository. Automake 1.12+ comes
@ -14,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.
@ -27,7 +48,18 @@ AC_PROG_MKDIR_P
AC_PATH_PROG([FIND], [find])
AS_IF([test -z "${FIND}"], [AC_MSG_ERROR(['find' utility not found])])
AC_PATH_PROG([CURL], [curl])
AC_ARG_WITH([curl],
[AS_HELP_STRING([[--with-curl[=CURL]]], [use CURL as absolute path to curl executable])],
[],
[with_curl=yes])
AS_CASE([${with_curl}],
[[yes]], [AC_PATH_PROG([CURL], [curl])],
[[no]], [CURL=],
[
AC_MSG_CHECKING([for curl])
CURL=${with_curl}
AC_MSG_RESULT([${CURL}])
]);
AS_IF([test -z "${CURL}"], [AC_MSG_ERROR([curl not found])])
AX_WITH_PROG([PERL], perl)
@ -40,6 +72,7 @@ AC_SUBST([PERL])
# package doesn't depend on all of them, so their availability can't
# be assumed.
m4_foreach_w([_m], [
Data::Dumper
File::Basename
File::Path
File::Temp
@ -54,9 +87,12 @@ m4_foreach_w([_m], [
# then some tests will fail. Only prints a warning if not installed.
m4_foreach_w([_m], [
B
Data::Dumper
Exporter
File::Spec::Functions
File::Temp
List::Util
Scalar::Util
re
], [AX_PROG_PERL_MODULES([_m], [],
[AC_MSG_WARN([some tests will fail due to missing module _m])])])
@ -65,24 +101,23 @@ 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
HTTP::Request
HTTP::Response
Scalar::Util
JSON::PP
Test::MockModule
Test::TCP
Test::Warnings
Time::HiRes
URI
parent
], [AX_PROG_PERL_MODULES([_m], [],
[AC_MSG_WARN([some tests may be skipped due to missing module _m])])])
AC_CONFIG_FILES([
Makefile
t/geturl_connectivity.pl
t/version.pl
])
AC_OUTPUT

View file

@ -1,12 +1,12 @@
######################################################################
##
## Define default global variables with lines like:
## var=value [, var=value]*
## var=value [, var=value]*
## These values will be used for each following host unless overridden
## with a local variable definition.
##
## Define local variables for one or more hosts with:
## var=value [, var=value]* host.and.domain[,host2.and.domain...]
## var=value [, var=value]* host.and.domain[,host2.and.domain...]
##
## Lines can be continued on the following line by ending the line
## with a \
@ -16,37 +16,47 @@
## are mentioned here.
##
######################################################################
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
pid=@runstatedir@/ddclient.pid # record PID in file.
ssl=yes # use ssl-support. Works with
# ssl-library
# postscript=script # run script after updating. The
# new IP is added as argument.
## Use encryption (TLS) when the scheme (either "http://" or "https://") is
## missing from a URL. Defaults to "yes".
#ssl=yes
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.
#
#use=watchguard-soho, fw=192.168.111.1:80 # via Watchguard's SOHO FW
#use=netopia-r910, fw=192.168.111.1:80 # via Netopia R910 FW
#use=smc-barricade, fw=192.168.123.254:80 # via SMC's Barricade FW
#use=netgear-rt3xx, fw=192.168.0.1:80 # via Netgear's internet FW
#use=linksys, fw=192.168.1.1:80 # via Linksys's internet FW
#use=maxgate-ugate3x00, fw=192.168.0.1:80 # via MaxGate's UGATE-3x00 FW
#use=elsa-lancom-dsl10, fw=10.0.0.254:80 # via ELSA LanCom DSL/10 DSL Router
#use=elsa-lancom-dsl10-ch01, fw=10.0.0.254:80 # via ELSA LanCom DSL/10 DSL Router
#use=elsa-lancom-dsl10-ch02, fw=10.0.0.254:80 # via ELSA LanCom DSL/10 DSL Router
#use=watchguard-soho, fw=192.168.111.1:80 # via Watchguard's SOHO FW
#use=netopia-r910, fw=192.168.111.1:80 # via Netopia R910 FW
#use=smc-barricade, fw=192.168.123.254:80 # via SMC's Barricade FW
#use=netgear-rt3xx, fw=192.168.0.1:80 # via Netgear's internet FW
#use=linksys, fw=192.168.1.1:80 # via Linksys's internet FW
#use=maxgate-ugate3x00, fw=192.168.0.1:80 # via MaxGate's UGATE-3x00 FW
#use=elsa-lancom-dsl10, fw=10.0.0.254:80 # via ELSA LanCom DSL/10 DSL Router
#use=elsa-lancom-dsl10-ch01, fw=10.0.0.254:80 # via ELSA LanCom DSL/10 DSL Router
#use=elsa-lancom-dsl10-ch02, fw=10.0.0.254:80 # via ELSA LanCom DSL/10 DSL Router
#use=alcatel-stp, fw=10.0.0.138:80 # via Alcatel Speed Touch Pro
#use=xsense-aero, fw=192.168.1.1:80 # via Xsense Aero Router
#use=allnet-1298, fw=192.168.1.1:80 # via AllNet 1298 DSL Router
#use=3com-oc-remote812, fw=192.168.0.254:80 # via 3com OfficeConnect Remote 812
#use=3com-oc-remote812, fw=192.168.0.254:80 # via 3com OfficeConnect Remote 812
#use=e-tech, fw=192.168.1.1:80 # via E-tech Router
#use=cayman-3220h, fw=192.168.0.1:1080 # via Cayman 3220-H DSL Router
#
#fw-login=admin, fw-password=XXXXXX # FW login and password
#fw-login=admin, fw-password=XXXXXX # FW login and password
#
## To obtain an IP address from FW status page (using fw-login, fw-password)
#use=fw, fw=192.168.1.254/status.htm, fw-skip='IP Address' # found after IP Address
#
## To obtain an IP address via UPnP from router
## Requires miniupnpc to be installed on the system.
#use=cmd, cmd=external-ip
#
## To obtain an IP address from Web status page (using the proxy if defined)
## by default, checkip.dyndns.org is used if you use the dyndns protocol.
## Using use=web is enough to get it working.
@ -54,75 +64,55 @@ ssl=yes # use ssl-support. Works with
## get banned from their service.
#use=web, web=checkip.dyndns.org/, web-skip='IP Address' # found after IP Address
#
#use=ip, ip=127.0.0.1 # via static IP's
#use=if, if=eth0 # via interfaces
#use=web # via web
#use=ip, ip=127.0.0.1 # via static IP's
#use=if, if=eth0 # via interfaces
#use=web # via web
#
#protocol=dyndns2 # default protocol
#proxy=fasthttp.sympatico.ca:80 # default proxy
#server=members.dyndns.org # default server
#server=members.dyndns.org:8245 # default server (bypassing proxies)
#protocol=dyndns2 # default protocol
#proxy=fasthttp.sympatico.ca:80 # default proxy
#server=members.dyndns.org # default server
#server=members.dyndns.org:8245 # default server (bypassing proxies)
#login=your-login # default login
#password=test # default password
#mx=mx.for.your.host # default MX
#backupmx=yes|no # host is primary MX?
#wildcard=yes|no # add wildcard CNAME?
#login=your-login # default login
#password=test # default password
#mx=mx.for.your.host # default MX
#backupmx=yes|no # host is primary MX?
#wildcard=yes|no # add wildcard CNAME?
##
## dyndns.org dynamic addresses
##
## (supports variables: wildcard,mx,backupmx)
##
# server=members.dyndns.org, \
# protocol=dyndns2 \
# server=members.dyndns.org, \
# protocol=dyndns2 \
# your-dynamic-host.dyndns.org
##
## dyndns.org static addresses
##
## (supports variables: wildcard,mx,backupmx)
##
# static=yes, \
# server=members.dyndns.org, \
# protocol=dyndns2 \
# your-static-host.dyndns.org
##
## dyndns.org custom addresses
##
## (supports variables: wildcard,mx,backupmx)
##
# custom=yes, \
# server=members.dyndns.org, \
# protocol=dyndns2 \
# your-domain.top-level,your-other-domain.top-level
##
## ZoneEdit (zoneedit.com)
##
# server=dynamic.zoneedit.com, \
# protocol=zoneedit1, \
# login=your-zoneedit-login, \
# password=your-zoneedit-password \
# server=dynamic.zoneedit.com, \
# protocol=zoneedit1, \
# login=your-zoneedit-login, \
# password=your-zoneedit-password \
# your.any.domain,your-2nd.any.dom
##
## EasyDNS (easydns.com)
##
# server=members.easydns.com, \
# protocol=easydns, \
# login=your-easydns-login, \
# password=your-easydns-password \
# server=members.easydns.com, \
# protocol=easydns, \
# login=your-easydns-login, \
# password=your-easydns-password \
# your.any.domain,your-2nd.any.domain
##
## dslreports.com dynamic-host monitoring
##
# server=members.dslreports.com \
# protocol=dslreports1, \
# login=dslreports-login, \
# password=dslreports-password \
# server=members.dslreports.com \
# protocol=dslreports1, \
# login=dslreports-login, \
# password=dslreports-password \
# dslreports-unique-id
##
@ -130,16 +120,16 @@ ssl=yes # use ssl-support. Works with
##
# use=web, web=members.orgdns.org/nic/ip
# protocol=dyndns2
# server=www.orgdns.org \
# login=yourLoginName \
# password=yourPassword \
# server=www.orgdns.org \
# login=yourLoginName \
# password=yourPassword \
# yourSubdomain.orgdns.org
##
## NameCheap (namecheap.com)
##
# protocol=namecheap, \
# server=dynamicdns.park-your-domain.com, \
# server=dynamicdns.park-your-domain.com, \
# login=example.com, \
# password=example.com-password \
# subdomain.example.com
@ -147,10 +137,10 @@ ssl=yes # use ssl-support. Works with
##
## 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
##
@ -171,7 +161,7 @@ ssl=yes # use ssl-support. Works with
# ssl=yes, \
# server=dynupdate.no-ip.com, \
# login=your-noip-login, \
# password=your-noip-password, \
# password=your-noip-password \
# your-host.domain.com, your-2nd-host.domain.com
##
@ -186,30 +176,40 @@ ssl=yes # use ssl-support. Works with
##
## CloudFlare (www.cloudflare.com)
##
#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" (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
# 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" (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
##
## Gandi (gandi.net)
##
## Single host update
# protocol=gandi, \
# zone=example.com, \
# password=my-gandi-api-key, \
# ttl=3h \
# protocol=gandi
# zone=example.com
# password=my-gandi-access-token
# use-personal-access-token=yes
# ttl=10800 # optional
# myhost.example.com
##
## Google Domains (www.google.com/domains)
## GoDaddy (godaddy.com)
##
# protocol=googledomains,
# login=my-auto-generated-username,
# password=my-auto-generated-password
# my.domain.tld, otherhost.domain.tld
# protocol=godaddy, \
# password=my-godaddy-api-key, \
# password=my-godaddy-secret, \
# ttl=600 \
# zone=example.com, \
# myhost.example.com,nexthost.example.com
##
## Hurricane Electric (dns.he.net)
##
# protocol=he.net, \
# password=my-genereated-password \
# myhost.example.com
##
## Duckdns (http://www.duckdns.org/)
@ -227,6 +227,14 @@ ssl=yes # use ssl-support. Works with
# password=my-token
# myhost
##
## DDNS.FM (https://ddns.fm/)
##
#
# protocol=ddns.fm,
# password=my-token
# myhost.example.com
##
## MyOnlinePortal (http://myonlineportal.net)
##
@ -243,23 +251,23 @@ ssl=yes # use ssl-support. Works with
##
## nsupdate.info IPV4(https://www.nsupdate.info)
##
#use=web, web=http://ipv4.nsupdate.info/myip
#protocol=dyndns2
#server=ipv4.nsupdate.info
#login=domain.nsupdate.info
#password='123'
#domain.nsupdate.info
# use=web, web=http://ipv4.nsupdate.info/myip
# protocol=dyndns2
# server=ipv4.nsupdate.info
# login=domain.nsupdate.info
# password='123'
# domain.nsupdate.info
##
## nsupdate.info IPV6 (https://www.nsupdate.info)
## ddclient releases <= 3.8.1 do not support IPv6
##
#usev6=if, if=eth0
#protocol=dyndns2
#server=ipv6.nsupdate.info
#login=domain.nsupdate.info
#password='123'
#domain.nsupdate.info
# usev6=if, if=eth0
# protocol=dyndns2
# server=ipv6.nsupdate.info
# login=domain.nsupdate.info
# password='123'
# domain.nsupdate.info
##
## Yandex.Mail for Domain (domain.yandex.com)
@ -291,8 +299,9 @@ ssl=yes # use ssl-support. Works with
# protocol=porkbun
# apikey=APIKey
# secretapikey=SecretAPIKey
# root-domain=example.com
# host.example.com,host2.sub.example.com
# on-root-domain=yes example.com,sub.example.com
# example.com,sub.example.com
##
## ClouDNS (https://www.cloudns.net)
@ -312,17 +321,17 @@ ssl=yes # use ssl-support. Works with
##
## dnsexit (www.dnsexit.com)
##
#protocol=dnsexit, \
#login=myusername, \
#password=mypassword, \
#subdomain-1.domain.com,subdomain-2.domain.com
# protocol=dnsexit, \
# login=myusername, \
# password=mypassword, \
# subdomain-1.domain.com,subdomain-2.domain.com
##
## dnsexit2 (API method www.dnsexit.com)
##
#protocol=dnsexit2
#password=MyAPIKey
#subdomain-1.domain.com,subdomain-2.domain.com
# protocol=dnsexit2
# password=MyAPIKey
# subdomain-1.domain.com,subdomain-2.domain.com
##
## domeneshop (www.domeneshop.no)
@ -358,10 +367,18 @@ ssl=yes # use ssl-support. Works with
##
## DigitalOcean (www.digitalocean.com)
##
#protocol=digitalocean, \
#zone=example.com, \
#password=api-token \
#example.com,sub.example.com
# protocol=digitalocean, \
# zone=example.com, \
# password=api-token \
# example.com,sub.example.com
##
## Directnic (directnic.com)
##
# protocol=directnic,
# urlv4=https://directnic.com/dns/gateway/ipv4_token/
# urlv6=https://directnic.com/dns/gateway/ipv6_token/
# my-domain.com
##
## Infomaniak (www.infomaniak.com)
@ -370,3 +387,35 @@ ssl=yes # use ssl-support. Works with
# login=ddns_username,
# password=ddns_password
# example.com
#
# N.B. the infomaniak protocol is obsolete. Please use dyndns2 instead:
#
# protocol=dyndns2,
# use=web, web=infomaniak.com/ip.php/
# login=ddns_username,
# password=ddns_password
# redirect=2
# example.com
##
## Email Only
##
# protocol=emailonly
# host.example.com
##
## dnsHome.de
##
# protocol=dyndns2 \
# server=www.dnshome.de \
# login=subdomain.domain.tld \
# password=your_password \
# subdomain.domain.tld
##
## INWX
##
# protocol=inwx \
# login=my-inwx-DynDNS-account-username \
# password=my-inwx-DynDNS-account-password \
# myhost.example.org

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,39 @@
# Provider implementations
Author: [@LenardHess](https://github.com/LenardHess/)\
Date: 2023-11-23
This document is meant to detail the mechanisms that provider implementation shall use. It differentiates between new and legacy provider implementations. The former are adhering to the IPv6 support updates being done to ddclient, the legacy ones are from before that update.
## New provider Implementation
1. Grab the IP(s) from $config{$host}{'wantipv4'} and/or $config{$host}{'wantipv6'}
2. Optional: Query the provider for the current IP record(s). If they are already good, skip updating IP record(s)
3. Update the IP record(s).
4. If successful (or if the records were already good):
- Set 'status-ipv4' and/or 'status-ipv6' to 'good'
- Set 'ipv4' and/or 'ipv6' to the IP that has been set
- Set 'mtime' to the current time
5. If not successful:
- Set 'status-ipv4' and/or 'status-ipv6' to an error message
- Set 'atime' to the current time
The new provider implementation should not set 'status' nor 'ip'. They're part of the legacy infrastructure and ddclient will take care of setting them correctly.
## Legacy provider implementations
1. Grab the IP from $config{$host}{'wantip'}
2. Optional: Query the provider for the current IP record. If it is already good, skip updating IP record
3. Update the IP record.
4. If successful (or if the record was already good):
- Set 'status' to 'good'
- Set 'ip' to the IP that has been set
- Set 'mtime' to the current time
5. If not successful:
- Set 'status' to an error message
- Set 'atime' to the current time
# ToDo
- Decide/Inquire whether services prefer querying the IP first. Then decide whether to make it mandatory.
- Write guidelines on checking existing records (i.e. check TTL as well?).
- Start a list of providers and their implementation state
- Add more details to this document
- Whether 'wantip*' ought to be deleted when read or not.

View file

@ -10,7 +10,3 @@
## force an update twice a month (only if you are not using daemon-mode)
##
## 30 23 1,15 * * root /usr/bin/ddclient -daemon=0 -syslog -quiet -force
######################################################################
## retry failed updates every hour (only if you are not using daemon-mode)
##
## 0 * * * * root /usr/bin/ddclient -daemon=0 -syslog -quiet retry

View file

@ -4,9 +4,10 @@ Wants=network-online.target
After=network-online.target nss-lookup.target
[Service]
Type=forking
PIDFile=/run/ddclient.pid
ExecStart=/usr/bin/ddclient
Type=exec
Environment=daemon_interval=5m
ExecStart=/usr/bin/ddclient --daemon ${daemon_interval} --foreground
Restart=on-failure
[Install]
WantedBy=multi-user.target

169
t/builtinfw_query.pl Normal file
View file

@ -0,0 +1,169 @@
use Test::More;
BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } }
BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); }
sub setbuiltinfw {
my ($fw) = @_;
no warnings 'once';
$ddclient::builtinfw{$fw->{name}} = $fw;
%ddclient::ip_strategies = ddclient::builtinfw_strategy($fw->{name});
%ddclient::ipv4_strategies = ddclient::builtinfwv4_strategy($fw->{name});
%ddclient::ipv6_strategies = ddclient::builtinfwv6_strategy($fw->{name});
}
my @gotcalls;
my $skip_test_fw = 't/builtinfw_query.pl skip test';
setbuiltinfw({
name => $skip_test_fw,
query => sub { return '192.0.2.1 skip1 192.0.2.2 skip2 192.0.2.3'; },
queryv4 => sub { return '192.0.2.4 skip1 192.0.2.5 skip3 192.0.2.6'; },
queryv6 => sub { return '2001:db8::1 skip1 2001:db8::2 skip4 2001:db8::3'; },
});
my @skip_test_cases = (
{
desc => 'query',
getip => \&ddclient::get_ip,
useopt => 'use',
cfgxtra => {},
want => '192.0.2.2',
},
{
desc => 'queryv4',
getip => \&ddclient::get_ipv4,
useopt => 'usev4',
cfgxtra => {'fwv4-skip' => 'skip3'},
want => '192.0.2.6',
},
{
desc => 'queryv4 with fw-skip fallback',
getip => \&ddclient::get_ipv4,
useopt => 'usev4',
cfgxtra => {},
want => '192.0.2.5',
},
{
desc => 'queryv6',
getip => \&ddclient::get_ipv6,
useopt => 'usev6',
cfgxtra => {'fwv6-skip' => 'skip4'},
want => '2001:db8::3',
},
{
# Support for --usev6=<builtin> wasn't added until after --fwv6-skip was added, so fallback
# to the deprecated --fw-skip option was never needed.
desc => 'queryv6 ignores fw-skip',
getip => \&ddclient::get_ipv6,
useopt => 'usev6',
cfgxtra => {},
want => '2001:db8::1',
},
);
for my $tc (@skip_test_cases) {
my $h = "t/builtinfw_query.pl $tc->{desc}";
$ddclient::config{$h} = {
$tc->{useopt} => $skip_test_fw,
'fw-skip' => 'skip1',
%{$tc->{cfgxtra}},
};
my $got = $tc->{getip}(ddclient::strategy_inputs($tc->{useopt}, $h));
is($got, $tc->{want}, $tc->{desc});
}
my $default_inputs_fw = 't/builtinfw_query.pl default inputs';
setbuiltinfw({
name => $default_inputs_fw,
query => sub { my %p = @_; push(@gotcalls, \%p); return '192.0.2.1'; },
queryv4 => sub { my %p = @_; push(@gotcalls, \%p); return '192.0.2.2'; },
queryv6 => sub { my %p = @_; push(@gotcalls, \%p); return '2001:db8::1'; },
});
my @default_inputs_test_cases = (
{
desc => 'use with default inputs',
getip => \&ddclient::get_ip,
useopt => 'use',
want => {use => $default_inputs_fw, fw => 'server', 'fw-skip' => 'skip',
'fw-login' => 'login', 'fw-password' => 'password', 'fw-ssl-validate' => 1},
},
{
desc => 'usev4 with default inputs',
getip => \&ddclient::get_ipv4,
useopt => 'usev4',
want => {usev4 => $default_inputs_fw, fwv4 => 'serverv4', fw => 'server',
'fwv4-skip' => 'skipv4', 'fw-skip' => 'skip', 'fw-login' => 'login',
'fw-password' => 'password', 'fw-ssl-validate' => 1},
},
{
desc => 'usev6 with default inputs',
getip => \&ddclient::get_ipv6,
useopt => 'usev6',
want => {usev6 => $default_inputs_fw, fwv6 => 'serverv6', 'fwv6-skip' => 'skipv6'},
},
);
for my $tc (@default_inputs_test_cases) {
my $h = "t/builtinfw_query.pl $tc->{desc}";
$ddclient::config{$h} = {
$tc->{useopt} => $default_inputs_fw,
'fw' => 'server',
'fwv4' => 'serverv4',
'fwv6' => 'serverv6',
'fw-login' => 'login',
'fw-password' => 'password',
'fw-ssl-validate' => 1,
'fw-skip' => 'skip',
'fwv4-skip' => 'skipv4',
'fwv6-skip' => 'skipv6',
};
@gotcalls = ();
$tc->{getip}(ddclient::strategy_inputs($tc->{useopt}, $h));
is_deeply(\@gotcalls, [$tc->{want}], $tc->{desc});
}
my $custom_inputs_fw = 't/builtinfw_query.pl custom inputs';
setbuiltinfw({
name => $custom_inputs_fw,
query => sub { my %p = @_; push(@gotcalls, \%p); return '192.0.2.1'; },
inputs => ['if'],
queryv4 => sub { my %p = @_; push(@gotcalls, \%p); return '192.0.2.2'; },
inputsv4 => ['ifv4'],
queryv6 => sub { my %p = @_; push(@gotcalls, \%p); return '2001:db8::1'; },
inputsv6 => ['ifv6'],
});
my @custom_inputs_test_cases = (
{
desc => 'use with custom inputs',
getip => \&ddclient::get_ip,
useopt => 'use',
want => {use => $custom_inputs_fw, if => 'eth0'},
},
{
desc => 'usev4 with custom inputs',
getip => \&ddclient::get_ipv4,
useopt => 'usev4',
want => {usev4 => $custom_inputs_fw, ifv4 => 'eth4'},
},
{
desc => 'usev6 with custom inputs',
getip => \&ddclient::get_ipv6,
useopt => 'usev6',
want => {usev6 => $custom_inputs_fw, ifv6 => 'eth6'},
},
);
for my $tc (@custom_inputs_test_cases) {
my $h = "t/builtinfw_query.pl $tc->{desc}";
$ddclient::config{$h} = {
$tc->{useopt} => $custom_inputs_fw,
'if' => 'eth0',
'ifv4' => 'eth4',
'ifv6' => 'eth6',
};
@gotcalls = ();
$tc->{getip}(ddclient::strategy_inputs($tc->{useopt}, $h));
is_deeply(\@gotcalls, [$tc->{want}], $tc->{desc});
}
done_testing();

53
t/check_value.pl Normal file
View file

@ -0,0 +1,53 @@
use Test::More;
use strict;
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
eval { require 'ddclient'; } or BAIL_OUT($@);
my @test_cases = (
{
type => ddclient::T_FQDN(),
input => 'example.com',
want => 'example.com',
},
{
type => ddclient::T_FQDN(),
input => 'example',
want_invalid => 1,
},
{
type => ddclient::T_URL(),
input => 'https://www.example.com',
want => 'https://www.example.com',
},
{
type => ddclient::T_URL(),
input => 'https://directnic.com/dns/gateway/ad133/',
want => 'https://directnic.com/dns/gateway/ad133/',
},
{
type => ddclient::T_URL(),
input => 'HTTPS://MixedCase.com/',
want => 'HTTPS://MixedCase.com/',
},
{
type => ddclient::T_URL(),
input => 'ftp://bad.protocol/',
want_invalid => 1,
},
{
type => ddclient::T_URL(),
input => 'bad-url',
want_invalid => 1,
},
);
for my $tc (@test_cases) {
my $got;
my $got_invalid = !(eval {
$got = ddclient::check_value($tc->{input},
ddclient::setv($tc->{type}, 0, 0, undef, undef));
1;
});
is($got_invalid, !!$tc->{want_invalid}, "$tc->{type}: $tc->{input}: validity");
is($got, $tc->{want}, "$tc->{type}: $tc->{input}: normalization") if !$tc->{want_invalid};
}
done_testing();

View file

@ -1,12 +1,7 @@
use Test::More;
BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } }
BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); }
use ddclient::t;
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
eval { require 'ddclient'; } or BAIL_OUT($@);
# To aid in debugging, uncomment the following lines. (They are normally left commented to avoid
# accidentally interfering with the Test Anything Protocol messages written by Test::More.)
#STDOUT->autoflush(1);
#$ddclient::globals{'debug'} = 1;
subtest "get_default_interface tests" => sub {
for my $sample (@ddclient::t::routing_samples) {
@ -39,23 +34,30 @@ subtest "get_ip_from_interface tests" => sub {
}
};
subtest "Get default interface and IP for test system" => sub {
subtest "Get default interface and IP for test system (IPv4)" => sub {
my $interface = ddclient::get_default_interface(4);
if ($interface) {
isnt($interface, "lo", "Check for loopback 'lo'");
isnt($interface, "lo0", "Check for loopback 'lo0'");
my $ip1 = ddclient::get_ip_from_interface("default", 4);
my $ip2 = ddclient::get_ip_from_interface($interface, 4);
is($ip1, $ip2, "Check IPv4 from default interface");
plan(skip_all => 'no IPv4 interface') if !$interface;
isnt($interface, "lo", "Check for loopback 'lo'");
isnt($interface, "lo0", "Check for loopback 'lo0'");
my $ip1 = ddclient::get_ip_from_interface("default", 4);
my $ip2 = ddclient::get_ip_from_interface($interface, 4);
is($ip1, $ip2, "Check IPv4 from default interface");
SKIP: {
skip('default interface does not have an appropriate IPv4 addresses') if !$ip1;
ok(ddclient::is_ipv4($ip1), "Valid IPv4 from get_ip_from_interface($interface)");
}
$interface = ddclient::get_default_interface(6);
if ($interface) {
isnt($interface, "lo", "Check for loopback 'lo'");
isnt($interface, "lo0", "Check for loopback 'lo0'");
my $ip1 = ddclient::get_ip_from_interface("default", 6);
my $ip2 = ddclient::get_ip_from_interface($interface, 6);
is($ip1, $ip2, "Check IPv6 from default interface");
};
subtest "Get default interface and IP for test system (IPv6)" => sub {
my $interface = ddclient::get_default_interface(6);
plan(skip_all => 'no IPv6 interface') if !$interface;
isnt($interface, "lo", "Check for loopback 'lo'");
isnt($interface, "lo0", "Check for loopback 'lo0'");
my $ip1 = ddclient::get_ip_from_interface("default", 6);
my $ip2 = ddclient::get_ip_from_interface($interface, 6);
is($ip1, $ip2, "Check IPv6 from default interface");
SKIP: {
skip('default interface does not have an appropriate IPv6 addresses') if !$ip1;
ok(ddclient::is_ipv6($ip1), "Valid IPv6 from get_ip_from_interface($interface)");
}
};

57
t/geturl_connectivity.pl Normal file
View file

@ -0,0 +1,57 @@
use Test::More;
BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } }
BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); }
use ddclient::t::HTTPD;
use ddclient::t::ip;
httpd_required();
$ddclient::globals{'ssl_ca_file'} = $ca_file;
for my $ipv ('4', '6') {
for my $ssl (0, 1) {
my $httpd = httpd($ipv, $ssl) or next;
$httpd->run(sub {
return [200, ['Content-Type' => 'application/octet-stream'], [$_[0]->as_string()]];
});
}
}
my @test_cases = (
{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 without ssl
{ server_ipv => '4', client_ipv => '' },
{ server_ipv => '4', client_ipv => '4' },
{ server_ipv => '6', client_ipv => '' },
{ server_ipv => '6', client_ipv => '6' },
# Fetch with ssl
{ ssl => 1, server_ipv => '4', client_ipv => '' },
{ ssl => 1, server_ipv => '4', client_ipv => '4' },
{ ssl => 1, server_ipv => '6', client_ipv => '' },
{ ssl => 1, server_ipv => '6', client_ipv => '6' },
);
for my $tc (@test_cases) {
$tc->{ipv6_opt} //= 0;
$tc->{ssl} //= 0;
SKIP: {
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)
if $tc->{server_ipv} eq '6' && !$httpd_ipv6_supported;
skip("HTTP::Daemon::SSL not available", 1) if $tc->{ssl} && !$httpd_ssl_supported;
my $uri = httpd($tc->{server_ipv}, $tc->{ssl})->endpoint();
my $name = sprintf("IPv%s client to %s%s",
$tc->{client_ipv} || '*', $uri, $tc->{ipv6_opt} ? ' (-ipv6)' : '');
$ddclient::globals{'ipv6'} = $tc->{ipv6_opt};
my $got = ddclient::geturl(url => $uri, ipversion => $tc->{client_ipv});
isnt($got // '', '', $name);
}
}
done_testing();

View file

@ -1,93 +0,0 @@
use Test::More;
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 $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);
};
# To aid in debugging, uncomment the following lines. (They are normally left commented to avoid
# accidentally interfering with the Test Anything Protocol messages written by Test::More.)
#STDOUT->autoflush(1);
#$ddclient::globals{'verbose'} = 1;
my $certdir = "$ENV{abs_top_srcdir}/t/lib/ddclient/Test/Fake/HTTPD";
$ddclient::globals{'ssl_ca_file'} = "$certdir/dummy-ca-cert.pem";
sub run_httpd {
my ($ipv6, $ssl) = @_;
return undef if $ssl && !$has_http_daemon_ssl;
return undef if $ipv6 && (!$ipv6_supported || !$http_daemon_supports_ipv6);
my $httpd = ddclient::Test::Fake::HTTPD->new(
host => $ipv6 ? '::1' : '127.0.0.1',
scheme => $ssl ? 'https' : 'http',
daemon_args => {
SSL_cert_file => "$certdir/dummy-server-cert.pem",
SSL_key_file => "$certdir/dummy-server-key.pem",
V6Only => 1,
},
);
$httpd->run(sub {
# Echo back the full request.
return [200, ['Content-Type' => 'application/octet-stream'], [$_[0]->as_string()]];
});
diag(sprintf("started IPv%s%s server running at %s",
$ipv6 ? '6' : '4', $ssl ? ' SSL' : '', $httpd->endpoint()));
return $httpd;
}
my %httpd = (
'4' => {'http' => run_httpd(0, 0), 'https' => run_httpd(0, 1)},
'6' => {'http' => run_httpd(1, 0), 'https' => run_httpd(1, 1)},
);
my @test_cases = (
{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 without ssl
{ server_ipv => '4', client_ipv => '' },
{ server_ipv => '4', client_ipv => '4' },
{ server_ipv => '6', client_ipv => '' },
{ server_ipv => '6', client_ipv => '6' },
# Fetch with ssl
{ ssl => 1, server_ipv => '4', client_ipv => '' },
{ ssl => 1, server_ipv => '4', client_ipv => '4' },
{ ssl => 1, server_ipv => '6', client_ipv => '' },
{ ssl => 1, server_ipv => '6', client_ipv => '6' },
);
for my $tc (@test_cases) {
$tc->{ipv6_opt} //= 0;
$tc->{ssl} //= 0;
SKIP: {
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)
if $tc->{server_ipv} eq '6' && !$http_daemon_supports_ipv6;
skip("HTTP::Daemon::SSL not available", 1) if $tc->{ssl} && !$has_http_daemon_ssl;
my $uri = $httpd{$tc->{server_ipv}}{$tc->{ssl} ? 'https' : 'http'}->endpoint();
my $name = sprintf("IPv%s client to %s%s",
$tc->{client_ipv} || '*', $uri, $tc->{ipv6_opt} ? ' (-ipv6)' : '');
$ddclient::globals{'ipv6'} = $tc->{ipv6_opt};
my $got = ddclient::geturl(url => $uri, ipversion => $tc->{client_ipv});
isnt($got // '', '', $name);
}
}
done_testing();

27
t/geturl_response.pl Normal file
View file

@ -0,0 +1,27 @@
use Test::More;
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
eval { require 'ddclient'; } or BAIL_OUT($@);
# Fake curl. Use the printf utility, which can process escapes. This allows Perl to drive the fake
# curl with plain ASCII and get arbitrary bytes back, avoiding problems caused by any encoding that
# might be done by Perl (e.g., "use open ':encoding(UTF-8)';").
my @fakecurl = ('sh', '-c', 'printf %b "$1"', '--');
my @test_cases = (
{
desc => 'binary body',
# Body is UTF-8 encoded ✨ (U+2728 Sparkles) followed by a 0xff byte (invalid UTF-8).
printf => join('\r\n', ('HTTP/1.1 200 OK', '', '\0342\0234\0250\0377')),
# The raw bytes should come through as equally valued codepoints. They must not be decoded.
want => "HTTP/1.1 200 OK\n\n\xe2\x9c\xa8\xff",
},
);
for my $tc (@test_cases) {
@ddclient::curl = (@fakecurl, $tc->{printf});
$ddclient::curl if 0; # suppress spurious warning "Name used only once: possible typo"
my $got = ddclient::geturl(url => 'http://ignored');
is($got, $tc->{want}, $tc->{desc});
}
done_testing();

113
t/group_hosts_by.pl Normal file
View file

@ -0,0 +1,113 @@
use Test::More;
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
eval { require 'ddclient'; } or BAIL_OUT($@);
eval { require Data::Dumper; } or skip($@, 1);
Data::Dumper->import();
my $h1 = 'h1';
my $h2 = 'h2';
my $h3 = 'h3';
$ddclient::config{$h1} = {
common => 'common',
h1h2 => 'h1 and h2',
unique => 'h1',
falsy => 0,
maybeunset => 'unique',
};
$ddclient::config{$h2} = {
common => 'common',
h1h2 => 'h1 and h2',
unique => 'h2',
falsy => '',
maybeunset => undef, # should not be grouped with unset
};
$ddclient::config{$h3} = {
common => 'common',
h1h2 => 'unique',
unique => 'h3',
falsy => undef,
# maybeunset is intentionally not set
};
my @test_cases = (
{
desc => 'empty attribute set yields single group with all hosts',
groupby => [qw()],
want => [{cfg => {}, hosts => [$h1, $h2, $h3]}],
},
{
desc => 'common attribute yields single group with all hosts',
groupby => [qw(common)],
want => [{cfg => {common => 'common'}, hosts => [$h1, $h2, $h3]}],
},
{
desc => 'subset share a value',
groupby => [qw(h1h2)],
want => [
{cfg => {h1h2 => 'h1 and h2'}, hosts => [$h1, $h2]},
{cfg => {h1h2 => 'unique'}, hosts => [$h3]},
],
},
{
desc => 'all unique',
groupby => [qw(unique)],
want => [
{cfg => {unique => 'h1'}, hosts => [$h1]},
{cfg => {unique => 'h2'}, hosts => [$h2]},
{cfg => {unique => 'h3'}, hosts => [$h3]},
],
},
{
desc => 'combination',
groupby => [qw(common h1h2)],
want => [
{cfg => {common => 'common', h1h2 => 'h1 and h2'}, hosts => [$h1, $h2]},
{cfg => {common => 'common', h1h2 => 'unique'}, hosts => [$h3]},
],
},
{
desc => 'falsy values',
groupby => [qw(falsy)],
want => [
{cfg => {falsy => 0}, hosts => [$h1]},
{cfg => {falsy => ''}, hosts => [$h2]},
# undef intentionally becomes unset because undef always means "fall back to global or
# default".
{cfg => {}, hosts => [$h3]},
],
},
{
desc => 'set, unset, undef',
groupby => [qw(maybeunset)],
want => [
{cfg => {maybeunset => 'unique'}, hosts => [$h1]},
# undef intentionally becomes unset because undef always means "fall back to global or
# default".
{cfg => {}, hosts => [$h2, $h3]},
],
},
{
desc => 'missing attribute',
groupby => [qw(thisdoesnotexist)],
want => [{cfg => {}, hosts => [$h1, $h2, $h3]}],
},
);
for my $tc (@test_cases) {
my @got = ddclient::group_hosts_by([$h1, $h2, $h3], @{$tc->{groupby}});
# @got is used as a set of sets. Sort everything to make comparison easier.
$_->{hosts} = [sort(@{$_->{hosts}})] for @got;
@got = sort({
for (my $i = 0; $i < @{$a->{hosts}} && $i < @{$b->{hosts}}; ++$i) {
my $x = $a->{hosts}[$i] cmp $b->{hosts}[$i];
return $x if $x != 0;
}
return @{$a->{hosts}} <=> @{$b->{hosts}};
} @got);
is_deeply(\@got, $tc->{want}, $tc->{desc})
or diag(Data::Dumper->new([\@got, $tc->{want}],
[qw(got want)])->Sortkeys(1)->Useqq(1)->Dump());
}
done_testing();

74
t/header_ok.pl Normal file
View file

@ -0,0 +1,74 @@
use Test::More;
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
eval { require 'ddclient'; } or BAIL_OUT($@);
my $have_mock = eval { require Test::MockModule; };
my $failmsg;
my $module;
if ($have_mock) {
$module = Test::MockModule->new('ddclient');
# Note: 'mock' is used instead of 'redefine' because 'redefine' is not available in the versions
# of Test::MockModule distributed with old Debian and Ubuntu releases.
$module->mock('failed', sub { $failmsg //= ''; $failmsg .= sprintf(shift, @_) . "\n"; });
}
my @test_cases = (
{
desc => 'malformed not OK',
input => 'malformed',
want => 0,
wantmsg => qr/unexpected/,
},
{
desc => 'HTTP/1.1 200 OK',
input => 'HTTP/1.1 200 OK',
want => 1,
},
{
desc => 'HTTP/2 200 OK',
input => 'HTTP/2 200 OK',
want => 1,
},
{
desc => 'HTTP/3 200 OK',
input => 'HTTP/3 200 OK',
want => 1,
},
{
desc => '401 not OK, fallback message',
input => 'HTTP/1.1 401 ',
want => 0,
wantmsg => qr/authentication failed/,
},
{
desc => '403 not OK, fallback message',
input => 'HTTP/1.1 403 ',
want => 0,
wantmsg => qr/not authorized/,
},
{
desc => 'other 4xx not OK',
input => 'HTTP/1.1 456 bad',
want => 0,
wantmsg => qr/bad/,
},
{
desc => 'only first line is logged on error',
input => "HTTP/1.1 404 not found\n\nbody",
want => 0,
wantmsg => qr/(?!body)/,
},
);
for my $tc (@test_cases) {
subtest $tc->{desc} => sub {
$failmsg = '';
is(ddclient::header_ok($tc->{input}), $tc->{want}, 'return value matches');
SKIP: {
skip('Test::MockModule not available') if !$have_mock;
like($failmsg, $tc->{wantmsg} // qr/^$/, 'fail message matches');
}
};
}
done_testing();

51
t/interval_expired.pl Normal file
View file

@ -0,0 +1,51 @@
use Test::More;
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
eval { require 'ddclient'; } or BAIL_OUT($@);
my $h = 't/interval_expired.pl';
my $default_now = 1000000000;
my @test_cases = (
{
interval => 'inf',
want => 0,
},
{
now => 'inf',
interval => 'inf',
want => 0,
},
{
cache => '-inf',
interval => 'inf',
want => 0,
},
{
cache => undef, # Falsy cache value.
interval => 'inf',
want => 0,
},
{
now => 0,
cache => 0, # Different kind of falsy cache value.
interval => 'inf',
want => 0,
},
);
for my $tc (@test_cases) {
$tc->{now} //= $default_now;
# For convenience, $tc->{cache} is an offset from $tc->{now}, not an absolute time..
my $cachetime = $tc->{now} + $tc->{cache} if defined($tc->{cache});
$ddclient::config{$h} = {'interval' => $tc->{interval}};
%ddclient::config if 0; # suppress spurious warning "Name used only once: possible typo"
$ddclient::cache{$h} = {'cached-time' => $cachetime} if defined($cachetime);
%ddclient::cache if 0; # suppress spurious warning "Name used only once: possible typo"
$ddclient::now = $tc->{now};
$ddclient::now if 0; # suppress spurious warning "Name used only once: possible typo"
my $desc = "now=$tc->{now}, cache=${\($cachetime // 'undef')}, interval=$tc->{interval}";
is(ddclient::interval_expired($h, 'cached-time', 'interval'), $tc->{want}, $desc);
}
done_testing();

View file

@ -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 <masaki@cpan.org>
# 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 || '<default>',
$@ 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;

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

@ -560,3 +560,5 @@ EOF
want_ipv6_if => "en0",
},
);
1;

161
t/lib/ddclient/t/HTTPD.pm Normal file
View file

@ -0,0 +1,161 @@
package ddclient::t::HTTPD;
use v5.10.1;
use strict;
use warnings;
use parent qw(ddclient::Test::Fake::HTTPD);
use Exporter qw(import);
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 $other_ca_file
$textplain
);
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);
}
sub httpd_ssl_required {
plan(skip_all => $httpd_ssl_support_error) if !$httpd_ssl_supported;
}
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;
sub httpd_ipv6_ok {
ok($httpd_ipv6_supported, "test HTTP server supports IPv6") or diag($httpd_ipv6_support_error);
}
sub httpd_ipv6_required {
plan(skip_all => $httpd_ipv6_support_error) if !$httpd_ipv6_supported;
}
our $textplain = ['content-type' => 'text/plain; charset=utf-8'];
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
$self->{_requests} = []; # Log of received requests.
$self->{_responses} = []; # Script of responses to play back.
return $self;
}
sub run {
my ($self, $app) = @_;
$self->SUPER::run(sub {
my ($req) = @_;
push(@{$self->{_requests}}, $req);
my $res = $app->($req) if defined($app);
return $res if defined($res);
if ($req->uri()->path() eq '/control') {
pop(@{$self->{_requests}});
if ($req->method() eq 'PUT') {
return [400, $textplain, ['content must be json']]
if $req->headers()->content_type() ne 'application/json';
eval { @{$self->{_responses}} = @{decode_json($req->content())}; 1; }
or return [400, $textplain, ['content is not valid json']];
@{$self->{_requests}} = ();
return [200, $textplain, ["successfully reset request log and response script"]];
} elsif ($req->method() eq 'GET') {
my @reqs = map($_->as_string(), @{$self->{_requests}});
return [200, ['content-type' => 'application/json'], [encode_json(\@reqs)]];
} else {
return [405, $textplain, ['unsupported method: ' . $req->method()]];
}
}
return shift(@{$self->{_responses}}) // [500, $textplain, ["no more scripted responses"]];
});
diag("started server running at " . $self->endpoint());
return $self;
}
sub reset {
my $self = shift;
my $ep = $self->endpoint();
my $got = ddclient::geturl(url => "$ep/control");
diag("http response:\n$got");
ddclient::header_ok($got)
or BAIL_OUT("failed to get log of requests from test http server at $ep");
$got =~ s/^.*?\n\n//s;
my @got = map(HTTP::Request->parse($_), @{decode_json($got)});
ddclient::header_ok(ddclient::geturl(
url => "$ep/control",
method => 'PUT',
headers => ['content-type: application/json'],
data => encode_json(\@_),
)) or BAIL_OUT("failed to reset the test http server at $ep");
return @got;
}
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;
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})) {
my $host
= $ipv eq '4' ? '127.0.0.1'
: $ipv eq '6' ? '::1'
: $httpd_ipv6_supported ? '::1'
: '127.0.0.1';
$daemons{$ipv}{$ssl} = __PACKAGE__->new(
host => $host,
scheme => $ssl ? 'https' : 'http',
daemon_args => {
(V6Only => $ipv eq '6' ? 1 : 0) x ($host eq '::1'),
(SSL_cert_file => "$certdir/dummy-server-cert.pem",
SSL_key_file => "$certdir/dummy-server-key.pem") x $ssl,
},
);
}
return $daemons{$ipv}{$ssl};
}
1;

View file

@ -0,0 +1,39 @@
package ddclient::t::Logger;
BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); }
use parent qw(-norequire ddclient::Logger);
{
package ddclient::t::LoggerAbort;
use overload '""' => qw(stringify);
sub new {
my ($class, %args) = @_;
return bless(\%args, $class);
}
sub stringify {
return 'logged a FATAL message';
}
}
sub new {
my ($class, $parent, $labelre) = @_;
my $self = $class->SUPER::new(undef, $parent);
$self->{logs} = [];
$self->{_labelre} = $labelre;
return $self;
}
sub _log {
my ($self, $args) = @_;
my $lre = $self->{_labelre};
my $lbl = $args->{label};
push(@{$self->{logs}}, $args) if !defined($lre) || (defined($lbl) && $lbl =~ $lre);
return $self->SUPER::_log($args);
}
sub _abort {
my ($self) = @_;
push(@{$self->{logs}}, 'aborted');
die(ddclient::t::LoggerAbort->new());
}
1;

30
t/lib/ddclient/t/ip.pm Normal file
View file

@ -0,0 +1,30 @@
package ddclient::t::ip;
use v5.10.1;
use strict;
use warnings;
use Exporter qw(import);
use Test::More;
our @EXPORT = qw(ipv6_ok ipv6_required $ipv6_supported $ipv6_support_error);
our $ipv6_support_error;
our $ipv6_supported = eval {
require IO::Socket::IP;
my $ipv6_socket = IO::Socket::IP->new(
Domain => 'PF_INET6',
LocalHost => '::1',
Listen => 1,
);
defined($ipv6_socket);
} or $ipv6_support_error = $@;
sub ipv6_ok {
ok($ipv6_supported, "system supports IPv6") or diag($ipv6_support_error);
}
sub ipv6_required {
plan(skip_all => $ipv6_support_error) if !$ipv6_supported;
}
1;

167
t/logmsg.pl Normal file
View file

@ -0,0 +1,167 @@
use Test::More;
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
eval { require 'ddclient'; } or BAIL_OUT($@);
my @test_cases = (
{
desc => 'adds a newline',
args => ['xyz'],
want => "xyz\n",
},
{
desc => 'removes one trailing newline (before adding a newline)',
args => ["xyz \n\t\n\n"],
want => "xyz \n\t\n\n",
},
{
desc => 'accepts msg keyword parameter',
args => [msg => 'xyz'],
want => "xyz\n",
},
{
desc => 'msg keyword parameter trumps message parameter',
args => [msg => 'kw', 'pos'],
want => "kw\n",
},
{
desc => 'msg keyword parameter trumps message parameter',
args => [msg => 'kw', 'pos'],
want => "kw\n",
},
{
desc => 'email appends to email body',
args => [email => 1, 'foo'],
init_email => "preexisting message\n",
want_email => "preexisting message\nfoo\n",
want => "foo\n",
},
{
desc => 'single-line label',
args => [label => 'LBL', 'foo'],
want => "LBL: > foo\n",
},
{
desc => 'multi-line label',
args => [label => 'LBL', "foo\nbar"],
want => ("LBL: > foo\n" .
"LBL: bar\n"),
},
{
desc => 'single-line long label',
args => [label => 'VERY LONG LABEL', 'foo'],
want => "VERY LONG LABEL: > foo\n",
},
{
desc => 'multi-line long label',
args => [label => 'VERY LONG LABEL', "foo\nbar"],
want => ("VERY LONG LABEL: > foo\n" .
"VERY LONG LABEL: bar\n"),
},
{
desc => 'single line, no label, single context',
args => ['foo'],
ctxs => ['only context'],
want => "[only context]> foo\n",
},
{
desc => 'single line, no label, two contexts',
args => ['foo'],
ctxs => ['context one', 'context two'],
want => "[context one][context two]> foo\n",
},
{
desc => 'single line, label, two contexts',
args => [label => 'LBL', 'foo'],
ctxs => ['context one', 'context two'],
want => "LBL: [context one][context two]> foo\n",
},
{
desc => 'multiple lines, label, two contexts',
args => [label => 'LBL', "foo\nbar"],
ctxs => ['context one', 'context two'],
want => ("LBL: [context one][context two]> foo\n" .
"LBL: [context one][context two] bar\n"),
},
{
desc => 'string ctx arg',
args => [label => 'LBL', ctx => 'three', "foo\nbar"],
ctxs => ['one', 'two'],
want => ("LBL: [one][two][three]> foo\n" .
"LBL: [one][two][three] bar\n"),
},
{
desc => 'arrayref ctx arg',
args => [label => 'LBL', ctx => ['three', 'four'], "foo\nbar"],
ctxs => ['one', 'two'],
want => ("LBL: [one][two][three][four]> foo\n" .
"LBL: [one][two][three][four] bar\n"),
},
{
desc => 'undef ctx',
args => [label => 'LBL', "foo"],
ctxs => ['one', undef],
want => "LBL: [one]> foo\n",
},
{
desc => 'arrayref ctx',
args => [label => 'LBL', "foo"],
ctxs => ['one', ['two', 'three']],
want => "LBL: [one][two][three]> foo\n",
},
);
for my $tc (@test_cases) {
subtest $tc->{desc} => sub {
$tc->{wantemail} //= '';
my $output;
open(my $fh, '>', \$output);
local $ddclient::emailbody = $tc->{init_email} // '';
local $ddclient::_l = $ddclient::_l;
$ddclient::_l = ddclient::pushlogctx($_) for @{$tc->{ctxs} // []};
{
local *STDERR = $fh;
ddclient::logmsg(@{$tc->{args}});
}
close($fh);
is($output, $tc->{want}, 'output text matches');
is($ddclient::emailbody, $tc->{want_email} // '', 'email content matches');
}
}
my @logfmt_test_cases = (
{
desc => 'single argument is printed directly, not via sprintf',
args => ['%%'],
want => "DEBUG: > %%\n",
},
{
desc => 'multiple arguments are formatted via sprintf',
args => ['%s', 'foo'],
want => "DEBUG: > foo\n",
},
{
desc => 'single argument with context',
args => [ctx => 'context', '%%'],
want => "DEBUG: [context]> %%\n",
},
{
desc => 'multiple arguments with context',
args => [ctx => 'context', '%s', 'foo'],
want => "DEBUG: [context]> foo\n",
},
);
for my $tc (@logfmt_test_cases) {
my $got;
open(my $fh, '>', \$got);
local $ddclient::globals{debug} = 1;
%ddclient::globals if 0;
{
local *STDERR = $fh;
ddclient::debug(@{$tc->{args}});
}
close($fh);
is($got, $tc->{want}, $tc->{desc});
}
done_testing();

View file

@ -44,8 +44,20 @@ my @test_cases = (
tc('unquoted escaped backslash', "a=\\\\", { a => "\\" }, ""),
tc('squoted escaped squote', "a='\\''", { a => "'" }, ""),
tc('dquoted escaped dquote', "a=\"\\\"\"", { a => '"' }, ""),
tc('env: empty', "a_env=", {}, ""),
tc('env: unset', "a_env=UNSET", {}, ""),
tc('env: set', "a_env=TEST", { a => 'val' }, ""),
tc('env: single quoted', "a_env='TEST'", { a => 'val' }, ""),
tc('newline: quoted value', "a='1\n2'", { a => "1\n2" }, ""),
tc('newline: escaped value', "a=1\\\n2", { a => "1\n2" }, ""),
tc('newline: between vars', "a=1 \n b=2", { a => '1' }, "\n b=2"),
tc('newline: terminating', "a=1 \n", { a => '1' }, "\n"),
);
delete($ENV{''});
delete($ENV{UNSET});
$ENV{TEST} = 'val';
for my $tc (@test_cases) {
my ($got_rest, %got_vars) = ddclient::parse_assignments($tc->{input});
subtest $tc->{name} => sub {

169
t/protocol_directnic.pl Normal file
View file

@ -0,0 +1,169 @@
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($@); }
use ddclient::t::HTTPD;
use ddclient::t::Logger;
httpd_required();
ddclient::load_json_support('directnic');
httpd()->run(sub {
my ($req) = @_;
diag('==============================================================================');
diag("Test server received request:\n" . $req->as_string());
my $headers = ['content-type' => 'text/plain; charset=utf-8'];
if ($req->uri->as_string =~ m/\/dns\/gateway\/(abc|def)\/\?data=([^&]*)/) {
return [200, ['Content-Type' => 'application/json'], [encode_json({
result => 'success',
message => "Your record was updated to $2",
})]];
} elsif ($req->uri->as_string =~ m/\/dns\/gateway\/bad_token\/\?data=([^&]*)/) {
return [200, ['Content-Type' => 'application/json'], [encode_json({
result => 'error',
message => "There was an error updating your record.",
})]];
} elsif ($req->uri->as_string =~ m/\/bad\/path\/\?data=([^&]*)/) {
return [200, ['Content-Type' => 'application/json'], ['unexpected response body']];
}
return [400, $headers, ['unexpected request: ' . $req->uri()]]
});
my $hostname = httpd()->endpoint();
my @test_cases = (
{
desc => 'IPv4, good',
cfg => {h1 => {urlv4 => "$hostname/dns/gateway/abc/", wantipv4 => '192.0.2.1'}},
wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
},
wantlogs => [
{label => 'SUCCESS', ctx => ['h1'], msg => qr/IPv4/},
],
},
{
desc => 'IPv4, failed',
cfg => {h1 => {urlv4 => "$hostname/dns/gateway/bad_token/", wantipv4 => '192.0.2.1'}},
wantrecap => {
h1 => {'status-ipv4' => 'failed'},
},
wantlogs => [
{label => 'FAILED', ctx => ['h1'], msg => qr/There was an error updating your record/},
],
},
{
desc => 'IPv4, bad',
cfg => {h1 => {urlv4 => "$hostname/bad/path/", wantipv4 => '192.0.2.1'}},
wantrecap => {
h1 => {'status-ipv4' => 'bad'},
},
wantlogs => [
{label => 'FAILED', ctx => ['h1'], msg => qr/response is not a JSON object:\nunexpected response body/},
],
},
{
desc => 'IPv4, unexpected response',
cfg => {h1 => {urlv4 => "$hostname/unexpected/path/", wantipv4 => '192.0.2.1'}},
wantrecap => {},
wantlogs => [
{label => 'FAILED', ctx => ['h1'], msg => qr/400 Bad Request/},
],
},
{
desc => 'IPv4, no urlv4',
cfg => {h1 => {wantipv4 => '192.0.2.1'}},
wantrecap => {},
wantlogs => [
{label => 'FAILED', ctx => ['h1'], msg => qr/missing urlv4 option/},
],
},
{
desc => 'IPv6, good',
cfg => {h1 => {urlv6 => "$hostname/dns/gateway/abc/", wantipv6 => '2001:db8::1'}},
wantrecap => {
h1 => {'status-ipv6' => 'good', 'ipv6' => '2001:db8::1', 'mtime' => $ddclient::now},
},
wantlogs => [
{label => 'SUCCESS', ctx => ['h1'], msg => qr/IPv6/},
],
},
{
desc => 'IPv4 and IPv6, good',
cfg => {h1 => {
urlv4 => "$hostname/dns/gateway/abc/",
urlv6 => "$hostname/dns/gateway/def/",
wantipv4 => '192.0.2.1',
wantipv6 => '2001:db8::1',
}},
wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1',
'status-ipv6' => 'good', 'ipv6' => '2001:db8::1',
'mtime' => $ddclient::now},
},
wantlogs => [
{label => 'SUCCESS', ctx => ['h1'], msg => qr/IPv4/},
{label => 'SUCCESS', ctx => ['h1'], msg => qr/IPv6/},
],
},
{
desc => 'IPv4 and IPv6, mixed success',
cfg => {h1 => {
urlv4 => "$hostname/dns/gateway/bad_token/",
urlv6 => "$hostname/dns/gateway/def/",
wantipv4 => '192.0.2.1',
wantipv6 => '2001:db8::1',
}},
wantips => {h1 => {wantipv4 => '192.0.2.1', wantipv6 => '2001:db8::1'}},
wantrecap => {
h1 => {'status-ipv4' => 'failed',
'status-ipv6' => 'good', 'ipv6' => '2001:db8::1',
'mtime' => $ddclient::now},
},
wantlogs => [
{label => 'FAILED', ctx => ['h1'], msg => qr/There was an error updating your record/},
{label => 'SUCCESS', ctx => ['h1'], msg => qr/IPv6/},
],
},
);
for my $tc (@test_cases) {
diag('==============================================================================');
diag("Starting test: $tc->{desc}");
diag('==============================================================================');
local $ddclient::globals{debug} = 1;
local $ddclient::globals{verbose} = 1;
my $l = ddclient::t::Logger->new($ddclient::_l, qr/^(?:WARNING|FATAL|SUCCESS|FAILED)$/);
local %ddclient::config = %{$tc->{cfg}};
local %ddclient::recap;
{
local $ddclient::_l = $l;
ddclient::nic_directnic_update(undef, sort(keys(%{$tc->{cfg}})));
}
is_deeply(\%ddclient::recap, $tc->{wantrecap}, "$tc->{desc}: recap")
or diag(ddclient::repr(Values => [\%ddclient::recap, $tc->{wantrecap}],
Names => ['*got', '*want']));
$tc->{wantlogs} //= [];
subtest("$tc->{desc}: logs" => sub {
my @got = @{$l->{logs}};
my @want = @{$tc->{wantlogs}};
for my $i (0..$#want) {
last if $i >= @got;
my $got = $got[$i];
my $want = $want[$i];
subtest("log $i" => sub {
is($got->{label}, $want->{label}, "label matches");
is_deeply($got->{ctx}, $want->{ctx}, "context matches");
like($got->{msg}, $want->{msg}, "message matches");
}) or diag(ddclient::repr(Values => [$got, $want], Names => ['*got', '*want']));
}
my @unexpected = @got[@want..$#got];
ok(@unexpected == 0, "no unexpected logs")
or diag(ddclient::repr(\@unexpected, Names => ['*unexpected']));
my @missing = @want[@got..$#want];
ok(@missing == 0, "no missing logs")
or diag(ddclient::repr(\@missing, Names => ['*missing']));
});
}
done_testing();

242
t/protocol_dnsexit2.pl Normal file
View file

@ -0,0 +1,242 @@
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($@); }
use ddclient::t::HTTPD;
use ddclient::t::Logger;
httpd_required();
local $ddclient::globals{debug} = 1;
local $ddclient::globals{verbose} = 1;
ddclient::load_json_support('dnsexit2');
httpd()->run(sub {
my ($req) = @_;
return undef if $req->uri()->path() eq '/control';
return [200, ['Content-Type' => 'application/json'], [encode_json({
code => 0,
message => 'Success'
})]];
});
sub cmp_update {
my ($a, $b) = @_;
return $a->{name} cmp $b->{name} || $a->{type} cmp $b->{type};
}
sub sort_updates {
my ($req) = @_;
return {
%$req,
update => [sort({ cmp_update($a, $b); } @{$req->{update}})],
};
}
sub sort_reqs {
my @reqs = map(sort_updates($_), @_);
my @sorted = sort({
my $ret = $a->{domain} cmp $b->{domain};
$ret = @{$a->{update}} <=> @{$b->{update}} if !$ret;
my $i = 0;
while (!$ret && $i < @{$a->{update}} && $i < @{$b->{update}}) {
$ret = cmp_update($a->{update}[$i], $b->{update}[$i]);
}
return $ret;
} @reqs);
return @sorted;
}
my @test_cases = (
{
desc => 'both IPv4 and IPv6 are updated together',
cfg => {
'host.my.example.com' => {
ttl => 5,
wantipv4 => '192.0.2.1',
wantipv6 => '2001:db8::1',
zone => 'my.example.com',
},
},
want => [{
apikey => 'key',
domain => 'my.example.com',
update => [
{
content => '192.0.2.1',
name => 'host',
ttl => 5,
type => 'A',
},
{
content => '2001:db8::1',
name => 'host',
ttl => 5,
type => 'AAAA',
},
],
}],
},
{
desc => 'zone defaults to host',
cfg => {
'host.my.example.com' => {
ttl => 10,
wantipv4 => '192.0.2.1',
},
},
want => [{
apikey => 'key',
domain => 'host.my.example.com',
update => [
{
content => '192.0.2.1',
name => '',
ttl => 10,
type => 'A',
},
],
}],
},
{
desc => 'two hosts, different zones',
cfg => {
'host1.example.com' => {
ttl => 5,
wantipv4 => '192.0.2.1',
# 'zone' intentionally not set, so it will default to 'host1.example.com'.
},
'host2.example.com' => {
ttl => 10,
wantipv6 => '2001:db8::1',
zone => 'example.com',
},
},
want => [
{
apikey => 'key',
domain => 'host1.example.com',
update => [
{
content => '192.0.2.1',
name => '',
ttl => 5,
type => 'A',
},
],
},
{
apikey => 'key',
domain => 'example.com',
update => [
{
content => '2001:db8::1',
name => 'host2',
ttl => 10,
type => 'AAAA',
},
],
},
],
},
{
desc => 'two hosts, same zone',
cfg => {
'host1.example.com' => {
ttl => 5,
wantipv4 => '192.0.2.1',
zone => 'example.com',
},
'host2.example.com' => {
ttl => 10,
wantipv6 => '2001:db8::1',
zone => 'example.com',
},
},
want => [
{
apikey => 'key',
domain => 'example.com',
update => [
{
content => '192.0.2.1',
name => 'host1',
ttl => 5,
type => 'A',
},
{
content => '2001:db8::1',
name => 'host2',
ttl => 10,
type => 'AAAA',
},
],
},
],
},
{
desc => 'host outside of zone',
cfg => {
'host.example' => {
wantipv4 => '192.0.2.1',
zone => 'example.com',
},
},
want_fatal => qr{hostname does not end with the zone: example.com},
},
);
for my $tc (@test_cases) {
subtest($tc->{desc} => sub {
local $ddclient::_l = ddclient::pushlogctx($tc->{desc});
local %ddclient::config = ();
my @hosts = keys(%{$tc->{cfg}});
for my $h (@hosts) {
$ddclient::config{$h} = {
password => 'key',
path => '/update',
server => httpd()->endpoint(),
%{$tc->{cfg}{$h}},
};
}
my $l = ddclient::t::Logger->new($ddclient::_l, qr/^FATAL$/);
my $err = do {
local $ddclient::_l = $l;
local $@;
(eval { ddclient::nic_dnsexit2_update(undef, @hosts); 1; })
? undef : ($@ // 'unknown error');
};
my @requests = httpd()->reset();
my @got;
for (my $i = 0; $i < @requests; $i++) {
subtest("request $i" => sub {
my $req = $requests[$i];
is($req->method(), 'POST', 'method is POST');
is($req->uri()->as_string(), '/update', 'path is /update');
is($req->header('content-type'), 'application/json', 'Content-Type is JSON');
is($req->header('accept'), 'application/json', 'Accept is JSON');
my $got = decode_json($req->content());
is(ref($got), 'HASH', 'request content is a JSON object');
is(ref($got->{update}), 'ARRAY', 'JSON object has array "update" property');
push(@got, $got);
});
}
@got = sort_reqs(@got);
my @want = sort_reqs(@{$tc->{want} // []});
is_deeply(\@got, \@want, 'request objects match');
subtest('expected (or lack of) error' => sub {
if (is(defined($err), defined($tc->{want_fatal}), 'error existence') && defined($err)) {
my @got = @{$l->{logs}};
if (is(scalar(@got), 2, 'logged two events')) {
is($got[0]->{label}, 'FATAL', 'first logged event is a FATAL message');
like($got[0]->{msg}, $tc->{want_fatal}, 'first logged event message matches');
is($got[1], 'aborted', 'second logged event is an "aborted" event');
isa_ok($err, qw(ddclient::t::LoggerAbort));
}
}
});
});
}
done_testing();

279
t/protocol_dyndns2.pl Normal file
View file

@ -0,0 +1,279 @@
use Test::More;
BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } }
use MIME::Base64;
BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); }
use ddclient::t::HTTPD;
use ddclient::t::Logger;
httpd_required();
httpd()->run(sub {
my ($req) = @_;
diag('==============================================================================');
diag("Test server received request:\n" . $req->as_string());
return undef if $req->uri()->path() eq '/control';
my $wantauthn = 'Basic ' . encode_base64('username:password', '');
return [401, [@$textplain, 'www-authenticate' => 'Basic realm="realm", charset="UTF-8"'],
['authentication required']] if ($req->header('authorization') // '') ne $wantauthn;
return [400, $textplain, ['invalid method: ' . $req->method()]] if $req->method() ne 'GET';
return undef;
});
my @test_cases = (
{
desc => 'IPv4, single host, good',
cfg => {h1 => {wantipv4 => '192.0.2.1'}},
resp => ['good'],
wantquery => 'hostname=h1&myip=192.0.2.1',
wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
},
wantlogs => [
{label => 'SUCCESS', ctx => ['h1'], msg => qr/IPv4/},
],
},
{
desc => 'IPv4, single host, nochg',
cfg => {h1 => {wantipv4 => '192.0.2.1'}},
resp => ['nochg'],
wantquery => 'hostname=h1&myip=192.0.2.1',
wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
},
wantlogs => [
{label => 'WARNING', ctx => ['h1'], msg => qr/nochg/},
{label => 'SUCCESS', ctx => ['h1'], msg => qr/IPv4/},
],
},
{
desc => 'IPv4, single host, bad',
cfg => {h1 => {wantipv4 => '192.0.2.1'}},
resp => ['nohost'],
wantquery => 'hostname=h1&myip=192.0.2.1',
wantrecap => {
h1 => {'status-ipv4' => 'nohost'},
},
wantlogs => [
{label => 'FAILED', ctx => ['h1'], msg => qr/nohost/},
],
},
{
desc => 'IPv4, single host, unexpected',
cfg => {h1 => {wantipv4 => '192.0.2.1'}},
resp => ['WAT'],
wantquery => 'hostname=h1&myip=192.0.2.1',
wantrecap => {
h1 => {'status-ipv4' => 'WAT'},
},
wantlogs => [
{label => 'FAILED', ctx => ['h1'], msg => qr/unexpected.*WAT/},
],
},
{
desc => 'IPv4, multiple hosts, multiple good',
cfg => {
h1 => {wantipv4 => '192.0.2.1'},
h2 => {wantipv4 => '192.0.2.1'},
},
resp => [
'good 192.0.2.1',
'good',
],
wantquery => 'hostname=h1,h2&myip=192.0.2.1',
wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
h2 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
},
wantlogs => [
{label => 'SUCCESS', ctx => ['h1'], msg => qr/IPv4/},
{label => 'SUCCESS', ctx => ['h2'], msg => qr/IPv4/},
],
},
{
desc => 'IPv4, multiple hosts, mixed success',
cfg => {
h1 => {wantipv4 => '192.0.2.1'},
h2 => {wantipv4 => '192.0.2.1'},
h3 => {wantipv4 => '192.0.2.1'},
},
resp => [
'good',
'nochg',
'dnserr',
],
wantquery => 'hostname=h1,h2,h3&myip=192.0.2.1',
wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
h2 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
h3 => {'status-ipv4' => 'dnserr'},
},
wantlogs => [
{label => 'SUCCESS', ctx => ['h1'], msg => qr/IPv4/},
{label => 'WARNING', ctx => ['h2'], msg => qr/nochg/},
{label => 'SUCCESS', ctx => ['h2'], msg => qr/IPv4/},
{label => 'FAILED', ctx => ['h3'], msg => qr/dnserr/},
],
},
{
desc => 'IPv6, single host, good',
cfg => {h1 => {wantipv6 => '2001:db8::1'}},
resp => ['good'],
wantquery => 'hostname=h1&myip=2001:db8::1',
wantrecap => {
h1 => {'status-ipv6' => 'good', 'ipv6' => '2001:db8::1', 'mtime' => $ddclient::now},
},
wantlogs => [
{label => 'SUCCESS', ctx => ['h1'], msg => qr/IPv6/},
],
},
{
desc => 'IPv4 and IPv6, single host, good',
cfg => {h1 => {wantipv4 => '192.0.2.1', wantipv6 => '2001:db8::1'}},
resp => ['good'],
wantquery => 'hostname=h1&myip=192.0.2.1,2001:db8::1',
wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1',
'status-ipv6' => 'good', 'ipv6' => '2001:db8::1',
'mtime' => $ddclient::now},
},
wantlogs => [
{label => 'SUCCESS', ctx => ['h1'], msg => qr/IPv4/},
{label => 'SUCCESS', ctx => ['h1'], msg => qr/IPv6/},
],
},
{
desc => 'excess status line',
cfg => {
h1 => {wantipv4 => '192.0.2.1'},
h2 => {wantipv4 => '192.0.2.1'},
},
resp => [
'good',
'good',
'WAT',
],
wantquery => 'hostname=h1,h2&myip=192.0.2.1',
wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
h2 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
},
wantlogs => [
{label => 'SUCCESS', ctx => ['h1'], msg => qr/IPv4/},
{label => 'SUCCESS', ctx => ['h2'], msg => qr/IPv4/},
{label => 'WARNING', ctx => ['h1,h2'], msg => qr/unexpected.*\nWAT$/},
],
},
{
desc => 'multiple hosts, single failure',
cfg => {
h1 => {wantipv4 => '192.0.2.1'},
h2 => {wantipv4 => '192.0.2.1'},
},
resp => ['abuse'],
wantquery => 'hostname=h1,h2&myip=192.0.2.1',
wantrecap => {
h1 => {'status-ipv4' => 'abuse'},
h2 => {'status-ipv4' => 'abuse'},
},
wantlogs => [
{label => 'FAILED', ctx => ['h1'], msg => qr/abuse/},
{label => 'FAILED', ctx => ['h2'], msg => qr/abuse/},
],
},
{
desc => 'multiple hosts, single success',
cfg => {
h1 => {wantipv4 => '192.0.2.1'},
h2 => {wantipv4 => '192.0.2.1'},
},
resp => ['good'],
wantquery => 'hostname=h1,h2&myip=192.0.2.1',
wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
h2 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
},
wantlogs => [
{label => 'WARNING', ctx => ['h1,h2'], msg => qr//},
{label => 'SUCCESS', ctx => ['h1'], msg => qr/IPv4/},
{label => 'SUCCESS', ctx => ['h2'], msg => qr/IPv4/},
],
},
{
desc => 'multiple hosts, fewer results',
cfg => {
h1 => {wantipv4 => '192.0.2.1'},
h2 => {wantipv4 => '192.0.2.1'},
h3 => {wantipv4 => '192.0.2.1'},
},
resp => [
'good',
'nochg',
],
wantquery => 'hostname=h1,h2,h3&myip=192.0.2.1',
wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
h2 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
h3 => {'status-ipv4' => 'unknown'},
},
wantlogs => [
{label => 'SUCCESS', ctx => ['h1'], msg => qr/IPv4/},
{label => 'WARNING', ctx => ['h2'], msg => qr/nochg/},
{label => 'SUCCESS', ctx => ['h2'], msg => qr/IPv4/},
{label => 'FAILED', ctx => ['h3'], msg => qr/assuming failure/},
],
},
);
for my $tc (@test_cases) {
diag('==============================================================================');
diag("Starting test: $tc->{desc}");
diag('==============================================================================');
local $ddclient::globals{debug} = 1;
local $ddclient::globals{verbose} = 1;
my $l = ddclient::t::Logger->new($ddclient::_l, qr/^(?:WARNING|FATAL|SUCCESS|FAILED)$/);
local %ddclient::config;
local %ddclient::recap;
$ddclient::config{$_} = {
login => 'username',
password => 'password',
server => httpd()->endpoint(),
script => '/nic/update',
%{$tc->{cfg}{$_}},
} for keys(%{$tc->{cfg}});
httpd()->reset([200, $textplain, [map("$_\n", @{$tc->{resp}})]]);
{
local $ddclient::_l = $l;
ddclient::nic_dyndns2_update(undef, sort(keys(%{$tc->{cfg}})));
}
my @requests = httpd()->reset();
is(scalar(@requests), 1, "$tc->{desc}: single update request");
my $req = shift(@requests);
is($req->uri()->path(), '/nic/update', "$tc->{desc}: request path");
is($req->uri()->query(), $tc->{wantquery}, "$tc->{desc}: request query");
is_deeply(\%ddclient::recap, $tc->{wantrecap}, "$tc->{desc}: recap")
or diag(ddclient::repr(Values => [\%ddclient::recap, $tc->{wantrecap}],
Names => ['*got', '*want']));
$tc->{wantlogs} //= [];
subtest("$tc->{desc}: logs" => sub {
my @got = @{$l->{logs}};
my @want = @{$tc->{wantlogs}};
for my $i (0..$#want) {
last if $i >= @got;
my $got = $got[$i];
my $want = $want[$i];
subtest("log $i" => sub {
is($got->{label}, $want->{label}, "label matches");
is_deeply($got->{ctx}, $want->{ctx}, "context matches");
like($got->{msg}, $want->{msg}, "message matches");
}) or diag(ddclient::repr(Values => [$got, $want], Names => ['*got', '*want']));
}
my @unexpected = @got[@want..$#got];
ok(@unexpected == 0, "no unexpected logs")
or diag(ddclient::repr(\@unexpected, Names => ['*unexpected']));
my @missing = @want[@got..$#want];
ok(@missing == 0, "no missing logs")
or diag(ddclient::repr(\@missing, Names => ['*missing']));
});
}
done_testing();

107
t/read_recap.pl Normal file
View file

@ -0,0 +1,107 @@
use Test::More;
BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } }
use File::Temp;
BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); }
local $ddclient::globals{debug} = 1;
local $ddclient::globals{verbose} = 1;
local %ddclient::protocols = (
protocol_a => ddclient::Protocol->new(
recapvars => {
host => ddclient::T_STRING(),
var_a => ddclient::T_BOOL(),
},
),
protocol_b => ddclient::Protocol->new(
recapvars => {
host => ddclient::T_STRING(),
var_b => ddclient::T_NUMBER(),
},
cfgvars => {
var_b_non_recap => {type => ddclient::T_ANY()},
},
),
);
local %ddclient::cfgvars = (merged => {map({ %{$ddclient::protocols{$_}{cfgvars} // {}}; }
sort(keys(%ddclient::protocols)))});
my @test_cases = (
{
desc => "ok value",
cachefile_lines => ["var_a=yes host_a"],
want => {host_a => {host => 'host_a', var_a => 1}},
},
{
desc => "unknown host",
cachefile_lines => ["var_a=yes host_c"],
want => {},
},
{
desc => "unknown var",
cachefile_lines => ["var_b=123 host_a"],
want => {host_a => {host => 'host_a'}},
},
{
desc => "invalid value",
cachefile_lines => ["var_a=wat host_a"],
want => {host_a => {host => 'host_a'}},
},
{
desc => "multiple entries",
cachefile_lines => [
"var_a=yes host_a",
"var_b=123 host_b",
],
want => {
host_a => {host => 'host_a', var_a => 1},
host_b => {host => 'host_b', var_b => 123},
},
},
{
desc => "non-recap vars are not loaded to %recap",
cachefile_lines => ["var_b_non_recap=foo host_b"],
want => {host_b => {host => 'host_b'}},
},
{
desc => "non-recap vars are scrubbed from %recap",
cachefile_lines => ["var_b_non_recap=foo host_b"],
recap => {host_b => {host => 'host_b', var_b_non_recap => 'foo'}},
want => {host_b => {host => 'host_b'}},
},
{
desc => "unknown hosts are scrubbed from %recap",
cachefile_lines => ["host_a", "host_c"],
recap => {host_a => {host => 'host_a'}, host_c => {host => 'host_c'}},
want => {host_a => {host => 'host_a'}},
},
);
for my $tc (@test_cases) {
my $cachef = File::Temp->new();
print($cachef join('', map("$_\n", "## $ddclient::program-$ddclient::version",
@{$tc->{cachefile_lines}})));
$cachef->close();
local $ddclient::globals{cache} = "$cachef";
local %ddclient::recap = %{$tc->{recap} // {}};
my %want_config = (
host_a => {protocol => 'protocol_a'},
host_b => {protocol => 'protocol_b'},
);
# Deep clone %want_config so we can check for changes.
local %ddclient::config;
$ddclient::config{$_} = {%{$want_config{$_}}} for keys(%want_config);
ddclient::read_recap($cachef->filename());
TODO: {
local $TODO = $tc->{want_TODO};
is_deeply(\%ddclient::recap, $tc->{want}, "$tc->{desc}: %recap")
or diag(ddclient::repr(Values => [\%ddclient::recap, $tc->{want}],
Names => ['*got', '*want']));
}
is_deeply(\%ddclient::config, \%want_config, "$tc->{desc}: %config")
or diag(ddclient::repr(Values => [\%ddclient::config, \%want_config],
Names => ['*got', '*want']));
}
done_testing();

150
t/skip.pl Normal file
View file

@ -0,0 +1,150 @@
use Test::More;
BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } }
BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); }
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(
sub { return [200, ['Content-Type' => 'text/plain'], ['::1 skip ::2']]; })
if httpd('6');
my $builtinwebv4 = 't/skip.pl webv4';
my $builtinwebv6 = 't/skip.pl webv6';
my $builtinfw = 't/skip.pl fw';
$ddclient::builtinweb{$builtinwebv4} = {'url' => httpd('4')->endpoint(), 'skip' => 'skip'};
$ddclient::builtinweb{$builtinwebv6} = {'url' => httpd('6')->endpoint(), 'skip' => 'skip'}
if httpd('6');
$ddclient::builtinfw{$builtinfw} = {name => 'test', skip => 'skip'};
%ddclient::builtinfw if 0; # suppress spurious warning "Name used only once: possible typo"
%ddclient::ip_strategies = (%ddclient::ip_strategies, ddclient::builtinfw_strategy($builtinfw));
%ddclient::ipv4_strategies =
(%ddclient::ipv4_strategies, ddclient::builtinfwv4_strategy($builtinfw));
%ddclient::ipv6_strategies =
(%ddclient::ipv6_strategies, ddclient::builtinfwv6_strategy($builtinfw));
sub run_test_case {
my %tc = @_;
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;
my $h = 't/skip.pl';
$ddclient::config{$h} = $tc{cfg};
%ddclient::config if 0; # suppress spurious warning "Name used only once: possible typo"
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});
}
}
subtest "use=web web='$builtinwebv4'" => sub {
run_test_case(
desc => "web-skip='' cancels built-in skip",
cfg => {
'use' => 'web',
'web' => $builtinwebv4,
'web-skip' => '',
},
want => '127.0.0.1',
);
run_test_case(
desc => 'web-skip=undef uses built-in skip',
cfg => {
'use' => 'web',
'web' => $builtinwebv4,
'web-skip' => undef,
},
want => '127.0.0.2',
);
};
subtest "usev4=webv4 webv4='$builtinwebv4'" => sub {
run_test_case(
desc => "webv4-skip='' cancels built-in skip",
cfg => {
'usev4' => 'webv4',
'webv4' => $builtinwebv4,
'webv4-skip' => '',
},
want => '127.0.0.1',
);
run_test_case(
desc => 'webv4-skip=undef uses built-in skip',
cfg => {
'usev4' => 'webv4',
'webv4' => $builtinwebv4,
'webv4-skip' => undef,
},
want => '127.0.0.2',
);
};
subtest "usev6=webv6 webv6='$builtinwebv6'" => sub {
run_test_case(
desc => "webv6-skip='' cancels built-in skip",
cfg => {
'usev6' => 'webv6',
'webv6' => $builtinwebv6,
'webv6-skip' => '',
},
ipv6 => 1,
want => '::1',
);
run_test_case(
desc => 'webv6-skip=undef uses built-in skip',
cfg => {
'usev6' => 'webv6',
'webv6' => $builtinwebv6,
'webv6-skip' => undef,
},
ipv6 => 1,
want => '::2',
);
};
subtest "use='$builtinfw'" => sub {
run_test_case(
desc => "fw-skip='' cancels built-in skip",
cfg => {
'fw' => httpd('4')->endpoint(),
'fw-skip' => '',
'use' => $builtinfw,
},
want => '127.0.0.1',
);
run_test_case(
desc => 'fw-skip=undef uses built-in skip',
cfg => {
'fw' => httpd('4')->endpoint(),
'fw-skip' => undef,
'use' => $builtinfw,
},
want => '127.0.0.2',
);
};
subtest "usev4='$builtinfw'" => sub {
run_test_case(
desc => "fwv4-skip='' cancels built-in skip",
cfg => {
'fwv4' => httpd('4')->endpoint(),
'fwv4-skip' => '',
'usev4' => $builtinfw,
},
want => '127.0.0.1',
);
run_test_case(
desc => 'fwv4-skip=undef uses built-in skip',
cfg => {
'fwv4' => httpd('4')->endpoint(),
'fwv4-skip' => undef,
'usev4' => $builtinfw,
},
want => '127.0.0.2',
);
};
done_testing();

94
t/ssl-validate.pl Normal file
View file

@ -0,0 +1,94 @@
use Test::More;
BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } }
BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); }
use ddclient::t::HTTPD;
use ddclient::t::ip;
local $ddclient::globals{debug} = 1;
local $ddclient::globals{verbose} = 1;
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);
my $h = 't/ssl-validate.pl';
my %ep = (
'4' => httpd('4', 1)->endpoint(),
'6' => httpd('6', 1) ? httpd('6', 1)->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) {
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::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});
is(ddclient::get_ipv6(ddclient::strategy_inputs('usev6', $h)), $tc->{want}, $tc->{desc})
if ($tc->{cfg}{usev6});
}
}
done_testing();

395
t/update_nics.pl Normal file
View file

@ -0,0 +1,395 @@
use Test::More;
BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } }
use File::Temp;
BEGIN { eval { require HTTP::Request; 1; } or plan(skip_all => $@); }
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($@); }
use ddclient::t::HTTPD;
use ddclient::t::ip;
httpd_required();
httpd('4')->run();
httpd('6')->run() if httpd('6');
local %ddclient::builtinweb = (
v4 => {url => "" . httpd('4')->endpoint()},
defined(httpd('6')) ? (v6 => {url => "" . httpd('6')->endpoint()}) : (),
);
# Sentinel value used by `mergecfg` that means "this hash entry should be deleted if it exists."
my $DOES_NOT_EXIST = [];
sub mergecfg {
my %ret;
for my $cfg (@_) {
next if !defined($cfg);
for my $h (keys(%$cfg)) {
if (refaddr($cfg->{$h}) == refaddr($DOES_NOT_EXIST)) {
delete($ret{$h});
next;
}
$ret{$h} = {%{$ret{$h} // {}}, %{$cfg->{$h}}};
for my $k (keys(%{$ret{$h}})) {
my $a = refaddr($ret{$h}{$k});
delete($ret{$h}{$k}) if defined($a) && $a == refaddr($DOES_NOT_EXIST);
}
}
}
return \%ret;
}
local $ddclient::globals{debug} = 1;
local $ddclient::globals{verbose} = 1;
local $ddclient::now = 1000;
our @updates;
local %ddclient::protocols = (
# The `legacy` protocol reads the legacy `wantip` property and sets the legacy `ip` and `status`
# properties. (Modern protocol implementations read `wantipv4` and `wantipv6` and set `ipv4`,
# `ipv6`, `status-ipv4`, and `status-ipv6`.) It always succeeds.
legacy => ddclient::LegacyProtocol->new(
update => sub {
my $self = shift;
ddclient::debug('in update');
push(@updates, [@_]);
for my $h (@_) {
local $ddclient::_l = ddclient::pushlogctx($h);
ddclient::debug('updating host');
$ddclient::recap{$h}{status} = 'good';
$ddclient::recap{$h}{ip} = delete($ddclient::config{$h}{wantip});
$ddclient::recap{$h}{mtime} = $ddclient::now;
}
ddclient::debug('returning from update');
},
),
);
my @test_cases = (
map({
my %cfg = %{delete($_->{cfg})};
my $desc = join(' ', map("$_=$cfg{$_}", keys(%cfg)));
{
desc => "legacy, fresh, $desc",
cfg => {host => {
'protocol' => 'legacy',
%cfg,
}},
want_reqs_webv4 => 1,
want_updates => [['host']],
want_recap_changes => {host => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
'status-ipv4' => 'good',
}},
%$_,
};
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
{
desc => 'legacy, fresh, use=web (IPv6)',
ipv6 => 1,
cfg => {host => {
'protocol' => 'legacy',
'use' => 'web',
'web' => 'v6',
}},
want_reqs_webv6 => 1,
want_updates => [['host']],
want_recap_changes => {host => {
'atime' => $ddclient::now,
'ipv6' => '2001:db8::1',
'mtime' => $ddclient::now,
'status-ipv6' => 'good',
}},
},
{
desc => 'legacy, fresh, usev6=webv6',
ipv6 => 1,
cfg => {host => {
'protocol' => 'legacy',
'usev6' => 'webv6',
}},
want_reqs_webv6 => 1,
want_updates => [['host']],
want_recap_changes => {host => {
'atime' => $ddclient::now,
'ipv6' => '2001:db8::1',
'mtime' => $ddclient::now,
'status-ipv6' => 'good',
}},
},
{
desc => 'legacy, fresh, usev4=webv4 usev6=webv6',
ipv6 => 1,
cfg => {host => {
'protocol' => 'legacy',
'usev4' => 'webv4',
'usev6' => 'webv6',
}},
want_reqs_webv4 => 1,
want_reqs_webv6 => 1,
want_updates => [['host']],
want_recap_changes => {host => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
'status-ipv4' => 'good',
}},
},
map({
my %cfg = %{delete($_->{cfg})};
my $desc = join(' ', map("$_=$cfg{$_}", keys(%cfg)));
{
desc => "legacy, no change, not yet time, $desc",
recap => {host => {
'atime' => $ddclient::now - ddclient::opt('min-interval'),
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now - ddclient::opt('min-interval'),
'status-ipv4' => 'good',
}},
cfg => {host => {
'protocol' => 'legacy',
%cfg,
}},
want_reqs_webv4 => 1,
%$_,
};
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
map({
my %cfg = %{delete($_->{cfg})};
my $desc = join(' ', map("$_=$cfg{$_}", keys(%cfg)));
{
desc => "legacy, min-interval elapsed but no change, $desc",
recap => {host => {
'atime' => $ddclient::now - ddclient::opt('min-interval') - 1,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now - ddclient::opt('min-interval') - 1,
'status-ipv4' => 'good',
}},
cfg => {host => {
'protocol' => 'legacy',
%cfg,
}},
want_reqs_webv4 => 1,
%$_,
};
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
map({
my %cfg = %{delete($_->{cfg})};
my $desc = join(' ', map("$_=$cfg{$_}", keys(%cfg)));
{
desc => "legacy, needs update, not yet time, $desc",
recap => {host => {
'atime' => $ddclient::now - ddclient::opt('min-interval'),
'ipv4' => '192.0.2.2',
'mtime' => $ddclient::now - ddclient::opt('min-interval'),
'status-ipv4' => 'good',
}},
cfg => {host => {
'protocol' => 'legacy',
%cfg,
}},
want_reqs_webv4 => 1,
want_recap_changes => {host => {
'warned-min-interval' => $ddclient::now,
}},
%$_,
};
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
map({
my %cfg = %{delete($_->{cfg})};
my $desc = join(' ', map("$_=$cfg{$_}", keys(%cfg)));
{
desc => "legacy, min-interval elapsed, needs update, $desc",
recap => {host => {
'atime' => $ddclient::now - ddclient::opt('min-interval') - 1,
'ipv4' => '192.0.2.2',
'mtime' => $ddclient::now - ddclient::opt('min-interval') - 1,
'status-ipv4' => 'good',
}},
cfg => {host => {
'protocol' => 'legacy',
%cfg,
}},
want_reqs_webv4 => 1,
want_updates => [['host']],
want_recap_changes => {host => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
}},
%$_,
};
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
map({
my %cfg = %{delete($_->{cfg})};
my $desc = join(' ', map("$_=$cfg{$_}", keys(%cfg)));
{
desc => "legacy, previous failed update, not yet time to retry, $desc",
recap => {host => {
'atime' => $ddclient::now - ddclient::opt('min-error-interval'),
'ipv4' => '192.0.2.2',
'mtime' => $ddclient::now - max(ddclient::opt('min-error-interval'),
ddclient::opt('min-interval')) - 1,
'status-ipv4' => 'failed',
}},
cfg => {host => {
'protocol' => 'legacy',
%cfg,
}},
want_reqs_webv4 => 1,
want_recap_changes => {host => {
'warned-min-error-interval' => $ddclient::now,
}},
%$_,
};
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
map({
my %cfg = %{delete($_->{cfg})};
my $desc = join(' ', map("$_=$cfg{$_}", keys(%cfg)));
{
desc => "legacy, previous failed update, time to retry, $desc",
recap => {host => {
'atime' => $ddclient::now - ddclient::opt('min-error-interval') - 1,
'ipv4' => '192.0.2.2',
'mtime' => $ddclient::now - ddclient::opt('min-error-interval') - 2,
'status-ipv4' => 'failed',
}},
cfg => {host => {
'protocol' => 'legacy',
%cfg,
}},
want_reqs_webv4 => 1,
want_updates => [['host']],
want_recap_changes => {host => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
'status-ipv4' => 'good',
}},
%$_,
};
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
map({
my %cfg = %{delete($_->{cfg})};
my $desc = join(' ', map("$_=$cfg{$_}", sort(keys(%cfg))));
{
desc => "deduplicates identical IP discovery, $desc",
cfg => {
hosta => {protocol => 'legacy', %cfg},
hostb => {protocol => 'legacy', %cfg},
},
want_reqs_webv4 => 1,
want_updates => [['hosta', 'hostb']],
want_recap_changes => {
hosta => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
'status-ipv4' => 'good',
},
hostb => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
'status-ipv4' => 'good',
},
},
%$_,
};
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
{
desc => "deduplicates identical IP discovery, usev6=webv6",
ipv6 => 1,
cfg => {
hosta => {protocol => 'legacy', usev6 => 'webv6'},
hostb => {protocol => 'legacy', usev6 => 'webv6'},
},
want_reqs_webv6 => 1,
want_updates => [['hosta', 'hostb']],
want_recap_changes => {
hosta => {
'atime' => $ddclient::now,
'ipv6' => '2001:db8::1',
'mtime' => $ddclient::now,
'status-ipv6' => 'good',
},
hostb => {
'atime' => $ddclient::now,
'ipv6' => '2001:db8::1',
'mtime' => $ddclient::now,
'status-ipv6' => 'good',
},
},
},
);
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} && !$httpd_ipv6_supported;
subtest($tc->{desc} => sub {
local $ddclient::_l = ddclient::pushlogctx($tc->{desc});
for my $ipv ('4', '6') {
$tc->{"want_reqs_webv$ipv"} //= 0;
my $want = $tc->{"want_reqs_webv$ipv"};
next if !defined(httpd($ipv)) && $want == 0;
local $ddclient::_l = ddclient::pushlogctx("IPv$ipv");
my $ip = $ipv eq '4' ? '192.0.2.1' : '2001:db8::1';
httpd($ipv)->reset(([200, $textplain, [$ip]]) x $want);
}
$tc->{recap}{$_}{host} //= $_ for keys(%{$tc->{recap} // {}});
# Deep copy `%{$tc->{recap}}` so that updates to `%ddclient::recap` don't mutate it.
local %ddclient::recap = %{mergecfg($tc->{recap})};
my $cachef = File::Temp->new();
# $cachef is an object that stringifies to a filename.
local $ddclient::globals{cache} = "$cachef";
$tc->{cfg} = {map({
($_ => {
host => $_,
web => 'v4',
webv4 => 'v4',
webv6 => 'v6',
%{$tc->{cfg}{$_}},
});
} keys(%{$tc->{cfg} // {}}))};
# Deep copy `%{$tc->{cfg}}` so that updates to `%ddclient::config` don't mutate it.
local %ddclient::config = %{mergecfg($tc->{cfg})};
local @updates;
ddclient::update_nics();
for my $ipv ('4', '6') {
next if !defined(httpd($ipv));
local $ddclient::_l = ddclient::pushlogctx("IPv$ipv");
my @gotreqs = httpd($ipv)->reset();
my $got = @gotreqs;
my $want = $tc->{"want_reqs_webv$ipv"};
is($got, $want, "number of requests to webv$ipv service");
}
TODO: {
local $TODO = $tc->{want_updates_TODO};
is_deeply(\@updates, $tc->{want_updates} // [], 'got expected updates')
or diag(ddclient::repr(Values => [\@updates, $tc->{want_updates}],
Names => ['*got', '*want']));
}
my %want_recap = %{mergecfg($tc->{recap}, $tc->{want_recap_changes})};
TODO: {
local $TODO = $tc->{want_recap_changes_TODO};
is_deeply(\%ddclient::recap, \%want_recap, 'recap matches')
or diag(ddclient::repr(Values => [\%ddclient::recap, \%want_recap],
Names => ['*got', '*want']));
}
my %want_cfg = %{mergecfg($tc->{cfg}, $tc->{want_cfg_changes})};
TODO: {
local $TODO = $tc->{want_cfg_changes_TODO};
is_deeply(\%ddclient::config, \%want_cfg, 'config matches')
or diag(ddclient::repr(Values => [\%ddclient::config, \%want_cfg],
Names => ['*got', '*want']));
}
});
}
}
done_testing();

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

87
t/use_web.pl Normal file
View file

@ -0,0 +1,87 @@
use Test::More;
BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } }
BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); }
use ddclient::t::HTTPD;
use ddclient::t::ip;
httpd_required();
my $builtinweb = 't/use_web.pl builtinweb';
my $h = 't/use_web.pl hostname';
my $headers = [
@$textplain,
'this-ipv4-should-be-ignored' => 'skip skip2 192.0.2.255',
'this-ipv6-should-be-ignored' => 'skip skip2 2001:db8::ff',
];
httpd('4')->run(sub { return [200, $headers, ['192.0.2.1 skip 192.0.2.2 skip2 192.0.2.3']]; });
httpd('6')->run(sub { return [200, $headers, ['2001:db8::1 skip 2001:db8::2 skip2 2001:db8::3']]; })
if httpd('6');
my %ep = (
'4' => httpd('4')->endpoint(),
'6' => httpd('6') ? httpd('6')->endpoint() : undef,
);
my @test_cases;
for my $ipv ('4', '6') {
my $ipv4 = $ipv eq '4';
for my $sfx ('', "v$ipv") {
push(
@test_cases,
{
desc => "use$sfx=web$sfx web$sfx=<url> IPv$ipv",
ipv6 => !$ipv4,
cfg => {"use$sfx" => "web$sfx", "web$sfx" => $ep{$ipv}},
want => $ipv4 ? '192.0.2.1' : '2001:db8::1',
},
{
desc => "use$sfx=web$sfx web$sfx=<url> web$sfx-skip=skip IPv$ipv",
ipv6 => !$ipv4,
cfg => {"use$sfx" => "web$sfx", "web$sfx" => $ep{$ipv}, "web$sfx-skip" => 'skip'},
# Note that "skip" should skip past the first "skip" and not past "skip2".
want => $ipv4 ? '192.0.2.2' : '2001:db8::2',
},
{
desc => "use$sfx=web$sfx web$sfx=<builtinweb> IPv$ipv",
ipv6 => !$ipv4,
cfg => {"use$sfx" => "web$sfx", "web$sfx" => $builtinweb},
biw => {url => $ep{$ipv}},
want => $ipv4 ? '192.0.2.1' : '2001:db8::1',
},
{
desc => "use$sfx=web$sfx web$sfx=<builtinweb w/skip> IPv$ipv",
ipv6 => !$ipv4,
cfg => {"use$sfx" => "web$sfx", "web$sfx" => $builtinweb},
biw => {url => $ep{$ipv}, skip => 'skip'},
# Note that "skip" should skip past the first "skip" and not past "skip2".
want => $ipv4 ? '192.0.2.2' : '2001:db8::2',
},
{
desc => "use$sfx=web$sfx web$sfx=<builtinweb w/skip> web$sfx-skip=skip2 IPv$ipv",
ipv6 => !$ipv4,
cfg => {"use$sfx" => "web$sfx", "web$sfx" => $builtinweb, "web$sfx-skip" => 'skip2'},
biw => {url => $ep{$ipv}, skip => 'skip'},
want => $ipv4 ? '192.0.2.3' : '2001:db8::3',
},
);
}
}
for my $tc (@test_cases) {
local $ddclient::builtinweb{$builtinweb} = $tc->{biw};
$ddclient::builtinweb if 0;
local $ddclient::config{$h} = $tc->{cfg};
$ddclient::config if 0;
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;
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();

100
t/variable_defaults.pl Normal file
View file

@ -0,0 +1,100 @@
use Test::More;
BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } }
BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); }
use re qw(is_regexp);
my %variable_collections = (
map({ ($_ => $ddclient::cfgvars{$_}) } grep($_ ne 'merged', keys(%ddclient::cfgvars))),
map({ ("protocol=$_" => $ddclient::protocols{$_}{cfgvars}); } keys(%ddclient::protocols)),
);
my %seen;
my @test_cases = (
map({
my $vcn = $_;
my $vc = $variable_collections{$_};
map({
my $def = $vc->{$_};
my $seen = exists($seen{$def});
$seen{$def} = undef;
({desc => "$vcn $_", def => $vc->{$_}}) x !$seen;
} sort(keys(%$vc)));
} sort(keys(%variable_collections))),
);
for my $tc (@test_cases) {
if ($tc->{def}{required}) {
is($tc->{def}{default}, undef, "'$tc->{desc}' (required) has no default");
} else {
# Preserve all existing variables in $cfgvars{merged} so that variables with dynamic
# defaults can reference them.
local %ddclient::cfgvars = (merged => {
%{$ddclient::cfgvars{merged}},
'var for test' => $tc->{def},
});
# Variables with dynamic defaults will need their own unit tests, but we can still check the
# clean-slate hostless default.
local %ddclient::config;
local %ddclient::opt;
local %ddclient::globals;
my $norm;
my $default = ddclient::default('var for test');
diag("'$tc->{desc}' default: " . ($default // '<undefined>'));
is($default, $tc->{def}{default}, "'$tc->{desc}' default() return value matches default")
if ref($tc->{def}{default}) ne 'CODE';
my $valid = eval { $norm = ddclient::check_value($default, $tc->{def}); 1; } or diag($@);
ok($valid, "'$tc->{desc}' (optional) has a valid default");
is($norm, $default, "'$tc->{desc}' default normalizes to itself") if $valid;
}
}
my @use_test_cases = (
{
desc => 'clean slate hostless default',
want => 'ip',
},
{
desc => 'usage string',
host => '<usage>',
want => qr/disabled.*ip|ip.*disabled/,
},
{
desc => 'usev4 disables use by default',
host => 'host',
cfg => {usev4 => 'webv4'},
want => 'disabled',
},
{
desc => 'usev6 disables use by default',
host => 'host',
cfg => {usev4 => 'webv4'},
want => 'disabled',
},
{
desc => 'explicitly setting use re-enables it',
host => 'host',
cfg => {use => 'web', usev4 => 'webv4'},
want => 'web',
},
);
for my $tc (@use_test_cases) {
my $desc = "'use' dynamic default: $tc->{desc}";
local %ddclient::protocols = (protocol => ddclient::Protocol->new());
local %ddclient::cfgvars = (merged => {
'protocol' => $ddclient::cfgvars{'merged'}{'protocol'},
'use' => $ddclient::cfgvars{'protocol-common-defaults'}{'use'},
'usev4' => $ddclient::cfgvars{'merged'}{'usev4'},
'usev6' => $ddclient::cfgvars{'merged'}{'usev6'},
});
local %ddclient::config = (host => {protocol => 'protocol', %{$tc->{cfg} // {}}});
local %ddclient::opt;
local %ddclient::globals;
my $got = ddclient::opt('use', $tc->{host});
if (is_regexp($tc->{want})) {
like($got, $tc->{want}, $desc);
} else {
is($got, $tc->{want}, $desc);
}
}
done_testing();

View file

@ -4,6 +4,59 @@ use version;
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
eval { require 'ddclient'; } or BAIL_OUT($@);
is(ddclient->VERSION(), version->parse('v@PACKAGE_VERSION@'), "version matches Autoconf config");
ok(ddclient::parse_version($ddclient::VERSION),
"module's Perl version string is in opinionated form");
my $n = qr/0|[1-9]\d{0,2}/;
like($ddclient::version, qr/^$n\.$n\.$n(?:-alpha|-beta\.$n|-rc\.$n|\+r\.$n)?$/,
"human-readable version is in opinionated form");
my @tcs = (
['v1.0_0', '1-alpha'],
['v1.0.0_0', '1.0-alpha'],
['v1.2.3.0_0', '1.2.3-alpha'],
['v1.2.3.4.0_0', '1.2.3.4-alpha'],
['v1.0_1', '1-beta.1'],
['v1.0.0_1', '1.0-beta.1'],
['v1.2.3.0_1', '1.2.3-beta.1'],
['v1.2.3.4.0_1', '1.2.3.4-beta.1'],
['v1.2.3.0_899', '1.2.3-beta.899'],
['v1.0_901', '1-rc.1'],
['v1.0.0_901', '1.0-rc.1'],
['v1.2.3.0_901', '1.2.3-rc.1'],
['v1.2.3.4.0_901', '1.2.3.4-rc.1'],
['v1.2.3.0_998', '1.2.3-rc.98'],
['v1.999', '1'],
['v1.0.999', '1.0'],
['v1.2.3.999', '1.2.3'],
['v1.2.3.4.999', '1.2.3.4'],
['v1.999.1', '1+r.1'],
['v1.0.999.1', '1.0+r.1'],
['v1.2.3.999.1', '1.2.3+r.1'],
['v1.2.3.4.999.1', '1.2.3.4+r.1'],
['v1.2.3.999.999', '1.2.3+r.999'],
[$ddclient::VERSION, $ddclient::version],
);
subtest 'humanize_version' => sub {
for my $tc (@tcs) {
my ($pv, $want) = @$tc;
is(ddclient::humanize_version($pv), $want, "$pv -> $want");
}
};
subtest 'human-readable version can be translated back to Perl version' => sub {
for my $tc (@tcs) {
my ($want, $hv) = @$tc;
my $pv = "v$hv";
$pv =~ s/^(?!.*-)(.*?)(?:\+r\.(\d+))?$/"$1.999" . (defined($2) ? ".$2" : "")/e;
$pv =~ s/-alpha$/.0_0/;
$pv =~ s/-beta\.(\d+)$/.0_$1/;
$pv =~ s/-rc\.(\d+)$/'.0_' . (900 + $1)/e;
is($pv, $want, "$hv -> $want");
}
};
is($ddclient::version, '@PACKAGE_VERSION@', "version matches version in Autoconf");
done_testing();

View file

@ -35,7 +35,7 @@ my @test_cases = (
for my $tc (@test_cases) {
$warning = undef;
ddclient::write_cache($tc->{f});
ddclient::write_recap($tc->{f});
subtest $tc->{name} => sub {
if (defined($tc->{warning_regex})) {
like($warning, $tc->{warning_regex}, "expected warning message");