The zone lookup walked the challenge name from the right and ended up
asking netcup for the full "_acme-challenge.<domain>" as a zone name.
That can never be a zone, so netcup answered 4013 "Validation Error",
which replaced the real 5028 "The zone <domain> could not be found" as
the error shown to the user.
Stop one label short of the full name, and fail explicitly when no zone
matched, reporting the last API response plus what to check. Before, a
run where every candidate returned 5028 fell through to logout and
returned success.
* feat: added nexdns dnsapi
Adds a DNS-01 hook for NexDNS, an authoritative DNS service with a REST API.
dns_nexdns_add walks the label list to find the zone that owns the challenge
name and creates the TXT record in it. dns_nexdns_rm lists the TXT records at
that name, picks the one carrying exactly this challenge value and deletes it
by id, so a wildcard and its base domain do not remove each other's record.
A 429 is waited out and the request retried, in the shape dns_hetznercloud.sh
and dns_bunny.sh already use.
* dns_nexdns: cap the rate-limit wait, judge success by status, add the tracking issue
_get_root_by_getList() matched the candidate suffix as an unanchored
substring of the whole domains.getList response and never looked at the
IsOurDNS attribute. A domain parked on Namecheap's webhosting DNS is
listed with IsOurDNS="false", yet it was still accepted as the root zone,
so _get_root() returned success and the domains.dns.getHosts probe that
would have found the real zone never ran. Every following getHosts call
was then refused with error 2030288 "not using proper DNS servers" and
the challenge failed with "invalid tld".
Match the exact <Domain Name="..."> entry instead and require
IsOurDNS="true", so a subdomain delegated to Namecheap BasicDNS/FreeDNS
under a parent that is not on Namecheap DNS now resolves to its own zone.
Matching the entry exactly also drops the old substring/regex match, in
which the dots of a domain matched any character.
Fixes#7178
Ubuntu / Ubuntu (Smallstep Intermediate CA, Smallstep Intermediate CA, , 1, https://localhost:9000/acme/acme/directory, 1, , 172.17.0.1) (push) Has been cancelled
_cyon_delete_txt relied on `printf "%b"` to convert a sed-injected literal
`\n` into a real newline, but `%b` also processes the `\"` escapes that the
JSON response is full of. glibc/bash/dash keep the backslash of such an
undefined escape, FreeBSD's printf (sh builtin and /usr/bin/printf alike)
drops it -- so `data-hash=\"..\"` became `data-hash=".."`, the extraction
regex matched nothing, _dns_entries stayed empty and no TXT record was ever
deleted.
Drop the newline injection and use _egrep_o, which already yields one match
per line, then parse each line with sed.
Also feed the read loop a newline-terminated list: `printf "%s"` left the
last line unterminated, so `read` returned non-zero at EOF and the loop
skipped the final entry on every platform.
Verified identical output on FreeBSD 14.3, Linux/bash and Linux/dash.
Fixes#7169
* Refactor dns_freemyip.sh for clarity and compatibility
Updated dns_freemyip.sh for better readability and compatibility with ASUSWRT-Merlin. Improved error handling and response logging.
* Update author information in dns_freemyip.sh
* replace both loops with POSIX shell counters
replace both loops with POSIX shell counters
* Typo
Typo
* Fix error message for freemyip API request failure
Remove existing token leak. Not my regression.
* Refactor retry logic and improve error handling
* Remove unnecessary blank lines in dns_freemyip.sh
* Clean up dns_freemyip.sh by removing blank lines
Removed unnecessary blank lines in the script to improve readability.
* dns_yc: restore YC_SA_Key_File in dns_yc_rm before signing the JWT
dns_yc_rm() never rebuilt YC_SA_Key_File from YC_SA_Key_File_PEM_b64 /
YC_SA_Key_File_Path like dns_yc_add() does. Per the DNS API dev guide,
add()/rm() run in separate subshells, so rm() must repeat add()'s setup
steps rather than rely on variables set during add().
Without it, when _yc_login() needs a fresh JWT during removal (the IAM
token from the add phase isn't available), it signs with an empty/unset
key path, and openssl fails with "Unknown key file format". The
resulting auth failure then surfaces misleadingly as "invalid domain" in
_get_root, and the TXT record is never deleted.
Verified against a real Yandex Cloud account/zone with --staging: before
the fix, removal failed with the same errors reported in the issue;
after adding the missing key-restoration block, add + remove both
succeed and the TXT record is actually deleted.
* dns_yc: preserve other TXT values when removing one at the same name
dns_yc_rm previously sent the full current data array (all existing
TXT values at the name) to the deletions API, wiping out the whole
rrset instead of only the value being removed. This breaks wildcard +
base domain issuance, where both share the same _acme-challenge name
with two different values: removing the first one deleted both,
leaving nothing for the second removal to find.
* dns_yc: read persisted config from domain conf before account conf
YC_Zone_ID, YC_Folder_ID, YC_SA_ID, YC_SA_Key_ID (zone-ID mode) and
YC_SA_Key_File_PEM_b64/Path were always saved via _savedomainconf
(domain.conf), but only ever read back via _readaccountconf_mutable
(account.conf). Once the env vars were unset, none of these could be
recovered from the saved config, so dns_yc_add/dns_yc_rm failed with
"You didn't specify a YC_SA_ID or YC_SA_Key_ID or YC_SA_Key_File."
even though the values had been persisted correctly on the prior run.
* dns_yc: replace grep -Fxv/sed with a portable loop in dns_yc_rm
Solaris's /usr/bin/grep supports neither -F nor -x, so
_remaining_txtvalue was always empty there and the preserve-other-
values logic silently fell back to deleting the whole rrset (with a
grep usage error on stderr on every rm). The sed trailing-comma strip
had a matching issue on Solaris, whose sed drops an unterminated last
line. CI didn't catch this because the fallback path also returns
"done: true". Use a plain for-loop with word splitting instead.
* dns_yc: use upsertRecordSets.deletions to remove a single TXT value
updateRecordSets has no "merges" field (only deletions/additions), so
the previous preserve-other-values logic silently did nothing -- the
TXT record was never actually removed, a regression from before that
change (which at least deleted the whole rrset). CI didn't catch it
because _clearupdns runs dns_yc_rm in a subshell and ignores its exit
code.
upsertRecordSets.deletions removes only the specified value from the
rrset directly, so the getRecordSet read and the remaining-value
recomputation are no longer needed at all.
Verified against a real zone (base + wildcard domain sharing one
_acme-challenge name): adding both values then removing one leaves
the other in place, and removing the second cleans up fully.
* dns_yc: don't delete the user's own key file in YC_SA_Key_File_Path mode
_yc_login unconditionally rm'd $YC_SA_Key_File after signing. That's
fine for the PEM_b64 path, where it's a decoded temp file, but in
YC_SA_Key_File_Path mode it's the user's own persistent key file --
the first successful login permanently deleted it, so every
subsequent dns_yc_rm/renewal hit "Unknown key file format" (the exact
symptom this PR is about, just from a different cause). Track whether
the key file is our own temp copy and only delete it in that case.
Verified with a stubbed _yc_login: a temp-mode key gets removed after
login, a path-mode key survives.
* dns_yc: clear both domain and account conf on invalid config
The failure branch in dns_yc_add only ever called _clearaccountconf,
but YC_Zone_ID/YC_Folder_ID/YC_SA_Key_File_PEM_b64/Path are persisted
via _savedomainconf, and YC_SA_ID/YC_SA_Key_ID may have been saved via
_saveaccountconf_mutable (Folder_ID mode, which stores under a
SAVED_ prefix read back by _readaccountconf_mutable). Clearing only
one store left stale values behind in whichever one wasn't touched.
Verified by seeding both domain.conf and account.conf with leftover
values, then triggering this branch and confirming both config files
end up empty.
Solaris /usr/bin/grep has no -A ("illegal option -- A"), so _getAKI
printed an error to stderr on every cron renewal and returned empty.
The empty AKI silently corrupts the RFC 9773 ARI certID, so ARI is
never available and renewal falls back to the fixed schedule.
Split the pipeline into a testable stdin filter _extractAKI and select
the value line with a portable sed range instead.
Same fix for the two hooks that still used grep -A: dns_world4you.sh
(also replaces the GNU-only "\s" in the same expression) and
deploy/keyhelp.sh (the -A 2 window could truncate the div range that
follows it, so it is just dropped).
https://github.com/acmesh-official/acme.sh/issues/7159
OmniOS native egrep -o infinite-loops emitting empty lines when the
pattern can match the empty string, so `_egrep_o "[^:]*$"` never lets
the pipeline finish and dns_yc hangs until the CI timeout. Require at
least one character instead. `+` is not usable because the sed fallback
in _egrep_o parses BRE.
Ubuntu / Ubuntu (Smallstep Intermediate CA, Smallstep Intermediate CA, , 1, https://localhost:9000/acme/acme/directory, 1, , 172.17.0.1) (push) Waiting to run
dnsapi/deploy: remove POSIX character classes from sed/grep patterns
Solaris /usr/bin/sed and /usr/bin/grep parse [[:space:]] etc. as a
literal bracket set and silently mis-match. Replace with [ ]* for
JSON matching, a printf-tab bracket for user-input trimming, and
[0-9] for digits; also drop GNU-only sed -r/-E in rage4, selfhost
and selectel, and reuse _strip_blank_lines in byteplus_alb.
The OCI DNS plugin cached the tenancy, user, region and signing key into
acme.sh's account.conf at issuance and then, on subsequent runs, read those
cached values before consulting ~/.oci/config. A value cached at issuance
therefore permanently shadowed the config file, so editing ~/.oci/config
afterwards (most visibly rotating the API signing key) had no effect and
renewals kept using stale credentials, failing authentication.
Resolve each field in the order: explicit environment variable, then
~/.oci/config when it exists, then the cached account.conf value as a
fallback for env-only installs that have no config file. The signing key
likewise prefers the key_file resolved from the environment or ~/.oci/config
over any cached key.
Signed-off-by: Avi Miller <me@dje.li>
The sed in _get_zone passed the raw JSON through when the response
contained no [{"fqdn":", so an API error turned the zone into "{" and
the failure only surfaced later as POST /2/zones/{/records
"method_not_found". Log the response at debug2, error out on
non-success results, and parse fqdn position-independently.
https://github.com/acmesh-official/acme.sh/issues/6851
The zone cannot be derived from the record name when the Knot server
is only authoritative for a delegated subdomain; let the user name it
explicitly, like NSUPDATE_ZONE.
fixes https://github.com/acmesh-official/acme.sh/issues/2881
The legacy plain _saveaccountconf copy in account.conf is sourced at
startup and silently overrides a newly exported DNSimple_OAUTH_TOKEN,
so rotated tokens never took effect.
fixes https://github.com/acmesh-official/acme.sh/issues/3422
* Add files via upload
* New Banner
Updated README to include responsive images for dark and light modes.
* Add files via upload
Remove usage of jq and curl
should be compliant with acme.sh api dev guide
* Add files via upload
* Add files via upload
change CR LF to LF
* Add files via upload
missing CR
* Add files via upload
* Add files via upload
* Add files via upload
* Add files via upload
* Add files via upload
* Add files via upload
correct auth
* Add files via upload
better manage group_id
* Add files via upload
manage wrong / missing domain
* Add files via upload
strip domain to manage subdomain requests
* Add files via upload
fix wrong id selection in get root
* Add files via upload
fix parsing of IDs
* Add files via upload
correct get_root to better handle unexisting domaines (acmetest)
* Add files via upload
correct token incorrect in auth
* Add files via upload
manage case web api reply is empty
* Add files via upload
try to resolve error when adding unexisting subdmain txt entry
* Add files via upload
correct domain parsing
* Add files via upload
revert changes when adding record (name)
change rm to better handle complex urls
* Add files via upload
correct rm function to better manage records deletion
* Add files via upload
ensure auth variable arent lost during calls
* Add files via upload
try to keep autj variables accross executions
* Add files via upload
* Add files via upload
* Add files via upload
* Add files via upload
* Add files via upload
* Add files via upload
fix stripping subdomains
* Add files via upload
* Add files via upload
keep auth token instead of requesting it each time
* Add files via upload
debug
* Add files via upload
* Add files via upload
* Add files via upload
better manage record deletion to avoid orphans
added some debug and checks
* Add files via upload
paginated api support for rm function
* Add files via upload
* Add files via upload
delay to manage api 404
* Add files via upload
enhance parsing of records in rm
* Add files via upload
fix incorrect record detection in rm
* Add files via upload
better manage filters on api to limit errors
* Add files via upload
try to handle 404 when requesting API too frequently
* Add files via upload
* Add files via upload
sleep during auth
* Add files via upload
manage 404 errors in get_root
* Add files via upload
correct variable overide
translate comments
prefix all variables
* Add files via upload
correct variable
* Add files via upload
typo
* Add files via upload
* Add files via upload
* Add files via upload
* Add files via upload
correct variable naming (_comlaude prefix missing)
correct rm return code when non existing record
typo
* Add files via upload
log an info instead of an error if no dns record found in RM function.
---------
Co-authored-by: neil <github@neilpang.com>
Co-authored-by: Matthiasvpfr <matthias.dupont@vetpartners.fr>
Co-authored-by: ZeroSSL-Andreas <andreas.schuster@hidglobal.com>
The unfiltered GET /zones lists every zone on the server; with large
installations (100k zones) root-zone detection took minutes per domain.
Probe each walk-up candidate with ?zone=<name> instead (exact match per
the PowerDNS API docs); servers that ignore the parameter return the
full list, which the existing check still handles.
https://github.com/acmesh-official/acme.sh/issues/6382
With both domain.tld and sub.domain.tld zones on the account, the first
endswith hit could pick the parent zone while cPanel stores the record
in the most specific one, so the cleanup never found the record and
left an orphaned _acme-challenge TXT entry. Pick the longest matching
zone with an exact literal suffix match (_endswith treats the needle as
a regex, letting xdomain.tld wrongly match zone domain.tld).
https://github.com/acmesh-official/acme.sh/issues/6807
* [Microwavenby--dns_hostinger] Adding initial dns support for Hostinger.com
* [Microwavenby--dns_hostinger] Creating a commit now that workflows are enabled
* [Microwavenby--dns_hostinger] Correcting shellcheck. Why is this not automatic?
* [Microwavenby-dns-hostinger] Responding to comments from Neil
* [dns-hostinger] SHfmt and Shellcheck
* [dns-hostinger] Writing non-greedy-ish regexes. correcting copypasta
Getting client_id failed due to incorrect extraction!
At least in version 3.2.7p1 and probably later the plugin is not working any more properly. The result of ```curResult="$(_post "${curData}" "${ISPC_Api}?client_get_id")"``` is something like this ```Result of _ISPC_ClientGetID: '[Tue Jan 23 11:44:57 CET 2024] Retrying post
{"code":"ok","message":"","response":3}[Tue Jan 23 11:44:57 CET 2024] _hcode 0'```. The parsing code does not work properly and leaves a non numeric value such as ```Client ID: '3[Tue Jan 23 11'```.
The Njalla API returns record ids as JSON strings now; the numeric-only
pattern matched nothing, so the removal never found the record id.
Match both quoted and bare ids.
from https://github.com/acmesh-official/acme.sh/pull/5121
Ubuntu / Ubuntu (Smallstep Intermediate CA, Smallstep Intermediate CA, , 1, https://localhost:9000/acme/acme/directory, 1, , 172.17.0.1) (push) Waiting to run
LC_ALL in the environment overrides both LC_TIME and LANG, so LANG=C
alone still produced localized day/month names on non-English systems
and DNS Made Easy rejected the request date header. An LC_ALL=C
command prefix beats every locale variable (same pattern as
dns_oci.sh).
Fixes#4272. Closes#4271. Thanks to @Nickinthebox.
OpenBSD grep treats \| in a BRE as a literal | character, so
_freedns_domain_id never matched any row and every domain lookup
failed with "Domain not found". Switch to ERE with -E, keeping the
parens escaped so the (.*) suffix branch still requires literal
parentheses and does not widen the match (e.g. searching example.com
must not match example.company).
Reported-by: @katiekloss @boretom
Ref: https://github.com/acmesh-official/acme.sh/issues/2305
* New Banner
Updated README to include responsive images for dark and light modes.
* acme-sh-creoline-as-dns-provider
* acme-sh-creoline-as-dns-provider - Review changes implemented according code review
* acme-sh-creoline-as-dns-provider - Review changes implemented according second code review, minding --cron
* acme-sh-creoline-as-dns-provider - Remove debug code
* acme-sh-creoline-as-dns-provider - shfmt formatting according Code of conduct
---------
Co-authored-by: neil <github@neilpang.com>
Co-authored-by: ZeroSSL-Andreas <andreas.schuster@hidglobal.com>
Co-authored-by: Steven Kauschke <s.kauschke@creoline.com>
* Added LaoDC API Module
* Cleaned up debug and info
revised get subdomain to filter by TXT records.
* Added commet to _get_root
* Removed PATCH logic of updating acme records as this doesn't work for wildcard DNS.
Revised rm() function to do explicit record matching.
* Revised _get_root() to handle different scenarios.
Fixed _laodc_api() function to check if query failed to run.
added basic json sanitation to handle \ and " in $value
unset _H2 _H3 after call as per request from copilot.
* fixed indentation of case statement block
* fixed condition checking.
_get_root should start at 1 so full fqdn can be tested
$? was being reference after export command failing response checks
removed export txtvalue
fixed docs link and issues link
* Verify key for both add and rm
* fixed dns alias condition check
validate key returns 1 if failed.
---------
Co-authored-by: neil <github@neilpang.com>
Co-authored-by: LaoDC <git@laodc.com>
_get_root matched a registered domain anywhere as a substring of the
challenge domain, so with both "test.com.ru" and "subtest.com.ru" in the
account, issuing for subtest.com.ru wrongly picked test.com.ru as the
root (it is a substring of "sub-test.com.ru"). Anchor the match to a '.'
boundary so a shorter domain no longer matches a longer subdomain label.
Fixes the issue reported in #5036 (thanks @koledas)
Closes#5036
The domain list is paginated at 1000 entries per page and only the
first page was fetched, so accounts with more than 1000 domains never
found the root zone.
fix https://github.com/acmesh-official/acme.sh/issues/5051
* Add dns_muumuu: muumuu-domain.com DNS API
* Fix: remove local keyword for POSIX sh compatibility
* Fix: lowercase fulldomain for API compatibility
* Style: use echo instead of printf for lower_case (consistent with other plugins)
* Fix: prefix rest vars, clear _H4/_H5, guard record_id, update Issues URL
* Add WEDOS WAPI DNS API (dns_wedos)
* dns_wedos: fix response parsing on systems without egrep -o
* dns_wedos: report WAPI auth errors, UTC fallback for hosts ignoring TZ
Ubuntu / Ubuntu (Smallstep Intermediate CA, Smallstep Intermediate CA, , 1, https://localhost:9000/acme/acme/directory, 1, , 172.17.0.1) (push) Waiting to run
Core-Networks' API rejects Unicode domain names with "invalid domain";
it requires punycode. dns_cn_add / dns_cn_rm passed the raw challenge
domain straight through, so IDN certs failed at the TXT add step
(issue #4804). Run fulldomain through _idn() in both functions. For
ASCII/punycode input _idn() is a pass-through, so non-IDN domains are
unaffected.
Fixes#4804
_get_root matched the candidate zone with _contains (grep), which treats
the domain as a regex. For "-d g.<zone>" the candidate "g.<zone>" matched
"<string>...<zone>" because '.' matches the '>' after "string" and the 'g'
comes from the "<string>" tag, so "g.<zone>" was wrongly taken as the root
zone (sub=_acme-challenge instead of _acme-challenge.g). Anchor the match
to <string>$h</string> and escape dots so the zone is compared literally.
Fixes#5129