@@ -130,10 +112,6 @@
|23|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT)
|24|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox: See Proxmox VE Wiki. Version [4.x, 5.0, 5.1](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x,_5.0_and_5.1)#Let.27s_Encrypt_using_acme.sh), version [5.2 and up](https://pve.proxmox.com/wiki/Certificate_Management)
|25|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml)|Haiku OS
-|26|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml)|Tribblix
-|27|[](https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml)|GhostBSD
-|28|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Hurd.yml)|GNU Hurd
-|29|[](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenEuler.yml)|openEuler
> 🧪 Check our [testing project](https://github.com/acmesh-official/acmetest)
@@ -168,7 +146,6 @@
| 🌐 DNS mode | Use DNS TXT records |
| 🔗 [DNS alias mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode) | Use DNS alias for verification |
| 📡 [Stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode) | Stateless verification |
-| 📌 [DNS persist mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-persist-mode) | Persistent DNS TXT record ([draft-ietf-acme-dns-persist-01](https://datatracker.ietf.org/doc/draft-ietf-acme-dns-persist/)) |
---
@@ -419,53 +396,7 @@ acme.sh --renew -d example.com
---
-### 🔟 Use DNS Persist Mode
-
-📖 Wiki: https://github.com/acmesh-official/acme.sh/wiki/DNS-persist-mode
-
-📚 Spec: [draft-ietf-acme-dns-persist-01](https://datatracker.ietf.org/doc/draft-ietf-acme-dns-persist/)
-
-DNS persist mode lets you place a **single, long‑lived `_validation-persist` TXT record** in your zone and reuse it for every subsequent issuance and renewal. There is no per-issuance challenge token, so renewals require **no DNS edits** — useful when DNS API access is not available but you still want unattended renewals.
-
-#### 🪄 Step 1: Print the TXT record value
-
-```bash
-acme.sh --make-dns-persist-value -d example.com [--server letsencrypt] [--dns-persist-wildcard] [--dns-persist-ca-name "sectigo.com"] [--dns-persist-days 365]
-```
-
-Options:
-
-| Flag | Description |
-|------|-------------|
-| `--server ` | Pick the CA (default is your configured default). The account is registered automatically if you have not used this CA before. |
-| `--dns-persist-wildcard` | Adds `policy=wildcard` to the record so it also authorizes wildcard / subdomain certs. |
-| `--dns-persist-ca-name ` | Use a specific CA identity domain (e.g. `sectigo.com`). If omitted, identities are read from the ACME directory's `caaIdentities` field and one record per identity is printed — you only need to add **any one** of them. |
-| `--dns-persist-days ` | Adds `persistUntil=` to the record, set to N days from now. The CA will refuse new validations against the record after that time. Omit for a record with no expiry. |
-
-You should get an output like:
-
-```sh
-TXT persist domain:_validation-persist.example.com
-TXT persist value :"letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/123456789"
-```
-
-#### ✍️ Step 2: Add the TXT record to your DNS
-
-Add the printed `TXT persist domain` / `TXT persist value` pair as a TXT record at your DNS provider, then wait for it to propagate.
-
-#### 📜 Step 3: Issue the certificate
-
-```bash
-acme.sh --issue -d example.com --dns-persist
-```
-
-✅ **Done!** No challenge token is provisioned during issuance — the CA reads the persistent TXT record directly.
-
-> 🔄 Renewals just work: `acme.sh --renew -d example.com` (or the cron job) reuses the same TXT record automatically — no further DNS edits needed.
-
----
-
-### 1️⃣1️⃣ Issue Certificates of Different Key Types (ECC or RSA)
+### 🔟 Issue Certificates of Different Key Types (ECC or RSA)
Just set the `keylength` to a valid, supported value.
@@ -496,7 +427,7 @@ acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com -
---
-### 1️⃣2️⃣ Issue Wildcard Certificates
+### 1️⃣1️⃣ Issue Wildcard Certificates
It's simple! Just give a wildcard domain as the `-d` parameter:
@@ -508,9 +439,9 @@ acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf
---
-### 1️⃣3️⃣ How to Renew Certificates
+### 1️⃣2️⃣ How to Renew Certificates
-> 🔄 No need to renew manually! All certs will be renewed automatically every **30** days, **or earlier when the CA's ARI says so** (see below).
+> 🔄 No need to renew manually! All certs will be renewed automatically every **30** days.
However, you can force a renewal:
@@ -524,43 +455,9 @@ acme.sh --renew -d example.com --force
acme.sh --renew -d example.com --force --ecc
```
-#### 📡 ACME Renewal Information (ARI) — RFC 9773
-
-📖 Wiki: https://github.com/acmesh-official/acme.sh/wiki/ARI
-
-If the CA exposes a `renewalInfo` endpoint in its ACME directory (Let's Encrypt, ZeroSSL, etc.), `acme.sh` follows [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html) automatically — **no flag needed, no opt-in**:
-
-| What | When | Why |
-|------|------|-----|
-| 🔍 **Polls `suggestedWindow`** | Every cron run, before deciding to skip | Lets the CA shift the renewal time forward in case of an incident (key compromise, mass revocation, etc.) |
-| 🎯 **Picks a random renewal time** inside the window | Right after a successful issuance/renewal | Disperses renewals across the network so all clients don't hit the CA at the same instant |
-| 🔗 **Sends `replaces=`** in `newOrder` | On renewal | Lets the CA correlate the new order with the certificate it supersedes (RFC 9773 §5) |
-| ↩️ **Retries without `replaces`** | If the CA rejects with `alreadyReplaced` or an ARI validation error | Robust against edge cases (e.g. switching CAs, retired issuers) |
-
-**Renewal trigger logic:** the cert is renewed if **any one** of the following becomes true:
-
-1. `--force` is given
-2. The CA's **ARI `suggestedWindow` has started**
-3. The cached `Le_NextRenewTime` has passed (default fallback for CAs without ARI)
-
-You can see the resulting next renewal time (already ARI-picked when applicable) in:
-
-```sh
-acme.sh --info -d example.com
-# Look for: Le_NextRenewTimeStr=...
-```
-
-For the live ARI window the CA is currently advertising, run with `--debug 2`:
-
-```sh
-acme.sh --renew -d example.com --debug 2 2>&1 | grep -i 'ARI suggestedWindow'
-```
-
-> 💡 If your CA does not advertise `renewalInfo`, `acme.sh` falls back to the classic 30-day rule — no behavior change.
-
---
-### 1️⃣4️⃣ How to Stop Certificate Renewal
+### 1️⃣3️⃣ How to Stop Certificate Renewal
To stop renewal of a cert, you can execute the following to remove the cert from the renewal list:
@@ -574,7 +471,7 @@ The cert/key file is not removed from the disk.
---
-### 1️⃣5️⃣ How to Upgrade acme.sh
+### 1️⃣4️⃣ How to Upgrade acme.sh
> 🚀 acme.sh is in constant development — it's strongly recommended to use the latest code.
@@ -598,25 +495,25 @@ acme.sh --upgrade --auto-upgrade 0
---
-### 1️⃣6️⃣ Issue a Certificate from an Existing CSR
+### 1️⃣5️⃣ Issue a Certificate from an Existing CSR
📚 https://github.com/acmesh-official/acme.sh/wiki/Issue-a-cert-from-existing-CSR
---
-### 1️⃣7️⃣ Send Notifications in Cronjob
+### 1️⃣6️⃣ Send Notifications in Cronjob
📚 https://github.com/acmesh-official/acme.sh/wiki/notify
---
-### 1️⃣8️⃣ Under the Hood
+### 1️⃣7️⃣ Under the Hood
> 🔧 Speak ACME language using shell, directly to "Let's Encrypt".
---
-### 1️⃣9️⃣ Acknowledgments
+### 1️⃣8️⃣ Acknowledgments
| Project | Link |
|---------|------|
@@ -633,8 +530,6 @@ This project exists thanks to all the people who contribute.
-If you want to become a contributor make sure to read [CONTRIBUTING.md](./CONTRIBUTING.md).
-
### 💰 Financial Contributors
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/acmesh/contribute)]
@@ -660,7 +555,7 @@ Support this project with your organization. Your logo will show up here with a
---
-### 2️⃣0️⃣ License & Others
+### 1️⃣9️⃣ License & Others
📄 **License:** GPLv3
@@ -670,7 +565,7 @@ Support this project with your organization. Your logo will show up here with a
---
-### 2️⃣1️⃣ Donate
+### 2️⃣0️⃣ Donate
> 💝 Your donation makes **acme.sh** better!
@@ -682,7 +577,7 @@ Support this project with your organization. Your logo will show up here with a
---
-### 2️⃣2️⃣ About This Repository
+### 2️⃣1️⃣ About This Repository
> [!NOTE]
> This repository is officially maintained by ZeroSSL as part of our commitment to providing secure and reliable SSL/TLS solutions. We welcome contributions and feedback from the community!
diff --git a/acme.sh b/acme.sh
index 4d4f6cbe..e780f61d 100755
--- a/acme.sh
+++ b/acme.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env sh
-VER=3.1.5
+VER=3.1.3
PROJECT_NAME="acme.sh"
@@ -59,7 +59,6 @@ DEFAULT_OPENSSL_BIN="openssl"
VTYPE_HTTP="http-01"
VTYPE_DNS="dns-01"
VTYPE_ALPN="tls-alpn-01"
-VTYPE_DNS_PERSIST="dns-persist-01"
ID_TYPE_DNS="dns"
ID_TYPE_IP="ip"
@@ -72,7 +71,6 @@ NO_VALUE="no"
W_DNS="dns"
W_ALPN="alpn"
-W_DNS_PERSIST="dns_persist"
DNS_ALIAS_PREFIX="="
MODE_STATELESS="stateless"
@@ -918,15 +916,6 @@ _json_decode() {
echo "$_j_str"
}
-#extract the authorization URLs from an order response on stdin, as a
-#comma-separated list. The entries are quoted URL strings and a quote cannot
-#occur inside a URL, so the first '"]' is always the end of the array. A
-#char-class scan would stop early on the brackets of an IPv6 host
-#(https://[2001:db8::1]/...). Outputs nothing if the field is missing.
-_authorizations_from_order() {
- sed -n 's/.*"authorizations" *: *\[//p' | sed 's/" *\].*//' | tr -d '" '
-}
-
#options file
_sed_i() {
options="$1"
@@ -1024,24 +1013,6 @@ _checkcert() {
fi
}
-#file
-_enddate() {
- _cf="$1"
- _res="$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -enddate -in "$_cf")"
- if [ "$?" != "0" ] || [ -z "$_res" ]; then
- return 1
- fi
-
- case "$_res" in
- notAfter=*)
- echo "${_res#notAfter=}"
- ;;
- *)
- return 1
- ;;
- esac
-}
-
#Usage: hashalg [outputhex]
#Output Base64-encoded digest
_digest() {
@@ -1066,25 +1037,6 @@ _digest() {
}
-#Usage: certpath hashalg
-#Output certificate fingerprint without colons
-_fingerprint() {
- cert="$1"
- alg="$2"
- if [ -z "$alg" ]; then
- _usage "Usage: _fingerprint certpath hashalg"
- return 1
- fi
-
- if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then
- # openssl prints "SHA1 Fingerprint=AA:BB:CC:..."; strip prefix and colons.
- ${ACME_OPENSSL_BIN:-openssl} x509 -in "$cert" -noout -fingerprint -"$alg" | sed 's/.*=//; s/://g'
- else
- _err "$alg is not supported yet"
- return 1
- fi
-}
-
#Usage: hashalg secret_hex [outputhex]
#Output binary hmac
_hmac() {
@@ -1188,11 +1140,6 @@ _createkey() {
length="$1"
f="$2"
_debug2 "_createkey for file:$f"
- if ! _exists "${ACME_OPENSSL_BIN:-openssl}"; then
- _err "Please install openssl first. ACME_OPENSSL_BIN=$ACME_OPENSSL_BIN"
- _err "We need openssl to generate keys."
- return 1
- fi
eccname="$length"
if _startswith "$length" "ec-"; then
length=$(printf "%s" "$length" | cut -d '-' -f 2-100)
@@ -1215,7 +1162,6 @@ _createkey() {
_debug "Using length $length"
- _new_key_file=""
if ! [ -e "$f" ]; then
if ! touch "$f" >/dev/null 2>&1; then
_f_path="$(dirname "$f")"
@@ -1229,7 +1175,6 @@ _createkey() {
return 1
fi
chmod 600 "$f"
- _new_key_file="1"
fi
if _isEccKey "$length"; then
@@ -1238,10 +1183,6 @@ _createkey() {
echo "$_opkey" >"$f"
else
_err "Error encountered for ECC key named $eccname"
- #do not leave an empty file behind, or the next run would treat the key as existing
- if [ "$_new_key_file" ]; then
- rm -f "$f"
- fi
return 1
fi
else
@@ -1254,10 +1195,6 @@ _createkey() {
echo "$_opkey" >"$f"
else
_err "Error encountered for RSA key of length $length"
- #do not leave an empty file behind, or the next run would treat the key as existing
- if [ "$_new_key_file" ]; then
- rm -f "$f"
- fi
return 1
fi
fi
@@ -1307,23 +1244,6 @@ _idn() {
}
#_createcsr cn san_list keyfile csrfile conf acmeValidationv1 extendedUsage
-#cn
-#The x509 Common Name is limited to 64 characters (RFC 5280 ub-common-name,
-#enforced by openssl in ASN1_mbstring_ncopy), and an IP address or an empty
-#name is not usable as CN either. When this rejects the name, _createcsr
-#omits CN from the CSR subject and the CA takes the identifiers from the
-#subjectAltName extension (issue 4867).
-_is_valid_cn() {
- _cn_v="$1"
- if [ -z "$_cn_v" ] || [ "${#_cn_v}" -gt 64 ]; then
- return 1
- fi
- if _isIP "$_cn_v"; then
- return 1
- fi
- return 0
-}
-
_createcsr() {
_debug _createcsr
domain="$1"
@@ -1387,16 +1307,16 @@ _createcsr() {
_csr_cn="$(_idn "$domain")"
_debug2 _csr_cn "$_csr_cn"
if _contains "$(uname -a)" "MINGW"; then
- if _is_valid_cn "$_csr_cn"; then
- ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//CN=$_csr_cn" -config "$csrconf" -out "$csr"
- else
+ if _isIP "$_csr_cn"; then
${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//O=$PROJECT_NAME" -config "$csrconf" -out "$csr"
+ else
+ ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//CN=$_csr_cn" -config "$csrconf" -out "$csr"
fi
else
- if _is_valid_cn "$_csr_cn"; then
- ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/CN=$_csr_cn" -config "$csrconf" -out "$csr"
- else
+ if _isIP "$_csr_cn"; then
${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/O=$PROJECT_NAME" -config "$csrconf" -out "$csr"
+ else
+ ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/CN=$_csr_cn" -config "$csrconf" -out "$csr"
fi
fi
}
@@ -1422,9 +1342,7 @@ _readSubjectFromCSR() {
_usage "_readSubjectFromCSR mycsr.csr"
return 1
fi
- # -config /dev/null: reading a CSR needs no config, but a missing default
- # openssl.cnf is fatal on some systems (e.g. NetBSD does not install one)
- ${ACME_OPENSSL_BIN:-openssl} req -noout -in "$_csrfile" -subject -config /dev/null | tr ',' "\n" | _egrep_o "CN *=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d ' \n'
+ ${ACME_OPENSSL_BIN:-openssl} req -noout -in "$_csrfile" -subject | tr ',' "\n" | _egrep_o "CN *=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d ' \n'
}
#_csrfile
@@ -1439,17 +1357,16 @@ _readSubjectAltNamesFromCSR() {
_csrsubj="$(_readSubjectFromCSR "$_csrfile")"
_debug _csrsubj "$_csrsubj"
- _dnsAltnames="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile" -config /dev/null | grep "^ *DNS:.*" | tr -d ' \n')"
+ _dnsAltnames="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile" | grep "^ *DNS:.*" | tr -d ' \n')"
_debug _dnsAltnames "$_dnsAltnames"
- # escape the wildcard '*' so it is not taken as a regex operator by grep/sed below
- _escapedAltnames="$(echo "$_dnsAltnames" | tr '*' '#')"
- _debug _escapedAltnames "$_escapedAltnames"
- _escapedSubject="$(echo "$_csrsubj" | tr '*' '#')"
- _debug _escapedSubject "$_escapedSubject"
- if _contains "$_escapedAltnames," "DNS:$_escapedSubject,"; then
+ if _contains "$_dnsAltnames," "DNS:$_csrsubj,"; then
_debug "AltNames contains subject"
- _dnsAltnames="$(echo "$_escapedAltnames," | sed "s/DNS:$_escapedSubject,//g" | tr '#' '*' | sed "s/,\$//g")"
+ _excapedAlgnames="$(echo "$_dnsAltnames" | tr '*' '#')"
+ _debug _excapedAlgnames "$_excapedAlgnames"
+ _escapedSubject="$(echo "$_csrsubj" | tr '*' '#')"
+ _debug _escapedSubject "$_escapedSubject"
+ _dnsAltnames="$(echo "$_excapedAlgnames," | sed "s/DNS:$_escapedSubject,//g" | tr '#' '*' | sed "s/,\$//g")"
_debug _dnsAltnames "$_dnsAltnames"
else
_debug "AltNames doesn't contain subject"
@@ -1466,7 +1383,7 @@ _readKeyLengthFromCSR() {
return 1
fi
- _outcsr="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile" -config /dev/null)"
+ _outcsr="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile")"
_debug2 _outcsr "$_outcsr"
if _contains "$_outcsr" "Public Key Algorithm: id-ecPublicKey"; then
_debug "ECC CSR"
@@ -1547,22 +1464,6 @@ _toPkcs() {
}
-_toPkcs8() {
- _cpkcs8="$1"
- _ckey="$2"
- pkcs8Password="$3"
-
- if [ "$pkcs8Password" ]; then
- ${ACME_OPENSSL_BIN:-openssl} pkcs8 -topk8 -inform PEM -outform PEM -v2 aes256 -passout "pass:$pkcs8Password" -in "$_ckey" -out "$_cpkcs8"
- else
- ${ACME_OPENSSL_BIN:-openssl} pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in "$_ckey" -out "$_cpkcs8"
- fi
- if [ "$?" = "0" ]; then
- _savedomainconf "Le_PKCS8Password" "$pkcs8Password" "base64"
- fi
-
-}
-
#domain [password] [isEcc]
toPkcs() {
domain="$1"
@@ -1584,21 +1485,20 @@ toPkcs() {
}
-#domain [password] [isEcc]
+#domain [isEcc]
toPkcs8() {
domain="$1"
- pkcs8Password="$2"
if [ -z "$domain" ]; then
- _usage "Usage: $PROJECT_ENTRY --to-pkcs8 --domain [--password ] [--ecc]"
+ _usage "Usage: $PROJECT_ENTRY --to-pkcs8 --domain [--ecc]"
return 1
fi
- _isEcc="$3"
+ _isEcc="$2"
_initpath "$domain" "$_isEcc"
- _toPkcs8 "$CERT_PKCS8_PATH" "$CERT_KEY_PATH" "$pkcs8Password"
+ ${ACME_OPENSSL_BIN:-openssl} pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in "$CERT_KEY_PATH" -out "$CERT_PKCS8_PATH"
if [ "$?" = "0" ]; then
_info "Success, $CERT_PKCS8_PATH"
@@ -1932,89 +1832,22 @@ _date2time() {
if gdate -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then
return
fi
- #Omnios. Pass the date as argv (sys.argv[1]) instead of interpolating it into
- #the -c program text, so a quote in the input cannot inject Python code.
- if python3 -c "import datetime,sys; print(int(datetime.datetime.strptime(sys.argv[1], \"%Y-%m-%d %H:%M:%S\").replace(tzinfo=datetime.timezone.utc).timestamp()))" "$1" 2>/dev/null; then
+ #Omnios
+ if python3 -c "import datetime; print(int(datetime.datetime.strptime(\"$1\", \"%Y-%m-%d %H:%M:%S\").replace(tzinfo=datetime.timezone.utc).timestamp()))" 2>/dev/null; then
return
fi
#Omnios
- if python3 -c "import datetime,sys; print(int(datetime.datetime.strptime(sys.argv[1], \"%Y-%m-%dT%H:%M:%SZ\").replace(tzinfo=datetime.timezone.utc).timestamp()))" "$1" 2>/dev/null; then
+ if python3 -c "import datetime; print(int(datetime.datetime.strptime(\"$1\", \"%Y-%m-%dT%H:%M:%SZ\").replace(tzinfo=datetime.timezone.utc).timestamp()))" 2>/dev/null; then
return
fi
_err "Cannot parse _date2time $1"
return 1
}
-#support the output format of openssl -enddate:
-# Apr 01 08:10:33 2022 GMT to 1641283833
-_ssldate2time() {
- #Linux
- if date -u -d "$1" +"%s" 2>/dev/null; then
- return
- fi
- #Solaris
- if gdate -u -d "$1" +"%s" 2>/dev/null; then
- return
- fi
- #Mac/BSD
- if date -j -f "%b %d %T %Y %Z" "$1" +"%s" 2>/dev/null; then
- return
- fi
- _err "Cannot parse _ssldate2time $1"
- return 1
-}
-
_utc_date() {
date -u "+%Y-%m-%d %H:%M:%S"
}
-#Usage: _calc_next_renew_time createtime renewaldays [endtime]
-#Prints createtime + renewaldays*86400 - 86400, capped so it never passes
-#the certificate expiry: with short-lived certs (internal CAs, upcoming
-#CA/B SC-081 47-day maximum) a fixed RenewalDays would otherwise schedule
-#the renewal after notAfter and leave an expired cert in place.
-#The cap is one day before endtime, or one hour before for certs whose
-#lifetime is 24 hours or less, mirroring the --valid-to scheduling.
-_calc_next_renew_time() {
- _cnrt_create="$1"
- _cnrt_days="$2"
- _cnrt_end="$3"
- _cnrt_next=$(_math "$_cnrt_create" + "$_cnrt_days" \* 24 \* 60 \* 60 - 86400)
- if [ -z "$_cnrt_end" ]; then
- printf "%s" "$_cnrt_next"
- return 0
- fi
- if [ "$(_math "$_cnrt_end" - "$_cnrt_create")" -gt 86400 ]; then
- _cnrt_cap=$(_math "$_cnrt_end" - 86400)
- else
- _cnrt_cap=$(_math "$_cnrt_end" - 3600)
- fi
- if [ "$_cnrt_next" -gt "$_cnrt_cap" ]; then
- _cnrt_next="$_cnrt_cap"
- fi
- printf "%s" "$_cnrt_next"
-}
-
-#Usage: _calc_validto_renew_time notaftertime renewaldays now
-#Prints the next renew time for a cert issued with a relative --valid-to.
-#A negative renewaldays is anchored to the expiry: notaftertime +
-#renewaldays*86400. Otherwise the cert renews one day before the expiry,
-#or one hour before for certs whose lifetime is 24 hours or less.
-_calc_validto_renew_time() {
- _cvrt_end="$1"
- _cvrt_days="$2"
- _cvrt_now="$3"
- if [ "$_cvrt_days" ] && [ "$_cvrt_days" -lt 0 ]; then
- _math "$_cvrt_end" + "$_cvrt_days" \* 24 \* 60 \* 60
- return 0
- fi
- if [ "$(_math "$_cvrt_end" - "$_cvrt_now")" -gt 86400 ]; then
- _math "$_cvrt_end" - 86400
- else
- _math "$_cvrt_end" - 3600
- fi
-}
-
_mktemp() {
if _exists mktemp; then
if mktemp 2>/dev/null; then
@@ -2641,13 +2474,6 @@ _savedeployconf() {
_cleardomainconf "$1"
}
-#key
-_cleardeployconf() {
- _cleardomainconf "SAVED_$1"
- #remove later
- _cleardomainconf "$1"
-}
-
#key
_getdeployconf() {
_rac_key="$1"
@@ -2715,21 +2541,6 @@ _clearcaconf() {
_clear_conf "$CA_CONF" "$1"
}
-#Starts a socat listener in the background, the pid is set to _socat_pid.
-#It uses the content, _content_len, _NC and _SOCAT_ERR of _startserver.
-#options
-_startsocat() {
- _socat_opts="$1"
- _debug "_NC" "$_NC $_socat_opts"
- $_NC $_socat_opts SYSTEM:"sleep 1; \
-echo 'HTTP/1.0 200 OK'; \
-echo 'Content-Length\: $_content_len'; \
-echo ''; \
-printf '%s' '$content';" 2>>"$_SOCAT_ERR" &
- _socat_pid="$!"
- _debug "_socat_pid" "$_socat_pid"
-}
-
# content localaddress
_startserver() {
content="$1"
@@ -2743,24 +2554,16 @@ _startserver() {
_debug Le_Listen_V4 "$Le_Listen_V4"
_debug Le_Listen_V6 "$Le_Listen_V6"
- _serverproc_v6=""
if _exists "socat"; then
_NC="socat"
- SOCAT_OPTIONS6=""
- if [ "$Le_Listen_V6" ] && [ -z "$Le_Listen_V4" ]; then
+ if [ "$Le_Listen_V6" ]; then
_NC="$_NC -6"
SOCAT_OPTIONS=TCP6-LISTEN
- elif [ "$Le_Listen_V4" ] && [ -z "$Le_Listen_V6" ]; then
+ elif [ "$Le_Listen_V4" ]; then
_NC="$_NC -4"
SOCAT_OPTIONS=TCP4-LISTEN
- elif [ "$ncaddr" ]; then
- #a single local address belongs to a single family, let socat pick it
- SOCAT_OPTIONS=TCP-LISTEN
else
- #listen on both ipv4 and ipv6, with one socket for each family:
- #ipv4-mapped ipv6 addresses are not available everywhere.
- SOCAT_OPTIONS=TCP4-LISTEN
- SOCAT_OPTIONS6=TCP6-LISTEN
+ SOCAT_OPTIONS=TCP-LISTEN
fi
if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then
@@ -2768,10 +2571,6 @@ _startserver() {
fi
SOCAT_OPTIONS=$SOCAT_OPTIONS:$Le_HTTPPort,crlf,reuseaddr,fork
- if [ "$SOCAT_OPTIONS6" ]; then
- #ipv6only keeps this socket from colliding with the ipv4 one
- SOCAT_OPTIONS6=$SOCAT_OPTIONS6:$Le_HTTPPort,crlf,reuseaddr,fork,ipv6only=1
- fi
#Adding bind to local-address
if [ "$ncaddr" ]; then
@@ -2780,14 +2579,14 @@ _startserver() {
_content_len="$(printf "%s" "$content" | wc -c)"
_debug _content_len "$_content_len"
+ _debug "_NC" "$_NC $SOCAT_OPTIONS"
export _SOCAT_ERR="$(_mktemp)"
- _startsocat "$SOCAT_OPTIONS"
- serverproc="$_socat_pid"
- if [ "$SOCAT_OPTIONS6" ]; then
- #best effort, the host may have no ipv6 support at all
- _startsocat "$SOCAT_OPTIONS6"
- _serverproc_v6="$_socat_pid"
- fi
+ $_NC $SOCAT_OPTIONS SYSTEM:"sleep 1; \
+echo 'HTTP/1.0 200 OK'; \
+echo 'Content-Length\: $_content_len'; \
+echo ''; \
+printf '%s' '$content';" 2>"$_SOCAT_ERR" &
+ serverproc="$!"
else
_PYTHON=""
if _exists "python3"; then
@@ -2799,40 +2598,21 @@ _startserver() {
fi
if [ "$_PYTHON" ]; then
_debug "Using python: $_PYTHON"
- #a comma separated list of addresses to listen on, one socket for each
- _BIND_ADDR="0.0.0.0,::"
- if [ "$Le_Listen_V6" ] && [ -z "$Le_Listen_V4" ]; then
+ _AF="socket.AF_INET"
+ _BIND_ADDR="0.0.0.0"
+ if [ "$Le_Listen_V6" ]; then
+ _AF="socket.AF_INET6"
_BIND_ADDR="::"
- elif [ "$Le_Listen_V4" ] && [ -z "$Le_Listen_V6" ]; then
- _BIND_ADDR="0.0.0.0"
fi
if [ "$ncaddr" ]; then
_BIND_ADDR="$ncaddr"
fi
- _debug "_BIND_ADDR" "$_BIND_ADDR"
export _SOCAT_ERR="$(_mktemp)"
- $_PYTHON -c "import socket,sys,select
-res='HTTP/1.0 200 OK\r\nContent-Length: '+str(len(sys.argv[3]))+'\r\n\r\n'+sys.argv[3]
-ads=sys.argv[2].split(',')
-ls=[]
-for ad in ads:
- try:
- sk=socket.socket(socket.AF_INET6 if ':' in ad else socket.AF_INET,socket.SOCK_STREAM)
- sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
- if ':' in ad and len(ads)>1:
- sk.setsockopt(socket.IPPROTO_IPV6,socket.IPV6_V6ONLY,1)
- sk.bind((ad,int(sys.argv[1])))
- sk.listen(5)
- ls.append(sk)
- except Exception:
- sys.stderr.write(str(sys.exc_info()[1])+'\n')
-if not ls:
- sys.exit(1)
+ $_PYTHON -c "import socket,sys;s=socket.socket($_AF,socket.SOCK_STREAM);s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1);s.bind((sys.argv[2],int(sys.argv[1])));s.listen(5);res='HTTP/1.0 200 OK\r\nContent-Length: '+str(len(sys.argv[3]))+'\r\n\r\n'+sys.argv[3];
while True:
- for sk in select.select(ls,[],[])[0]:
- c,a=sk.accept()
- c.sendall(res.encode() if hasattr(res, 'encode') else res)
- c.close()" "$Le_HTTPPort" "$_BIND_ADDR" "$content" 2>"$_SOCAT_ERR" &
+ c,a=s.accept()
+ c.sendall(res.encode() if hasattr(res, 'encode') else res)
+ c.close()" "$Le_HTTPPort" "$_BIND_ADDR" "$content" 2>"$_SOCAT_ERR" &
serverproc="$!"
_NC="$_PYTHON"
else
@@ -2855,11 +2635,6 @@ while True:
_stopserver() {
pid="$1"
_debug "pid" "$pid"
- if [ "$_serverproc_v6" ]; then
- _debug "_serverproc_v6" "$_serverproc_v6"
- kill $_serverproc_v6 >/dev/null 2>&1
- _serverproc_v6=""
- fi
if [ -z "$pid" ]; then
rm -f "$_SOCAT_ERR"
return
@@ -2933,11 +2708,9 @@ _starttlsserver() {
_debug Le_Listen_V4 "$Le_Listen_V4"
_debug Le_Listen_V6 "$Le_Listen_V6"
- #openssl s_server binds a single socket, so both options together can only
- #mean: do not force a family, same as when neither of them is given.
- if [ "$Le_Listen_V4" ] && [ -z "$Le_Listen_V6" ]; then
+ if [ "$Le_Listen_V4" ]; then
__S_OPENSSL="$__S_OPENSSL -4"
- elif [ "$Le_Listen_V6" ] && [ -z "$Le_Listen_V4" ]; then
+ elif [ "$Le_Listen_V6" ]; then
__S_OPENSSL="$__S_OPENSSL -6"
fi
@@ -3003,45 +2776,14 @@ __initHome() {
_debug "Using default home: $DEFAULT_INSTALL_HOME"
LE_WORKING_DIR="$DEFAULT_INSTALL_HOME"
fi
- # Convert a relative --home to an absolute path: later code cd's around
- # (e.g. installOnline extracts and enters the archive dir), where a
- # relative path would point into the wrong directory.
- # https://github.com/acmesh-official/acme.sh/issues/6477
- case "$LE_WORKING_DIR" in
- /*) ;;
- *)
- if [ -d "$LE_WORKING_DIR" ]; then
- LE_WORKING_DIR="$(cd "$LE_WORKING_DIR" && pwd)"
- fi
- ;;
- esac
export LE_WORKING_DIR
if [ -z "$LE_CONFIG_HOME" ]; then
LE_CONFIG_HOME="$LE_WORKING_DIR"
fi
- case "$LE_CONFIG_HOME" in
- /*) ;;
- *)
- if [ -d "$LE_CONFIG_HOME" ]; then
- LE_CONFIG_HOME="$(cd "$LE_CONFIG_HOME" && pwd)"
- fi
- ;;
- esac
_debug "Using config home: $LE_CONFIG_HOME"
export LE_CONFIG_HOME
- # Paths with whitespace break the unquoted $_CURL/$_WGET command expansion,
- # so fail early with a clear error instead of a cryptic curl/wget failure.
- # https://github.com/acmesh-official/acme.sh/issues/2163
- case "$LE_WORKING_DIR$LE_CONFIG_HOME" in
- *" "*)
- _err "The --home or --config-home path can not contain spaces: '$LE_WORKING_DIR'"
- _err "Please install $PROJECT_NAME to a path without spaces."
- exit 1
- ;;
- esac
-
_DEFAULT_ACCOUNT_CONF_PATH="$LE_CONFIG_HOME/account.conf"
if [ -z "$ACCOUNT_CONF_PATH" ]; then
@@ -3137,11 +2879,6 @@ _initAPI() {
return 0
fi
_err "Cannot init API for $_api_server"
- if [ "$_api_server" = "$CA_ZEROSSL" ]; then
- _info "$(__green "If this host is IPv6-only: ZeroSSL currently has no IPv6 endpoint.")"
- _info "$(__green "Try another CA, e.g.: $PROJECT_ENTRY --set-default-ca --server letsencrypt")"
- _info "See: $(__green "https://github.com/acmesh-official/acme.sh/issues/6872")"
- fi
return 1
}
@@ -3572,14 +3309,9 @@ _setNginx() {
fi
echo "$NGINX_START
-location ^~ /.well-known/acme-challenge/ {
- # the ^~ prefix wins over regex-skipping blocks like \"location ^~ /\",
- # the nested regex location still captures the token as \$1
- location ~ \"^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\$\" {
- default_type text/plain;
- return 200 \"\$1.$_thumbpt\";
- }
- return 404;
+location ~ \"^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\$\" {
+ default_type text/plain;
+ return 200 \"\$1.$_thumbpt\";
}
#NGINX_START
" >>"$FOUND_REAL_NGINX_CONF"
@@ -3723,7 +3455,7 @@ _restoreNginx() {
done
_info "Reloading nginx"
- if ! nginx -s reload >/dev/null 2>&1; then
+ if ! nginx -s reload >/dev/null; then
_err "An error occurred while reloading nginx, please open an issue on $PROJECT."
return 1
fi
@@ -4027,10 +3759,10 @@ _on_issue_success() {
#account_key_length eab-kid eab-hmac-key
registeraccount() {
_account_key_length="$1"
- _eab_kid="$2"
+ _eab_id="$2"
_eab_hmac_key="$3"
_initpath
- _regAccount "$_account_key_length" "$_eab_kid" "$_eab_hmac_key"
+ _regAccount "$_account_key_length" "$_eab_id" "$_eab_hmac_key"
}
__calcAccountKeyHash() {
@@ -4041,16 +3773,6 @@ __calc_account_thumbprint() {
printf "%s" "$jwk" | tr -d ' ' | _digest "sha256" | _url_replace
}
-#Reads a comma- or space-separated email list from stdin and prints
-#the ACME contact list items: "mailto:a@example.com","mailto:b@example.com"
-_mailto_contacts() {
- _mc_out=""
- for _mc_m in $(tr ',' ' '); do
- _mc_out="$_mc_out,\"mailto:$_mc_m\""
- done
- echo "$_mc_out" | cut -c 2-
-}
-
_getAccountEmail() {
if [ "$ACCOUNT_EMAIL" ]; then
echo "$ACCOUNT_EMAIL"
@@ -4070,14 +3792,14 @@ _getAccountEmail() {
_regAccount() {
_initpath
_reg_length="$1"
- _eab_kid="$2"
+ _eab_id="$2"
_eab_hmac_key="$3"
_debug3 _regAccount "$_regAccount"
_initAPI
mkdir -p "$CA_DIR"
- if [ ! -s "$ACCOUNT_KEY_PATH" ]; then
+ if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
if ! _create_account_key "$_reg_length"; then
_err "Error creating account key."
return 1
@@ -4087,13 +3809,13 @@ _regAccount() {
if ! _calcjwk "$ACCOUNT_KEY_PATH"; then
return 1
fi
- if [ "$_eab_kid" ] && [ "$_eab_hmac_key" ]; then
- _savecaconf CA_EAB_KEY_ID "$_eab_kid"
+ if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then
+ _savecaconf CA_EAB_KEY_ID "$_eab_id"
_savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key"
fi
- _eab_kid=$(_readcaconf "CA_EAB_KEY_ID")
+ _eab_id=$(_readcaconf "CA_EAB_KEY_ID")
_eab_hmac_key=$(_readcaconf "CA_EAB_HMAC_KEY")
- _secure_debug3 _eab_kid "$_eab_kid"
+ _secure_debug3 _eab_id "$_eab_id"
_secure_debug3 _eab_hmac_key "$_eab_hmac_key"
_email="$(_getAccountEmail)"
if [ "$_email" ]; then
@@ -4101,7 +3823,7 @@ _regAccount() {
fi
if [ "$ACME_DIRECTORY" = "$CA_ZEROSSL" ]; then
- if [ -z "$_eab_kid" ] || [ -z "$_eab_hmac_key" ]; then
+ if [ -z "$_eab_id" ] || [ -z "$_eab_hmac_key" ]; then
_info "No EAB credentials found for ZeroSSL, let's obtain them"
if [ -z "$_email" ]; then
_info "$(__green "$PROJECT_NAME is using ZeroSSL as default CA now.")"
@@ -4110,19 +3832,17 @@ _regAccount() {
_info "See: $(__green "$_ZEROSSL_WIKI")"
return 1
fi
- #the ZeroSSL EAB endpoint takes a single address, use the first one
- _eab_email="$(echo "$_email" | tr ',' ' ' | awk '{print $1}')"
- _eabresp=$(_post "email=$_eab_email" $_ZERO_EAB_ENDPOINT)
+ _eabresp=$(_post "email=$_email" $_ZERO_EAB_ENDPOINT)
if [ "$?" != "0" ]; then
_debug2 "$_eabresp"
_err "Cannot get EAB credentials from ZeroSSL."
return 1
fi
_secure_debug2 _eabresp "$_eabresp"
- _eab_kid="$(echo "$_eabresp" | tr ',}' '\n\n' | grep '"eab_kid"' | cut -d : -f 2 | tr -d '"')"
- _secure_debug2 _eab_kid "$_eab_kid"
- if [ -z "$_eab_kid" ]; then
- _err "Cannot resolve _eab_kid"
+ _eab_id="$(echo "$_eabresp" | tr ',}' '\n\n' | grep '"eab_kid"' | cut -d : -f 2 | tr -d '"')"
+ _secure_debug2 _eab_id "$_eab_id"
+ if [ -z "$_eab_id" ]; then
+ _err "Cannot resolve _eab_id"
return 1
fi
_eab_hmac_key="$(echo "$_eabresp" | tr ',}' '\n\n' | grep '"eab_hmac_key"' | cut -d : -f 2 | tr -d '"')"
@@ -4131,12 +3851,12 @@ _regAccount() {
_err "Cannot resolve _eab_hmac_key"
return 1
fi
- _savecaconf CA_EAB_KEY_ID "$_eab_kid"
+ _savecaconf CA_EAB_KEY_ID "$_eab_id"
_savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key"
fi
fi
- if [ "$_eab_kid" ] && [ "$_eab_hmac_key" ]; then
- eab_protected="{\"alg\":\"HS256\",\"kid\":\"$_eab_kid\",\"url\":\"${ACME_NEW_ACCOUNT}\"}"
+ if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then
+ eab_protected="{\"alg\":\"HS256\",\"kid\":\"$_eab_id\",\"url\":\"${ACME_NEW_ACCOUNT}\"}"
_debug3 eab_protected "$eab_protected"
eab_protected64=$(printf "%s" "$eab_protected" | _base64 | _url_replace)
@@ -4150,10 +3870,6 @@ _regAccount() {
key_hex="$(_durl_replace_base64 "$_eab_hmac_key" | _dbase64 | _hex_dump | tr -d ' ')"
_debug3 key_hex "$key_hex"
- if [ -z "$key_hex" ]; then
- _err "Cannot base64-decode the eab-hmac-key. Please check the value, and your openssl version."
- return 1
- fi
eab_signature=$(printf "%s" "$eab_sign_t" | _hmac sha256 $key_hex | _base64 | _url_replace)
_debug3 eab_signature "$eab_signature"
@@ -4162,7 +3878,7 @@ _regAccount() {
_debug3 externalBinding "$externalBinding"
fi
if [ "$_email" ]; then
- email_sg="\"contact\": [$(echo "$_email" | _mailto_contacts)], "
+ email_sg="\"contact\": [\"mailto:$_email\"], "
fi
regjson="{$email_sg\"termsOfServiceAgreed\": true$externalBinding}"
@@ -4198,7 +3914,7 @@ _regAccount() {
fi
_savecaconf "ACCOUNT_URL" "$_accUri"
else
- _accUri="$(_readcaconf ACCOUNT_URL)"
+ ACCOUNT_URL="$(_readcaconf ACCOUNT_URL)"
fi
export ACCOUNT_URL="$_accUri"
@@ -4206,9 +3922,7 @@ _regAccount() {
_debug "Calc CA_KEY_HASH" "$CA_KEY_HASH"
_savecaconf CA_KEY_HASH "$CA_KEY_HASH"
- #RFC 8555 sec 7.3.6 requires 401 for requests from a deactivated account,
- #but Boulder (Let's Encrypt) historically returns 403. Accept both.
- if [ "$code" = '403' ] || [ "$code" = '401' ]; then
+ if [ "$code" = '403' ]; then
_err "It seems that the account key has been deactivated, please use a new account key."
return 1
fi
@@ -4242,7 +3956,7 @@ updateaccount() {
_email="$(_getAccountEmail)"
if [ "$_email" ]; then
- updjson='{"contact": ['$(echo "$_email" | _mailto_contacts)']}'
+ updjson='{"contact": ["mailto:'$_email'"]}'
else
updjson='{"contact": []}'
fi
@@ -4252,12 +3966,6 @@ updateaccount() {
if [ "$code" = '200' ]; then
echo "$response" >"$ACCOUNT_JSON_PATH"
_info "Account update success for $_accUri."
- # persist the effective mailbox like _regAccount does; otherwise
- # "--update-account -m new@..." updates the CA but the local conf
- # keeps showing the old address (issue 4673)
- if [ "$_email" ]; then
- _savecaconf "CA_EMAIL" "$_email"
- fi
ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)"
_info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT"
@@ -4267,93 +3975,6 @@ updateaccount() {
fi
}
-#Implement account key rollover
-updateaccountkey() {
- _length="$1"
- _initpath
-
- if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
- _err "Account key not found at: $ACCOUNT_KEY_PATH"
- return 1
- fi
- ACCOUNT_KEY_PATH_NEW="$ACCOUNT_KEY_PATH.new"
-
- _accUri=$(_readcaconf "ACCOUNT_URL")
- _debug _accUri "$_accUri"
-
- if [ -z "$_accUri" ]; then
- _err "The account URL is empty, please run '--update-account' first to update the account info, then try again."
- return 1
- fi
- if ! _calcjwk "$ACCOUNT_KEY_PATH"; then
- return 1
- fi
- _inner_payload="{\"account\": \"$_accUri\", \"oldKey\": $jwk}"
-
- _initAPI
- if [ -z "$ACME_KEY_CHANGE" ]; then
- _err "Server does not expose keyChange url."
- return 1
- fi
-
- _url="$ACME_KEY_CHANGE"
- if _createkey "$_length" "$ACCOUNT_KEY_PATH_NEW"; then
- _info "New account key creation OK."
- else
- _err "New account key creation error."
- return 1
- fi
-
- if ! _calcjwk "$ACCOUNT_KEY_PATH_NEW"; then
- rm -f "$ACCOUNT_KEY_PATH_NEW"
- return 1
- fi
- _inner_protected="{\"url\": \"${_url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
- _inner_protected64="$(printf "%s" "$_inner_protected" | _base64 | _url_replace)"
- _inner_payload64="$(printf "%s" "$_inner_payload" | _base64 | _url_replace)"
- if ! _inner_sig_t="$(printf "%s" "$_inner_protected64.$_inner_payload64" | _sign "$ACCOUNT_KEY_PATH_NEW" "sha256")"; then
- _err "Sign request failed."
- rm -f "$ACCOUNT_KEY_PATH_NEW"
- return 1
- fi
- _debug3 _inner_sig_t "$_inner_sig_t"
-
- _inner_sig="$(printf "%s" "$_inner_sig_t" | _url_replace)"
- _debug3 _inner_sig "$_inner_sig"
-
- _body="{\"protected\": \"$_inner_protected64\", \"payload\": \"$_inner_payload64\", \"signature\": \"$_inner_sig\"}"
-
- if ! _send_signed_request "$_url" "$_body" "" "$ACCOUNT_KEY_PATH"; then
- _err "Error rotating account key: $response."
- rm -f "$ACCOUNT_KEY_PATH_NEW"
- return 1
- fi
-
- if [ "$code" = '200' ]; then
- echo "$response" >"$ACCOUNT_JSON_PATH"
- mv -f "$ACCOUNT_KEY_PATH_NEW" "$ACCOUNT_KEY_PATH"
- _info "Account key rotation success for $_accUri."
- elif [ "$code" = "409" ]; then
- _err "An existing account is using the new key"
- rm -f "$ACCOUNT_KEY_PATH_NEW"
- return 1
- else
- _err "Account key rollover error: $response"
- rm -f "$ACCOUNT_KEY_PATH_NEW"
- return 1
- fi
-
- __CACHED_JWK_KEY_FILE=""
- _calcjwk "$ACCOUNT_KEY_PATH"
-
- ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)"
- _info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT"
-
- CA_KEY_HASH="$(__calcAccountKeyHash)"
- _debug "Calc CA_KEY_HASH" "$CA_KEY_HASH"
- _savecaconf CA_KEY_HASH "$CA_KEY_HASH"
-}
-
#Implement deactivate account
deactivateaccount() {
_initpath
@@ -4381,8 +4002,7 @@ deactivateaccount() {
if _send_signed_request "$_accUri" "$_djson" && _contains "$response" '"deactivated"'; then
_info "Successfully deactivated account $_accUri."
_accid=$(echo "$response" | _egrep_o "\"id\" *: *[^,]*," | cut -d : -f 2 | tr -d ' ,')
- elif [ "$code" = "403" ] || [ "$code" = "401" ]; then
- #RFC 8555 sec 7.3.6: 401 from a deactivated account; Boulder returns 403
+ elif [ "$code" = "403" ]; then
_info "The account is already deactivated."
_accid=$(_getfield "$_accUri" "999" "/")
else
@@ -4408,134 +4028,6 @@ deactivateaccount() {
fi
}
-#domain
-#Print the Validation Domain Name where the persistent TXT record must be
-#published: the "_validation-persist" label prepended to the domain being
-#validated (draft-ietf-acme-dns-persist-01 sec 4).
-#A wildcard identifier is validated by the record at its base domain, so the
-#leading "*." label is dropped: the wildcard scope comes from 'policy=wildcard'
-#in the record value, not from a "*" label in the record name (sec 5.1, 10.2).
-_dns_persist_txt_name() {
- _dpt_domain="$1"
- if _startswith "$_dpt_domain" "*."; then
- _dpt_domain="$(echo "$_dpt_domain" | sed 's/^\*\.//')"
- fi
- if [ -z "$_dpt_domain" ]; then
- return 1
- fi
- echo "_validation-persist.$_dpt_domain"
-}
-
-#domain wildcard ca_name days
-#Print the TXT record(s) the user must add to enable persistent DNS validation
-#per draft-ietf-acme-dns-persist-01.
-makednspersistvalue() {
- _mdpv_domain="$1"
- _mdpv_wildcard="$2"
- _mdpv_ca_name="$3"
- _mdpv_days="$4"
-
- if [ -z "$_mdpv_domain" ]; then
- _err "Please specify a domain with -d."
- return 1
- fi
-
- _txt_name="$(_dns_persist_txt_name "$_mdpv_domain")"
- if [ -z "$_txt_name" ]; then
- _err "Invalid domain: $_mdpv_domain"
- return 1
- fi
- _debug _txt_name "$_txt_name"
-
- #A wildcard identifier can only be issued if the record carries
- #'policy=wildcard', so don't print a record that is guaranteed to fail.
- if _startswith "$_mdpv_domain" "*." && [ "$_mdpv_wildcard" != "1" ]; then
- _info "$_mdpv_domain is a wildcard domain, adding 'policy=wildcard' automatically."
- _mdpv_wildcard="1"
- fi
-
- if [ -n "$_mdpv_days" ]; then
- case "$_mdpv_days" in
- '' | *[!0-9]*)
- _err "--dns-persist-days must be a positive integer, got: $_mdpv_days"
- return 1
- ;;
- esac
- if [ "$_mdpv_days" -lt 1 ]; then
- _err "--dns-persist-days must be at least 1."
- return 1
- fi
- fi
-
- _initpath
-
- _accUri="$(_readcaconf ACCOUNT_URL)"
- if [ -z "$_accUri" ]; then
- _info "No account is registered for $ACME_DIRECTORY yet, registering one now..."
- if ! _regAccount "$DEFAULT_ACCOUNT_KEY_LENGTH"; then
- _err "Cannot register account."
- return 1
- fi
- _accUri="$(_readcaconf ACCOUNT_URL)"
- fi
-
- if [ -z "$_accUri" ]; then
- _err "Cannot determine the ACME account URL."
- return 1
- fi
- _debug "Account URL" "$_accUri"
-
- _txt_suffix="; accounturi=$_accUri"
- if [ "$_mdpv_wildcard" = "1" ]; then
- _txt_suffix="$_txt_suffix; policy=wildcard"
- fi
- if [ -n "$_mdpv_days" ]; then
- _persist_until=$(_math "$(_time)" + "$_mdpv_days" \* 86400)
- _txt_suffix="$_txt_suffix; persistUntil=$_persist_until"
- _info "persistUntil set to $(__green "$(_time2str "$_persist_until")") ($_mdpv_days days from now)"
- fi
-
- if [ -n "$_mdpv_ca_name" ]; then
- _info ""
- _info "Add the following DNS TXT record to enable persistent DNS validation:"
- _info ""
- _info "$(printf 'TXT persist domain:%s' "$(__green "$_txt_name")")"
- _info "$(printf 'TXT persist value :%s' "$(__green "\"$_mdpv_ca_name$_txt_suffix\"")")"
- _info ""
- return 0
- fi
-
- _info "Fetching ACME directory: $ACME_DIRECTORY"
- _dir_resp="$(_get "$ACME_DIRECTORY" "" 30)"
- if [ "$?" != "0" ] || [ -z "$_dir_resp" ]; then
- _err "Cannot fetch ACME directory: $ACME_DIRECTORY"
- return 1
- fi
- _dir_resp="$(echo "$_dir_resp" | _json_decode)"
- _debug2 _dir_resp "$_dir_resp"
-
- _caa_array="$(echo "$_dir_resp" | tr -d ' \r\n\t' | _egrep_o '"caaIdentities":\[[^]]*\]')"
- _debug2 _caa_array "$_caa_array"
- _caaids="$(echo "$_caa_array" | sed 's/.*\[//' | sed 's/\].*//' | tr ',' '\n' | tr -d '"')"
- _debug2 _caaids "$_caaids"
-
- if [ -z "$_caaids" ]; then
- _err "The directory does not include 'caaIdentities'. Please specify --dns-persist-ca-name explicitly."
- return 1
- fi
-
- _info ""
- _info "Add ANY ONE of the following DNS TXT records to enable persistent DNS validation."
- _info "(You only need to add one; pick whichever issuer identity you prefer.)"
- for _id in $_caaids; do
- [ -z "$_id" ] && continue
- _info ""
- _info "$(printf 'TXT persist domain:%s' "$(__green "$_txt_name")")"
- _info "$(printf 'TXT persist value :%s' "$(__green "\"$_id$_txt_suffix\"")")"
- done
- _info ""
-}
-
# domain folder file
_findHook() {
_hookdomain="$1"
@@ -4885,25 +4377,16 @@ _match_issuer() {
#ip
_isIPv4() {
- #splitting must not glob: a "*" segment would match files in cwd
- set -f
- _ipv4_saved_ifs="$IFS"
- IFS='.'
- # shellcheck disable=SC2086
- set -- $1
- IFS="$_ipv4_saved_ifs"
- set +f
- if [ $# -ne 4 ]; then
- return 1
- fi
- for _ipv4_seg in "$@"; do
- _debug2 _ipv4_seg "$_ipv4_seg"
- case "$_ipv4_seg" in
- *[!0-9]* | "") return 1 ;;
- esac
- if [ "${#_ipv4_seg}" -gt 3 ] || [ "$_ipv4_seg" -gt 255 ]; then
+ for seg in $(echo "$1" | tr '.' ' '); do
+ _debug2 seg "$seg"
+ if [ "$(echo "$seg" | tr -d '[0-9]')" ]; then
+ #not all number
return 1
fi
+ if [ $seg -ge 0 ] && [ $seg -lt 256 ]; then
+ continue
+ fi
+ return 1
done
return 0
}
@@ -5002,18 +4485,11 @@ issue() {
if [ -z "$_ACME_IS_RENEW" ]; then
_initpath "$_main_domain" "$_key_length"
mkdir -p "$DOMAIN_PATH"
- elif [ -z "$Le_Vlist" ]; then
- # Whether the saved order is resumed is decided by Le_Vlist below, so key
- # this on Le_Vlist too. With no pending order to resume a new one is
- # created, and a stale order link from the previous issuance must not be
- # reused. https://github.com/acmesh-official/acme.sh/issues/3635
+ elif ! _hasfield "$_web_roots" "$W_DNS"; then
Le_OrderFinalize=""
Le_LinkOrder=""
+ Le_LinkCert=""
fi
- # Per-run state only: it is set after finalize and never read back from the
- # saved domain conf. Carrying it over would make a run that gives up while
- # the order is still 'processing' download the previous certificate again.
- Le_LinkCert=""
if _hasfield "$_web_roots" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then
_err "$_DNS_MANUAL_ERROR"
@@ -5023,7 +4499,7 @@ issue() {
if [ -f "$DOMAIN_CONF" ]; then
Le_NextRenewTime=$(_readdomainconf Le_NextRenewTime)
_debug Le_NextRenewTime "$Le_NextRenewTime"
- if [ -z "$FORCE" ] && [ -z "$_ari_should_renew" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then
+ if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then
_valid_to_saved=$(_readdomainconf Le_Valid_To)
if [ "$_valid_to_saved" ] && ! _startswith "$_valid_to_saved" "+"; then
_info "The domain is set to be valid to: $_valid_to_saved"
@@ -5075,13 +4551,6 @@ issue() {
else
_cleardomainconf "Le_ChallengeAlias"
fi
- # Save Le_DNSSleep unconditionally here: the save inside the dns_entries
- # branch is skipped when all authorizations are already valid (e.g. issuing
- # the ECC twin of a just-issued RSA cert), which left the setting out of
- # that cert's conf. https://github.com/acmesh-official/acme.sh/issues/6986
- if [ "$Le_DNSSleep" ]; then
- _savedomainconf "Le_DNSSleep" "$Le_DNSSleep"
- fi
if [ "$_preferred_chain" ]; then
_savedomainconf "Le_Preferred_Chain" "$_preferred_chain" "base64"
else
@@ -5111,7 +4580,7 @@ issue() {
_debug2 _saved_account_key_hash "$_saved_account_key_hash"
if [ -z "$ACCOUNT_URL" ] || [ -z "$_saved_account_key_hash" ] || [ "$_saved_account_key_hash" != "$(__calcAccountKeyHash)" ]; then
- if ! _regAccount "$_accountkeylength" "$_eab_kid" "$_eab_hmac_key"; then
+ if ! _regAccount "$_accountkeylength"; then
_on_issue_err "$_post_hook"
return 1
fi
@@ -5240,45 +4709,13 @@ issue() {
if [ "$_certificate_profile" ]; then
_newOrderObj="$_newOrderObj,\"profile\": \"$_certificate_profile\""
fi
-
- # RFC 9773 Section 5: include "replaces" only when this is an actual
- # renewal (--renew path), the CA advertises renewalInfo, and a prior
- # cert exists. --issue (even with --force) is not a renewal per RFC 9773
- # which speaks of "a clear predecessor certificate" issued by this CA.
- # NO_ARI=1 (env, account.conf, or ca.conf) disables ARI entirely, so the
- # "replaces" field is also omitted.
- _replaces_certID=""
- if [ "$NO_ARI" = "1" ]; then
- _debug "NO_ARI=1, omitting ARI 'replaces' field from newOrder"
- elif [ "$_ACME_IS_RENEW" = "1" ] && [ "$ACME_RENEWAL_INFO" ] && [ -f "$CERT_PATH" ]; then
- _replaces_certID="$(_getARICertID "$CERT_PATH")"
- _debug "Adding ARI replaces" "$_replaces_certID"
- fi
-
_debug "STEP 1, Ordering a Certificate"
- _newOrderReplacesObj="$_newOrderObj"
- if [ "$_replaces_certID" ]; then
- _newOrderReplacesObj="$_newOrderObj,\"replaces\": \"$_replaces_certID\""
- fi
- if ! _send_signed_request "$ACME_NEW_ORDER" "$_newOrderReplacesObj}"; then
+ if ! _send_signed_request "$ACME_NEW_ORDER" "$_newOrderObj}"; then
_err "Error creating new order."
_clearup
_on_issue_err "$_post_hook"
return 1
fi
- # RFC 9773 Section 5 only defines the "alreadyReplaced" error, but real CAs
- # (Let's Encrypt) may also reject with a malformed error if the prior cert
- # was issued by a different issuer / different CA. Retry without "replaces"
- # whenever the failure mentions ARI or the replaces field.
- if [ "$_replaces_certID" ] && { _contains "$response" "alreadyReplaced" || _contains "$response" "urn:ietf:params:acme:error:malformed" || _contains "$response" "'replaces'" || _contains "$response" "ARI"; }; then
- _info "ARI 'replaces' rejected by CA, retrying newOrder without 'replaces'."
- if ! _send_signed_request "$ACME_NEW_ORDER" "$_newOrderObj}"; then
- _err "Error creating new order."
- _clearup
- _on_issue_err "$_post_hook"
- return 1
- fi
- fi
if _contains "$response" "invalid"; then
if echo "$response" | _normalizeJson | grep '"status":"invalid"' >/dev/null 2>&1; then
_err "Create new order with invalid status."
@@ -5303,7 +4740,7 @@ issue() {
#for dns manual mode
_savedomainconf "Le_OrderFinalize" "$Le_OrderFinalize"
- _authorizations_seg="$(echo "$response" | _json_decode | _authorizations_from_order)"
+ _authorizations_seg="$(echo "$response" | _json_decode | _egrep_o '"authorizations" *: *\[[^\[]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
_debug2 _authorizations_seg "$_authorizations_seg"
if [ -z "$_authorizations_seg" ]; then
_err "_authorizations_seg not found."
@@ -5369,9 +4806,7 @@ $_authorizations_map"
vtype="$VTYPE_HTTP"
#todo, v2 wildcard force to use dns
- if [ "$_currentRoot" = "$W_DNS_PERSIST" ]; then
- vtype="$VTYPE_DNS_PERSIST"
- elif _startswith "$_currentRoot" "$W_DNS"; then
+ if _startswith "$_currentRoot" "$W_DNS"; then
vtype="$VTYPE_DNS"
fi
@@ -5414,7 +4849,7 @@ $_authorizations_map"
fi
# Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018
- entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^{]*"type":"'$vtype'"[^}]*')"
+ entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
_debug entry "$entry"
if [ -z "$keyauthorization" -a -z "$entry" ]; then
@@ -5429,7 +4864,18 @@ $_authorizations_map"
fi
if [ -z "$keyauthorization" ]; then
+ token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
+ _debug token "$token"
+
+ if [ -z "$token" ]; then
+ _err "Cannot get domain token $entry"
+ _clearup
+ _on_issue_err "$_post_hook"
+ return 1
+ fi
+
uri="$(echo "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)"
+
_debug uri "$uri"
if [ -z "$uri" ]; then
@@ -5438,26 +4884,8 @@ $_authorizations_map"
_on_issue_err "$_post_hook"
return 1
fi
-
- if [ "$vtype" = "$VTYPE_DNS_PERSIST" ]; then
- # dns-persist-01 challenges have no token; the TXT record is
- # provisioned out-of-band. Use a non-empty placeholder so the
- # downstream code does not treat this entry as already verified.
- keyauthorization="$VTYPE_DNS_PERSIST"
- _debug keyauthorization "$keyauthorization"
- else
- token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
- _debug token "$token"
-
- if [ -z "$token" ]; then
- _err "Cannot get domain token $entry"
- _clearup
- _on_issue_err "$_post_hook"
- return 1
- fi
- keyauthorization="$token.$thumbprint"
- _debug keyauthorization "$keyauthorization"
- fi
+ keyauthorization="$token.$thumbprint"
+ _debug keyauthorization "$keyauthorization"
fi
dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot$sep$_authz_url"
@@ -5493,8 +4921,6 @@ $_authorizations_map"
fi
_d_alias="$(_getfield "$_challenge_alias" "$_alias_index")"
test "$_d_alias" = "$NO_VALUE" && _d_alias=""
- # strip the trailing dot of a fully-qualified alias domain
- _d_alias="${_d_alias%.}"
_alias_index="$(_math "$_alias_index" + 1)"
_debug "_d_alias" "$_d_alias"
if [ "$_d_alias" ]; then
@@ -5689,8 +5115,6 @@ $_authorizations_map"
fi
fi
elif [ "$vtype" = "$VTYPE_ALPN" ]; then
- _ncaddr="$(_getfield "$_local_addr" "$_ncIndex")"
- _ncIndex="$(_math $_ncIndex + 1)"
acmevalidationv1="$(printf "%s" "$keyauthorization" | _digest "sha256" "hex")"
_debug acmevalidationv1 "$acmevalidationv1"
if ! _starttlsserver "$d" "" "$Le_TLSPort" "$keyauthorization" "$_ncaddr" "$acmevalidationv1"; then
@@ -5746,7 +5170,7 @@ $_authorizations_map"
status=$(echo "$response" | _egrep_o '"status":"[^"]*' | cut -d : -f 2 | tr -d '"')
_debug2 status "$status"
if _contains "$status" "invalid"; then
- error="$(echo "$response" | _egrep_o '"error":[{][^}]*')"
+ error="$(echo "$response" | _egrep_o '"error":\{[^\}]*')"
_debug2 error "$error"
errordetail="$(echo "$error" | _egrep_o '"detail": *"[^"]*' | cut -d '"' -f 4)"
_debug2 errordetail "$errordetail"
@@ -5921,14 +5345,7 @@ $_authorizations_map"
return 1
fi
- if ! _contains "$response" "$BEGIN_CERT"; then
- response="$(echo "$response" | _dbase64 "multiline" | tr -d '\0' | _normalizeJson)"
- _err "Signing failed: $(echo "$response" | _egrep_o '"detail":"[^"]*"')"
- _on_issue_err "$_post_hook"
- return 1
- fi
-
- echo "$response" | _strip_blank_lines >"$CERT_PATH"
+ echo "$response" >"$CERT_PATH"
_split_cert_chain "$CERT_PATH" "$CERT_FULLCHAIN_PATH" "$CA_CERT_PATH"
if [ -z "$_preferred_chain" ]; then
_preferred_chain=$(_readcaconf DEFAULT_PREFERRED_CHAIN)
@@ -5947,15 +5364,10 @@ $_authorizations_map"
_err "$response"
continue
fi
-
- if ! _contains "$response" "$BEGIN_CERT"; then
- _debug2 "Skipping alternate cert link due to unexpected response format."
- continue
- fi
_relcert="$CERT_PATH.alt"
_relfullchain="$CERT_FULLCHAIN_PATH.alt"
_relca="$CA_CERT_PATH.alt"
- echo "$response" | _strip_blank_lines >"$_relcert"
+ echo "$response" >"$_relcert"
_split_cert_chain "$_relcert" "$_relfullchain" "$_relca"
if [ "$DEBUG" ]; then
_debug "rel chain issuers: " "$(_get_chain_issuers "$_relfullchain")"
@@ -6015,7 +5427,7 @@ $_authorizations_map"
Le_CertCreateTimeStr=$(_time2str "$Le_CertCreateTime")
_savedomainconf "Le_CertCreateTimeStr" "$Le_CertCreateTimeStr"
- if [ -z "$Le_RenewalDays" ]; then
+ if [ -z "$Le_RenewalDays" ] || [ "$Le_RenewalDays" -lt "0" ]; then
Le_RenewalDays="$DEFAULT_RENEW"
else
_savedomainconf "Le_RenewalDays" "$Le_RenewalDays"
@@ -6039,17 +5451,12 @@ $_authorizations_map"
_clearaccountconf "HTTPS_INSECURE"
fi
- if [ "$Le_Listen_V4" ] || [ "$Le_Listen_V6" ]; then
- if [ "$Le_Listen_V4" ]; then
- _savedomainconf "Le_Listen_V4" "$Le_Listen_V4"
- else
- _cleardomainconf Le_Listen_V4
- fi
- if [ "$Le_Listen_V6" ]; then
- _savedomainconf "Le_Listen_V6" "$Le_Listen_V6"
- else
- _cleardomainconf Le_Listen_V6
- fi
+ if [ "$Le_Listen_V4" ]; then
+ _savedomainconf "Le_Listen_V4" "$Le_Listen_V4"
+ _cleardomainconf Le_Listen_V6
+ elif [ "$Le_Listen_V6" ]; then
+ _savedomainconf "Le_Listen_V6" "$Le_Listen_V6"
+ _cleardomainconf Le_Listen_V4
fi
if [ "$Le_ForceNewDomainKey" = "1" ]; then
@@ -6065,75 +5472,25 @@ $_authorizations_map"
_info "It cannot be renewed automatically"
_info "See: $_VALIDITY_WIKI"
else
- Le_NextRenewTime=$(_calc_validto_renew_time "$Le_NextRenewTime" "$Le_RenewalDays" "$(_time)")
- Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
- fi
- elif [ "$Le_RenewalDays" -lt "0" ]; then
- _enddate_value=$(_enddate "$CERT_PATH")
- if [ "$?" != "0" ] || [ -z "$_enddate_value" ]; then
- _err "Failed to get certificate end date for $CERT_PATH"
- return 1
- fi
-
- _endtime=$(_ssldate2time "$_enddate_value")
- if [ "$?" != "0" ] || [ -z "$_endtime" ]; then
- _err "Cannot parse _enddate_value: $_enddate_value"
- return 1
- fi
- Le_NextRenewTime=$(_math "$_endtime" + "$Le_RenewalDays" \* 24 \* 60 \* 60)
- Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
- else
- _endtime_for_cap=""
- _enddate_value=$(_enddate "$CERT_PATH")
- if [ "$?" = "0" ] && [ "$_enddate_value" ]; then
- _endtime_for_cap=$(_ssldate2time "$_enddate_value")
- fi
- Le_NextRenewTime=$(_calc_next_renew_time "$Le_CertCreateTime" "$Le_RenewalDays" "$_endtime_for_cap")
- Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
- fi
-
- # RFC 9773 ARI: if the CA exposes renewalInfo, override Le_NextRenewTime
- # with a time picked at random within the suggestedWindow. This both gives
- # the CA full control over renewal scheduling and disperses renewals across
- # the network so all clients don't hit the CA at the same instant.
- # Set NO_ARI=1 (env, account.conf, or ca.conf) to opt out and fall back to
- # the legacy time-based renewal calculation.
- if [ "$NO_ARI" = "1" ]; then
- _debug "NO_ARI=1, skipping ARI suggestedWindow override"
- elif [ "$ACME_RENEWAL_INFO" ] && [ -f "$CERT_PATH" ] && [ -z "$_notAfter" ]; then
- _ari_resp_new="$(_get_ARI "$CERT_PATH")"
- _debug2 "_ari_resp_new" "$_ari_resp_new"
- _ari_start_new="$(echo "$_ari_resp_new" | _egrep_o '"start" *: *"[^"]*' | sed 's/.*"//')"
- _ari_end_new="$(echo "$_ari_resp_new" | _egrep_o '"end" *: *"[^"]*' | sed 's/.*"//')"
- if [ "$_ari_start_new" ] && [ "$_ari_end_new" ]; then
- _ari_start_t_new="$(_date2time "$(echo "$_ari_start_new" | sed 's/\.[0-9]*//')")"
- _ari_end_t_new="$(_date2time "$(echo "$_ari_end_new" | sed 's/\.[0-9]*//')")"
- if [ "$_ari_start_t_new" ] && [ "$_ari_end_t_new" ] && [ "$_ari_end_t_new" -gt "$_ari_start_t_new" ]; then
- _ari_window=$(_math "$_ari_end_t_new" - "$_ari_start_t_new")
- _ari_offset=$(_math "$(_time)" % "$_ari_window")
- Le_NextRenewTime=$(_math "$_ari_start_t_new" + "$_ari_offset")
+ _now=$(_time)
+ _debug2 "_now" "$_now"
+ _lifetime=$(_math $Le_NextRenewTime - $_now)
+ _debug2 "_lifetime" "$_lifetime"
+ if [ $_lifetime -gt 86400 ]; then
+ #if lifetime is logner than one day, it will renew one day before
+ Le_NextRenewTime=$(_math $Le_NextRenewTime - 86400)
+ Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
+ else
+ #if lifetime is less than 24 hours, it will renew one hour before
+ Le_NextRenewTime=$(_math $Le_NextRenewTime - 3600)
Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
- _info "ARI suggestedWindow: $(__green "$_ari_start_new") to $(__green "$_ari_end_new")"
- _info "Next renewal time picked from ARI window: $(__green "$Le_NextRenewTimeStr")"
fi
fi
+ else
+ Le_NextRenewTime=$(_math "$Le_CertCreateTime" + "$Le_RenewalDays" \* 24 \* 60 \* 60)
+ Le_NextRenewTime=$(_math "$Le_NextRenewTime" - 86400)
+ Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
fi
-
- # Warn when the scheduled renewal falls after the cert has already expired,
- # e.g. a 1-day cert from an internal CA combined with the default 30-day
- # schedule, which computes from the creation date and never looks at
- # notAfter. Skip the warning for a fixed-date --valid-to: there
- # Le_NextRenewTime equals the expiry by design and the non-renewable state
- # was already reported above. https://github.com/acmesh-official/acme.sh/issues/6917
- if [ -z "$_valid_to" ] || _startswith "$_valid_to" "+"; then
- _renew_chk_enddate="$(_enddate "$CERT_PATH")"
- _renew_chk_endtime="$(_ssldate2time "$_renew_chk_enddate")"
- if [ "$Le_NextRenewTime" ] && [ "$_renew_chk_endtime" ] && [ "$Le_NextRenewTime" -ge "$_renew_chk_endtime" ]; then
- _info "$(__red "WARNING: the cert expires at $_renew_chk_enddate, BEFORE the next scheduled renewal time $Le_NextRenewTimeStr.")"
- _info "$(__red "The cert will already be expired when the renewal runs. If your CA issues short-lived certs, use a negative --days value (e.g. --days -1) to renew relative to the expiry time.")"
- fi
- fi
-
_savedomainconf "Le_NextRenewTimeStr" "$Le_NextRenewTimeStr"
_savedomainconf "Le_NextRenewTime" "$Le_NextRenewTime"
@@ -6143,12 +5500,6 @@ $_authorizations_map"
_toPkcs "$CERT_PFX_PATH" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$Le_PFXPassword"
fi
- #convert to pkcs8
- Le_PKCS8Password="$(_readdomainconf Le_PKCS8Password)"
- if [ "$Le_PKCS8Password" ]; then
- _toPkcs8 "$CERT_PKCS8_PATH" "$CERT_KEY_PATH" "$Le_PKCS8Password"
- fi
-
if [ "$_real_cert$_real_key$_real_ca$_reload_cmd$_real_fullchain" ]; then
_savedomainconf "Le_RealCertPath" "$_real_cert"
_savedomainconf "Le_RealCACertPath" "$_real_ca"
@@ -6167,18 +5518,6 @@ $_authorizations_map"
}
#in_out_cert out_fullchain out_ca
-#Reads a PEM chain from stdin, prints it without the blank lines.
-#Some CAs (Let's Encrypt) separate the certificates of a chain with a blank
-#line, others (ZeroSSL) don't. The blank lines are valid PEM (RFC 7468), but
-#some devices and APIs reject them, so the certs are stored back to back.
-#https://github.com/acmesh-official/acme.sh/issues/1940
-_strip_blank_lines() {
- #spell out space and tab: Solaris sed treats [[:space:]] as a literal
- #bracket set and silently stops matching the blank lines
- _sbl_tab="$(printf '\t')"
- sed "/^[ $_sbl_tab]*\$/d"
-}
-
_split_cert_chain() {
_certf="$1"
_fullchainf="$2"
@@ -6207,7 +5546,7 @@ renew() {
_debug "_renewServer" "$_renewServer"
_initpath "$Le_Domain" "$_isEcc"
- _info "Renew: $Le_Domain"
+
_set_level=${NOTIFY_LEVEL:-$NOTIFY_LEVEL_DEFAULT}
_info "$(__green "Renewing: '$Le_Domain'")"
if [ ! -f "$DOMAIN_CONF" ]; then
@@ -6239,14 +5578,6 @@ renew() {
fi
_info "Renewing using Le_API=$Le_API"
- # Honor --local-address given on the renew/renewAll command line: it overrides
- # the value saved at issue time (and gets re-saved by issue() below), so certs
- # issued before the machine gained multiple addresses can still be renewed.
- # https://github.com/acmesh-official/acme.sh/issues/7009
- if [ "$_local_address" ]; then
- Le_LocalAddress="$_local_address"
- fi
-
_clearAPI
_clearCA
export ACME_DIRECTORY="$Le_API"
@@ -6255,77 +5586,7 @@ renew() {
_debug2 "initpath again."
_initpath "$Le_Domain" "$_isEcc"
- # ARI (RFC 9773): fetch the CA's suggestedWindow on every renewal check.
- # If the window has started, renew now even if Le_NextRenewTime is in the future.
- # Set NO_ARI=1 (env, account.conf, or ca.conf) to opt out and use only
- # Le_NextRenewTime for the renewal decision.
- if [ "$NO_ARI" = "1" ]; then
- _debug "NO_ARI=1, skipping ARI suggestedWindow check"
- elif [ -z "$FORCE" ] && [ -f "$CERT_PATH" ]; then
- if _initAPI && [ "$ACME_RENEWAL_INFO" ]; then
- _ari_resp="$(_get_ARI "$CERT_PATH")"
- _debug2 "_ari_resp" "$_ari_resp"
- _ari_start="$(echo "$_ari_resp" | _egrep_o '"start" *: *"[^"]*' | sed 's/.*"//')"
- _ari_end="$(echo "$_ari_resp" | _egrep_o '"end" *: *"[^"]*' | sed 's/.*"//')"
- _debug "ARI suggestedWindow.start" "$_ari_start"
- _debug "ARI suggestedWindow.end" "$_ari_end"
- if [ "$_ari_start" ] && [ "$_ari_end" ]; then
- _ari_start_t="$(_date2time "$(echo "$_ari_start" | sed 's/\.[0-9]*//')")"
- _ari_end_t="$(_date2time "$(echo "$_ari_end" | sed 's/\.[0-9]*//')")"
- _ari_explanation_url="$(echo "$_ari_resp" | _egrep_o '"explanationURL" *: *"[^"]*' | sed 's/.*"//')"
- _debug "_ari_start_t" "$_ari_start_t"
- _debug "_ari_end_t" "$_ari_end_t"
- _debug "_ari_explanation_url" "$_ari_explanation_url"
- _debug "Le_NextRenewTime" "$Le_NextRenewTime"
- # Update ARI if needed
- if [ "$_ari_start_t" ] && [ "$_ari_end_t" ] && [ "$Le_NextRenewTime" ] && [ "$_ari_end_t" -gt "$_ari_start_t" ] && ([ "$Le_NextRenewTime" -lt "$_ari_start_t" ] || [ "$Le_NextRenewTime" -gt "$_ari_end_t" ]); then
- _ari_old_time_str="$Le_NextRenewTimeStr"
- _info "Current renewal time: $(__green "$_ari_old_time_str")"
- _ari_window=$(_math "$_ari_end_t" - "$_ari_start_t")
- _ari_offset=$(_math "$(_time)" % "$_ari_window")
- Le_NextRenewTime=$(_math "$_ari_start_t" + "$_ari_offset")
- Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
- _info "ARI suggestedWindow: $(__green "$_ari_start") to $(__green "$_ari_end")"
- _info "Updating renewal time picked from ARI window: $(__green "$Le_NextRenewTimeStr")"
- _savedomainconf Le_NextRenewTime "$Le_NextRenewTime"
- _savedomainconf Le_NextRenewTimeStr "$Le_NextRenewTimeStr"
- fi
- if [ "$Le_NextRenewTime" ] && [ "$(_time)" -ge "$Le_NextRenewTime" ]; then
- _info "ARI suggested renewal has passed ($(__green "$Le_NextRenewTimeStr")), proceeding with renewal."
- if [ "$_ari_explanation_url" ]; then
- _info "For more information on this renewal: $(__green "$_ari_explanation_url")"
- fi
- fi
- fi
- fi
- fi
-
if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then
- _renew_retry_fixed=""
- res="0"
- _ensure_install "$Le_Domain"
- res="$?"
- if [ "$Le_DeployHook" ] && [ "$res" = "0" ]; then
- _ensure_deploy "$Le_Domain"
- res="$?"
- fi
- if [ "$res" != "0" ]; then
- if [ -z "$_ACME_IN_RENEWALL" ]; then
- if [ $_set_level -ge $NOTIFY_LEVEL_ERROR ]; then
- _send_notify "Renew $Le_Domain error" "There is an error." "$NOTIFY_HOOK" 1
- fi
- fi
- return 1
- fi
- if [ "$_renew_retry_fixed" ]; then
- _info "Install/deploy retry succeeded, no renewal is needed."
- if [ -z "$_ACME_IN_RENEWALL" ]; then
- if [ $_set_level -ge $NOTIFY_LEVEL_RENEW ]; then
- _send_notify "Renew $Le_Domain success" "Good, the cert install/deploy retry succeeded." "$NOTIFY_HOOK" 0
- fi
- fi
- return 0
- fi
_info "Skipping. Next renewal time is: $(__green "$Le_NextRenewTimeStr")"
_info "Add '$(__red '--force')' to force renewal."
if [ -z "$_ACME_IN_RENEWALL" ]; then
@@ -6368,8 +5629,11 @@ renew() {
fi
issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress" "$Le_ChallengeAlias" "$Le_Preferred_Chain" "$Le_Valid_From" "$Le_Valid_To" "$Le_Certificate_Profile" "$Le_ExtKeyUse"
res="$?"
+ if [ "$res" != "0" ]; then
+ return "$res"
+ fi
- if [ "$Le_DeployHook" ] && [ "$res" = "0" ]; then
+ if [ "$Le_DeployHook" ]; then
_deploy "$Le_Domain" "$Le_DeployHook"
res="$?"
fi
@@ -6409,10 +5673,6 @@ renewAll() {
_set_level=${NOTIFY_LEVEL:-$NOTIFY_LEVEL_DEFAULT}
_debug "_set_level" "$_set_level"
export _ACME_IN_RENEWALL=1
- if ! [ -d "$CERT_HOME" ]; then
- _err "$CERT_HOME is not a directory, please check your configuration."
- return 1
- fi
for di in "${CERT_HOME}"/*.* "${CERT_HOME}"/*:*; do
_debug di "$di"
if ! [ -d "$di" ]; then
@@ -6421,19 +5681,12 @@ renewAll() {
fi
d=$(basename "$di")
_debug d "$d"
- _d_ari="$di.ari"
- _debug _d_ari "$_d_ari"
(
if _endswith "$d" "$ECC_SUFFIX"; then
_isEcc=$(echo "$d" | cut -d "$ECC_SEP" -f 2)
d=$(echo "$d" | cut -d "$ECC_SEP" -f 1)
fi
renew "$d" "$_isEcc" "$_server"
- rc="$?"
- if [ "$rc" = "0" ] && [ "$_ari_explanation_url" ]; then
- echo "$_ari_explanation_url" >"$_d_ari"
- fi
- return $rc
)
rc="$?"
_debug "Return code: $rc"
@@ -6448,13 +5701,8 @@ renewAll() {
_send_notify "Renew $d success" "Good, the cert is renewed." "$NOTIFY_HOOK" 0
fi
fi
- _renewal_explanation=""
- if [ -f "$_d_ari" ]; then
- _renewal_explanation=" ($(cat "$_d_ari"))"
- rm -f "$_d_ari"
- fi
- _success_msg="${_success_msg} $d$_renewal_explanation
+ _success_msg="${_success_msg} $d
"
elif [ "$rc" = "$RENEW_SKIP" ]; then
if [ $_error_level -gt $NOTIFY_LEVEL_SKIP ]; then
@@ -6697,7 +5945,7 @@ list_profiles() {
fi
normalized_response=$(echo "$response" | _normalizeJson)
- profiles_json=$(echo "$normalized_response" | _egrep_o '"profiles" *: *[{][^}]*[}]')
+ profiles_json=$(echo "$normalized_response" | _egrep_o '"profiles" *: *\{[^\}]*\}')
if [ -z "$profiles_json" ]; then
_info "The CA '$_l_server_name' does not publish certificate profiles via its directory endpoint."
@@ -6759,45 +6007,6 @@ _deploy() {
_info "$(__green Success)"
fi
done
-
- _deploy_success_time="$(_time)"
- _savedomainconf "Le_DeploySuccessTime" "$_deploy_success_time"
- _savedomainconf "Le_DeploySuccessTimeStr" "$(_time2str "$_deploy_success_time")"
-}
-
-_ensure_deploy() {
- _d="$1"
- if [ -z "$Le_DeployHook" ]; then
- return 0
- fi
- if [ -z "$Le_CertCreateTime" ]; then
- return 0
- fi
-
- _deploy_success_time="$(_readdomainconf Le_DeploySuccessTime)"
- if [ -z "$_deploy_success_time" ]; then
- _debug "Le_DeploySuccessTime is empty, skip deploy retry check."
- return 0
- fi
- case "$_deploy_success_time$Le_CertCreateTime" in
- *[!0-9]*)
- _debug "Le_DeploySuccessTime or Le_CertCreateTime is not a number, skip deploy retry check."
- return 0
- ;;
- esac
-
- if [ "$_deploy_success_time" -lt "$Le_CertCreateTime" ]; then
- _info "The cert was created after the last successful deploy, retrying deploy hooks."
- if _deploy "$_d" "$Le_DeployHook"; then
- _info "Deploy retry succeeded."
- _renew_retry_fixed=1
- return 0
- fi
- _err "Deploy retry failed."
- return 1
- fi
-
- return 0
}
#domain hooks
@@ -6818,13 +6027,7 @@ deploy() {
fi
_debug2 DOMAIN_CONF "$DOMAIN_CONF"
- # The cert dir may exist without a domain conf (e.g. the conf was deleted, or
- # the cert was placed here manually). Deploy can still proceed using env-provided
- # settings, and _savedomainconf below will recreate the conf, so only source it
- # when present instead of failing on a missing file.
- if [ -f "$DOMAIN_CONF" ]; then
- . "$DOMAIN_CONF"
- fi
+ . "$DOMAIN_CONF"
_savedomainconf Le_DeployHook "$_hooks"
@@ -6957,54 +6160,9 @@ _installcert() {
_info "$(__green "Reload successful")"
else
_err "Reload error for: $_main_domain"
- return 1
fi
fi
- _installcert_success_time="$(_time)"
- _savedomainconf "Le_InstallCertSuccessTime" "$_installcert_success_time"
- _savedomainconf "Le_InstallCertSuccessTimeStr" "$(_time2str "$_installcert_success_time")"
-}
-
-_ensure_install() {
- _d="$1"
- if [ -z "$Le_CertCreateTime" ]; then
- return 0
- fi
-
- _real_cert="$(_readdomainconf Le_RealCertPath)"
- _real_key="$(_readdomainconf Le_RealKeyPath)"
- _real_ca="$(_readdomainconf Le_RealCACertPath)"
- _reload_cmd="$(_readdomainconf Le_ReloadCmd)"
- _real_fullchain="$(_readdomainconf Le_RealFullChainPath)"
- if [ -z "$_real_cert$_real_key$_real_ca$_reload_cmd$_real_fullchain" ]; then
- return 0
- fi
-
- _installcert_success_time="$(_readdomainconf Le_InstallCertSuccessTime)"
- if [ -z "$_installcert_success_time" ]; then
- _debug "Le_InstallCertSuccessTime is empty, skip install retry check."
- return 0
- fi
- case "$_installcert_success_time$Le_CertCreateTime" in
- *[!0-9]*)
- _debug "Le_InstallCertSuccessTime or Le_CertCreateTime is not a number, skip install retry check."
- return 0
- ;;
- esac
-
- if [ "$_installcert_success_time" -lt "$Le_CertCreateTime" ]; then
- _info "The cert was created after the last successful install, retrying install cert."
- if _installcert "$_d" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd"; then
- _info "Install cert retry succeeded."
- _renew_retry_fixed=1
- return 0
- fi
- _err "Install cert retry failed."
- return 1
- fi
-
- return 0
}
__read_password() {
@@ -7024,7 +6182,6 @@ _install_win_taskscheduler() {
_lesh="$1"
_centry="$2"
_randomminute="$3"
- _randomhour="$4"
if ! _exists cygpath; then
_err "cygpath not found"
return 1
@@ -7051,10 +6208,8 @@ _install_win_taskscheduler() {
_info "$PROJECT_NAME will not save your password."
_info "Please input your Windows password for: $(__green "$_myname")"
_password="$(__read_password)"
- #schtasks.exe /ST requires the HH:mm format, so the minute must be zero-padded (issue 4950)
- _st_minute="$(printf "%02d" "$_randomminute")"
- #SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_st_minute" '/RU' "$_myname" '/RP' "$_password" '/TR' "$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'" >/dev/null
- echo SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_st_minute" '/RU' "$_myname" '/RP' "$_password" '/TR' "\"$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'\"" | cmd.exe >/dev/null
+ #SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_randomminute" '/RU' "$_myname" '/RP' "$_password" '/TR' "$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'" >/dev/null
+ echo SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_randomminute" '/RU' "$_myname" '/RP' "$_password" '/TR' "\"$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'\"" | cmd.exe >/dev/null
echo
}
@@ -7096,7 +6251,7 @@ installcronjob() {
fi
_t=$(_time)
random_minute=$(_math $_t % 60)
- random_hour=$(_math $_t / 60 % 6)
+ random_hour=$(_math $_t / 60 % 24)
if ! _exists "$_CRONTAB" && _exists "fcrontab"; then
_CRONTAB="fcrontab"
@@ -7105,7 +6260,7 @@ installcronjob() {
if ! _exists "$_CRONTAB"; then
if _exists cygpath && _exists schtasks.exe; then
_info "It seems you are on Windows, let's install the Windows scheduler task."
- if _install_win_taskscheduler "$lesh" "$_c_entry" "$random_minute" "$random_hour"; then
+ if _install_win_taskscheduler "$lesh" "$_c_entry" "$random_minute"; then
_info "Successfully installed Windows scheduler task."
return 0
else
@@ -7119,39 +6274,15 @@ installcronjob() {
return 1
fi
_info "Installing cron job"
- _cron_entry="$random_minute $random_hour,$(_math "$random_hour" + 6),$(_math "$random_hour" + 12),$(_math "$random_hour" + 18) * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null"
- _cron_entries="$($_CRONTAB -l 2>/dev/null)"
- if [ "$?" != "0" ]; then
- #when the user has no crontab yet, crontab -l also exits non-zero;
- #only that case may proceed with an empty list. Any other listing
- #failure must abort: piping an incomplete list back into 'crontab -'
- #would wipe the user's existing cron jobs (issue 3079)
- _cron_list_err="$($_CRONTAB -l 2>&1 >/dev/null)"
- #separate greps: BRE alternation \| is a GNU extension and Solaris
- #grep takes only a single -e pattern
- if echo "$_cron_list_err" | grep -i "no crontab" >/dev/null ||
- echo "$_cron_list_err" | grep -i "no fcrontab" >/dev/null ||
- echo "$_cron_list_err" | grep -i "can't open" >/dev/null; then
- _cron_entries=""
- else
- _err "Can not list the current cron jobs: $_cron_list_err"
- _err "Refusing to install the cron job, that could wipe your existing cron jobs."
- _err "Please add this cron job manually:"
- _err "$_cron_entry"
- return 1
- fi
- fi
- if ! echo "$_cron_entries" | grep "$PROJECT_ENTRY --cron"; then
+ if ! $_CRONTAB -l | grep "$PROJECT_ENTRY --cron"; then
if _exists uname && uname -a | grep SunOS >/dev/null; then
_CRONTAB_STDIN="$_CRONTAB --"
else
_CRONTAB_STDIN="$_CRONTAB -"
fi
- {
- if [ "$_cron_entries" ]; then
- echo "$_cron_entries"
- fi
- echo "$_cron_entry"
+ $_CRONTAB -l | {
+ cat
+ echo "$random_minute $random_hour * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null"
} | $_CRONTAB_STDIN
fi
if [ "$?" != "0" ]; then
@@ -7347,7 +6478,7 @@ _deactivate() {
_err "Cannot get new order for domain."
return 1
fi
- _authorizations_seg="$(echo "$response" | _json_decode | _authorizations_from_order)"
+ _authorizations_seg="$(echo "$response" | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
_debug2 _authorizations_seg "$_authorizations_seg"
if [ -z "$_authorizations_seg" ]; then
_err "_authorizations_seg not found."
@@ -7380,7 +6511,7 @@ _deactivate() {
_debug "Trigger validation."
vtype="$(_getIdType "$_d_domain")"
# Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018
- entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^{]*"type":"'$vtype'"[^}]*')"
+ entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
_debug entry "$entry"
if [ -z "$entry" ]; then
_err "$d: Cannot get domain token"
@@ -7464,60 +6595,34 @@ deactivate() {
done
}
-#reads the output of "openssl x509 -text" from stdin, prints the hex AKI
-#the value is on the line right after the extension header; "grep -A" is not
-#portable (Solaris /usr/bin/grep: "illegal option -- A"), so select from the
-#header to EOF and keep the second line of that range
-_extractAKI() {
- sed -n '/X509v3 Authority Key Identifier/,$p' | _head_n 2 | _tail_n 1 | tr -d ': ' | sed "s/keyid//"
-}
-
#cert
_getAKI() {
_cert="$1"
- ${ACME_OPENSSL_BIN:-openssl} x509 -in "$_cert" -text -noout | _extractAKI
+ openssl x509 -in "$_cert" -text -noout | grep "X509v3 Authority Key Identifier" -A 1 | _tail_n 1 | tr -d ' :'
}
#cert
_getSerial() {
_cert="$1"
- ${ACME_OPENSSL_BIN:-openssl} x509 -in "$_cert" -serial -noout | cut -d = -f 2
+ openssl x509 -in "$_cert" -serial -noout | cut -d = -f 2
}
#cert
-#Compute the ARI/replaces certID for a cert: base64url(AKI).base64url(Serial)
-#per RFC 9773 Section 4.1.
-_getARICertID() {
+_get_ARI() {
_cert="$1"
_aki=$(_getAKI "$_cert")
_ser=$(_getSerial "$_cert")
_debug2 "_aki" "$_aki"
_debug2 "_ser" "$_ser"
- # RFC 9773 Section 4.1 requires the DER-encoded INTEGER value bytes of
- # serialNumber. When the high bit of the first byte is set (>= 0x80) DER
- # prepends a 0x00 sign byte to keep the integer positive; openssl's hex
- # output strips that, so add it back. Boulder (LE) accepts either form,
- # but Sectigo (ZeroSSL) is strict and rejects newOrder with HTTP 401
- # "replaces field does not identify a certificate" if the byte is missing.
- case "$_ser" in
- [89aAbBcCdDeEfF]*) _ser="00$_ser" ;;
- esac
-
- _akiurl="$(echo "$_aki" | _h2b | _base64 | _url_replace)"
+ _akiurl="$(echo "$_aki" | _h2b | _base64 | tr -d = | _url_encode)"
_debug2 "_akiurl" "$_akiurl"
- _serurl="$(echo "$_ser" | _h2b | _base64 | _url_replace)"
+ _serurl="$(echo "$_ser" | _h2b | _base64 | tr -d = | _url_encode)"
_debug2 "_serurl" "$_serurl"
- printf "%s.%s" "$_akiurl" "$_serurl"
-}
-
-#cert
-_get_ARI() {
- _cert="$1"
- _ari_certID="$(_getARICertID "$_cert")"
- _ARI_URL="$ACME_RENEWAL_INFO/$_ari_certID"
+ _ARI_URL="$ACME_RENEWAL_INFO/$_akiurl.$_serurl"
_get "$_ARI_URL"
+
}
# Detect profile file if not specified as environment variable
@@ -7605,9 +6710,9 @@ _precheck() {
fi
if ! _exists "socat" && ! _exists "python" && ! _exists "python2" && ! _exists "python3"; then
- _info "It is recommended to install socat or python first."
- _info "We use socat or python for the standalone server, which is used for standalone mode."
- _info "If you don't want to use standalone mode, you may ignore this warning."
+ _err "It is recommended to install socat or python first."
+ _err "We use socat or python for the standalone server, which is used for standalone mode."
+ _err "If you don't want to use standalone mode, you may ignore this warning."
fi
return 0
@@ -7631,15 +6736,6 @@ _installalias() {
_c_home="$1"
_initpath
- _alias_bin="$LE_WORKING_DIR/$PROJECT_ENTRY"
- if [ ! -f "$_alias_bin" ]; then
- #ACME_PACKAGED install: no copy in LE_WORKING_DIR, alias the current script
- _script="$(_readlink "$_SCRIPT_")"
- if [ -f "$_script" ]; then
- _alias_bin="$_script"
- fi
- fi
-
_envfile="$LE_WORKING_DIR/$PROJECT_ENTRY.env"
if [ "$_upgrading" ] && [ "$_upgrading" = "1" ]; then
echo "$(cat "$_envfile")" | sed "s|^LE_WORKING_DIR.*$||" >"$_envfile"
@@ -7657,11 +6753,7 @@ _installalias() {
else
_sed_i "/^export LE_CONFIG_HOME/d" "$_envfile"
fi
- _setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$_alias_bin$_c_entry\""
- if [ -f "$LE_WORKING_DIR/$PROJECT_ENTRY.completion" ]; then
- #the completion file does nothing when sourced by a non-bash shell
- _setopt "$_envfile" ". \"$LE_WORKING_DIR/$PROJECT_ENTRY.completion\""
- fi
+ _setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
_profile="$(_detect_profile)"
if [ "$_profile" ]; then
@@ -7684,7 +6776,7 @@ _installalias() {
else
_sed_i "/^setenv LE_CONFIG_HOME/d" "$_cshfile"
fi
- _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$_alias_bin$_c_entry\""
+ _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
_setopt "$_csh_profile" "source \"$_cshfile\""
fi
@@ -7696,7 +6788,7 @@ _installalias() {
if [ "$_c_home" ]; then
_setopt "$_cshfile" "setenv LE_CONFIG_HOME" " " "\"$LE_CONFIG_HOME\""
fi
- _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$_alias_bin$_c_entry\""
+ _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
_setopt "$_tcsh_profile" "source \"$_cshfile\""
fi
@@ -7770,38 +6862,25 @@ install() {
chmod 700 "$LE_CONFIG_HOME"
fi
- if [ "$ACME_PACKAGED" ]; then
- #the script and its hooks are managed by a system package manager,
- #do not copy them into LE_WORKING_DIR. https://github.com/acmesh-official/acme.sh/issues/7135
- _info "ACME_PACKAGED is set, skipping the script copy."
- else
- cp "$PROJECT_ENTRY" "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/$PROJECT_ENTRY"
+ cp "$PROJECT_ENTRY" "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/$PROJECT_ENTRY"
- if [ "$?" != "0" ]; then
- _err "Installation failed, cannot copy $PROJECT_ENTRY"
- return 1
- fi
-
- _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY"
-
- if [ -f "$PROJECT_ENTRY.completion" ]; then
- cp "$PROJECT_ENTRY.completion" "$LE_WORKING_DIR/"
- _debug "Installed bash completion to $LE_WORKING_DIR/$PROJECT_ENTRY.completion"
- fi
+ if [ "$?" != "0" ]; then
+ _err "Installation failed, cannot copy $PROJECT_ENTRY"
+ return 1
fi
+ _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY"
+
if [ "$_ACME_IN_CRON" != "1" ] && [ -z "$_noprofile" ]; then
_installalias "$_c_home"
fi
- if [ -z "$ACME_PACKAGED" ]; then
- for subf in $_SUB_FOLDERS; do
- if [ -d "$subf" ]; then
- mkdir -p "$LE_WORKING_DIR/$subf"
- cp "$subf"/* "$LE_WORKING_DIR"/"$subf"/
- fi
- done
- fi
+ for subf in $_SUB_FOLDERS; do
+ if [ -d "$subf" ]; then
+ mkdir -p "$LE_WORKING_DIR/$subf"
+ cp "$subf"/* "$LE_WORKING_DIR"/"$subf"/
+ fi
+ done
if [ ! -f "$ACCOUNT_CONF_PATH" ]; then
_initconf
@@ -7813,12 +6892,6 @@ install() {
if [ "$_DEFAULT_CERT_HOME" != "$CERT_HOME" ]; then
_saveaccountconf "CERT_HOME" "$CERT_HOME"
- # Create the custom cert home now instead of on first issuance, so the
- # user can see --install honored it.
- # https://github.com/acmesh-official/acme.sh/issues/4756
- if [ ! -d "$CERT_HOME" ]; then
- mkdir -p "$CERT_HOME"
- fi
fi
if [ "$_DEFAULT_ACCOUNT_KEY_PATH" != "$ACCOUNT_KEY_PATH" ]; then
@@ -7829,7 +6902,7 @@ install() {
installcronjob "$_c_home"
fi
- if [ -z "$NO_DETECT_SH" ] && [ -z "$ACME_PACKAGED" ]; then
+ if [ -z "$NO_DETECT_SH" ]; then
#Modify shebang
if _exists bash; then
_bash_path="$(bash -c "command -v bash 2>/dev/null")"
@@ -7854,9 +6927,7 @@ install() {
if [ "$_accountemail" ]; then
_saveaccountconf "ACCOUNT_EMAIL" "$_accountemail"
fi
- if [ -z "$ACME_PACKAGED" ]; then
- _saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)"
- fi
+ _saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)"
_info OK
}
@@ -7870,12 +6941,7 @@ uninstall() {
_uninstallalias
- if [ -z "$ACME_PACKAGED" ]; then
- #don't remove the script when it is managed by a system package manager,
- #LE_WORKING_DIR may point to the packaged files
- rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY"
- rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY.completion"
- fi
+ rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY"
_info "The keys and certs are in \"$(__green "$LE_CONFIG_HOME")\". You can remove them by yourself."
}
@@ -7911,24 +6977,20 @@ cron() {
_initpath
_info "$(__green "===Starting cron===")"
if [ "$AUTO_UPGRADE" = "1" ]; then
- if [ "$ACME_PACKAGED" ]; then
- _info "ACME_PACKAGED is set, skipping the auto upgrade."
- else
- export LE_WORKING_DIR
- (
- if ! upgrade; then
- _err "Cron: Upgrade failed!"
- return 1
- fi
- )
- . "$LE_WORKING_DIR/$PROJECT_ENTRY" >/dev/null
-
- if [ -t 1 ]; then
- __INTERACTIVE="1"
+ export LE_WORKING_DIR
+ (
+ if ! upgrade; then
+ _err "Cron: Upgrade failed!"
+ return 1
fi
+ )
+ . "$LE_WORKING_DIR/$PROJECT_ENTRY" >/dev/null
- _info "Automatically upgraded to: $VER"
+ if [ -t 1 ]; then
+ __INTERACTIVE="1"
fi
+
+ _info "Automatically upgraded to: $VER"
fi
_TREAT_SKIP_AS_SUCCESS="1"
renewAll
@@ -7977,14 +7039,6 @@ _send_notify() {
continue
fi
if ! (
- # The dns/deploy hooks export _H1.._H5 in the main process, so the
- # values are inherited here. Clear them: a stale Authorization header
- # from another service must not leak into the notify request.
- export _H1=""
- export _H2=""
- export _H3=""
- export _H4=""
- export _H5=""
if ! . "$_n_hook_file"; then
_err "Error loading file $_n_hook_file. Please check your API file and try again."
return 1
@@ -8102,11 +7156,8 @@ Commands:
-ccr, --create-csr Create CSR, professional use.
--create-domain-key Create an domain private key, professional use.
--update-account Update account info.
- --update-account-key Rotate account key.
--register-account Register account key.
--deactivate-account Deactivate the account.
- --make-dns-persist-value Print the DNS TXT record(s) to enable persistent DNS validation
- (draft-ietf-acme-dns-persist-01). Use with -d .
--create-account-key Create an account private key, professional use.
--install-cronjob Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job.
--uninstall-cronjob Uninstall the cron job. The 'uninstall' command can do this automatically.
@@ -8154,34 +7205,16 @@ Parameters:
--dns [dns_hook] Use dns manual mode or dns api. Defaults to manual mode when argument is omitted.
See: $_DNS_API_WIKI
- --dns-persist Use dns-persist-01 validation (draft-ietf-acme-dns-persist-01).
- Requires the persistent _validation-persist TXT record to already
- exist. Use '--make-dns-persist-value' to print the value to add.
-
--dnssleep The time in seconds to wait for all the txt records to propagate in dns api mode.
It's not necessary to use this by default, $PROJECT_NAME polls dns status by DOH automatically.
- -k, --keylength Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256 (default), ec-384, ec-521.
- -ak, --accountkeylength Specifies the account key length: 2048, 3072, 4096, 8192 or ec-256 (default), ec-384, ec-521.
+ -k, --keylength Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384, ec-521.
+ -ak, --accountkeylength Specifies the account key length: 2048, 3072, 4096
--log [file] Specifies the log file. Defaults to \"$DEFAULT_LOG_FILE\" if argument is omitted.
--log-level <1|2> Specifies the log level, default is $DEFAULT_LOG_LEVEL.
--syslog <0|3|6|7> Syslog level, 0: disable syslog, 3: error, 6: info, 7: debug.
--eab-kid Key Identifier for External Account Binding.
--eab-hmac-key HMAC key for External Account Binding.
- --dns-persist-wildcard Used with '--make-dns-persist-value'. Adds 'policy=wildcard' to the
- generated TXT record so the issuer is also authorized for wildcards
- and subdomains (draft-ietf-acme-dns-persist-01). It is implied when
- the domain given to -d is a wildcard (e.g. '*.example.com'); the
- record itself is always published at the base domain.
- --dns-persist-ca-name Used with '--make-dns-persist-value'. Use the given CA identity domain
- (e.g. 'ssl.com') as the issuer-domain-name in the TXT record. If
- omitted, the identities are read from the ACME directory's
- 'caaIdentities' field and one record is printed per identity.
- --dns-persist-days Used with '--make-dns-persist-value'. Add a 'persistUntil' field to
- the TXT record so the record self-expires N days from now (the CA
- will refuse new validations against the record after that time).
- If omitted, the record has no expiry.
-
These parameters are to install the cert to nginx/Apache or any other server after issue/renew a cert:
@@ -8200,11 +7233,8 @@ Parameters:
--config-home Specifies the home dir to save all the configurations.
--useragent Specifies the user agent string. it will be saved for future use too.
-m, --email Specifies the account email, only valid for the '--install' and '--update-account' command.
- Multiple emails can be given as a comma-separated list: 'a@example.com,b@example.com'
--accountkey Specifies the account key path, only valid for the '--install' command.
--days Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days.
- A negative value renews that many days before the cert expiry.
- Negative values could be used to specify a number of days relative to the expiration date of the certificate.
--httpport Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer.
--tlsport Specifies the standalone tls listening port. Only valid if the server is behind a reverse proxy or load balancer.
--local-address Specifies the standalone/tls server listening address, in case you have multiple ip addresses.
@@ -8230,9 +7260,8 @@ Parameters:
--ocsp, --ocsp-must-staple Generate OCSP-Must-Staple extension.
--always-force-new-domain-key Generate new domain key on renewal. Otherwise, the domain key is not changed by default.
--auto-upgrade [0|1] Valid for '--upgrade' command, indicating whether to upgrade automatically in future. Defaults to 1 if argument is omitted.
- --listen-v4 Force standalone/tls server to listen at ipv4 only.
- By default the standalone server listens on both ipv4 and ipv6.
- --listen-v6 Force standalone/tls server to listen at ipv6 only.
+ --listen-v4 Force standalone/tls server to listen at ipv4.
+ --listen-v6 Force standalone/tls server to listen at ipv6.
--request-v4 Force client requests to use ipv4 to connect to the CA server.
--request-v6 Force client requests to use ipv6 to connect to the CA server.
--openssl-bin Specifies a custom openssl bin location.
@@ -8254,17 +7283,13 @@ Parameters:
--revoke-reason <0-10> The reason for revocation, can be used in conjunction with the '--revoke' command.
See: $_REVOKE_WIKI
- --password Add a password to the exported pfx or pkcs8 file. Use with '--to-pkcs12' or '--to-pkcs8'.
+ --password Add a password to exported pfx file. Use with --to-pkcs12.
"
}
installOnline() {
- if [ "$ACME_PACKAGED" ]; then
- _err "ACME_PACKAGED is set: acme.sh is managed by the system package manager, please use it to upgrade."
- return 1
- fi
_info "Installing from online archive."
_branch="$BRANCH"
@@ -8288,9 +7313,7 @@ installOnline() {
cd "$PROJECT_NAME-$_branch"
chmod +x $PROJECT_ENTRY
- ./$PROJECT_ENTRY --install "$@"
- _install_rc="$?"
- if [ "$_install_rc" = "0" ]; then
+ if ./$PROJECT_ENTRY --install "$@"; then
_info "Install success!"
fi
@@ -8298,9 +7321,6 @@ installOnline() {
rm -rf "$PROJECT_NAME-$_branch"
rm -f "$localname"
- # Propagate the install result so a failed upgrade is not reported as
- # success. https://github.com/acmesh-official/acme.sh/issues/6477
- exit "$_install_rc"
)
}
@@ -8322,10 +7342,6 @@ _getUpgradeHash() {
}
upgrade() {
- if [ "$ACME_PACKAGED" ]; then
- _err "ACME_PACKAGED is set: acme.sh is managed by the system package manager, please use it to upgrade."
- exit 1
- fi
if (
_initpath
[ -z "$FORCE" ] && [ "$(_getUpgradeHash)" = "$(_readaccountconf "UPGRADE_HASH")" ] && _info "Already up to date!" && exit 0
@@ -8397,16 +7413,9 @@ _checkSudo() {
return 0
fi
if [ -n "$SUDO_COMMAND" ]; then
- #The SUDO_* env vars are often inherited into shells that were not
- #started as `sudo acme.sh` at all (e.g. `sudo su - user`, or
- #`sudo pct enter ` on Proxmox, which copies them into the
- #container). Only warn when sudo was used to run acme.sh itself;
- #anything else means the sudo happened further up and is fine.
- #https://github.com/acmesh-official/acme.sh/issues/6400
- if _contains "$SUDO_COMMAND" "$PROJECT_ENTRY"; then
- return 1
- fi
- return 0
+ #it's a normal user doing "sudo su", or `sudo -i` or `sudo -s`, or `sudo su acmeuser1`
+ _endswith "$SUDO_COMMAND" /bin/su || _contains "$SUDO_COMMAND" "/bin/su " || grep "^$SUDO_COMMAND\$" /etc/shells >/dev/null 2>&1
+ return $?
fi
#otherwise
return 1
@@ -8576,9 +7585,6 @@ _process() {
_valid_to=""
_certificate_profile=""
_extended_key_usage=""
- _dns_persist_wildcard=""
- _dns_persist_ca_name=""
- _dns_persist_days=""
while [ ${#} -gt 0 ]; do
case "${1}" in
@@ -8667,29 +7673,12 @@ _process() {
--update-account | --updateaccount)
_CMD="updateaccount"
;;
- --update-account-key | --updateaccountkey)
- _CMD="updateaccountkey"
- ;;
--register-account | --registeraccount)
_CMD="registeraccount"
;;
--deactivate-account)
_CMD="deactivateaccount"
;;
- --make-dns-persist-value | --makednspersistvalue)
- _CMD="makednspersistvalue"
- ;;
- --dns-persist-wildcard | --dnspersistwildcard)
- _dns_persist_wildcard="1"
- ;;
- --dns-persist-ca-name | --dnspersistcaname)
- _dns_persist_ca_name="$2"
- shift
- ;;
- --dns-persist-days | --dnspersistdays)
- _dns_persist_days="$2"
- shift
- ;;
--set-notify)
_CMD="setnotify"
;;
@@ -8833,14 +7822,6 @@ _process() {
_webroot="$_webroot,$wvalue"
fi
;;
- --dns-persist)
- wvalue="$W_DNS_PERSIST"
- if [ -z "$_webroot" ]; then
- _webroot="$wvalue"
- else
- _webroot="$_webroot,$wvalue"
- fi
- ;;
--dnssleep)
_dnssleep="$2"
Le_DNSSleep="$_dnssleep"
@@ -9207,22 +8188,6 @@ _process() {
_debug2 LE_WORKING_DIR "$LE_WORKING_DIR"
- # --valid-to pins the cert lifetime, so a creation-anchored (positive)
- # --days schedule can not apply and is rejected. A negative --days is
- # anchored to the expiry and composes with a relative --valid-to: the
- # cert renews that many days before the expiry.
- if [ "$_days" ] && [ "$_valid_to" ]; then
- if ! _startswith "$_valid_to" "+"; then
- _err "--days can not be used together with a fixed-date --valid-to: such a cert can not be renewed automatically."
- return 1
- fi
- if ! _startswith "$_days" "-"; then
- _err "A positive --days can not be used together with --valid-to, the renewal time is derived from the expiry time."
- _err "Use a negative --days to renew that many days before the expiry, or omit --days to renew 1 day before the expiry."
- return 1
- fi
- fi
-
if [ "$DEBUG" ]; then
version
if [ "$_server" ]; then
@@ -9270,15 +8235,9 @@ _process() {
updateaccount)
updateaccount
;;
- updateaccountkey)
- updateaccountkey "$_accountkeylength"
- ;;
deactivateaccount)
deactivateaccount
;;
- makednspersistvalue)
- makednspersistvalue "$_domain" "$_dns_persist_wildcard" "$_dns_persist_ca_name" "$_dns_persist_days"
- ;;
list)
list "$_listraw" "$_domain"
;;
@@ -9292,7 +8251,7 @@ _process() {
toPkcs "$_domain" "$_password" "$_ecc"
;;
toPkcs8)
- toPkcs8 "$_domain" "$_password" "$_ecc"
+ toPkcs8 "$_domain" "$_ecc"
;;
createAccountKey)
createAccountKey "$_accountkeylength"
diff --git a/acme.sh.completion b/acme.sh.completion
deleted file mode 100644
index 26cb88da..00000000
--- a/acme.sh.completion
+++ /dev/null
@@ -1,341 +0,0 @@
-# Bash completion for acme.sh: https://github.com/acmesh-official/acme.sh
-#
-# "acme.sh --install" copies this file to the acme.sh home dir and wires
-# it into acme.sh.env, so the completion is loaded automatically in new
-# bash sessions after installation.
-#
-# To use it without installing acme.sh, source it from ~/.bashrc, or copy
-# it to /usr/share/bash-completion/completions/acme.sh
-#
-# Zsh users can load it with:
-# autoload -U +X bashcompinit && bashcompinit
-# . /path/to/acme.sh.completion
-
-# This file may also be sourced by non-bash shells via acme.sh.env,
-# so silently do nothing if the "complete" builtin is not available.
-if ! command -v complete >/dev/null 2>&1; then
- return 0 2>/dev/null || exit 0
-fi
-
-# Add each word of $1 that starts with $cur to COMPREPLY.
-# The words are read line by line, so that candidates like a wildcard
-# domain "*.example.com" are never glob-expanded against the cwd.
-_acme_sh_add_matches() {
- local _word
- while read -r _word; do
- [ -n "$_word" ] || continue
- case "$_word" in
- "$cur"*) COMPREPLY=("${COMPREPLY[@]}" "$_word") ;;
- esac
- done </dev/null 2>&1; then
- compopt -o filenames 2>/dev/null
- fi
- return 0
-}
-
-_acme_sh_dirs() {
- local _dir
- while IFS= read -r _dir; do
- [ -n "$_dir" ] || continue
- COMPREPLY=("${COMPREPLY[@]}" "$_dir")
- done </dev/null 2>&1; then
- compopt -o filenames 2>/dev/null
- fi
- return 0
-}
-
-# Complete the domains that already have a cert: every directory in the
-# config home that contains a ".conf" file ("_ecc" suffix stripped).
-_acme_sh_domains() {
- local _dir _name _domains=""
- [ -n "${ZSH_VERSION:-}" ] && setopt localoptions nonomatch 2>/dev/null
- for _dir in "$_acme_conf_home"/*/; do
- [ -d "$_dir" ] || continue
- _name="${_dir%/}"
- _name="${_name##*/}"
- _name="${_name%_ecc}"
- if [ -f "${_dir}${_name}.conf" ]; then
- case " $_domains " in
- *" $_name "*) ;;
- *) _domains="$_domains $_name" ;;
- esac
- fi
- done
- _acme_sh_add_matches "$_domains"
-}
-
-# Complete hook names from a subfolder of the acme.sh home dir.
-# $1: subfolder (dnsapi/deploy/notify), $2: file name prefix or empty.
-_acme_sh_hooks() {
- local _file _hooks=""
- [ -n "${ZSH_VERSION:-}" ] && setopt localoptions nonomatch 2>/dev/null
- for _file in "$_acme_home/$1/$2"*.sh; do
- [ -f "$_file" ] || continue
- _file="${_file##*/}"
- _hooks="$_hooks ${_file%.sh}"
- done
- _acme_sh_add_matches "$_hooks"
-}
-
-_acme_sh_completion() {
- local cur prev _acme_home _acme_conf_home
- COMPREPLY=()
- cur="${COMP_WORDS[COMP_CWORD]}"
- prev=""
- if [ "$COMP_CWORD" -gt 0 ]; then
- prev="${COMP_WORDS[COMP_CWORD - 1]}"
- fi
- _acme_home="${LE_WORKING_DIR:-$HOME/.acme.sh}"
- _acme_conf_home="${LE_CONFIG_HOME:-$_acme_home}"
-
- # The first argument is the command.
- if [ "$COMP_CWORD" -eq 1 ]; then
- _acme_sh_add_matches "
- --help
- --version
- --install
- --install-online
- --uninstall
- --upgrade
- --issue
- --deploy
- --sign-csr
- --show-csr
- --install-cert
- --renew
- --renew-all
- --revoke
- --remove
- --list
- --list-profiles
- --info
- --to-pkcs12
- --to-pkcs8
- --create-account-key
- --create-domain-key
- --create-csr
- --deactivate
- --update-account
- --register-account
- --deactivate-account
- --make-dns-persist-value
- --install-cronjob
- --uninstall-cronjob
- --cron
- --set-notify
- --set-default-ca
- --set-default-chain
- "
- return 0
- fi
-
- # Complete the value of the previous option.
- case "$prev" in
- -d | --domain | --challenge-alias | --domain-alias)
- _acme_sh_domains
- return 0
- ;;
- --dns)
- # The dns hook argument is optional, keep completing options if the
- # current word already looks like one.
- case "$cur" in
- -*) ;;
- *)
- _acme_sh_hooks "dnsapi" "dns_"
- return 0
- ;;
- esac
- ;;
- --deploy-hook)
- _acme_sh_hooks "deploy" ""
- return 0
- ;;
- --notify-hook)
- _acme_sh_hooks "notify" ""
- return 0
- ;;
- --server)
- _acme_sh_add_matches "letsencrypt letsencrypt_test zerossl sslcom google google_test actalis"
- return 0
- ;;
- -k | --keylength | -ak | --accountkeylength)
- _acme_sh_add_matches "2048 3072 4096 8192 ec-256 ec-384 ec-521"
- return 0
- ;;
- --debug)
- # Optional argument.
- case "$cur" in
- -*) ;;
- *)
- _acme_sh_add_matches "0 1 2 3"
- return 0
- ;;
- esac
- ;;
- --log)
- # Optional argument.
- case "$cur" in
- -*) ;;
- *)
- _acme_sh_files
- return 0
- ;;
- esac
- ;;
- --nginx)
- # Optional argument.
- case "$cur" in
- -*) ;;
- *)
- _acme_sh_files
- return 0
- ;;
- esac
- ;;
- --auto-upgrade | --always-force-new-domain-key)
- # Optional argument.
- case "$cur" in
- -*) ;;
- *)
- _acme_sh_add_matches "0 1"
- return 0
- ;;
- esac
- ;;
- --log-level)
- _acme_sh_add_matches "1 2"
- return 0
- ;;
- --syslog)
- _acme_sh_add_matches "0 3 6 7"
- return 0
- ;;
- --notify-level)
- _acme_sh_add_matches "0 1 2 3"
- return 0
- ;;
- --notify-mode)
- _acme_sh_add_matches "0 1"
- return 0
- ;;
- --revoke-reason)
- _acme_sh_add_matches "0 1 2 3 4 5 6 7 8 9 10"
- return 0
- ;;
- --cert-file | --key-file | --ca-file | --fullchain-file | --csr | --accountconf | --accountkey | --ca-bundle | --openssl-bin)
- _acme_sh_files
- return 0
- ;;
- -w | --webroot | --home | --cert-home | --config-home | --ca-path)
- _acme_sh_dirs
- return 0
- ;;
- -m | --email | --password | --useragent | --days | --valid-from | --valid-to | --httpport | --tlsport | --local-address | --dnssleep | --pre-hook | --post-hook | --renew-hook | --reloadcmd | --extended-key-usage | -b | --branch | --notify-source | --eab-kid | --eab-hmac-key | --preferred-chain | --cert-profile | --certificate-profile | --dns-persist-ca-name | --dns-persist-days)
- # These options take a free-form value, offer nothing.
- return 0
- ;;
- esac
-
- # Complete the parameters.
- _acme_sh_add_matches "
- --accountconf
- --accountkey
- --accountkeylength
- --alpn
- --always-force-new-domain-key
- --apache
- --auto-upgrade
- --branch
- --ca-bundle
- --ca-file
- --ca-path
- --cert-file
- --cert-home
- --cert-profile
- --challenge-alias
- --config-home
- --csr
- --days
- --debug
- --deploy-hook
- --dns
- --dns-persist
- --dns-persist-ca-name
- --dns-persist-days
- --dns-persist-wildcard
- --dnssleep
- --domain
- --domain-alias
- --eab-hmac-key
- --eab-kid
- --ecc
- --email
- --extended-key-usage
- --force
- --force-color
- --fullchain-file
- --home
- --httpport
- --insecure
- --key-file
- --keylength
- --listen-v4
- --listen-v6
- --listraw
- --local-address
- --log
- --log-level
- --nginx
- --no-color
- --no-cron
- --no-profile
- --notify-hook
- --notify-level
- --notify-mode
- --notify-source
- --ocsp-must-staple
- --openssl-bin
- --output-insecure
- --password
- --post-hook
- --pre-hook
- --preferred-chain
- --reloadcmd
- --renew-hook
- --revoke-reason
- --server
- --staging
- --standalone
- --stateless
- --stop-renew-on-error
- --syslog
- --tlsport
- --treat-skip-as-success
- --use-wget
- --useragent
- --valid-from
- --valid-to
- --webroot
- --yes-I-know-dns-manual-mode-enough-go-ahead-please
- "
- return 0
-}
-
-complete -F _acme_sh_completion acme.sh
diff --git a/deploy/baidu_cdn.sh b/deploy/baidu_cdn.sh
deleted file mode 100644
index 7fe31f9b..00000000
--- a/deploy/baidu_cdn.sh
+++ /dev/null
@@ -1,222 +0,0 @@
-#!/usr/bin/env sh
-# shellcheck disable=SC2034,SC2154
-
-# Deploy hook: Baidu Cloud CDN
-#
-# Code generated by GitHub Copilot with Claude Sonnet 4.6 and OpenAI Codex with GPT-5.6 Sol
-#
-# API Doc: https://cloud.baidu.com/doc/CDN/s/Zkna2r57w
-#
-# Uses the same credential variables as dnsapi/dns_baidu.sh:
-# export Baidu_AK="your-access-key-id"
-# export Baidu_SK="your-secret-access-key"
-#
-# To deploy to a CDN domain different from the certificate CN
-# (e.g. wildcard or multi-domain certs):
-# export DEPLOY_BAIDU_CDN_DOMAIN="cdn.example.com"
-#
-# Multiple CDN domains sharing the same certificate:
-# export DEPLOY_BAIDU_CDN_DOMAIN="cdn1.example.com cdn2.example.com"
-
-BAIDU_CDN_HOST="cdn.baidubce.com"
-_BAIDU_CDN_BCE_AUTH_RESULT=""
-
-baidu_cdn_deploy() {
- _cdomain="$1"
- _ckey="$2"
- _ccert="$3"
- _cca="$4"
- _cfullchain="$5"
-
- _debug _cdomain "$_cdomain"
- _debug _ckey "$_ckey"
- _debug _ccert "$_ccert"
- _debug _cca "$_cca"
- _debug _cfullchain "$_cfullchain"
-
- if ! _baidu_cdn_load_credentials; then
- return 1
- fi
-
- _getdeployconf DEPLOY_BAIDU_CDN_DOMAIN
- if [ "$DEPLOY_BAIDU_CDN_DOMAIN" ]; then
- _savedeployconf DEPLOY_BAIDU_CDN_DOMAIN "$DEPLOY_BAIDU_CDN_DOMAIN"
- else
- DEPLOY_BAIDU_CDN_DOMAIN="$_cdomain"
- fi
-
- # Build JSON "domains" array from space-separated domain list
- _domains_json=""
- for _d in $DEPLOY_BAIDU_CDN_DOMAIN; do
- _d_e="$(_baidu_cdn_json_escape "$_d")"
- if [ -z "$_domains_json" ]; then
- _domains_json="\"${_d_e}\""
- else
- _domains_json="${_domains_json},\"${_d_e}\""
- fi
- done
-
- # Build a valid cert name: must start with a letter, allow [A-Za-z0-9-/.], max 65 chars
- _cert_name="$(printf "%s" "$_cdomain" | sed 's/\*\./wildcard./g;s/[^A-Za-z0-9./]/-/g' | cut -c 1-65)"
- case "$_cert_name" in
- [A-Za-z]*) ;;
- *) _cert_name="c${_cert_name}" ;;
- esac
-
- # PEM content is already Base64 inside the -----BEGIN/END----- wrappers.
- # The API expects the raw PEM as a JSON string, so newlines must be escaped as \n.
- _cert_pem="$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')"
- _key_pem="$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')"
-
- _debug2 _cert_name "$_cert_name"
- _debug2 _domains_json "[$_domains_json]"
-
- # Build JSON payload
- _payload="{\"domains\":[${_domains_json}],\"certificate\":{\"certName\":\"${_cert_name}\",\"certServerData\":\"${_cert_pem}\",\"certPrivateData\":\"${_key_pem}\"}}"
-
- # Generate BCE v1 authorization header (query string included in canonical request)
- _cdn_path="/v2/domain/certificate"
- _cdn_query="action=put"
- _ts="$(_utc_date | sed 's/ /T/')Z"
- _content_type="application/json; charset=utf-8"
- _payload_hash="$(printf "%s" "$_payload" | _digest sha256 hex)"
-
- if ! _baidu_cdn_bce_auth "POST" "$_cdn_path" "$_cdn_query" "$BAIDU_CDN_HOST" "$_ts" "3600" "$_content_type" "$_payload_hash"; then
- _err "Failed to sign request"
- return 1
- fi
-
- _H1="Authorization: $_BAIDU_CDN_BCE_AUTH_RESULT"
- _H2="x-bce-date: $_ts"
- _H3="x-bce-content-sha256: $_payload_hash"
- _H4="Host: $BAIDU_CDN_HOST"
- _H5=""
-
- _url="https://${BAIDU_CDN_HOST}${_cdn_path}?${_cdn_query}"
- response="$(_post "$_payload" "$_url" "" "POST" "$_content_type")"
- if [ "$?" != "0" ]; then
- _err "Failed to call Baidu Cloud CDN API"
- return 1
- fi
-
- _debug2 response "$response"
-
- if _contains "$response" "\"certId\""; then
- _info "Certificate deployed to Baidu Cloud CDN for: $DEPLOY_BAIDU_CDN_DOMAIN"
- return 0
- fi
-
- _err "Failed to deploy certificate to Baidu Cloud CDN: $response"
- return 1
-}
-
-# BCE v1 signing with canonical query string support.
-# The CDN endpoint uses ?action=put so it must be included in the canonical request.
-_baidu_cdn_bce_auth() {
- _method="$1"
- _uri="$2"
- _query="$3"
- _host="$4"
- _ts="$5"
- _expire="$6"
- _ct="$7"
- _payload_hash="$8"
-
- _BAIDU_CDN_BCE_AUTH_RESULT=""
-
- _auth_prefix="bce-auth-v1/${Baidu_AK}/${_ts}/${_expire}"
- _signed_headers="content-type;host;x-bce-content-sha256;x-bce-date"
- _canonical_uri="$(_baidu_cdn_bce_encode_path "$_uri")"
-
- _host_e="$(printf "%s" "$_host" | _url_encode upper-hex)"
- _date_e="$(printf "%s" "$_ts" | _url_encode upper-hex)"
- _ct_e="$(printf "%s" "$_ct" | _url_encode upper-hex)"
- _hash_e="$(printf "%s" "$_payload_hash" | _url_encode upper-hex)"
-
- _canonical_headers="content-type:${_ct_e}
-host:${_host_e}
-x-bce-content-sha256:${_hash_e}
-x-bce-date:${_date_e}"
-
- _canonical_request="${_method}
-${_canonical_uri}
-${_query}
-${_canonical_headers}"
-
- _sk_hex="$(printf "%s" "$Baidu_SK" | _hex_dump | tr -d " ")"
- _signing_key="$(_baidu_cdn_hmac_sha256_hexkey "$_sk_hex" "$_auth_prefix")"
- _signing_key_hex="$(printf "%s" "$_signing_key" | _hex_dump | tr -d " ")"
- _signature="$(_baidu_cdn_hmac_sha256_hexkey "$_signing_key_hex" "$_canonical_request")"
-
- _BAIDU_CDN_BCE_AUTH_RESULT="${_auth_prefix}/${_signed_headers}/${_signature}"
-}
-
-_baidu_cdn_load_credentials() {
- Baidu_AK="${Baidu_AK:-$(_readaccountconf_mutable Baidu_AK)}"
- Baidu_SK="${Baidu_SK:-$(_readaccountconf_mutable Baidu_SK)}"
-
- Baidu_AK="$(_baidu_cdn_trim_ws "$Baidu_AK")"
- Baidu_SK="$(_baidu_cdn_trim_ws "$Baidu_SK")"
-
- if [ -z "$Baidu_AK" ] || [ -z "$Baidu_SK" ]; then
- _err "Baidu_AK and Baidu_SK are required"
- return 1
- fi
-
- _saveaccountconf_mutable Baidu_AK "$Baidu_AK"
- _saveaccountconf_mutable Baidu_SK "$Baidu_SK"
-
- return 0
-}
-
-_baidu_cdn_bce_encode_path() {
- _p="$1"
- _out=""
- if [ "${_p#"/"}" != "$_p" ]; then
- _out="/"
- fi
-
- _rest="${_p#/}"
- while [ -n "$_rest" ]; do
- _seg="${_rest%%/*}"
- if [ "$_seg" ]; then
- if [ -z "$_out" ] || [ "$_out" = "/" ]; then
- _out="${_out}$(printf "%s" "$_seg" | _url_encode upper-hex)"
- else
- _out="${_out}/$(printf "%s" "$_seg" | _url_encode upper-hex)"
- fi
- fi
- if [ "${_rest#*/}" = "$_rest" ]; then
- break
- fi
- _rest="${_rest#*/}"
- done
-
- if [ -z "$_out" ]; then
- _out="/"
- fi
- printf "%s" "$_out"
-}
-
-_baidu_cdn_trim_ws() {
- printf "%s" "$1" | tr '\r\n\t' ' ' | tr -s ' ' | sed 's/^ *//;s/ *$//'
-}
-
-_baidu_cdn_json_escape() {
- _s="$1"
- _s="$(printf "%s" "$_s" | tr -d '\r\n')"
- printf "%s" "$_s" |
- sed 's/\\/\\\\/g; s/ /\\t/g' |
- _baidu_cdn_json_encode
-}
-
-_baidu_cdn_json_encode() {
- _j_str="$(sed 's/"/\\"/g' | sed "s/\r/\\r/g")"
- printf "%s" "$_j_str" | _hex_dump | _lower_case | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d "\r\n"
-}
-
-_baidu_cdn_hmac_sha256_hexkey() {
- _key_hex="$1"
- _msg="$2"
- printf "%s" "$_msg" | _hmac sha256 "$_key_hex" hex
-}
diff --git a/deploy/byteplus_alb.sh b/deploy/byteplus_alb.sh
index 394b431f..8443bb99 100644
--- a/deploy/byteplus_alb.sh
+++ b/deploy/byteplus_alb.sh
@@ -163,8 +163,8 @@ byteplus_alb_deploy() {
# ── 3. Read cert and key ─────────────────────────────────────────────────────
# BytePlus requires NO blank lines between PEM blocks in the certificate chain
- _public_key=$(_strip_blank_lines <"$_cfullchain" | tr -d '\r')
- _private_key=$(_strip_blank_lines <"$_ckey" | tr -d '\r')
+ _public_key=$(sed '/^[[:space:]]*$/d' "$_cfullchain" | tr -d '\r')
+ _private_key=$(sed '/^[[:space:]]*$/d' "$_ckey" | tr -d '\r')
if [ -z "$_public_key" ] || [ -z "$_private_key" ]; then
_err "Failed to read certificate or key file."
diff --git a/deploy/cpanel_uapi.sh b/deploy/cpanel_uapi.sh
index 02ef6b3e..e5381b61 100644
--- a/deploy/cpanel_uapi.sh
+++ b/deploy/cpanel_uapi.sh
@@ -52,15 +52,7 @@ cpanel_uapi_deploy() {
# read cert and key files and urlencode both
_cert=$(_url_encode <"$_ccert")
- # with --signcsr the private key was never handed to acme.sh, so the key
- # file does not exist; skip it instead of spilling a shell redirection
- # error on every renewal (cPanel keeps using the already-installed key)
- if [ -f "$_ckey" ]; then
- _key=$(_url_encode <"$_ckey")
- else
- _debug "Key file $_ckey does not exist (csr mode), not sending a key."
- _key=""
- fi
+ _key=$(_url_encode <"$_ckey")
_debug2 _cert "$_cert"
_debug2 _key "$_key"
@@ -87,11 +79,7 @@ cpanel_uapi_deploy() {
# Auto mode
if [ "$DEPLOY_CPANEL_AUTO_ENABLED" = "true" ]; then
# call API for site config
- if [ -n "$_uapi_user" ]; then
- _response=$(uapi --user="$_uapi_user" DomainInfo list_domains)
- else
- _response=$(uapi DomainInfo list_domains)
- fi
+ _response=$(uapi DomainInfo list_domains)
# exit if error in response
if [ -z "$_response" ] || [ "${_response#*"$uapi_error_response"}" != "$_response" ]; then
_err "Error in deploying certificate - cannot retrieve sitelist:"
@@ -206,8 +194,7 @@ __cpanel_parse_response() {
printf("%s%s=%s\n", prefix, $2, $3);
}
}' |
- sed -En -e 's/^result\/data\/(main_domain|sub_domains\/-|addon_domains\/-|parked_domains\/-)=(.*)$/\2/p' |
- sed -e 's/^"//' -e 's/"$//' # YAML double-quotes values starting with '*' (wildcard subdomains)
+ sed -En -e 's/^result\/data\/(main_domain|sub_domains\/-|addon_domains\/-|parked_domains\/-)=(.*)$/\2/p'
}
# Load parameter by prefix+name - fallback to default if not set, and save to config
diff --git a/deploy/docker.sh b/deploy/docker.sh
index 276172aa..264963ae 100755
--- a/deploy/docker.sh
+++ b/deploy/docker.sh
@@ -3,8 +3,6 @@
#DEPLOY_DOCKER_CONTAINER_LABEL="xxxxxxx"
#DEPLOY_DOCKER_CONTAINER_KEY_FILE="/path/to/key.pem"
-#DEPLOY_DOCKER_CONTAINER_KEY_MODE="0640"
-#DEPLOY_DOCKER_CONTAINER_KEY_OWNER="1000:1000"
#DEPLOY_DOCKER_CONTAINER_CERT_FILE="/path/to/cert.pem"
#DEPLOY_DOCKER_CONTAINER_CA_FILE="/path/to/ca.pem"
#DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE="/path/to/fullchain.pem"
@@ -73,18 +71,6 @@ docker_deploy() {
_savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"
fi
- _getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_MODE
- _debug2 DEPLOY_DOCKER_CONTAINER_KEY_MODE "$DEPLOY_DOCKER_CONTAINER_KEY_MODE"
- if [ "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" ]; then
- _savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_MODE "$DEPLOY_DOCKER_CONTAINER_KEY_MODE"
- fi
-
- _getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_OWNER
- _debug2 DEPLOY_DOCKER_CONTAINER_KEY_OWNER "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
- if [ "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" ]; then
- _savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_OWNER "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
- fi
-
_getdeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE
_debug2 DEPLOY_DOCKER_CONTAINER_CERT_FILE "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"
if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then
@@ -126,20 +112,6 @@ docker_deploy() {
if ! _docker_cp "$_cid" "$_ckey" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
return 1
fi
- if [ "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" ]; then
- _info "Setting key file owner to $DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
- if ! _docker_exec "$_cid" chown "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
- _err "Can not change owner of key file in container"
- return 1
- fi
- fi
- if [ "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" ]; then
- _info "Setting key file mode to $DEPLOY_DOCKER_CONTAINER_KEY_MODE"
- if ! _docker_exec "$_cid" chmod "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
- _err "Can not change mode of key file in container"
- return 1
- fi
- fi
fi
if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then
@@ -217,22 +189,10 @@ _docker_exec() {
_debug2 cjson "$cjson"
execid="$(echo "$cjson" | cut -d '"' -f 4)"
_debug execid "$execid"
- #Detach:true is required for podman's docker-compatible API: with
- #Detach:false it streams the command output on the connection, so the
- #non-empty response was misread as an error (issue #4977). The real
- #result is checked via the exec inspect ExitCode below instead.
- ejson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/exec/$execid/start" "{\"Detach\": true,\"Tty\": false}")"
+ ejson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/exec/$execid/start" "{\"Detach\": false,\"Tty\": false}")"
_debug2 ejson "$ejson"
- _et=0
- ijson="$(_curl_unix_sock "$_DOCKER_SOCK" GET "/exec/$execid/json")"
- while _contains "$ijson" "\"Running\":true" && [ "$_et" -lt 10 ]; do
- sleep 1
- _et="$(_math "$_et" + 1)"
- ijson="$(_curl_unix_sock "$_DOCKER_SOCK" GET "/exec/$execid/json")"
- done
- _debug2 ijson "$ijson"
- if ! echo "$ijson" | _egrep_o "\"ExitCode\": *0[,}]" >/dev/null 2>&1; then
- _err "docker exec error: $ijson"
+ if [ "$ejson" ]; then
+ _err "$ejson"
return 1
fi
else
diff --git a/deploy/fortigate.sh b/deploy/fortigate.sh
deleted file mode 100644
index f00ca1cb..00000000
--- a/deploy/fortigate.sh
+++ /dev/null
@@ -1,175 +0,0 @@
-#!/usr/bin/env sh
-# Script to deploy a certificate to FortiGate via API and set it as the current web GUI certificate.
-#
-# FortiGate's native ACME integration does not support wildcard certificates or domain validation,
-# and is not supported if you have a custom management web port (eg. DNAT web traffic).
-#
-# REQUIRED:
-# export FGT_HOST="fortigate_hostname-or-ip"
-# export FGT_TOKEN="fortigate_api_token"
-#
-# OPTIONAL:
-# export FGT_PORT="10443" # Custom HTTPS port (defaults to 443 if not set)
-#
-# Run `acme.sh --deploy -d example.com --deploy-hook fortigate --insecure` to use this script.
-# `--insecure` is required on first run if not already using a valid SSL certificate on firewall.
-
-# Function to parse a FortiGate API response
-_fortigate_parse_response() {
- _fortigate_response="$1"
- _fortigate_func="$2"
- _fortigate_status=$(echo "$_fortigate_response" | _egrep_o '"status":[ ]*"[^"]*"' | cut -d '"' -f 4)
-
- if [ "$_fortigate_status" != "success" ]; then
- _err "[$_fortigate_func] Operation failed. Deploy with --insecure if current certificate is invalid. Try deploying with --debug to troubleshoot."
- return 1
- fi
-
- _debug "[$_fortigate_func] Operation successful."
- return 0
-}
-
-# Function to deploy a base64-encoded certificate to the firewall
-_fortigate_deployer() {
- _fortigate_cert_base64=$(_base64 <"$_fortigate_cfullchain" | tr -d '\n')
- _fortigate_key_base64=$(_base64 <"$_fortigate_ckey" | tr -d '\n')
- _fortigate_payload=$(
- cat <"
-
- return 0
-}
diff --git a/deploy/keyhelp.sh b/deploy/keyhelp.sh
index f66d27ce..97f9c21c 100644
--- a/deploy/keyhelp.sh
+++ b/deploy/keyhelp.sh
@@ -83,7 +83,7 @@ keyhelp_deploy() {
_request_body="submit=1&certificate_name=$certificate_name&add_type=upload&text_private_key=$encoded_key&text_certificate=$encoded_ccert&text_ca_certificate=$encoded_cca"
_H1="Cookie: $_cookie"
_response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=ssl_certificates&action=add" "" "POST")
- _message=$(echo "$_response" | sed -n '/
/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//')
+ _message=$(echo "$_response" | grep -A 2 'message-body' | sed -n '/
/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//')
_info "_message" "$_message"
if [ -z "$_message" ]; then
_err "Fail to upload certificate."
@@ -118,7 +118,7 @@ keyhelp_deploy() {
_request_body="submit=1&id=$DOMAIN_ID&target_type=$target_type&path=$path&is_prefer_https=$is_prefer_https&hsts_enabled=$hsts_enabled&certificate_type=custom&certificate_id=$cert_value&enforce_https=$DEPLOY_KEYHELP_ENFORCE_HTTPS"
_response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit" "" "POST")
- _message=$(echo "$_response" | sed -n '/
/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//')
+ _message=$(echo "$_response" | grep -A 2 'message-body' | sed -n '/
/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//')
_info "_message" "$_message"
if [ -z "$_message" ]; then
_err "Fail to apply certificate."
diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh
index 4a8c9dc9..ef920f64 100644
--- a/deploy/multideploy.sh
+++ b/deploy/multideploy.sh
@@ -10,10 +10,6 @@
# Usage (shown values are the examples):
# 1. Set optional environment variables
# - export MULTIDEPLOY_FILENAME="multideploy.yaml" - "multideploy.yml" will be automatically used if not set"
-# A name without a leading '/' is looked up in the certificate directory
-# of the domain. An absolute path is used as is, so a single deploy file
-# can be shared by all domains, e.g.
-# - export MULTIDEPLOY_FILENAME="/etc/acme/multideploy.yml"
#
# 2. Run command:
# acme.sh --deploy --deploy-hook multideploy -d example.com
@@ -53,7 +49,7 @@ multideploy_deploy() {
_debug _cfullchain "$_cfullchain"
_debug _cpfx "$_cpfx"
- _getdeployconf MULTIDEPLOY_FILENAME
+ MULTIDEPLOY_FILENAME="${MULTIDEPLOY_FILENAME:-$(_getdeployconf MULTIDEPLOY_FILENAME)}"
if [ -z "$MULTIDEPLOY_FILENAME" ]; then
MULTIDEPLOY_FILENAME="multideploy.yml"
_info "MULTIDEPLOY_FILENAME is not set, so I will use 'multideploy.yml'."
@@ -79,8 +75,7 @@ multideploy_deploy() {
# This function preprocesses the deploy file by checking if 'yq' is installed,
# verifying the existence of the deploy file, and ensuring only one deploy file is present.
# Arguments:
-# $@ - Posible deploy file names. A name starting with '/' is treated as an
-# absolute path, any other name is relative to the domain directory.
+# $@ - Posible deploy file names.
# Usage:
# _preprocess_deployfile "" "?"
_preprocess_deployfile() {
@@ -92,21 +87,15 @@ _preprocess_deployfile() {
_debug3 "yq is installed."
# Check if deploy file exists
- found_file=""
for file in "$@"; do
- if _startswith "$file" "/"; then
- _multideploy_path="$file"
- else
- _multideploy_path="$DOMAIN_PATH/$file"
- fi
- _debug3 "Checking file" "$_multideploy_path"
- if [ -f "$_multideploy_path" ]; then
+ _debug3 "Checking file" "$DOMAIN_PATH/$file"
+ if [ -f "$DOMAIN_PATH/$file" ]; then
_debug3 "File found"
if [ -n "$found_file" ]; then
_err "Multiple deploy files found. Please keep only one deploy file."
return 1
fi
- found_file="$_multideploy_path"
+ found_file="$file"
else
_debug3 "File not found"
fi
@@ -116,12 +105,12 @@ _preprocess_deployfile() {
_err "Deploy file not found. Go to https://github.com/acmesh-official/acme.sh/wiki/deployhooks#36-deploying-to-multiple-services-with-the-same-hooks to see how to create one."
return 1
fi
- if ! _check_deployfile "$found_file"; then
- _err "Deploy file is not valid: $found_file"
+ if ! _check_deployfile "$DOMAIN_PATH/$found_file"; then
+ _err "Deploy file is not valid: $DOMAIN_PATH/$found_file"
return 1
fi
- echo "$found_file"
+ echo "$DOMAIN_PATH/$found_file"
}
# Description:
@@ -221,7 +210,7 @@ _clear_envs() {
echo "$env_pairs" | while IFS='=' read -r _key _value; do
_debug3 "Deleting key" "$_key"
- _cleardeployconf "$_key"
+ _cleardomainconf "SAVED_$_key"
unset -v "$_key"
done
}
diff --git a/deploy/mydevil.sh b/deploy/mydevil.sh
index 8954f822..bd9868aa 100755
--- a/deploy/mydevil.sh
+++ b/deploy/mydevil.sh
@@ -54,8 +54,6 @@ mydevil_deploy() {
# Usage: ip=$(mydevil_get_ip domain.com)
# echo $ip
mydevil_get_ip() {
- # tr squeezes runs of blanks into one tab so plain cut works everywhere;
- # cut -w is BSD-only and unknown to GNU coreutils
- devil dns list "$1" | tr -s ' \t' '\t' | cut -s -f 3,7 | grep "^A$(printf '\t')" | cut -s -f 2 || return 1
+ devil dns list "$1" | cut -w -s -f 3,7 | grep "^A$(printf '\t')" | cut -w -s -f 2 || return 1
return 0
}
diff --git a/deploy/panos.sh b/deploy/panos.sh
index fcfd6fb5..00badffc 100644
--- a/deploy/panos.sh
+++ b/deploy/panos.sh
@@ -296,20 +296,9 @@ panos_deploy() {
_err "Unable to generate an API key. The user and pass may be invalid or not authorized to generate a new key. Please check the PANOS_USER and PANOS_PASS credentials and try again"
return 1
else
- # A commit of a failed import would leave a mismatched cert/key pair
- # on the firewall and can lock the admin out of the management
- # interface, see https://github.com/acmesh-official/acme.sh/issues/4716
- if ! deployer cert; then
- _err "Cert import failed. Aborting without committing."
- return 1
- fi
- if ! deployer key; then
- _err "Key import failed. Aborting without committing. Warning: the firewall now has an uncommitted mismatched cert/key pair in its candidate config."
- return 1
- fi
- if ! deployer commit; then
- return 1
- fi
+ deployer cert
+ deployer key
+ deployer commit
if [ "$_panos_template_stack" ]; then
# try to get job status for 20 times in 30 sec interval
i=0
diff --git a/deploy/proxmoxbs.sh b/deploy/proxmoxbs.sh
index 30599a44..179b0369 100644
--- a/deploy/proxmoxbs.sh
+++ b/deploy/proxmoxbs.sh
@@ -116,24 +116,17 @@ HEREDOC
export HTTPS_INSECURE=1
export _H1="Authorization: PBSAPIToken=${_proxmoxbs_header_api_token}"
response=$(_post "$_json_payload" "$_target_url" "" POST "application/json")
- _retval=$?
- # The API errors out with a non-2xx HTTP status and an empty body,
- # so the status line is checked too, not only the response body.
- _status_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
- _debug2 "HTTP status" "$_status_code"
response="$(echo "$response" | _json_decode | _normalizeJson)"
message=$(echo "$response" | _egrep_o '"message":"[^"]*' | cut -d : -f 2 | tr -d '"')
- case "$_status_code" in
- 2[0-9][0-9])
- if [ "${_retval}" -eq 0 ] && [ -z "$message" ]; then
- _debug3 response "$response"
- _info "Certificate successfully deployed"
- return 0
- fi
- ;;
- esac
- _err "Certificate deployment failed (HTTP status $_status_code). $message"
- _debug "Response" "$response"
- return 1
+ _retval=$?
+ if [ "${_retval}" -eq 0 ] && [ -z "$message" ]; then
+ _debug3 response "$response"
+ _info "Certificate successfully deployed"
+ return 0
+ else
+ _err "Certificate deployment failed: $message"
+ _debug "Response" "$response"
+ return 1
+ fi
}
diff --git a/deploy/proxmoxve.sh b/deploy/proxmoxve.sh
index fd8d69d8..b6298ee7 100644
--- a/deploy/proxmoxve.sh
+++ b/deploy/proxmoxve.sh
@@ -128,24 +128,17 @@ HEREDOC
export HTTPS_INSECURE=1
export _H1="Authorization: PVEAPIToken=${_proxmoxve_header_api_token}"
response=$(_post "$_json_payload" "$_target_url" "" POST "application/json")
- _retval=$?
- # The API errors out with a non-2xx HTTP status and an empty body,
- # so the status line is checked too, not only the response body.
- _status_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
- _debug2 "HTTP status" "$_status_code"
response="$(echo "$response" | _json_decode | _normalizeJson)"
message=$(echo "$response" | _egrep_o '"message":"[^"]*' | cut -d : -f 2 | tr -d '"')
- case "$_status_code" in
- 2[0-9][0-9])
- if [ "${_retval}" -eq 0 ] && [ -z "$message" ]; then
- _debug3 response "$response"
- _info "Certificate successfully deployed"
- return 0
- fi
- ;;
- esac
- _err "Certificate deployment failed (HTTP status $_status_code). $message"
- _debug "Response" "$response"
- return 1
+ _retval=$?
+ if [ "${_retval}" -eq 0 ] && [ -z "$message" ]; then
+ _debug3 response "$response"
+ _info "Certificate successfully deployed"
+ return 0
+ else
+ _err "Certificate deployment failed: $message"
+ _debug "Response" "$response"
+ return 1
+ fi
}
diff --git a/deploy/routeros.sh b/deploy/routeros.sh
index 328fabbd..ef9c6954 100644
--- a/deploy/routeros.sh
+++ b/deploy/routeros.sh
@@ -125,7 +125,7 @@ routeros_deploy() {
_savedeployconf ROUTER_OS_PORT "$ROUTER_OS_PORT"
_savedeployconf ROUTER_OS_SSH_CMD "$ROUTER_OS_SSH_CMD"
_savedeployconf ROUTER_OS_SCP_CMD "$ROUTER_OS_SCP_CMD"
- _savedeployconf ROUTER_OS_ADDITIONAL_SERVICES "$ROUTER_OS_ADDITIONAL_SERVICES" "base64"
+ _savedeployconf ROUTER_OS_ADDITIONAL_SERVICES "$ROUTER_OS_ADDITIONAL_SERVICES"
# push key to routeros
if ! _scp_certificate "$_ckey" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.key"; then
@@ -143,7 +143,6 @@ comment=\"generated by routeros deploy script in acme.sh\" \
source=\"/certificate remove [ find name=$_cdomain.cer_0 ];\
\n/certificate remove [ find name=$_cdomain.cer_1 ];\
\n/certificate remove [ find name=$_cdomain.cer_2 ];\
-\n/certificate remove [ find name=$_cdomain.cer_3 ];\
\ndelay 1;\
\n/certificate import file-name=\\\"$_cdomain.cer\\\" passphrase=\\\"\\\";\
\n/certificate import file-name=\\\"$_cdomain.key\\\" passphrase=\\\"\\\";\
diff --git a/deploy/shelly.sh b/deploy/shelly.sh
deleted file mode 100644
index dbdab346..00000000
--- a/deploy/shelly.sh
+++ /dev/null
@@ -1,280 +0,0 @@
-#!/usr/bin/env sh
-
-# Here is a script to deploy cert to a Shelly Gen3+ device.
-# Deploy the HTTPS server certificate to a Shelly device on the local network.
-#
-# ```sh
-# export SHELLY_HOST=192.168.1.100
-# export SHELLY_PASSWORD=mysecret # only if auth is enabled on the device
-# acme.sh --deploy -d shelly.example.com --deploy-hook shelly
-# ```
-#
-# Environment variables:
-# SHELLY_HOST (required) IP or hostname of the Shelly device
-# SHELLY_PASSWORD (optional) Admin password for digest authentication.
-# Omit if auth is disabled on the device.
-# SHELLY_USER (optional) Username for auth. Default: admin
-# SHELLY_REBOOT (optional) Set to "0" to skip auto-reboot.
-# Default: 1 (reboot after upload)
-#
-# Requirements:
-# - Shelly Gen3+ device (Gen4 recommended)
-# - Firmware 2.0.0+ for HTTPS server certificate support
-# - curl or wget
-# - openssl (for SHA-256 digest and random cnonce)
-#
-# The device must be reachable via HTTP on the local network.
-# The hook uploads the fullchain.pem and private key,
-# then reboots the device to apply the new certificate.
-#
-# Authentication uses standard RFC 7616 HTTP Digest (SHA-256) since
-# firmware 2.0.0. The JSON-RPC auth object is not used for HTTP transport.
-#
-# returns 0 means success, otherwise error.
-
-######## Public functions #####################
-
-#domain keyfile certfile cafile fullchain
-shelly_deploy() {
- _cdomain="$1"
- _ckey="$2"
- _ccert="$3"
- _cca="$4"
- _cfullchain="$5"
-
- _debug _cdomain "$_cdomain"
- _debug _ckey "$_ckey"
- _debug _ccert "$_ccert"
- _debug _cca "$_cca"
- _debug _cfullchain "$_cfullchain"
-
- _getdeployconf SHELLY_HOST
- _getdeployconf SHELLY_PASSWORD
- _getdeployconf SHELLY_USER
- _getdeployconf SHELLY_REBOOT
-
- _debug SHELLY_HOST "$SHELLY_HOST"
- _debug SHELLY_USER "$SHELLY_USER"
- _secure_debug SHELLY_PASSWORD "$SHELLY_PASSWORD"
- _debug SHELLY_REBOOT "$SHELLY_REBOOT"
-
- if [ -z "$SHELLY_HOST" ]; then
- _err "SHELLY_HOST is required. Please set the IP or hostname of your Shelly device."
- return 1
- fi
-
- SHELLY_USER="${SHELLY_USER:-admin}"
- SHELLY_REBOOT="${SHELLY_REBOOT:-1}"
-
- _savedeployconf SHELLY_HOST "$SHELLY_HOST"
- _savedeployconf SHELLY_PASSWORD "$SHELLY_PASSWORD"
- _savedeployconf SHELLY_USER "$SHELLY_USER"
- _savedeployconf SHELLY_REBOOT "$SHELLY_REBOOT"
-
- # --- Auth handshake (only if password is set) ---
- _shelly_auth_header=""
- if [ -n "$SHELLY_PASSWORD" ]; then
- _info "Authenticating to Shelly device at $SHELLY_HOST"
- if ! _shelly_handshake; then
- _err "Authentication handshake failed. Check SHELLY_PASSWORD and device accessibility."
- return 1
- fi
- _info "Authentication successful"
- fi
-
- # --- Upload certificate ---
- _info "Uploading certificate to Shelly device at $SHELLY_HOST"
- if ! _shelly_upload_cert; then
- _err "Certificate upload failed"
- return 1
- fi
-
- # --- Upload key ---
- _info "Uploading private key to Shelly device"
- if ! _shelly_upload_key; then
- _err "Private key upload failed"
- return 1
- fi
-
- _info "Certificate and key uploaded successfully"
-
- # --- Reboot ---
- if [ "$SHELLY_REBOOT" != "0" ]; then
- _info "Rebooting Shelly device to apply certificate"
- # Reboot may close the connection before sending a response
- _shelly_rpc "Shelly.Reboot" '{}' || _debug "Reboot may have closed connection (expected)"
- _info "Reboot command sent. Device will restart shortly."
- else
- _info "Skipping reboot (SHELLY_REBOOT=0). Certificate will apply on next restart."
- fi
-
- # Clear auth header so it does not leak to other hooks
- export _H1=""
-
- return 0
-}
-
-# --- Helper functions ---
-
-# Perform RFC 7616 HTTP Digest auth handshake.
-# Sets _shelly_auth_header on success (the Authorization header value).
-_shelly_handshake() {
- _inithttp
-
- _debug "Probing device for auth challenge"
-
- # Use a protected method (Shelly.GetStatus) to trigger 401.
- # Shelly.GetDeviceInfo is excluded from auth and would miss the challenge.
- _post '{"id":1,"method":"Shelly.GetStatus"}' \
- "http://${SHELLY_HOST}/rpc" "" "" "application/json"
-
- # Detect auth from HTTP status line rather than response body
- if ! _shelly_has_auth_challenge "$HTTP_HEADER"; then
- # No auth challenge — device accepted the request without credentials
- _debug "Device responded without auth challenge. Proceeding without auth."
- return 0
- fi
-
- _shelly_realm="$(grep -i '^WWW-Authenticate:' "$HTTP_HEADER" | sed 's/.*realm="//;s/".*//')"
- _shelly_nonce="$(grep -i '^WWW-Authenticate:' "$HTTP_HEADER" | sed 's/.*nonce="//;s/".*//')"
- _shelly_qop="$(grep -i '^WWW-Authenticate:' "$HTTP_HEADER" | sed 's/.*qop="//;s/".*//')"
-
- if [ -z "$_shelly_nonce" ]; then
- _err "Failed to extract nonce from WWW-Authenticate header. Is SHELLY_PASSWORD correct?"
- return 1
- fi
-
- _shelly_qop="${_shelly_qop:-auth}"
-
- _debug "Shelly realm: $_shelly_realm"
- _debug "Shelly qop: $_shelly_qop"
- _secure_debug "Shelly nonce" "$_shelly_nonce"
-
- # ha1 = SHA256(username:realm:password)
- _shelly_ha1="$(printf '%s' "${SHELLY_USER}:${_shelly_realm}:${SHELLY_PASSWORD}" | _digest sha256 hex)"
- _secure_debug "Shelly ha1" "$_shelly_ha1"
-
- # Generate client nonce (openssl is required for _digest, so always available)
- _shelly_cnonce="$(${ACME_OPENSSL_BIN:-openssl} rand -hex 8 2>/dev/null)"
- _debug "Shelly cnonce: $_shelly_cnonce"
-
- # Build the digest Authorization header value (stored for reuse)
- _shelly_nc=1
- _shelly_build_auth_header
-
- return 0
-}
-
-# Check whether the HTTP response headers contain a digest auth challenge.
-# Returns 0 (true) if a 401 with WWW-Authenticate is present.
-_shelly_has_auth_challenge() {
- _shelly_headers_file="$1"
- _shelly_status="$(grep -i '^HTTP/' "$_shelly_headers_file" | _tail_n 1 | awk '{print $2}')"
- [ "$_shelly_status" = "401" ] && grep -qi '^WWW-Authenticate:' "$_shelly_headers_file"
-}
-
-# Build or rebuild the RFC 7616 Authorization header.
-# Uses: _shelly_ha1, _shelly_nonce, _shelly_cnonce, _shelly_qop, _shelly_realm, _shelly_nc
-# Sets: _shelly_auth_header
-_shelly_build_auth_header() {
- _shelly_nc_hex="$(printf '%08x' "$_shelly_nc")"
-
- # ha2 = SHA256(POST:/rpc)
- _shelly_ha2="$(printf '%s' "POST:/rpc" | _digest sha256 hex)"
-
- # response = SHA256(ha1:nonce:nc:cnonce:qop:ha2)
- _shelly_digest_response="$(printf '%s' "${_shelly_ha1}:${_shelly_nonce}:${_shelly_nc_hex}:${_shelly_cnonce}:${_shelly_qop}:${_shelly_ha2}" | _digest sha256 hex)"
-
- # Build the Authorization header value (without the "Authorization: " prefix)
- _shelly_auth_header="Digest username=\"${SHELLY_USER}\", realm=\"${_shelly_realm}\", nonce=\"${_shelly_nonce}\", uri=\"/rpc\", qop=${_shelly_qop}, nc=${_shelly_nc_hex}, cnonce=\"${_shelly_cnonce}\", response=\"${_shelly_digest_response}\", algorithm=SHA-256"
-
- _secure_debug "Authorization header" "$_shelly_auth_header"
-}
-
-# Make a Shelly JSON-RPC call.
-# Usage: _shelly_rpc
-# Returns 0 on success, 1 on error.
-_shelly_rpc() {
- _shelly_method="$1"
- _shelly_params="$2"
-
- _shelly_body='{"id":1,"method":"'"$_shelly_method"'","params":'"$_shelly_params"'}'
-
- _debug "RPC method: $_shelly_method"
- _debug2 "RPC body: $_shelly_body"
-
- # shellcheck disable=SC2090
- if [ -n "$_shelly_auth_header" ]; then
- export _H1="Authorization: $_shelly_auth_header"
- else
- export _H1=""
- fi
-
- _post "$_shelly_body" "http://${SHELLY_HOST}/rpc" "" "" "application/json"
- _shelly_ret=$?
-
- if [ "$_shelly_ret" != "0" ]; then
- _err "HTTP request failed for $_shelly_method (curl/wget error $_shelly_ret)"
- return 1
- fi
-
- # Empty response means something went wrong (auth required but not provided, etc.)
- if [ -z "$response" ]; then
- _err "Empty response from Shelly device. If authentication is enabled on the device, set SHELLY_PASSWORD."
- return 1
- fi
-
- # Validate response looks like a Shelly JSON-RPC response.
- # Catches non-JSON responses such as HTTP 429 "Too Many Requests" which
- # would otherwise pass the empty and "error" checks below.
- if ! _startswith "$response" '{' || ! _contains "$response" '"id"'; then
- _err "Invalid response from Shelly device: $response"
- return 1
- fi
-
- # Check for JSON-RPC error in response
- if _contains "$response" '"error"'; then
- _err "RPC error from Shelly: $response"
- return 1
- fi
-
- _debug "RPC response: $response"
-
- # Increment nonce counter and rebuild auth header for next request
- if [ -n "$_shelly_auth_header" ]; then
- _shelly_nc=$((_shelly_nc + 1))
- _shelly_build_auth_header
- fi
-
- return 0
-}
-
-# Upload the certificate to the device.
-# Note: We do NOT clear the existing certificate first, because the Shelly
-# auto-removes all three files (cert, key, CA) when any one is cleared.
-# Uploading overwrites in place — no clearing needed.
-_shelly_upload_cert() {
- _shelly_cert_data="$(_json_encode <"$_cfullchain")"
-
- _debug "Uploading certificate"
- if ! _shelly_rpc "Shelly.PutHTTPServerCert" '{"data":"'"$_shelly_cert_data"'"}'; then
- _err "Failed to upload certificate to device"
- return 1
- fi
-
- return 0
-}
-
-# Upload the private key to the device.
-# Note: Do not clear first — see _shelly_upload_cert for rationale.
-_shelly_upload_key() {
- _shelly_key_data="$(_json_encode <"$_ckey")"
-
- _debug "Uploading key"
- if ! _shelly_rpc "Shelly.PutHTTPServerKey" '{"data":"'"$_shelly_key_data"'"}'; then
- _err "Failed to upload key to device"
- return 1
- fi
-
- return 0
-}
diff --git a/deploy/ssh.sh b/deploy/ssh.sh
index 0bf3ee48..848380a5 100644
--- a/deploy/ssh.sh
+++ b/deploy/ssh.sh
@@ -25,8 +25,7 @@
# export DEPLOY_SSH_MULTI_CALL="" # yes or no, default to no or previously saved value
# export DEPLOY_SSH_USE_SCP="" yes or no, default to no
# export DEPLOY_SSH_SCP_CMD="" defaults to "scp -q"
-# export DEPLOY_SSH_REMOTE_SHELL="" # defaults to sh -c
-# export DEPLOY_SSH_REMOTE_CMD_QUOTE="" # yes or no, defaults to yes
+#
######## Public functions #####################
#domain keyfile certfile cafile fullchain
@@ -72,24 +71,6 @@ ssh_deploy() {
fi
_savedeployconf DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD"
- # REMOTE_SHELL is optional. If not provided then use sh
- _migratedeployconf Le_Deploy_ssh_remote_shell DEPLOY_SSH_REMOTE_SHELL
- _getdeployconf DEPLOY_SSH_REMOTE_SHELL
- _debug2 DEPLOY_SSH_REMOTE_SHELL "$DEPLOY_SSH_REMOTE_SHELL"
- if [ -z "$DEPLOY_SSH_REMOTE_SHELL" ]; then
- DEPLOY_SSH_REMOTE_SHELL="sh -c"
- fi
- _savedeployconf DEPLOY_SSH_REMOTE_SHELL "$DEPLOY_SSH_REMOTE_SHELL"
-
- # REMOTE_CMD_QUOTE is optional. If not provided then yes
- _migratedeployconf Le_Deploy_ssh_remote_cmd_quote DEPLOY_SSH_REMOTE_CMD_QUOTE
- _getdeployconf DEPLOY_SSH_REMOTE_CMD_QUOTE
- _debug2 DEPLOY_SSH_REMOTE_CMD_QUOTE "$DEPLOY_SSH_REMOTE_CMD_QUOTE"
- if [ -z "$DEPLOY_SSH_REMOTE_CMD_QUOTE" ]; then
- DEPLOY_SSH_REMOTE_CMD_QUOTE="yes"
- fi
- _savedeployconf DEPLOY_SSH_REMOTE_CMD_QUOTE "$DEPLOY_SSH_REMOTE_CMD_QUOTE"
-
# BACKUP is optional. If not provided then default to previously saved value or yes.
_migratedeployconf Le_Deploy_ssh_backup DEPLOY_SSH_BACKUP
_getdeployconf DEPLOY_SSH_BACKUP
@@ -189,16 +170,10 @@ ssh_deploy() {
_info "Required commands batched and sent in single call to remote host"
fi
- _returnCode=0
_deploy_ssh_servers="$DEPLOY_SSH_SERVER"
for DEPLOY_SSH_SERVER in $_deploy_ssh_servers; do
- if ! _ssh_deploy; then
- # in case of an error, remember it, but keep going for the remaining servers
- _returnCode=1
- fi
+ _ssh_deploy
done
-
- return $_returnCode
}
_ssh_deploy() {
@@ -453,13 +428,9 @@ _ssh_remote_cmd() {
_secure_debug "Remote commands to execute: $_cmd"
_info "Submitting sequence of commands to remote server by $_ssh_cmd"
- if [ "$DEPLOY_SSH_REMOTE_CMD_QUOTE" = "yes" ]; then
- # quotations in bash cmd below intended. Squash travis spellcheck error
- # shellcheck disable=SC2029
- $_ssh_cmd "$DEPLOY_SSH_USER@$_host" "$DEPLOY_SSH_REMOTE_SHELL" "'$_cmd'"
- else
- $_ssh_cmd "$DEPLOY_SSH_USER@$_host" "$DEPLOY_SSH_REMOTE_SHELL" "$_cmd"
- fi
+ # quotations in bash cmd below intended. Squash travis spellcheck error
+ # shellcheck disable=SC2029
+ $_ssh_cmd "$DEPLOY_SSH_USER@$_host" sh -c "'$_cmd'"
_err_code="$?"
if [ "$_err_code" != "0" ]; then
diff --git a/deploy/synology_dsm.sh b/deploy/synology_dsm.sh
index 336980a5..e28a4036 100644
--- a/deploy/synology_dsm.sh
+++ b/deploy/synology_dsm.sh
@@ -1,4 +1,4 @@
-#!/usr/bin/env sh
+#!/bin/bash
################################################################################
# ACME.sh 3rd party deploy plugin for Synology DSM
@@ -72,7 +72,7 @@ synology_dsm_deploy() {
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
if ! _exists synouser || ! _exists synogroup || ! _exists synosetkeyvalue; then
- _err "Missing required tools to create temp admin user, please set SYNO_USERNAME and SYNO_PASSWORD instead."
+ _err "Missing required tools to creat temp admin user, please set SYNO_USERNAME and SYNO_PASSWORD instead."
_err "Notice: temp admin user authorization method only supports local deployment on DSM."
return 1
fi
@@ -234,11 +234,11 @@ synology_dsm_deploy() {
fi
fi
- error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+')
+ error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*')
_debug2 error_code "$error_code"
# Account has 2FA-OTP enabled, since error 403 reported.
# https://global.download.synology.com/download/Document/Software/DeveloperGuide/Os/DSM/All/enu/DSM_Login_Web_API_Guide_enu.pdf
- if [ "$error_code" = "403" ]; then
+ if [ "$error_code" == "403" ]; then
if [ -z "$SYNO_DEVICE_NAME" ]; then
printf "Enter device name or leave empty for default (CertRenewal): "
read -r SYNO_DEVICE_NAME
@@ -269,27 +269,27 @@ synology_dsm_deploy() {
_secure_debug2 SYNO_DEVICE_ID "$SYNO_DEVICE_ID"
fi
fi
- error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+')
+ error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*')
_debug2 error_code "$error_code"
fi
if [ -n "$error_code" ]; then
- if [ "$error_code" = "403" ] && [ -n "$SYNO_DEVICE_ID" ]; then
+ if [ "$error_code" == "403" ] && [ -n "$SYNO_DEVICE_ID" ]; then
_cleardeployconf SYNO_DEVICE_ID
- _err "Failed to authenticate with SYNO_DEVICE_ID (may be expired or invalid), please try again in a new terminal window."
- elif [ "$error_code" = "404" ]; then
+ _err "Failed to authenticate with SYNO_DEVICE_ID (may expired or invalid), please try again in a new terminal window."
+ elif [ "$error_code" == "404" ]; then
_err "Failed to authenticate with provided 2FA-OTP code, please try again in a new terminal window."
- elif [ "$error_code" = "406" ]; then
+ elif [ "$error_code" == "406" ]; then
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
_err "Failed with unexcepted error, please report this by providing full log with '--debug 3'."
else
_err "Enforce auth with 2FA-OTP enabled, please configure the user to enable 2FA-OTP to continue."
fi
- elif [ "$error_code" = "400" ]; then
+ elif [ "$error_code" == "400" ]; then
_err "Failed to authenticate, no such account or incorrect password."
- elif [ "$error_code" = "401" ]; then
+ elif [ "$error_code" == "401" ]; then
_err "Failed to authenticate with a non-existent account."
- elif [ "$error_code" = "408" ] || [ "$error_code" = "409" ] || [ "$error_code" = "410" ]; then
+ elif [ "$error_code" == "408" ] || [ "$error_code" == "409" ] || [ "$error_code" == "410" ]; then
_err "Failed to authenticate, the account password has expired or must be changed."
else
_err "Failed to authenticate with error: $error_code."
@@ -322,8 +322,8 @@ synology_dsm_deploy() {
_savedeployconf SYNO_USE_TEMP_ADMIN "$SYNO_USE_TEMP_ADMIN"
_savedeployconf SYNO_LOCAL_HOSTNAME "$SYNO_LOCAL_HOSTNAME"
else
- _savedeployconf SYNO_USERNAME "$SYNO_USERNAME" "base64"
- _savedeployconf SYNO_PASSWORD "$SYNO_PASSWORD" "base64"
+ _savedeployconf SYNO_USERNAME "$SYNO_USERNAME"
+ _savedeployconf SYNO_PASSWORD "$SYNO_PASSWORD"
_savedeployconf SYNO_DEVICE_ID "$SYNO_DEVICE_ID"
_savedeployconf SYNO_DEVICE_NAME "$SYNO_DEVICE_NAME"
fi
@@ -336,7 +336,7 @@ synology_dsm_deploy() {
id=$(echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\"id\":\"\([^\"]*\).*/\1/p")
_debug2 id "$id"
- error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+')
+ error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*')
_debug2 error_code "$error_code"
if [ -n "$error_code" ]; then
if [ "$error_code" -eq 105 ]; then
@@ -344,7 +344,6 @@ synology_dsm_deploy() {
else
_err "Failed to fetch certificate info: $error_code, please try again or contact Synology to learn more."
fi
- _logout
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
return 1
fi
@@ -355,7 +354,6 @@ synology_dsm_deploy() {
if [ -z "$id" ] && [ -z "$SYNO_CREATE" ]; then
_err "Unable to find certificate: $SYNO_CERTIFICATE and \$SYNO_CREATE is not set."
- _logout
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
return 1
fi
@@ -391,13 +389,13 @@ synology_dsm_deploy() {
else
_info "Restart HTTP services not necessary."
fi
- _logout
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
+ _logout
return 0
else
+ _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
_err "Unable to update certificate, got error response: $response."
_logout
- _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
return 1
fi
}
@@ -405,8 +403,6 @@ synology_dsm_deploy() {
#################### Private functions below ##################################
_logout() {
# Logout CERT user only to not occupy a permanent session, e.g. in DSM's "Connected Users" widget (based on previous variables)
- # Must be called before _temp_admin_cleanup: once the temp admin is deleted, its session can no longer be logged out.
- # Note: this overwrites $response, so print any error message that needs it before calling.
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=logout&_sid=$sid")
_debug3 response "$response"
}
@@ -428,6 +424,11 @@ _temp_admin_cleanup() {
fi
}
+#_cleardeployconf key
+_cleardeployconf() {
+ _cleardomainconf "SAVED_$1"
+}
+
# key
_check2cleardeployconfexp() {
_key="$1"
diff --git a/deploy/truenas_ws.sh b/deploy/truenas_ws.sh
index 33e3dfa0..df34f927 100644
--- a/deploy/truenas_ws.sh
+++ b/deploy/truenas_ws.sh
@@ -16,12 +16,7 @@
#
# # API KEY
# # Use the folowing URL to create a new API token: /ui/apikeys
-# export DEPLOY_TRUENAS_APIKEY=""
-# Optional:
-# export DEPLOY_TRUENAS_HOSTNAME=""
-# export DEPLOY_TRUENAS_PROTOCOL="wss" # ws or wss
-# export DEPLOY_TRUENAS_PORT="443" # optional, e.g. 80, 443, 8443
-
+# export DEPLOY_TRUENAS_APIKEY="/dev/null)"
- if [ -z "$otpcode" ]; then
- _err "Failed to generate TOTP code from One984HOSTING_TOTP_Secret."
- return 1
- fi
- otpkey="$(printf '%s' "$otpcode" | _url_encode)"
- fi
-
- # Fetch the login page to obtain CSRF and session cookies.
- # Note: _get sets the global 'url', so assign the auth URL afterwards.
- _get "https://1984.hosting/accounts/login/" >/dev/null
- csrftoken="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | _head_n 1 | tr -d ';')"
- sessionid="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'cookie1984nammnamm=[^;]*;' | _head_n 1 | tr -d ';')"
+ _get "https://1984.hosting/accounts/login/" | grep "csrfmiddlewaretoken"
+ csrftoken="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')"
+ sessionid="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'cookie1984nammnamm=[^;]*;' | tr -d ';')"
if [ -z "$csrftoken" ] || [ -z "$sessionid" ]; then
_err "One or more cookies are empty: '$csrftoken', '$sessionid'."
@@ -158,23 +140,17 @@ _1984hosting_login() {
csrf_header=$(echo "$csrftoken" | sed 's/csrftoken=//' | _head_n 1)
export _H3="X-CSRFToken: $csrf_header"
- url="https://1984.hosting/api/auth/"
- response="$(_post "username=$username&password=$password&otpkey=$otpkey" "$url")"
+ response="$(_post "username=$username&password=$password&otpkey=" $url)"
response="$(echo "$response" | _normalizeJson)"
_debug2 response "$response"
if _contains "$response" '"loggedin": true'; then
- One984HOSTING_SESSIONID_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'cookie1984nammnamm=[^;]*;' | _head_n 1 | tr -d ';')"
- One984HOSTING_CSRFTOKEN_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | _head_n 1 | tr -d ';')"
+ One984HOSTING_SESSIONID_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'cookie1984nammnamm=[^;]*;' | tr -d ';')"
+ One984HOSTING_CSRFTOKEN_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')"
export One984HOSTING_SESSIONID_COOKIE
export One984HOSTING_CSRFTOKEN_COOKIE
_saveaccountconf_mutable One984HOSTING_Username "$One984HOSTING_Username"
_saveaccountconf_mutable One984HOSTING_Password "$One984HOSTING_Password"
- if [ -n "$One984HOSTING_TOTP_Secret" ]; then
- _saveaccountconf_mutable One984HOSTING_TOTP_Secret "$One984HOSTING_TOTP_Secret"
- else
- _clearaccountconf_mutable One984HOSTING_TOTP_Secret
- fi
_saveaccountconf_mutable One984HOSTING_SESSIONID_COOKIE "$One984HOSTING_SESSIONID_COOKIE"
_saveaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE "$One984HOSTING_CSRFTOKEN_COOKIE"
return 0
@@ -185,7 +161,6 @@ _1984hosting_login() {
_check_credentials() {
One984HOSTING_Username="${One984HOSTING_Username:-$(_readaccountconf_mutable One984HOSTING_Username)}"
One984HOSTING_Password="${One984HOSTING_Password:-$(_readaccountconf_mutable One984HOSTING_Password)}"
- One984HOSTING_TOTP_Secret="${One984HOSTING_TOTP_Secret:-$(_readaccountconf_mutable One984HOSTING_TOTP_Secret)}"
if [ -z "$One984HOSTING_Username" ] || [ -z "$One984HOSTING_Password" ]; then
One984HOSTING_Username=""
One984HOSTING_Password=""
@@ -250,15 +225,9 @@ _get_root() {
# Usage: _get_zone_id url domain.com
# Returns zone id for domain.com
-# Memoized per-domain so add/rm don't re-fetch the same zone list within a run.
-# Keyed on domain (not url) since the url is always the domains listing.
_get_zone_id() {
url=$1
domain=$2
- if [ "$_zone_id_for" = "$domain" ] && [ -n "$_zone_id" ]; then
- _debug2 _zone_id "$_zone_id (cached)"
- return 0
- fi
_htmlget "$url" "$domain"
_zone_id="$(echo "$_response" | _egrep_o 'zone\/[0-9]+' | _head_n 1)"
_debug2 _zone_id "$_zone_id"
@@ -266,7 +235,6 @@ _get_zone_id() {
_err "Error getting _zone_id for $2."
return 1
fi
- _zone_id_for="$domain"
return 0
}
@@ -289,8 +257,9 @@ _htmlget() {
# Add extra headers to request
_authpost() {
- _get_zone_id "https://1984.hosting/domains" "$_domain"
- csrf_header="$(echo "$One984HOSTING_CSRFTOKEN_COOKIE" | sed 's/csrftoken=//' | _head_n 1)"
+ url="https://1984.hosting/domains"
+ _get_zone_id "$url" "$_domain"
+ csrf_header="$(echo "$One984HOSTING_CSRFTOKEN_COOKIE" | _egrep_o "=[^=][0-9a-zA-Z]*" | tr -d "=")"
export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE"
export _H2="Referer: https://1984.hosting/domains/$_zone_id"
export _H3="X-CSRFToken: $csrf_header"
diff --git a/dnsapi/dns_acmedns.sh b/dnsapi/dns_acmedns.sh
index a21f8ef0..f3f50233 100755
--- a/dnsapi/dns_acmedns.sh
+++ b/dnsapi/dns_acmedns.sh
@@ -37,16 +37,6 @@ dns_acmedns_add() {
ACMEDNS_PASSWORD="${ACMEDNS_PASSWORD:-$(_readdomainconf ACMEDNS_PASSWORD)}"
ACMEDNS_SUBDOMAIN="${ACMEDNS_SUBDOMAIN:-$(_readdomainconf ACMEDNS_SUBDOMAIN)}"
- #for compatibility: old versions stored ACMEDNS_UPDATE_URL in the account
- #conf (issue 3899). Do not clear it here: it must stay available for the
- #other domains that have not migrated to their domain conf yet.
- if [ -z "$ACMEDNS_BASE_URL" ]; then
- _acmedns_update_url="$(_readaccountconf_mutable ACMEDNS_UPDATE_URL)"
- if [ "$_acmedns_update_url" ]; then
- ACMEDNS_BASE_URL="$(echo "$_acmedns_update_url" | sed 's#/update$##')"
- fi
- fi
-
if [ "$ACMEDNS_BASE_URL" = "" ]; then
ACMEDNS_BASE_URL="https://auth.acme-dns.io"
fi
@@ -81,7 +71,7 @@ dns_acmedns_add() {
data="{\"subdomain\":\"$ACMEDNS_SUBDOMAIN\", \"txt\": \"$txtvalue\"}"
_debug data "$data"
- response="$(_post "$data" "$ACMEDNS_UPDATE_URL" "" "POST" "application/json")"
+ response="$(_post "$data" "$ACMEDNS_UPDATE_URL" "" "POST")"
_debug response "$response"
if ! echo "$response" | grep "\"$txtvalue\"" >/dev/null; then
diff --git a/dnsapi/dns_ali.sh b/dnsapi/dns_ali.sh
index b8ca9169..90196c69 100755
--- a/dnsapi/dns_ali.sh
+++ b/dnsapi/dns_ali.sh
@@ -18,9 +18,7 @@ Ali_DNS_API="https://alidns.aliyuncs.com/"
#Usage: dns_ali_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_ali_add() {
- # the API only accepts punycode for IDN domains, and a raw UTF-8 domain
- # also breaks the request signature (issue 4733)
- fulldomain=$(_idn "$1")
+ fulldomain=$1
txtvalue=$2
_prepare_ali_credentials || return 1
@@ -35,7 +33,7 @@ dns_ali_add() {
}
dns_ali_rm() {
- fulldomain=$(_idn "$1")
+ fulldomain=$1
txtvalue=$2
Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
@@ -71,8 +69,8 @@ _ali_rest() {
ign="$2"
mtd="${3:-GET}"
- signature=$(printf "%s" "$mtd&%2F&$(printf "%s" "$query" | _ali_urlencode_upper)" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64)
- signature=$(printf "%s" "$signature" | _ali_urlencode_upper)
+ signature=$(printf "%s" "$mtd&%2F&$(printf "%s" "$query" | _url_encode upper-hex)" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64)
+ signature=$(printf "%s" "$signature" | _url_encode upper-hex)
url="$endpoint?Signature=$signature"
if [ "$mtd" = "GET" ]; then
@@ -98,20 +96,6 @@ _ali_rest() {
fi
}
-# stdin stdout
-# The Aliyun signature requires percent-encoding with upper-case hex.
-# Do not use "_url_encode upper-hex" here: this file is also bundled by
-# third parties (e.g. Proxmox VE proxmox-acme) whose older copies of the
-# acme.sh function library ignore the upper-hex argument and output
-# lower-case hex, which invalidates the signature.
-# https://github.com/acmesh-official/acme.sh/issues/6272
-_ali_urlencode_upper() {
- {
- _url_encode
- echo
- } | sed 's/%a/%A/g;s/%b/%B/g;s/%c/%C/g;s/%d/%D/g;s/%e/%E/g;s/%f/%F/g;s/%\(.\)a/%\1A/g;s/%\(.\)b/%\1B/g;s/%\(.\)c/%\1C/g;s/%\(.\)d/%\1D/g;s/%\(.\)e/%\1E/g;s/%\(.\)f/%\1F/g'
-}
-
_ali_nonce() {
if [ "$ACME_OPENSSL_BIN" ]; then
"$ACME_OPENSSL_BIN" rand -hex 16 2>/dev/null && return 0
diff --git a/dnsapi/dns_arubabusiness.sh b/dnsapi/dns_arubabusiness.sh
deleted file mode 100644
index 90b3f18d..00000000
--- a/dnsapi/dns_arubabusiness.sh
+++ /dev/null
@@ -1,490 +0,0 @@
-#!/usr/bin/env sh
-
-# shellcheck disable=SC2034
-dns_arubabusiness_info='ArubaBusiness
-Site: business.aruba.it
-Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_arubabusiness
-Options:
- AB_Key Your ArubaBusiness API Key
- AB_User Your account user
- AB_Pass Your account password
-'
-
-#
-# A word of warning: as of this writing, api.arubabusiness.it only supports oauth authentication using the "password" grant type.
-# If you are REALLY sure you want to use it, it would be wise set up a dedicated technical user without administrative privileges
-#
-
-ARUBABUSINESS_API='https://api.arubabusiness.it'
-
-######## Public functions ########
-
-#
-# Usage: dns_arubabusiness_add _acme-challenge.www.domain.com aaaabbbbcccc111122223333
-#
-# Add a new TXT record whose name and value match the given domain and value
-#
-# Variables
-# _full_domain: $1 - the name of the TXT record
-# _txt_value: $2 - the value of the TXT record
-# _body
-# dns_details
-# domain_id
-# dns_record_id
-# response
-#
-dns_arubabusiness_add() {
- _full_domain=$1
- _txt_value=$2
-
- if ! _ab_authenticate; then
- return 1
- fi
-
- if ! _ab_domain_id "$_full_domain"; then
- return 1
- fi
-
- if _ab_dns_record_id "$_full_domain" "$_txt_value" "$dns_details"; then
- # This is very unlikely, but allow the process to use the existing record
- _info "A TXT record with name: $_full_domain and value: $_txt_value already exists (id: $dns_record_id)"
- return 0
- fi
-
- _body="{ \"IdDomain\": $domain_id, \"Type\": \"TXT\", \"Name\": \"$_full_domain\", \"Content\": \"\\\"$_txt_value\\\"\" }"
-
- _debug "Adding TXT record with name: $_full_domain and value: $_txt_value"
-
- if ! _ab_rest POST "api/domains/dns/record" "$_body" || ! _contains "$response" "DomainId"; then
- _err "Failed to add TXT record with name: $_full_domain"
- return 1
- fi
-
- _info "Sleeping 10 seconds to let ArubaBusiness do its magic"
- _sleep 10
-
- # Refresh dns details and check that the record was really added
- if ! _ab_dns_details "$root_domain"; then
- return 1
- fi
-
- if ! _ab_dns_record_id "$_full_domain" "$_txt_value" "$dns_details"; then
- # This should never happen
- _err "The TXT record with name: $_full_domain was not set"
- _err "Please check that the dns records are clean"
- return 1
- fi
-
- _info "Added TXT record with id: $dns_record_id"
- return 0
-}
-
-#
-# Usage: dns_arubabusiness_rm _acme-challenge.www.domain.com aaaabbbbcccc111122223333
-#
-# Remove the TXT record whose name and value match the given domain and value
-#
-# Variables
-# _full_domain: $1 - the name of the TXT record
-# _txt_value: $2 - the value of the TXT record
-# dns_details
-# dns_record_id
-#
-dns_arubabusiness_rm() {
- _full_domain=$1
- _txt_value=$2
-
- if ! _ab_authenticate; then
- return 1
- fi
-
- if ! _ab_domain_id "$_full_domain"; then
- return 1
- fi
-
- if ! _ab_dns_record_id "$_full_domain" "$_txt_value" "$dns_details" || [ -z "$dns_record_id" ]; then
- _err "Could not retrieve the record id for: $_full_domain"
- return 1
- fi
-
- _debug "Deleting TXT record: $dns_record_id"
- if ! _ab_rest DELETE "api/domains/dns/record/$dns_record_id" || ! _contains "$response" "DomainId"; then
- _err "Failed to delete TXT record: $dns_record_id"
- return 1
- fi
-
- _info "Deleted TXT record: $dns_record_id"
- return 0
-}
-
-######## Private functions ########
-
-#
-# Usage: _ab_domain_id _acme-challenge.www.domain.com
-#
-# Split the input domain into subdomain + root domain and get the id of the root domain
-#
-# Variables
-# _full_domain: $1 - the domain whose root needs to be extracted
-# _domain_sections
-# _current_index
-# _candidate_subdomain
-# _candidate_domain
-# sub_domain
-# root_domain
-# domain_id
-# dns_details: a json containing all dns records registered on the root domain
-#
-# Example
-# _get_root _acme-challenge.www.domain.com
-#
-# Should return
-# sub_domain=_acme-challenge.www
-# root_domain=domain.com
-# domain_id=123123123123
-# dns_details="{JSON_CONTENT}"
-#
-_ab_domain_id() {
- _full_domain=$1
-
- _info "Attempting to retrieve root domain details for: $_full_domain"
-
- _domain_sections=$(_math "$(printf "%s" "$_full_domain" | tr '.' '\n' | wc -l)" + 1)
-
- if [ "$_domain_sections" -lt 1 ]; then
- _err "Invalid input $_full_domain"
- return 1
- fi
-
- _current_index=1
- while true; do
- _candidate_subdomain=$(if [ "$_current_index" = "1" ]; then printf ""; else printf "%s" "$_full_domain" | cut -d . -f 1-"$(_math "$_current_index" - 1)"; fi)
- _candidate_domain=$(printf "%s" "$_full_domain" | cut -d . -f "$_current_index"-"$_domain_sections")
-
- if ! _ab_dns_details "$_candidate_domain"; then
- _debug2 "Could not fetch dns details for: $_candidate_domain"
- _current_index=$(_math "$_current_index" + 1)
-
- # Fail if there are no candidates left
- if [ "$_current_index" -gt "$_domain_sections" ]; then
- _err "Could not determine the root domain for: $_full_domain"
- return 1
- fi
- else
- sub_domain="$_candidate_subdomain"
- root_domain="$_candidate_domain"
- # Extract the domain id, which is an integer and contains no commas
- domain_id="$(printf "%s" "$dns_details" | _egrep_o '"Id":[^,]*' | _head_n 1 | cut -d : -f 2 | tr -d ' "')"
-
- if [ -z "$domain_id" ]; then
- _err "Could not determine the domain id for: $root_domain"
- return 1
- fi
-
- _debug "Retrieved root domain id: $domain_id"
- return 0
- fi
- done
-}
-
-#
-# Usage: _ab_dns_record_id _acme-challenge.www.domain.com "aaaabbbbcccc111122223333" "{JSON_CONTENT}"
-#
-# Extract the record id of the first TXT record whose name and content match the input values
-#
-# Variables
-# _record_name: $1
-# _txt_value: $2
-# _dns_details: $3 - the json returned by a previous call to '_ab_dns_details() $root_domain'
-# _record_ids
-# _record_names
-# _record_types
-# _record_contents
-# _record_ids_count
-# _record_names_count
-# _record_types_count
-# _record_contents_count
-# _i
-# dns_record_id
-#
-# Notes
-# TXT correspond to record type 5
-# ArubaBusiness appends a terminating dot (.) to the record name
-# The content field may contain the following character sequence: \"
-# All record names are always converted to lowercase
-#
-_ab_dns_record_id() {
- _record_name=$1
- _txt_value=$2
- _dns_details=$3
-
- _record_name_lowercase=$(printf "%s" "$_record_name" | _lower_case)
-
- # Extract the record ids, which are integers and contain no commas, colons or spaces
- # The first id is skipped because it refers to the domain id
- _record_ids=$(printf "%s" "$_dns_details" | sed 's/"Id":/\n"Id":/g' | _egrep_o '"Id":[^,]*' | _tail_n +2 | cut -d : -f 2 | tr -d ' ' | tr '\n' ' ')
-
- # Extract the record names, which are strings but cannot contain commas, colons, spaces and quotes
- # The first name is skipped because it refers to the domain name
- _record_names=$(printf "%s" "$_dns_details" | sed 's/"Name":/\n"Name":/g' | _egrep_o '"Name":[^,]*' | _tail_n +2 | cut -d : -f 2 | tr -d ' "' | tr '\n' ' ')
-
- # Extract the record types, which are integers (except for the first one) and contain no commas, colons or spaces
- # The first type is skipped because it refers to the domain type
- _record_types=$(printf "%s" "$_dns_details" | sed 's/"Type":/\n"Type":/g' | _egrep_o '"Type":[^,]*' | _tail_n +2 | cut -d : -f 2 | tr -d ' ' | tr '\n' ' ')
-
- # Extract the record contents, which are strings and may contain no quotes except for TXT records, which must be delimited by two \" literals
- # Note: There is no domain related entry here
- # Note: A " character is appended at the end of each content to make it easier to process the list later
- _record_contents=$(printf "%s" "$_dns_details" | sed 's/"Content":/\n"Content":/g' | sed 's/\\"//g' | _egrep_o '"Content": *"[^"]*"' | cut -d : -f 2- | sed -n 's/"\(.*\)"/\1/p' | tr '\n' '#')
-
- _info "IDS: $_record_ids"
- _info "NAMES: $_record_names"
- _info "TYPEs: $_record_types"
- _info "CONTENTS: $_record_contents"
-
- _record_ids_count=$(printf "%s" "$_record_ids" | tr ' ' '\n' | wc -l)
- _record_names_count=$(printf "%s" "$_record_names" | tr ' ' '\n' | wc -l)
- _record_types_count=$(printf "%s" "$_record_types" | tr ' ' '\n' | wc -l)
- _record_contents_count=$(printf "%s" "$_record_contents" | tr '#' '\n' | wc -l)
-
- _info "Ids: $_record_ids_count, names: $_record_names_count, types: $_record_types_count, contents: $_record_contents_count"
-
- if [ "$_record_ids_count" != "$_record_names_count" ] || [ "$_record_ids_count" != "$_record_types_count" ] || [ "$_record_ids_count" != "$_record_contents_count" ]; then
- _err "Failed to parse record elements. Ids: $_record_ids_count, names: $_record_names_count, types: $_record_types_count, contents: $_record_contents_count"
- return 1
- fi
-
- _info "Looking for a TXT record matching inputs - name: $_record_name_lowercase value: $_txt_value"
-
- _i=1
- while [ "$_i" -le "$_record_ids_count" ]; do
- _current_name=$(printf "%s" "$_record_names" | cut -d " " -f "$_i")
- _current_type=$(printf "%s" "$_record_types" | cut -d " " -f "$_i")
- _current_content=$(printf "%s" "$_record_contents" | cut -d "#" -f "$_i")
-
- if [ "$_record_name_lowercase." = "$_current_name" ] && [ "5" = "$_current_type" ] && [ "$_txt_value" = "$_current_content" ]; then
- dns_record_id=$(printf "%s" "$_record_ids" | cut -d " " -f "$_i")
- _info "Found matching record with id: $dns_record_id"
- return 0
- else
- _debug2 "Record does not match - type: '$_current_type' name: '$_current_name' value: '$_current_content'; Expected '$_record_name_lowercase.' '5' '$_txt_value'"
- fi
- _i=$(_math "$_i" + 1)
- done
-
- _debug2 "No matching record was found in $_dns_details"
- return 1
-}
-
-#
-# Usage: _ab_dns_details domain.com
-#
-# Retrieve dns info for the given input domain
-#
-# Variables
-# _domain: $1
-# dns_details: the json returned by the call to $ARUBABUSINESS_API/api/domains/dns/$_domain/details (if return status is 0)
-# response
-#
-_ab_dns_details() {
- _domain=$1
-
- if ! _ab_rest GET "api/domains/dns/$_domain/details" || ! _contains "$response" "DomainId"; then
- return 1
- fi
-
- dns_details="$response"
- return 0
-}
-
-#
-# Usage: _ab_authenticate
-#
-# Read account conf, update domain conf and perform user authentication to acquire an access token
-#
-# Variables
-# AB_Key
-# AB_User
-# AB_Pass
-# AB_Token
-#
-_ab_authenticate() {
- AB_Key="${AB_Key:-$(_readaccountconf_mutable AB_Key)}"
- AB_User="${AB_User:-$(_readaccountconf_mutable AB_User)}"
- AB_Pass="${AB_Pass:-$(_readaccountconf_mutable AB_Pass)}"
-
- if [ -z "$AB_Key" ] || [ -z "$AB_User" ] || [ -z "$AB_Pass" ]; then
- AB_Key=""
- AB_User=""
- AB_Pass=""
- _err "Either the ArubaBusiness API key, the user or the password has not been defined yet."
- _err "Please configure them and try again."
- return 1
- fi
-
- _saveaccountconf_mutable AB_Key "$AB_Key"
- _saveaccountconf_mutable AB_User "$AB_User"
- _saveaccountconf_mutable AB_Pass "$AB_Pass"
-
- if ! _ab_get_token || [ -z "$AB_Token" ]; then
- _err "Failed to acquire an access token"
- return 1
- fi
-
- return 0
-}
-
-#
-# Usage: _ab_get_token
-#
-# Try acquiring a temporary access token. The token should have a 24h lifespan
-#
-# Variables
-# _ab_user_enc
-# _ab_pass_enc
-# _ab_authdata
-# AB_User
-# AB_Pass
-# AB_Token
-# response
-# _H2
-#
-_ab_get_token() {
- _ab_user_enc=$(printf "%s" "$AB_User" | _url_encode)
- _ab_pass_enc=$(printf "%s" "$AB_Pass" | _url_encode)
- _ab_authdata="grant_type=password&username=$_ab_user_enc&password=$_ab_pass_enc"
-
- _H2="Content-Type: application/x-www-form-urlencoded"
-
- if ! _ab_rest POST "auth/token" "$_ab_authdata" || ! _contains "$response" "access_token"; then
- _err "Authentication failure"
- return 1
- fi
-
- AB_Token="$(printf "%s" "$response" | _egrep_o '"access_token":"[^\"]*"' | cut -d : -f 2 | tr -d '"')"
-
- if [ -z "$AB_Token" ]; then
- _err "Could not extract access token"
- return 1
- fi
-
- _debug "Acquired access token"
- return 0
-}
-
-#
-# Usage: _ab_rest POST "example/endpoint" "password=123"
-#
-# Perform a REST request using the given method, endpoint and data
-#
-# Variables
-# _method: $1 - The http method
-# _endpoint: $2 - The api path (relative to $ARUBABUSINESS_API)
-# _data: $3 - The body of the request (optional)
-# _key_trimmed
-# _token_trimmed
-# _ret_code
-# AB_Key
-# AB_Token
-# ARUBABUSINESS_API
-# _H1
-# _H2
-# _H3
-# _H4
-#
-_ab_rest() {
- _method=$1
- _endpoint="$2"
- _data="$3"
-
- _key_trimmed=$(printf "%s" "$AB_Key" | tr -d '"')
- _token_trimmed=$(printf "%s" "$AB_Token" | tr -d '"')
-
- _H1="Accept: application/json"
-
- if [ -z "$_H2" ]; then
- # Default to application/json
- _H2="Content-Type: application/json"
- fi
-
- if [ "$_key_trimmed" ]; then
- _H3="Authorization-Key: $_key_trimmed"
- else
- _err "Missing Api Key"
- _ab_cleanup_headers
- return 1
- fi
-
- if [ "$_token_trimmed" ]; then
- _H4="Authorization: Bearer $_token_trimmed"
- else
- _debug "No access token set"
- fi
-
- if [ "$_method" != "GET" ]; then
- response="$(_post "$_data" "$ARUBABUSINESS_API/$_endpoint" "" "$_method")"
- else
- response="$(_get "$ARUBABUSINESS_API/$_endpoint")"
- fi
-
- _ret_code=$?
-
- if [ "$_ret_code" = "0" ] && _ab_call_is_success; then
- # Normalize the json response
- response="$(printf "%s" "$response" | _normalizeJson)"
- _ret_code=0
- else
- _err "Failed to call endpoint: $_endpoint"
- _ret_code=1
- fi
-
- _ab_cleanup_headers
-
- return $_ret_code
-}
-
-#
-# Usage: _ab_cleanup_headers
-#
-# Unset header variables to avoid interfering with other calls
-#
-# Variables
-# _H1
-# _H2
-# _H3
-# _H4
-#
-_ab_cleanup_headers() {
- # Cleanup request headers
- unset _H1 _H2 _H3 _H4 _H5
-
- # Cleanup response headers
- if [ -f "$HTTP_HEADER" ]; then
- : >"$HTTP_HEADER"
- fi
-}
-
-#
-# Usage: _ab_call_is_success
-#
-# Check whether a call's response http status is one of 200, 201, 202 or 204 (other 2xx are not handled)
-#
-# Variables
-# _status
-# _http_status
-# _success_http_codes
-# HTTP_HEADER
-#
-_ab_call_is_success() {
- _success_http_codes="200 201 202 204"
- if [ -f "$HTTP_HEADER" ]; then
- _http_status=$(_egrep_o "^HTTP[\/0-9. ]*" <"$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)
- for _status in $_success_http_codes; do
- if [ "$_status" = "$_http_status" ]; then
- return 0
- fi
- done
- fi
-
- return 1
-}
diff --git a/dnsapi/dns_autodns.sh b/dnsapi/dns_autodns.sh
index e26d699b..ce566978 100644
--- a/dnsapi/dns_autodns.sh
+++ b/dnsapi/dns_autodns.sh
@@ -139,21 +139,12 @@ _get_autodns_zone() {
return 1
}
-# Escape the XML special characters (& < > ' ") so that credentials
-# containing them do not break the request document (issue 5317).
-_autodns_xml_encode() {
- sed "s/&/\&/g;s/\</g;s/>/\>/g;s/'/\'/g;s/\"/\"/g"
-}
-
_build_request_auth_xml() {
- _autodns_user_xml="$(printf "%s" "$AUTODNS_USER" | _autodns_xml_encode)"
- _autodns_password_xml="$(printf "%s" "$AUTODNS_PASSWORD" | _autodns_xml_encode)"
- _autodns_context_xml="$(printf "%s" "$AUTODNS_CONTEXT" | _autodns_xml_encode)"
printf "%s%s%s
- " "$_autodns_user_xml" "$_autodns_password_xml" "$_autodns_context_xml"
+ " "$AUTODNS_USER" "$AUTODNS_PASSWORD" "$AUTODNS_CONTEXT"
}
# Arguments:
diff --git a/dnsapi/dns_aws.sh b/dnsapi/dns_aws.sh
index 1face1c8..b76d69c2 100755
--- a/dnsapi/dns_aws.sh
+++ b/dnsapi/dns_aws.sh
@@ -11,8 +11,7 @@ Options:
# All `_sleep` commands are included to avoid Route53 throttling, see
# https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests
-# Updated from "route53.amazonaws.com"
-AWS_HOST="route53.global.api.aws"
+AWS_HOST="route53.amazonaws.com"
AWS_URL="https://$AWS_HOST"
AWS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Amazon-Route53-API"
diff --git a/dnsapi/dns_baidu.sh b/dnsapi/dns_baidu.sh
index dfad8eeb..8651deab 100644
--- a/dnsapi/dns_baidu.sh
+++ b/dnsapi/dns_baidu.sh
@@ -49,95 +49,26 @@ Options:
Baidu_SK SecretAccessKey
OptionsAlt:
Baidu_BCD_Host API host, default: bcd.baidubce.com
- Baidu_DNS_Host New DNS API host, default: dns.baidubce.com
- Baidu_API_Preference Engine preference, default: auto
Baidu_BCD_Version API version number, default: 1
Baidu_BCD_Expire Signature expiration seconds, default: 3600
Baidu_View Resolve view, default: DEFAULT
- Baidu_Line New DNS line, default: default
Baidu_TTL Resolve ttl seconds, default: 300
Baidu_RM_Max Max records to delete in one run, default: 20
'
BAIDU_BCD_DEFAULT_HOST="bcd.baidubce.com"
-BAIDU_DNS_DEFAULT_HOST="dns.baidubce.com"
# --- Public API ---
dns_baidu_add() {
fulldomain=$(_idn "$1")
txtvalue=$2
- if ! _baidu_run_with_fallback "add" "$fulldomain" "$txtvalue"; then
- _baidu_err "all baidu api engines failed for add: $fulldomain"
+ if ! _baidu_prepare_record "$fulldomain"; then
+ _baidu_err "baidu_prepare_record failed for add: $fulldomain"
return 1
fi
- return 0
-}
-
-dns_baidu_rm() {
- fulldomain=$(_idn "$1")
- txtvalue=$2
-
- if ! _baidu_run_with_fallback "rm" "$fulldomain" "$txtvalue"; then
- _baidu_err "all baidu api engines failed for delete: $fulldomain"
- return 1
- fi
-
- return 0
-}
-
-_baidu_run_with_fallback() {
- _action="$1"
- _fulldomain="$2"
- _txtvalue="$3"
-
- if ! _baidu_load_credentials; then
- _baidu_err "baidu_load_credentials failed"
- return 1
- fi
-
- for _baidu_api_engine in $(_baidu_engine_order); do
- if ! _baidu_prepare_record "$_fulldomain"; then
- _baidu_info "prepare failed for engine: $_baidu_api_engine"
- continue
- fi
-
- if [ "$_action" = "add" ]; then
- if _baidu_add_record "$_txtvalue"; then
- return 0
- fi
- else
- if _baidu_rm_record "$_txtvalue"; then
- return 0
- fi
- fi
-
- _baidu_info "engine failed, try next if available: $_baidu_api_engine"
- done
-
- return 1
-}
-
-_baidu_engine_order() {
- _pref="$(_lower_case "$(_baidu_trim_ws "${Baidu_API_Preference:-auto}")")"
- case "$_pref" in
- legacy)
- printf "%s" "legacy new"
- ;;
- new)
- printf "%s" "new legacy"
- ;;
- *)
- printf "%s" "new legacy"
- ;;
- esac
-}
-
-_baidu_add_record() {
- _txtvalue="$1"
-
- if ! _baidu_find_record_ids_current "$_zone_name" "$_record_domain" "TXT" "$_txtvalue"; then
+ if ! _baidu_find_record_ids "$_zone_name" "$_record_domain" "TXT" "$txtvalue"; then
_baidu_err "baidu_find_record_ids failed for add: $_record_domain.$_zone_name"
return 1
fi
@@ -154,28 +85,16 @@ _baidu_add_record() {
_ttl="300"
;;
esac
-
- txtvalue="$(_baidu_trim_ws "$_txtvalue")"
+ _view="$(_baidu_trim_ws "${Baidu_View:-DEFAULT}")"
+ txtvalue="$(_baidu_trim_ws "$txtvalue")"
_record_domain="$(_baidu_trim_ws "$_record_domain")"
_zone_name="$(_baidu_trim_ws "$_zone_name")"
- if [ "$_baidu_api_engine" = "new" ]; then
- _line="$(_baidu_trim_ws "${Baidu_Line:-default}")"
- if [ -z "$_line" ]; then
- _line="default"
- fi
- _body="$(_baidu_payload_add_txt_dns "$_record_domain" "$txtvalue" "$_ttl" "$_line")"
- if ! _baidu_dns_call "POST" "/v1/dns/zone/${_zone_name}/record" "$_body"; then
- _baidu_err "baidu_dns_call failed: add record"
- return 1
- fi
- else
- _view="$(_baidu_trim_ws "${Baidu_View:-DEFAULT}")"
- _body="$(_baidu_payload_add_txt "$_zone_name" "$_record_domain" "$txtvalue" "$_ttl" "$_view")"
- if ! _baidu_bcd_post "/domain/resolve/add" "$_body"; then
- _baidu_err "baidu_bcd_post failed: add record"
- return 1
- fi
+ _body="$(_baidu_payload_add_txt "$_zone_name" "$_record_domain" "$txtvalue" "$_ttl" "$_view")"
+
+ if ! _baidu_bcd_post "/domain/resolve/add" "$_body"; then
+ _baidu_err "baidu_bcd_post failed: add record"
+ return 1
fi
if _baidu_is_api_error "$response"; then
@@ -186,10 +105,16 @@ _baidu_add_record() {
return 0
}
-_baidu_rm_record() {
- _txtvalue="$1"
+dns_baidu_rm() {
+ fulldomain=$(_idn "$1")
+ txtvalue=$2
- if ! _baidu_find_record_ids_current "$_zone_name" "$_record_domain" "TXT" "$_txtvalue"; then
+ if ! _baidu_prepare_record "$fulldomain"; then
+ _baidu_err "baidu_prepare_record failed for delete: $fulldomain"
+ return 1
+ fi
+
+ if ! _baidu_find_record_ids "$_zone_name" "$_record_domain" "TXT" "$txtvalue"; then
_baidu_err "baidu_find_record_ids failed for delete: $_record_domain.$_zone_name"
return 1
fi
@@ -213,37 +138,28 @@ _baidu_rm_record() {
fi
for _rid in $_ids; do
- if [ "$_baidu_api_engine" = "new" ]; then
- if ! _baidu_dns_call "DELETE" "/v1/dns/zone/${_zone_name}/record/${_rid}" ""; then
- _baidu_err "baidu_dns_call failed: delete recordId=$_rid"
- return 1
- fi
- else
- _body="$(_baidu_payload_delete "$_zone_name" "$_rid")"
- if ! _baidu_bcd_post "/domain/resolve/delete" "$_body"; then
- _baidu_err "baidu_bcd_post failed: delete recordId=$_rid"
- return 1
- fi
- if _baidu_is_api_error "$response"; then
- _baidu_err "$response"
- return 1
- fi
+ _body="$(_baidu_payload_delete "$_zone_name" "$_rid")"
+ if ! _baidu_bcd_post "/domain/resolve/delete" "$_body"; then
+ _baidu_err "baidu_bcd_post failed: delete recordId=$_rid"
+ return 1
+ fi
+ if _baidu_is_api_error "$response"; then
+ _baidu_err "$response"
+ return 1
fi
done
- if [ "$_baidu_api_engine" = "legacy" ]; then
- if ! _baidu_find_record_ids "$_zone_name" "$_record_domain" "TXT" "$_txtvalue"; then
- _baidu_err "baidu_find_record_ids failed for delete verify: $_record_domain.$_zone_name"
- return 1
- fi
- _left_ids="$_BAIDU_FIND_RESULT"
- if [ -z "$_left_ids" ]; then
- return 0
- fi
- if [ -n "$_left_ids" ]; then
- _baidu_err "delete verification failed: $_record_domain.$_zone_name still has TXT records"
- return 1
- fi
+ if ! _baidu_find_record_ids "$_zone_name" "$_record_domain" "TXT" "$txtvalue"; then
+ _baidu_err "baidu_find_record_ids failed for delete verify: $_record_domain.$_zone_name"
+ return 1
+ fi
+ _left_ids="$_BAIDU_FIND_RESULT"
+ if [ -z "$_left_ids" ]; then
+ return 0
+ fi
+ if [ -n "$_left_ids" ]; then
+ _baidu_err "delete verification failed: $_record_domain.$_zone_name still has TXT records"
+ return 1
fi
return 0
@@ -266,7 +182,6 @@ _baidu_load_credentials() {
_saveaccountconf_mutable Baidu_SK "$Baidu_SK"
BAIDU_BCD_HOST="${Baidu_BCD_Host:-$BAIDU_BCD_DEFAULT_HOST}"
- BAIDU_DNS_HOST="${Baidu_DNS_Host:-$BAIDU_DNS_DEFAULT_HOST}"
BAIDU_BCD_VERSION="${Baidu_BCD_Version:-1}"
return 0
@@ -274,16 +189,13 @@ _baidu_load_credentials() {
_baidu_prepare_record() {
_fulldomain="$1"
- if [ "$_baidu_api_engine" = "new" ]; then
- if ! _baidu_get_root_dns "$_fulldomain"; then
- _baidu_err "Could not find zone by new dns api for $_fulldomain"
- return 1
- fi
- else
- if ! _baidu_get_root "$_fulldomain"; then
- _baidu_err "Could not find zone by legacy bcd api for $_fulldomain"
- return 1
- fi
+ if ! _baidu_load_credentials; then
+ _baidu_err "baidu_load_credentials failed"
+ return 1
+ fi
+ if ! _baidu_get_root "$_fulldomain"; then
+ _baidu_err "Could not find zone for $_fulldomain"
+ return 1
fi
_record_domain="$_sub_domain"
_zone_name="$_domain"
@@ -322,43 +234,6 @@ _baidu_get_root() {
done
}
-_baidu_get_root_dns() {
- domain=$1
- i=1
- p=1
-
- while true; do
- h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
- if [ -z "$h" ]; then
- _baidu_err "invalid domain: $domain"
- return 1
- fi
-
- if ! _baidu_dns_call "GET" "/v1/dns/zone/${h}/record" ""; then
- _baidu_info "baidu_dns_call failed: list zones"
- elif ! _baidu_is_api_error "$response" && (_contains "$response" "\"records\"" || _contains "$response" "\"maxKeys\""); then
- _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
- _domain=$h
- if [ "$_sub_domain" = "$_domain" ]; then
- _sub_domain="@"
- fi
- _baidu_info "zone matched by dns api: $_domain (host: $_sub_domain)"
- return 0
- fi
-
- p=$i
- i=$(_math "$i" + 1)
- done
-}
-
-_baidu_find_record_ids_current() {
- if [ "$_baidu_api_engine" = "new" ]; then
- _baidu_find_record_ids_dns "$@"
- else
- _baidu_find_record_ids "$@"
- fi
-}
-
_baidu_find_record_ids() {
_zone_name="$1"
_record_domain="$2"
@@ -418,39 +293,6 @@ EOF
_BAIDU_FIND_RESULT="$_ids"
}
-_baidu_find_record_ids_dns() {
- _zone_name="$1"
- _record_domain="$2"
- _rdtype="$3"
- _rdata="$4"
- _BAIDU_FIND_RESULT=""
-
- if ! _baidu_dns_call "GET" "/v1/dns/zone/${_zone_name}/record" ""; then
- _baidu_err "baidu_dns_call failed: list records"
- return 1
- fi
-
- if _baidu_is_api_error "$response"; then
- _baidu_err "baidu_dns error: $(_baidu_json_get_str "$response" "code") $(_baidu_json_get_str "$response" "message")"
- return 1
- fi
-
- _normalized="$(printf "%s" "$response" | _normalizeJson)"
- _records=$(printf "%s" "$_normalized" | sed 's/},{/}\n{/g')
- _ids=""
-
- while IFS= read -r _line; do
- _id="$(_baidu_match_record_id_dns "$_line" "$_record_domain" "$_rdtype" "$_rdata")"
- if [ "$_id" ]; then
- _ids="$_ids $_id"
- fi
- done < ${_now})"
- return 0
- fi
-
- _info "ComLaude auth..."
- _comlaude_body="{\"username\":\"$COMLAUDE_USERNAME\",\"password\":\"$COMLAUDE_PASSWORD\",\"api_key\":\"$COMLAUDE_API_KEY\"}"
- _comlaude_response="$(_post "$_comlaude_body" "$COMLAUDE_API/api_login" "" "POST" "application/json")"
-
- if ! _contains "$_comlaude_response" "access_token"; then
- _err "Auth failed: $_comlaude_response"
- return 1
- fi
-
- COMLAUDE_ACCESS_TOKEN=$(echo "$_comlaude_response" | _egrep_o '"access_token":"[^"]*"' | cut -d'"' -f4)
- # store expiracy from api reply l'API ("expires_in" in seconds)
- _comlaude_expires_in=$(echo "$_comlaude_response" | _egrep_o '"expires_in":[0-9]*' | cut -d: -f2)
- [ -z "$_comlaude_expires_in" ] && _comlaude_expires_in=3000 # fallback if no info
-
- COMLAUDE_TOKEN_EXPIRY=$(($(_time) + _comlaude_expires_in - 60)) # margin of 60s to secure renew
-
- _saveaccountconf_mutable COMLAUDE_ACCESS_TOKEN "$COMLAUDE_ACCESS_TOKEN"
- _saveaccountconf_mutable COMLAUDE_TOKEN_EXPIRY "$COMLAUDE_TOKEN_EXPIRY"
-
- return 0
-}
-
-########## DOMAIN RESOLUTION ##########
-
-_comlaude_get_root() {
- COMLAUDE_GROUP_ID="${COMLAUDE_GROUP_ID:-$(_readaccountconf_mutable COMLAUDE_GROUP_ID)}"
- if [ -z "$COMLAUDE_GROUP_ID" ]; then
- _err "Missing COMLAUDE_GROUP_ID"
- return 1
- fi
-
- _comlaude_input_domain="$1"
- _comlaude_input_domain="${_comlaude_input_domain#_acme-challenge.}"
- case "$_comlaude_input_domain" in
- \*.*) _comlaude_input_domain="${_comlaude_input_domain#*.}" ;;
- esac
-
- _debug "Normalized domain: $_comlaude_input_domain"
-
- _comlaude_i=1
- while true; do
- _comlaude_d=$(printf "%s" "$_comlaude_input_domain" | cut -d . -f "$_comlaude_i-")
- [ -z "$_comlaude_d" ] && {
- _debug "No matching domain found for $_comlaude_input_domain"
- return 1
- }
-
- # don't test unnecessary levels
- # registered domain : TLD only (no dot after cut).
- case "$_comlaude_d" in
- *.*) : ;;
- *)
- _debug "Skipping bare TLD candidate: $_comlaude_d"
- _comlaude_i=$((_comlaude_i + 1))
- continue
- ;;
- esac
-
- _debug "Checking domain: $_comlaude_d"
-
- _comlaude_retry=0
- _comlaude_max_retry=3 # to avoid network errors
- _comlaude_DOM_ID=""
- _comlaude_Z_ID=""
-
- while [ "$_comlaude_retry" -lt "$_comlaude_max_retry" ]; do
- export _H1="Authorization: Bearer $COMLAUDE_ACCESS_TOKEN"
- _debug "Full URL: $COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/domains?filter[name]=$_comlaude_d&fields=id,name,active_zone"
- _comlaude_response="$(_get "$COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/domains?filter[name]=$_comlaude_d&fields=id,name,active_zone")"
- _H1=""
-
- _debug "RAW response for $_comlaude_d (try $((_comlaude_retry + 1))): $_comlaude_response"
-
- # If empty -> true network issue, we retry
- if [ -z "$_comlaude_response" ]; then
- _comlaude_retry=$((_comlaude_retry + 1))
- [ "$_comlaude_retry" -lt "$_comlaude_max_retry" ] && sleep 2
- continue
- fi
-
- # 404 -> domain not found in that level. no retry : continue
- if echo "$_comlaude_response" | grep -q '"status_code":404'; then
- _debug "404 for $_comlaude_d, moving to next level (not retrying)"
- break
- fi
-
- # Domain missing (200 reply, data empty) -> continue
- if echo "$_comlaude_response" | grep -q '"data":\[\]'; then
- _debug "Empty data for $_comlaude_d, moving to next level"
- break
- fi
-
- # Extraction via _egrep_o
- _comlaude_DOM_ID="$(echo "$_comlaude_response" | _egrep_o '"id":"[^"]*"' | head -n1 | cut -d':' -f2 | tr -d '"')"
- _comlaude_Z_ID="$(echo "$_comlaude_response" | _egrep_o '"active_zone":\{"id":"[^"]*"' | _egrep_o '"id":"[^"]*"$' | cut -d':' -f2 | tr -d '"')"
-
- if [ -n "$_comlaude_DOM_ID" ] && [ -n "$_comlaude_Z_ID" ]; then
- break
- fi
-
- # 200 reply but malformed data / noid -> retry transport
- _comlaude_retry=$((_comlaude_retry + 1))
- [ "$_comlaude_retry" -lt "$_comlaude_max_retry" ] && sleep 2
- done
-
- _debug "_comlaude_DOM_ID=$_comlaude_DOM_ID"
- _debug "_comlaude_Z_ID=$_comlaude_Z_ID"
-
- if [ -n "$_comlaude_DOM_ID" ] && [ -n "$_comlaude_Z_ID" ]; then
- _comlaude_domain="$_comlaude_d"
- _comlaude_domain_id="$_comlaude_DOM_ID"
- _comlaude_zone_id="$_comlaude_Z_ID"
- return 0
- fi
-
- _comlaude_i=$((_comlaude_i + 1))
- done
-}
-########## ADD TXT ##########
-
-dns_comlaude_add() {
- fulldomain="$1"
- txtvalue="$2"
-
- COMLAUDE_USERNAME="${COMLAUDE_USERNAME:-$(_readaccountconf_mutable COMLAUDE_USERNAME)}"
- COMLAUDE_PASSWORD="${COMLAUDE_PASSWORD:-$(_readaccountconf_mutable COMLAUDE_PASSWORD)}"
- COMLAUDE_API_KEY="${COMLAUDE_API_KEY:-$(_readaccountconf_mutable COMLAUDE_API_KEY)}"
- COMLAUDE_GROUP_ID="${COMLAUDE_GROUP_ID:-$(_readaccountconf_mutable COMLAUDE_GROUP_ID)}"
-
- if [ -z "$COMLAUDE_USERNAME" ] || [ -z "$COMLAUDE_PASSWORD" ] || [ -z "$COMLAUDE_API_KEY" ]; then
- _err "You didn't specify ComLaude credentials (COMLAUDE_USERNAME, COMLAUDE_PASSWORD, COMLAUDE_API_KEY)."
- return 1
- fi
-
- # Backup variable after validation
- _saveaccountconf_mutable COMLAUDE_USERNAME "$COMLAUDE_USERNAME"
- _saveaccountconf_mutable COMLAUDE_PASSWORD "$COMLAUDE_PASSWORD"
- _saveaccountconf_mutable COMLAUDE_API_KEY "$COMLAUDE_API_KEY"
- _saveaccountconf_mutable COMLAUDE_GROUP_ID "$COMLAUDE_GROUP_ID"
-
- _info "Adding TXT: $fulldomain"
- _comlaude_auth || return 1
- _comlaude_get_root "$fulldomain" || return 1
-
- _debug "Root: $_comlaude_domain"
-
- _comlaude_data="{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"value\":\"$txtvalue\",\"ttl\":60}"
-
- export _H1="Authorization: Bearer $COMLAUDE_ACCESS_TOKEN"
- export _H2="Content-Type: application/json"
-
- _comlaude_response="$(_post "$_comlaude_data" "$COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/zones/$_comlaude_zone_id/records")"
-
- _H1=""
- _H2=""
- if ! echo "$_comlaude_response" | grep -q '"id"'; then
- _err "Failed to create TXT"
- _debug "$_comlaude_response"
- return 1
- fi
-
- return 0
-}
-
-########## REMOVE TXT ##########
-
-dns_comlaude_rm() {
- fulldomain="$1"
- txtvalue="$2"
-
- COMLAUDE_USERNAME="${COMLAUDE_USERNAME:-$(_readaccountconf_mutable COMLAUDE_USERNAME)}"
- COMLAUDE_PASSWORD="${COMLAUDE_PASSWORD:-$(_readaccountconf_mutable COMLAUDE_PASSWORD)}"
- COMLAUDE_API_KEY="${COMLAUDE_API_KEY:-$(_readaccountconf_mutable COMLAUDE_API_KEY)}"
- COMLAUDE_GROUP_ID="${COMLAUDE_GROUP_ID:-$(_readaccountconf_mutable COMLAUDE_GROUP_ID)}"
-
- _info "Removing TXT: $fulldomain"
-
- _comlaude_auth || return 1
- _comlaude_get_root "$fulldomain" || return 1
-
- export _H1="Authorization: Bearer $COMLAUDE_ACCESS_TOKEN"
- _comlaude_encoded_name="$(printf '%s' "$fulldomain" | _url_encode)"
- _comlaude_encoded_value="$(printf '%s' "$txtvalue" | _url_encode)"
- _comlaude_url="$COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/zones/$_comlaude_zone_id/records?filter[type]=TXT&filter[name]=$_comlaude_encoded_name&filter[value]=$_comlaude_encoded_value"
- _comlaude_response="$(_get "$_comlaude_url")"
- _H1=""
-
- _debug "Filtered records response: $_comlaude_response"
-
- # first "id" top-level of reply (record itself,
- # always on first position of each data[] object)
- _comlaude_record_id="$(echo "$_comlaude_response" | _egrep_o '"data":\[\{"id":"[^"]*"' | _egrep_o '"[^"]*"$' | tr -d '"')"
-
- if [ -z "$_comlaude_record_id" ]; then
- _info "No matching TXT record found to delete for $fulldomain / $txtvalue"
- return 0
- fi
-
- _debug "Deleting record $_comlaude_record_id"
-
- export _H1="Authorization: Bearer $COMLAUDE_ACCESS_TOKEN"
- _comlaude_del_url="$COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/zones/$_comlaude_zone_id/records/$_comlaude_record_id"
- _comlaude_del_resp="$(_post "" "$_comlaude_del_url" "" "DELETE")"
- _H1=""
-
- if echo "$_comlaude_del_resp" | grep -q '"error"'; then
- _err "Delete failed for $_comlaude_record_id"
- _debug "$_comlaude_del_resp"
- return 1
- fi
-
- _info "Deleted record $_comlaude_record_id"
- return 0
-}
diff --git a/dnsapi/dns_cpanel.sh b/dnsapi/dns_cpanel.sh
index 6939c3f1..a6991403 100755
--- a/dnsapi/dns_cpanel.sh
+++ b/dnsapi/dns_cpanel.sh
@@ -38,7 +38,7 @@ dns_cpanel_add() {
fi
# adding entry
_info "Adding the entry"
- stripped_fulldomain="${fulldomain%."$_domain"}"
+ stripped_fulldomain=$(echo "$fulldomain" | sed "s/.$_domain//")
_debug "Adding $stripped_fulldomain to $_domain zone"
_myget "json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=add_zone_record&domain=$_domain&name=$stripped_fulldomain&type=TXT&txtdata=$txtvalue&ttl=1"
if _successful_update; then return 0; fi
@@ -128,27 +128,13 @@ _get_root() {
_err "Primary domain list not found!"
return 1
fi
- # Pick the LONGEST matching zone, dot-anchored: with both domain.tld and
- # sub.domain.tld zones on the account, cPanel stores the record in the
- # most specific zone, so add and rm must both resolve to that one.
- _domain=""
- for d in $_domains; do
- _debug "Checking if $fulldomain ends with $d"
- # case with quoted patterns gives an exact literal suffix match;
- # _endswith treats the needle as a regex, so its dots would let
- # xdomain.tld wrongly match zone domain.tld
- case "$fulldomain" in
- "$d" | *".$d")
- if [ "${#d}" -gt "${#_domain}" ]; then
- _domain="$d"
- fi
- ;;
- esac
+ for _domain in $_domains; do
+ _debug "Checking if $fulldomain ends with $_domain"
+ if (_endswith "$fulldomain" "$_domain"); then
+ _debug "Root domain: $_domain"
+ return 0
+ fi
done
- if [ -n "$_domain" ]; then
- _debug "Root domain: $_domain"
- return 0
- fi
return 1
}
diff --git a/dnsapi/dns_creoline.sh b/dnsapi/dns_creoline.sh
deleted file mode 100644
index f4d76f8e..00000000
--- a/dnsapi/dns_creoline.sh
+++ /dev/null
@@ -1,181 +0,0 @@
-#!/usr/bin/env sh
-# shellcheck disable=SC2034
-dns_creoline_info='creoline
-Site: https://www.creoline.com/de
-Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_creoline
-Help: https://help.creoline.com
-Options:
- creolineApiToken
- creolineApiSecret
-Issues: github.com/acmesh-official/acme.sh/issues/7103
-'
-
-creolineApi="https://api.creoline.com/v1"
-
-######## Public functions #####################
-
-# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPB8"
-dns_creoline_add() {
- fulldomain=$1
- txtvalue=$2
-
- creolineApiToken="${creolineApiToken:-$(_readaccountconf_mutable creolineApiToken)}"
- creolineApiSecret="${creolineApiSecret:-$(_readaccountconf_mutable creolineApiSecret)}"
-
- if [ -z "$creolineApiToken" ] || [ -z "$creolineApiSecret" ]; then
- _err "Error required creoline API Token or creoline API Secret not specified."
- _err "Please set it with the Command 'export creolineApiToken=' and 'export creolineApiSecret='."
- return 1
- else
- _saveaccountconf_mutable creolineApiToken "$creolineApiToken"
- _saveaccountconf_mutable creolineApiSecret "$creolineApiSecret"
- fi
-
- _debug "Detecting the root dns zone."
- if ! _get_root "$fulldomain"; then
- _err "Error on detecting the root dns zone."
- return 1
- fi
-
- _info "Adding record"
- if _creoline_rest POST "dns/zone/$_domain/record" "{\"type\":\"TXT\",\"host\":\"$_sub_domain\",\"record\":\"$txtvalue\",\"ttl\":\"60\"}"; then
- if _contains "$response" "$txtvalue"; then
- _info "Added, OK"
- return 0
- else
- _err "Add txt record error."
- return 1
- fi
- fi
- _err "Add txt record error."
- return 1
-}
-
-#fulldomain txtvalue
-dns_creoline_rm() {
- fulldomain=$1
- txtvalue=$2
-
- creolineApiToken="${creolineApiToken:-$(_readaccountconf_mutable creolineApiToken)}"
- creolineApiSecret="${creolineApiSecret:-$(_readaccountconf_mutable creolineApiSecret)}"
-
- _debug "Detecting the root dns zone."
- if ! _get_root "$fulldomain"; then
- _err "Error on detecting the root dns zone."
- return 1
- fi
-
- _info "Getting earlier created txt record."
- if ! _creoline_rest GET "dns/zone/$_domain/record/type/TXT/record/$txtvalue"; then
- if _contains "$response" "errors" || _contains "$response" "message"; then
- _err "Error on getting earlier created txt record."
- return 1
- fi
- _err "Error on getting earlier created txt record."
- return 1
- fi
-
- record_id=$(echo "$response" | _egrep_o "\"id\"[ ]*:[ ]*[0-9]+" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
- _debug "record_id" "$record_id"
-
- if [ -z "$record_id" ]; then
- _err "Error on deleting earlier created txt record. No record id found in response."
- return 1
- fi
-
- _info "Deleting earlier created txt record."
- if ! _creoline_rest DELETE "dns/zone/$_domain/record/$record_id"; then
- if _contains "$response" "errors" || _contains "$response" "message"; then
- _err "Error on deleting earlier created txt record."
- return 1
- fi
- _err "Error on deleting earlier created txt record."
- return 1
- fi
-
- _info "Deleted, OK"
- return 0
-}
-
-#################### Private functions below ##################################
-#_acme-challenge.www.domain.com
-#returns
-# _sub_domain=_acme-challenge.www
-# _domain=domain.com
-_get_root() {
- domain=$1
- if ! _creoline_rest GET "dns/zone/root/$domain"; then
- return 1
- fi
-
- _sub_domain=$(echo "$response" | _egrep_o "\"subDomain\"[ ]*:[ ]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
- _debug _sub_domain "$_sub_domain"
-
- _domain=$(echo "$response" | _egrep_o "\"domain\"[ ]*:[ ]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
- _debug _domain "$_domain"
-
- if [ -z "$_domain" ] || [ -z "$_sub_domain" ]; then
- return 1
- fi
-}
-
-_creoline_rest() {
- method=$1
- uri="$2"
- data="$3"
- timestamp=$(_time)
- canonical_request="${timestamp}.${creolineApi}/${uri}"
- signature_hash=$(printf "%s" "$canonical_request" | _hmac sha256 "$(printf "%s" "$creolineApiSecret" | _hex_dump | tr -d " ")" hex)
-
- _debug method "$method"
- _debug uri "$uri"
- _debug data "$data"
-
- _debug2 timestamp "$timestamp"
- _debug2 canonical_request "$canonical_request"
- _debug2 signature_hash "$signature_hash"
-
- token_trimmed=$(echo "$creolineApiToken" | tr -d '"')
- hmac_trimmed=$(echo "$signature_hash" | tr -d '"')
-
- export _H1="Content-Type: application/json"
-
- if [ "$token_trimmed" ]; then
- export _H2="X-Api-Token: $token_trimmed"
- fi
-
- if [ "$hmac_trimmed" ]; then
- export _H3="X-Creoline-Api-Signature: $hmac_trimmed"
- fi
-
- if [ "$timestamp" ]; then
- export _H4="X-Creoline-Api-Timestamp: $timestamp"
- fi
-
- if [ "$method" != "GET" ]; then
- response="$(_post "$data" "$creolineApi/$uri" "" "$method")"
- else
- response="$(_get "$creolineApi/$uri")"
- fi
-
- if [ "$?" != "0" ]; then
- _err "error $uri"
- return 1
- fi
-
- _debug response "$response"
-
- if _contains "$response" "errors"; then
- error=$(echo "$response" | _egrep_o "\"errors\":[[]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | tr -d "[")
- _err "Error: $error"
- _err "URI:$uri"
- return 1
- elif _contains "$response" "message"; then
- message=$(echo "$response" | _egrep_o "\"message\"[ ]*:[ ]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \")
- _err "Error: $message"
- _err "URI:$uri"
- return 1
- fi
-
- return 0
-}
diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh
index 6677b32f..d4b6b6e8 100644
--- a/dnsapi/dns_cyon.sh
+++ b/dnsapi/dns_cyon.sh
@@ -285,15 +285,15 @@ _cyon_delete_txt() {
list_txt_url="https://my.cyon.ch/domain/dnseditor/list-async"
- list_txt_response="$(_get "${list_txt_url}")"
+ list_txt_response="$(_get "${list_txt_url}" | sed -e 's/data-hash/\\ndata-hash/g')"
_debug list_txt_response "${list_txt_response}"
if ! _cyon_check_if_2fa_missed "${list_txt_response}"; then return 1; fi
# Find and delete all acme challenge entries for the $fulldomain.
- _dns_entries="$(printf "%s\n" "${list_txt_response}" | _egrep_o 'data-hash=\\"[^"]*\\" data-identifier=\\"[^"]*\\"' | sed 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\"/\1 \2/')"
+ _dns_entries="$(printf "%b\n" "${list_txt_response}" | sed -n 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\".*/\1 \2/p')"
- printf "%s\n" "${_dns_entries}" | while read -r _hash _identifier; do
+ printf "%s" "${_dns_entries}" | while read -r _hash _identifier; do
dns_type="$(printf "%s" "$_identifier" | cut -d'|' -f1)"
dns_domain="$(printf "%s" "$_identifier" | cut -d'|' -f2)"
diff --git a/dnsapi/dns_czechia.sh b/dnsapi/dns_czechia.sh
index 6ad60442..f0f4c32e 100644
--- a/dnsapi/dns_czechia.sh
+++ b/dnsapi/dns_czechia.sh
@@ -30,9 +30,8 @@ dns_czechia_add() {
return 1
fi
- _czechia_tab="$(printf '\t')"
- _cz=$(printf "%s" "$_current_zone" | _lower_case | sed "s/[ $_czechia_tab]//g; s/\.\$//")
- _tk=$(printf "%s" "$CZ_AuthorizationToken" | sed "s/^[ $_czechia_tab]*//; s/[ $_czechia_tab]*\$//")
+ _cz=$(printf "%s" "$_current_zone" | _lower_case | sed 's/[[:space:]]//g; s/\.$//')
+ _tk=$(printf "%s" "$CZ_AuthorizationToken" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
if [ -z "$_cz" ] || [ -z "$_tk" ]; then
_err "Missing zone or CZ_AuthorizationToken."
@@ -77,7 +76,7 @@ dns_czechia_add() {
return 0
fi
- _nres="$(printf '%s' "$_res" | _normalizeJson)"
+ _nres="$(_normalizeJson "$_res")"
if [ "$?" -ne 0 ] || [ -z "$_nres" ]; then
_nres="$_res"
fi
@@ -109,9 +108,8 @@ dns_czechia_rm() {
return 1
fi
- _czechia_tab="$(printf '\t')"
- _cz=$(printf "%s" "$_current_zone" | _lower_case | sed "s/[ $_czechia_tab]//g; s/\.\$//")
- _tk=$(printf "%s" "$CZ_AuthorizationToken" | sed "s/^[ $_czechia_tab]*//; s/[ $_czechia_tab]*\$//")
+ _cz=$(printf "%s" "$_current_zone" | _lower_case | sed 's/[[:space:]]//g; s/\.$//')
+ _tk=$(printf "%s" "$CZ_AuthorizationToken" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
if [ -z "$_cz" ] || [ -z "$_tk" ]; then
_err "Missing zone or CZ_AuthorizationToken."
@@ -182,13 +180,12 @@ _czechia_load_conf() {
}
_czechia_pick_zone() {
- _czechia_pz_tab="$(printf '\t')"
_fd=$(printf "%s" "$1" | _lower_case | sed 's/\.$//')
_best_zone=""
_zones_space=$(printf "%s" "$CZ_Zones" | sed 's/,/ /g')
for _z in $_zones_space; do
- _clean_z=$(printf "%s" "$_z" | _lower_case | sed "s/[ $_czechia_pz_tab]//g; s/\.\$//")
+ _clean_z=$(printf "%s" "$_z" | _lower_case | sed 's/[[:space:]]//g; s/\.$//')
[ -z "$_clean_z" ] && continue
case "$_fd" in
diff --git a/dnsapi/dns_da.sh b/dnsapi/dns_da.sh
index d9cf6247..36251b05 100755
--- a/dnsapi/dns_da.sh
+++ b/dnsapi/dns_da.sh
@@ -4,7 +4,7 @@ dns_da_info='DirectAdmin Server API
Site: DirectAdmin.com/api.php
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_da
Options:
- DA_Api API Server URL. E.g. "https://remoteUser:remotePassword@da.domain.tld:8443". Special characters in the user/password must be percent-encoded, e.g. "@" -> "%40".
+ DA_Api API Server URL. E.g. "https://remoteUser:remotePassword@da.domain.tld:8443"
DA_Api_Insecure Insecure TLS. 0: check for cert validity, 1: always accept
Issues: github.com/TigerP/acme.sh/issues
'
diff --git a/dnsapi/dns_desec.sh b/dnsapi/dns_desec.sh
index e5e4809a..d6b9c355 100644
--- a/dnsapi/dns_desec.sh
+++ b/dnsapi/dns_desec.sh
@@ -4,7 +4,7 @@ dns_desec_info='deSEC.io
Site: desec.readthedocs.io/en/latest/
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_desec
Options:
- DEDYN_TOKEN API Token
+ DDNSS_Token API Token
Issues: github.com/acmesh-official/acme.sh/issues/2180
Author: Zheng Qian
'
@@ -39,7 +39,6 @@ dns_desec_add() {
_err "invalid domain"
return 1
fi
- _sub_domain=$(echo "$_sub_domain" | _lower_case)
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
@@ -49,7 +48,7 @@ dns_desec_add() {
_desec_rest GET "$REST_API/$_domain/rrsets/$_sub_domain/TXT/"
if [ "$_code" = "200" ]; then
- oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"[^ ]*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")"
+ oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"\\S*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")"
_debug "existing TXT found"
_debug oldtxtvalues "$oldtxtvalues"
if [ -n "$oldtxtvalues" ]; then
@@ -101,7 +100,7 @@ dns_desec_rm() {
_err "invalid domain"
return 1
fi
- _sub_domain=$(echo "$_sub_domain" | _lower_case)
+
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
@@ -111,7 +110,7 @@ dns_desec_rm() {
_desec_rest GET "$REST_API/$_domain/rrsets/$_sub_domain/TXT/"
if [ "$_code" = "200" ]; then
- oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"[^ ]*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")"
+ oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"\\S*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")"
_debug "existing TXT found"
_debug oldtxtvalues "$oldtxtvalues"
if [ -n "$oldtxtvalues" ]; then
@@ -151,8 +150,6 @@ _desec_rest() {
if [ "$m" != "GET" ]; then
_secure_debug2 data "$data"
response="$(_post "$data" "$ep" "" "$m")"
- _info "Sleeping 1s to respect deSEC write rate limit"
- _sleep 1
else
response="$(_get "$ep")"
fi
diff --git a/dnsapi/dns_dnsexit.sh b/dnsapi/dns_dnsexit.sh
index b92482cf..ec3b07a4 100644
--- a/dnsapi/dns_dnsexit.sh
+++ b/dnsapi/dns_dnsexit.sh
@@ -5,11 +5,14 @@ Site: DNSExit.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dnsexit
Options:
DNSEXIT_API_KEY API Key
+ DNSEXIT_AUTH_USER Username
+ DNSEXIT_AUTH_PASS Password
Issues: github.com/acmesh-official/acme.sh/issues/4719
Author: Samuel Jimenez
'
DNSEXIT_API_URL="https://api.dnsexit.com/dns/"
+DNSEXIT_HOSTS_URL="https://update.dnsexit.com/ipupdate/hosts.jsp"
######## Public functions #####################
#Usage: dns_dnsexit_add _acme-challenge.*.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
@@ -25,7 +28,20 @@ dns_dnsexit_add() {
return 1
fi
- _dnsexit_zone_op add ',"ttl":1,"overwrite":false'
+ _debug 'First detect the root zone'
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ if ! _dnsexit_rest "{\"domain\":\"$_domain\",\"add\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":0,\"overwrite\":false}}"; then
+ _err "$response"
+ return 1
+ fi
+
+ _debug2 _response "$response"
+ return 0
}
#Usage: fulldomain txtvalue
@@ -42,43 +58,54 @@ dns_dnsexit_rm() {
return 1
fi
- _dnsexit_zone_op delete ''
+ _debug 'First detect the root zone'
+ if ! _get_root "$fulldomain"; then
+ _err "$response"
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ if ! _dnsexit_rest "{\"domain\":\"$_domain\",\"delete\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\"}}"; then
+ _err "$response"
+ return 1
+ fi
+
+ _debug2 _response "$response"
+ return 0
}
#################### Private functions below ##################################
-# The legacy zone-detection endpoint (update.dnsexit.com/ipupdate/hosts.jsp)
-# was shut down by DNSExit and now returns 503, and the JSON API offers no
-# zone-list call. So find the root zone by attempting the actual operation at
-# each domain level: the API answers "code":0 only when the domain matches a
-# zone of the account. https://github.com/acmesh-official/acme.sh/issues/6914
-#Usage: _dnsexit_zone_op
-_dnsexit_zone_op() {
- _op="$1"
- _extra="$2"
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ domain=$1
i=1
while true; do
- _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100)
- _debug _domain "$_domain"
+ _domain=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
+ _debug h "$_domain"
if [ -z "$_domain" ]; then
- _err "Could not find the root zone of $fulldomain in your DNSExit account"
return 1
fi
- _sub_domain="$(printf "%s" "$fulldomain" | sed "s/\\.$_domain\$//")"
- if [ "$_sub_domain" = "$fulldomain" ]; then
- _sub_domain=""
- fi
- _debug _sub_domain "$_sub_domain"
+ _debug login "$DNSEXIT_AUTH_USER"
+ _debug password "$DNSEXIT_AUTH_PASS"
+ _debug domain "$_domain"
- if _dnsexit_rest "{\"domain\":\"$_domain\",\"$_op\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\"$_extra}}"; then
- if _contains "$response" "\"code\":0" || _contains "$response" "\"code\": 0"; then
- _debug2 _response "$response"
- return 0
- fi
- _debug "Zone $_domain was not accepted, trying the next level" "$response"
+ _dnsexit_http "login=$DNSEXIT_AUTH_USER&password=$DNSEXIT_AUTH_PASS&domain=$_domain"
+
+ if _contains "$response" "0=$_domain"; then
+ _sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")"
+ return 0
+ else
+ _debug "Go to next level of $_domain"
fi
i=$(_math "$i" + 1)
done
+
+ return 1
}
_dnsexit_rest() {
@@ -109,7 +136,27 @@ _dnsexit_rest() {
return 0
}
+_dnsexit_http() {
+ m=GET
+ param="$1"
+ _debug param "$param"
+ _debug get "$DNSEXIT_HOSTS_URL?$param"
+
+ response="$(_get "$DNSEXIT_HOSTS_URL?$param")"
+
+ _debug response "$response"
+
+ if [ "$?" != "0" ]; then
+ _err "Error $param"
+ return 1
+ fi
+
+ _debug2 response "$response"
+ return 0
+}
+
get_account_info() {
+
DNSEXIT_API_KEY="${DNSEXIT_API_KEY:-$(_readaccountconf_mutable DNSEXIT_API_KEY)}"
if test -z "$DNSEXIT_API_KEY"; then
DNSEXIT_API_KEY=''
@@ -119,5 +166,23 @@ get_account_info() {
_saveaccountconf_mutable DNSEXIT_API_KEY "$DNSEXIT_API_KEY"
+ DNSEXIT_AUTH_USER="${DNSEXIT_AUTH_USER:-$(_readaccountconf_mutable DNSEXIT_AUTH_USER)}"
+ if test -z "$DNSEXIT_AUTH_USER"; then
+ DNSEXIT_AUTH_USER=""
+ _err 'DNSEXIT_AUTH_USER was not exported'
+ return 1
+ fi
+
+ _saveaccountconf_mutable DNSEXIT_AUTH_USER "$DNSEXIT_AUTH_USER"
+
+ DNSEXIT_AUTH_PASS="${DNSEXIT_AUTH_PASS:-$(_readaccountconf_mutable DNSEXIT_AUTH_PASS)}"
+ if test -z "$DNSEXIT_AUTH_PASS"; then
+ DNSEXIT_AUTH_PASS=""
+ _err 'DNSEXIT_AUTH_PASS was not exported'
+ return 1
+ fi
+
+ _saveaccountconf_mutable DNSEXIT_AUTH_PASS "$DNSEXIT_AUTH_PASS"
+
return 0
}
diff --git a/dnsapi/dns_dnsimple.sh b/dnsapi/dns_dnsimple.sh
index 257549b4..10a3821d 100644
--- a/dnsapi/dns_dnsimple.sh
+++ b/dnsapi/dns_dnsimple.sh
@@ -5,7 +5,6 @@ Site: DNSimple.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dnsimple
Options:
DNSimple_OAUTH_TOKEN OAuth Token
- DNSimple_ACCOUNT_ID Account ID. Optional, only needed when the token can access multiple accounts.
Issues: github.com/pho3nixf1re/acme.sh/issues
'
@@ -18,7 +17,6 @@ dns_dnsimple_add() {
fulldomain=$1
txtvalue=$2
- DNSimple_OAUTH_TOKEN="${DNSimple_OAUTH_TOKEN:-$(_readaccountconf_mutable DNSimple_OAUTH_TOKEN)}"
if [ -z "$DNSimple_OAUTH_TOKEN" ]; then
DNSimple_OAUTH_TOKEN=""
_err "You have not set the dnsimple oauth token yet."
@@ -27,10 +25,10 @@ dns_dnsimple_add() {
fi
# save the oauth token for later
- _saveaccountconf_mutable DNSimple_OAUTH_TOKEN "$DNSimple_OAUTH_TOKEN"
+ _saveaccountconf DNSimple_OAUTH_TOKEN "$DNSimple_OAUTH_TOKEN"
if ! _get_account_id; then
- _err "failed to retrieve account id"
+ _err "failed to retrive account id"
return 1
fi
@@ -58,14 +56,8 @@ dns_dnsimple_add() {
dns_dnsimple_rm() {
fulldomain=$1
- DNSimple_OAUTH_TOKEN="${DNSimple_OAUTH_TOKEN:-$(_readaccountconf_mutable DNSimple_OAUTH_TOKEN)}"
- if [ -z "$DNSimple_OAUTH_TOKEN" ]; then
- _err "You have not set the dnsimple oauth token yet."
- return 1
- fi
-
if ! _get_account_id; then
- _err "failed to retrieve account id"
+ _err "failed to retrive account id"
return 1
fi
@@ -130,16 +122,13 @@ _get_root() {
# returns _account_id
_get_account_id() {
- DNSimple_ACCOUNT_ID="${DNSimple_ACCOUNT_ID:-$(_readaccountconf_mutable DNSimple_ACCOUNT_ID)}"
- if [ "$DNSimple_ACCOUNT_ID" ]; then
- _saveaccountconf_mutable DNSimple_ACCOUNT_ID "$DNSimple_ACCOUNT_ID"
- _account_id="$DNSimple_ACCOUNT_ID"
- _debug _account_id "$_account_id"
- return 0
+ _debug "retrive account id"
+ if ! _dnsimple_rest GET "whoami"; then
+ return 1
fi
- _debug "retrieve account id"
- if ! _dnsimple_rest GET "whoami"; then
+ if _contains "$response" "\"account\":null"; then
+ _err "no account associated with this token"
return 1
fi
@@ -148,25 +137,7 @@ _get_account_id() {
return 1
fi
- if _contains "$response" "\"account\":null"; then
- # the whoami of a user token (dnsimple_u_*) carries no account,
- # so list the accounts the token can access instead
- # https://github.com/acmesh-official/acme.sh/issues/6491
- if ! _dnsimple_rest GET "accounts"; then
- return 1
- fi
- fi
-
_account_id=$(printf "%s" "$response" | _egrep_o "\"id\":[^,]*,\"email\":" | cut -d: -f2 | cut -d, -f1)
- if [ -z "$_account_id" ]; then
- _err "no account associated with this token"
- return 1
- fi
- if [ "$(echo "$_account_id" | wc -l)" -gt 1 ]; then
- _err "The token has access to multiple accounts, please pick one and set it explicitly:"
- _err "export DNSimple_ACCOUNT_ID="
- return 1
- fi
_debug _account_id "$_account_id"
return 0
diff --git a/dnsapi/dns_dynu.sh b/dnsapi/dns_dynu.sh
index 3ac5c2f1..1d1fc311 100644
--- a/dnsapi/dns_dynu.sh
+++ b/dnsapi/dns_dynu.sh
@@ -23,8 +23,6 @@ dns_dynu_add() {
fulldomain=$1
txtvalue=$2
- Dynu_ClientId="${Dynu_ClientId:-$(_readaccountconf_mutable Dynu_ClientId)}"
- Dynu_Secret="${Dynu_Secret:-$(_readaccountconf_mutable Dynu_Secret)}"
if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then
Dynu_ClientId=""
Dynu_Secret=""
@@ -34,8 +32,8 @@ dns_dynu_add() {
fi
#save the client id and secret to the account conf file.
- _saveaccountconf_mutable Dynu_ClientId "$Dynu_ClientId"
- _saveaccountconf_mutable Dynu_Secret "$Dynu_Secret"
+ _saveaccountconf Dynu_ClientId "$Dynu_ClientId"
+ _saveaccountconf Dynu_Secret "$Dynu_Secret"
if [ -z "$Dynu_Token" ]; then
_info "Getting Dynu token."
@@ -71,8 +69,6 @@ dns_dynu_rm() {
fulldomain=$1
txtvalue=$2
- Dynu_ClientId="${Dynu_ClientId:-$(_readaccountconf_mutable Dynu_ClientId)}"
- Dynu_Secret="${Dynu_Secret:-$(_readaccountconf_mutable Dynu_Secret)}"
if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then
Dynu_ClientId=""
Dynu_Secret=""
@@ -82,8 +78,8 @@ dns_dynu_rm() {
fi
#save the client id and secret to the account conf file.
- _saveaccountconf_mutable Dynu_ClientId "$Dynu_ClientId"
- _saveaccountconf_mutable Dynu_Secret "$Dynu_Secret"
+ _saveaccountconf Dynu_ClientId "$Dynu_ClientId"
+ _saveaccountconf Dynu_Secret "$Dynu_Secret"
if [ -z "$Dynu_Token" ]; then
_info "Getting Dynu token."
@@ -218,11 +214,11 @@ _dynu_authentication() {
response="$(_get "$Dynu_EndPoint/oauth2/token")"
if [ "$?" != "0" ]; then
- _err "Authentication failed: no response from $Dynu_EndPoint/oauth2/token"
+ _err "Authentication failed."
return 1
fi
if _contains "$response" "Authentication Exception"; then
- _err "Authentication failed. Server response: $response"
+ _err "Authentication failed."
return 1
fi
if _contains "$response" "access_token"; then
diff --git a/dnsapi/dns_edgedns.sh b/dnsapi/dns_edgedns.sh
index 9ff1cc06..e88a1483 100755
--- a/dnsapi/dns_edgedns.sh
+++ b/dnsapi/dns_edgedns.sh
@@ -363,12 +363,17 @@ _edgedns_rest() {
_edgedns_eg_timestamp() {
_debug "Generating signature Timestamp"
- #Akamai accepts a clock skew of +/-30s, so use the system clock directly.
- #The previous code fetched the Date header from www.ntp.org, which is not
- #a reliable time source (it served a wrong time for hours, issue 3973),
- #cost an extra https round-trip for every API request, and combined the
- #remote time of day with the LOCAL date, breaking around UTC midnight.
- _eg_timestamp="$(date -u "+%Y%m%dT%H:%M:%S+0000")"
+ _debug3 "Retriving ntp time"
+ _timeheaders="$(_get "https://www.ntp.org" "onlyheader")"
+ _debug3 "_timeheaders" "$_timeheaders"
+ _ntpdate="$(echo "$_timeheaders" | grep -i "Date:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n")"
+ _debug3 "_ntpdate" "$_ntpdate"
+ _ntpdate="$(echo "${_ntpdate}" | sed -e 's/^[[:space:]]*//')"
+ _debug3 "_NTPDATE" "$_ntpdate"
+ _ntptime="$(echo "${_ntpdate}" | _head_n 1 | cut -d " " -f 5 | tr -d "\r\n")"
+ _debug3 "_ntptime" "$_ntptime"
+ _eg_timestamp=$(date -u "+%Y%m%dT")
+ _eg_timestamp="$(printf "%s%s+0000" "$_eg_timestamp" "$_ntptime")"
_debug "_eg_timestamp" "$_eg_timestamp"
}
diff --git a/dnsapi/dns_eurodns.sh b/dnsapi/dns_eurodns.sh
deleted file mode 100644
index 0fac4cb5..00000000
--- a/dnsapi/dns_eurodns.sh
+++ /dev/null
@@ -1,267 +0,0 @@
-#!/usr/bin/env sh
-# shellcheck disable=SC2034
-dns_eurodns_info='EuroDNS
-Site: eurodns.com
-Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_eurodns
-Options:
- EURODNS_APP_ID Application ID
- EURODNS_API_KEY API Key
- EURODNS_TTL TTL. Default: "600".
-Issues: github.com/acmesh-official/acme.sh/issues
-Author: Nicolas Santorelli
-'
-
-#
-# EuroDNS DNS API
-#
-# EuroDNS API documentation:
-# https://docapi.eurodns.com
-#
-# Usage:
-# export EURODNS_APP_ID="your-app-id"
-# export EURODNS_API_KEY="your-api-key"
-# acme.sh --issue --dns dns_eurodns -d example.com -d *.example.com
-#
-# The credentials will be saved in ~/.acme.sh/account.conf
-#
-# Optional:
-# export EURODNS_API_URL="https://rest-api.eurodns.com" # Default API URL
-# export EURODNS_TTL=600 # Default TTL (minimum 600 for EuroDNS)
-#
-
-EURODNS_API_DEFAULT="https://rest-api.eurodns.com"
-EURODNS_TTL_DEFAULT=600
-
-######## Public functions #####################
-
-#Usage: dns_eurodns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
-dns_eurodns_add() {
- fulldomain="$(echo "$1" | _lower_case)"
- txtvalue=$2
-
- _info "Using EuroDNS DNS API"
- _debug fulldomain "$fulldomain"
- _debug txtvalue "$txtvalue"
-
- EURODNS_APP_ID="${EURODNS_APP_ID:-$(_readaccountconf_mutable EURODNS_APP_ID)}"
- EURODNS_API_KEY="${EURODNS_API_KEY:-$(_readaccountconf_mutable EURODNS_API_KEY)}"
- EURODNS_API_URL="${EURODNS_API_URL:-$(_readaccountconf_mutable EURODNS_API_URL)}"
- EURODNS_API_URL="${EURODNS_API_URL:-$EURODNS_API_DEFAULT}"
- EURODNS_TTL="${EURODNS_TTL:-$(_readaccountconf_mutable EURODNS_TTL)}"
- EURODNS_TTL="${EURODNS_TTL:-$EURODNS_TTL_DEFAULT}"
-
- if [ -z "$EURODNS_APP_ID" ] || [ -z "$EURODNS_API_KEY" ]; then
- EURODNS_APP_ID=""
- EURODNS_API_KEY=""
- _err "You didn't specify EuroDNS App ID and API Key."
- _err "Please export EURODNS_APP_ID and EURODNS_API_KEY and try again."
- return 1
- fi
-
- _saveaccountconf_mutable EURODNS_APP_ID "$EURODNS_APP_ID"
- _saveaccountconf_mutable EURODNS_API_KEY "$EURODNS_API_KEY"
- if [ "$EURODNS_API_URL" != "$EURODNS_API_DEFAULT" ]; then
- _saveaccountconf_mutable EURODNS_API_URL "$EURODNS_API_URL"
- fi
- if [ "$EURODNS_TTL" != "$EURODNS_TTL_DEFAULT" ]; then
- _saveaccountconf_mutable EURODNS_TTL "$EURODNS_TTL"
- fi
-
- _debug "First detect the root zone"
- if ! _get_root "$fulldomain"; then
- _err "Invalid domain"
- return 1
- fi
- _debug _domain "$_domain"
- _debug _sub_domain "$_sub_domain"
-
- _info "Adding TXT record"
- if _eurodns_add_txt_record "$_domain" "$_sub_domain" "$txtvalue"; then
- _info "Added TXT record successfully."
- return 0
- else
- _err "Failed to add TXT record."
- return 1
- fi
-}
-
-#Usage: fulldomain txtvalue
-dns_eurodns_rm() {
- fulldomain="$(echo "$1" | _lower_case)"
- txtvalue=$2
-
- _info "Using EuroDNS DNS API"
- _debug fulldomain "$fulldomain"
- _debug txtvalue "$txtvalue"
-
- EURODNS_APP_ID="${EURODNS_APP_ID:-$(_readaccountconf_mutable EURODNS_APP_ID)}"
- EURODNS_API_KEY="${EURODNS_API_KEY:-$(_readaccountconf_mutable EURODNS_API_KEY)}"
- EURODNS_API_URL="${EURODNS_API_URL:-$(_readaccountconf_mutable EURODNS_API_URL)}"
- EURODNS_API_URL="${EURODNS_API_URL:-$EURODNS_API_DEFAULT}"
-
- if [ -z "$EURODNS_APP_ID" ] || [ -z "$EURODNS_API_KEY" ]; then
- EURODNS_APP_ID=""
- EURODNS_API_KEY=""
- _err "You didn't specify EuroDNS App ID and API Key."
- return 1
- fi
-
- _debug "First detect the root zone"
- if ! _get_root "$fulldomain"; then
- _err "Invalid domain"
- return 1
- fi
- _debug _domain "$_domain"
- _debug _sub_domain "$_sub_domain"
-
- _info "Removing TXT record"
- if _eurodns_rm_txt_record "$_domain" "$_sub_domain" "$txtvalue"; then
- _info "Removed TXT record successfully."
- return 0
- else
- _err "Failed to remove TXT record."
- return 1
- fi
-}
-
-#################### Private functions below ##################################
-
-# _sub_domain=_acme-challenge.www
-# _domain=domain.com
-_get_root() {
- domain=$1
- i=1
- p=1
-
- while true; do
- h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
- _debug h "$h"
- if [ -z "$h" ]; then
- return 1
- fi
-
- _eurodns_rest GET "dns-zones/$h"
- if [ "$?" != "0" ]; then
- if [ "$_code" = "404" ]; then
- _debug "Zone $h not found, continuing..."
- else
- _err "API error looking up zone $h"
- return 1
- fi
- p=$i
- i=$(_math "$i" + 1)
- continue
- fi
-
- if _contains "$response" '"name"'; then
- if [ "$i" = "1" ]; then
- _sub_domain="@"
- else
- _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
- fi
- _domain=$h
- return 0
- fi
-
- p=$i
- i=$(_math "$i" + 1)
- done
-
- return 1
-}
-
-_eurodns_add_txt_record() {
- domain=$1
- subdomain=$2
- txtvalue=$3
-
- data='[{"type":"TXT","host":"'"$subdomain"'","rdata":"'"$txtvalue"'","ttl":'"$EURODNS_TTL"'}]'
-
- _debug "Adding TXT record via API"
- if _eurodns_rest POST "dns-zones/$domain/dns-records" "$data"; then
- if _contains "$response" "$txtvalue"; then
- return 0
- fi
- fi
- _err "Failed to add TXT record"
- return 1
-}
-
-_eurodns_rm_txt_record() {
- domain=$1
- subdomain=$2
- txtvalue=$3
-
- _debug "Getting current zone data for $domain"
-
- if ! _eurodns_rest GET "dns-zones/$domain"; then
- _err "Failed to get zone data"
- return 1
- fi
-
- zone_data=$(echo "$response" | _normalizeJson)
- _debug2 zone_data "$zone_data"
-
- # Find the record ID matching our TXT record
- record_id=$(echo "$zone_data" | tr '{' '\n' | grep -F '"TXT"' | grep -F "\"$subdomain\"" | grep -F "\"$txtvalue\"" | _egrep_o '"id" *: *[0-9]+' | cut -d : -f 2 | _head_n 1)
- _debug record_id "$record_id"
-
- if [ -z "$record_id" ]; then
- _info "TXT record not found or already removed"
- return 0
- fi
-
- _debug "Deleting TXT record $record_id"
- if ! _eurodns_rest DELETE "dns-zones/$domain/dns-records/$record_id"; then
- _err "Failed to delete TXT record"
- return 1
- fi
-
- return 0
-}
-
-# Usage: _eurodns_rest METHOD ENDPOINT [DATA]
-_eurodns_rest() {
- method=$1
- endpoint=$2
- data="$3"
-
- export _H1="X-APP-ID: $EURODNS_APP_ID"
- export _H2="X-API-KEY: $EURODNS_API_KEY"
- export _H3="Content-Type: application/json"
-
- url="$EURODNS_API_URL/$endpoint"
-
- _debug2 url "$url"
- _debug2 method "$method"
- _debug2 data "$data"
-
- : >"$HTTP_HEADER"
-
- if [ "$method" = "GET" ]; then
- response="$(_get "$url")"
- else
- response="$(_post "$data" "$url" "" "$method")"
- fi
-
- _ret="$?"
- unset _H1 _H2 _H3
- _debug2 response "$response"
-
- _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
- _debug2 _code "$_code"
-
- if [ "$_ret" != "0" ]; then
- _err "Error calling API: $endpoint"
- return 1
- fi
-
- if [ "$_code" != "200" ] && [ "$_code" != "201" ] && [ "$_code" != "204" ]; then
- if [ "$_code" != "404" ]; then
- _err "API error (HTTP $_code): $response"
- fi
- return 1
- fi
-
- return 0
-}
diff --git a/dnsapi/dns_firestorm.sh b/dnsapi/dns_firestorm.sh
deleted file mode 100644
index 808c2b89..00000000
--- a/dnsapi/dns_firestorm.sh
+++ /dev/null
@@ -1,110 +0,0 @@
-#!/usr/bin/env sh
-# shellcheck disable=SC2034
-dns_firestorm_info='Firestorm.ch
-Site: firestorm.ch
-Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_firestorm
-Options:
- FST_Key Customer ID
- FST_Secret API Secret
- FST_Url API URL. Optional. Default "https://api.firestorm.ch/acme-dns".
-Issues: github.com/acmesh-official/acme.sh/issues/6839
-Author: FireStorm GmbH
-'
-
-FST_Url_DEFAULT="https://api.firestorm.ch/acme-dns"
-
-######## Public functions #####################
-
-# Usage: dns_firestorm_add _acme-challenge.www.example.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
-dns_firestorm_add() {
- fulldomain=$1
- txtvalue=$2
-
- FST_Key="${FST_Key:-$(_readaccountconf_mutable FST_Key)}"
- FST_Secret="${FST_Secret:-$(_readaccountconf_mutable FST_Secret)}"
- FST_Url="${FST_Url:-$(_readaccountconf_mutable FST_Url)}"
-
- if [ -z "$FST_Key" ] || [ -z "$FST_Secret" ]; then
- _err "FST_Key and FST_Secret must be set"
- _err "Get your API credentials at https://admin.firestorm.ch"
- return 1
- fi
-
- FST_Url="${FST_Url:-$FST_Url_DEFAULT}"
-
- _saveaccountconf_mutable FST_Key "$FST_Key"
- _saveaccountconf_mutable FST_Secret "$FST_Secret"
- if [ "$FST_Url" != "$FST_Url_DEFAULT" ]; then
- _saveaccountconf_mutable FST_Url "$FST_Url"
- else
- _clearaccountconf_mutable FST_Url
- fi
-
- subdomain=$(printf "%s" "$fulldomain" | sed 's/^_acme-challenge\.//')
-
- _info "Adding TXT record for $fulldomain"
- _debug "Subdomain" "$subdomain"
- _debug "TXT value" "$txtvalue"
-
- body="{\"subdomain\":\"$(_json_safe "$subdomain")\",\"txt\":\"$(_json_safe "$txtvalue")\"}"
-
- response="$(_firestorm_api "update" "$body")"
-
- if _contains "$response" "$txtvalue"; then
- _info "TXT record added successfully"
- return 0
- fi
-
- _err "Failed to add TXT record: $response"
- return 1
-}
-
-# Usage: dns_firestorm_rm _acme-challenge.www.example.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
-dns_firestorm_rm() {
- fulldomain=$1
- txtvalue=$2
-
- FST_Key="${FST_Key:-$(_readaccountconf_mutable FST_Key)}"
- FST_Secret="${FST_Secret:-$(_readaccountconf_mutable FST_Secret)}"
- FST_Url="${FST_Url:-$(_readaccountconf_mutable FST_Url)}"
- FST_Url="${FST_Url:-$FST_Url_DEFAULT}"
-
- if [ -z "$FST_Key" ] || [ -z "$FST_Secret" ]; then
- _err "FST_Key and FST_Secret must be set"
- return 1
- fi
-
- subdomain=$(printf "%s" "$fulldomain" | sed 's/^_acme-challenge\.//')
-
- _info "Removing TXT record for $fulldomain"
-
- body="{\"subdomain\":\"$(_json_safe "$subdomain")\",\"txt\":\"$(_json_safe "$txtvalue")\"}"
-
- response="$(_firestorm_api "remove" "$body")"
-
- if _contains "$response" "removed"; then
- _info "TXT record removed"
- return 0
- fi
-
- _err "Failed to remove TXT record: $response"
- return 1
-}
-
-#################### Private functions below ##################################
-
-# Escape special characters for safe JSON string interpolation
-_json_safe() {
- printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
-}
-
-_firestorm_api() {
- action=$1
- data=$2
-
- export _H1="X-Api-User: $FST_Key"
- export _H2="X-Api-Key: $FST_Secret"
- export _H3="Content-Type: application/json"
-
- _post "$data" "$FST_Url/$action" "" "POST"
-}
diff --git a/dnsapi/dns_freedns.sh b/dnsapi/dns_freedns.sh
index 8ea86c24..13d9f68b 100755
--- a/dnsapi/dns_freedns.sh
+++ b/dnsapi/dns_freedns.sh
@@ -305,7 +305,7 @@ _freedns_domain_id() {
fi
domain_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's/
/@
/g' | tr '@' '\n' |
- grep -E "
$search_domain
|
$search_domain\(.*\)
" |
+ grep "
$search_domain
\|
$search_domain(.*)
" |
sed -n 's/.*\(edit\.php?edit_domain_id=[0-9a-zA-Z]*\).*/\1/p' |
cut -d = -f 2)"
# The above beauty extracts domain ID from the html page...
diff --git a/dnsapi/dns_freemyip.sh b/dnsapi/dns_freemyip.sh
index 18d8e7f9..d598a657 100644
--- a/dnsapi/dns_freemyip.sh
+++ b/dnsapi/dns_freemyip.sh
@@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_freemyip
Options:
FREEMYIP_Token API Token
Issues: github.com/acmesh-official/acme.sh/issues/6247
-Author: Recolic Keghart , @Giova96, ExtremeFiretop
+Author: Recolic Keghart , @Giova96
'
FREEMYIP_DNS_API="https://freemyip.com/update?"
@@ -68,30 +68,22 @@ dns_freemyip_rm() {
return $?
}
-################ Private functions below ################
+################ Private functions below ################
_get_root() {
_fmi_d="$1"
- echo "$_fmi_d" | sed 's/.*\.\([^.]*\.[^.]*\.[^.]*\)$/\1/'
+ echo "$_fmi_d" | rev | cut -d '.' -f 1-3 | rev
}
# There is random failure while calling freemyip API too fast. This function automatically retry until success.
_freemyip_get_until_ok() {
_fmi_url="$1"
- _fmi_i=1
- while [ "$_fmi_i" -le 8 ]; do
- _debug "HTTP GET freemyip.com API '$_fmi_url', retry $_fmi_i/8..."
- _fmi_response="$(_get "$_fmi_url")"
- printf '%s\n' "$_fmi_response" >&2
-
- if _contains "$_fmi_response" "OK"; then
- return 0
- fi
-
+ for i in $(seq 1 8); do
+ _debug "HTTP GET freemyip.com API '$_fmi_url', retry $i/8..."
+ _get "$_fmi_url" | tee /dev/fd/2 | grep OK && return 0
_sleep 1 # DO NOT send the request too fast
- _fmi_i=$((_fmi_i + 1))
done
- _err "Failed to request freemyip API. Server does not say 'OK'"
+ _err "Failed to request freemyip API: $_fmi_url . Server does not say 'OK'"
return 1
}
@@ -101,16 +93,13 @@ _is_root_domain_published() {
_webroot="$(_get_root "$_fmi_d")"
_info "Verifying '""$_fmi_d""' freemyip webroot (""$_webroot"") is not published yet"
- _fmi_i=1
- while [ "$_fmi_i" -le 3 ]; do
- _debug "'$_webroot' ns lookup, retry $_fmi_i/3..."
-
+ for i in $(seq 1 3); do
+ _debug "'$_webroot' ns lookup, retry $i/3..."
if [ "$(_ns_lookup "$_fmi_d" TXT)" ]; then
_debug "'$_webroot' already has a TXT record published!"
return 0
fi
_sleep 10 # Give it some time to propagate the TXT record
- _fmi_i=$((_fmi_i + 1))
done
return 1
}
diff --git a/dnsapi/dns_gd.sh b/dnsapi/dns_gd.sh
index c92bdfa2..ee66ee19 100755
--- a/dnsapi/dns_gd.sh
+++ b/dnsapi/dns_gd.sh
@@ -69,12 +69,7 @@ dns_gd_add() {
return 1
fi
- if _contains "$response" "UNKNOWN_DOMAIN"; then
- # GoDaddy sometimes returns UNKNOWN_DOMAIN when reading a record back even
- # though the PUT above succeeded; skip the local readback check and let
- # acme.sh's own DNS propagation check verify the record was published.
- _info "GoDaddy API won't allow reading the record back; skipping local verification."
- elif ! _contains "$response" "$txtvalue"; then
+ if ! _contains "$response" "$txtvalue"; then
_err "TXT record '${txtvalue}' for '${fulldomain}', value wasn't set!"
return 1
fi
@@ -150,8 +145,8 @@ dns_gd_rm() {
# _domain=domain.com
_get_root() {
domain=$1
- i=1
- p=0
+ i=2
+ p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
if [ -z "$h" ]; then
@@ -159,41 +154,17 @@ _get_root() {
return 1
fi
- # The record name is whatever precedes the candidate zone. Do not assume
- # _acme-challenge here: with DNS alias mode it can be any name, and the
- # record may even sit at the zone apex (name "@").
- if [ "$p" = "0" ]; then
- _probe_sub="@"
- else
- _probe_sub=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
- fi
-
- # Probe with the records endpoint instead of "GET domains/$h": since
- # 2024-05 GoDaddy rejects the domain details call for accounts with
- # fewer than 10 domains, while record-level calls keep working.
- # https://github.com/acmesh-official/acme.sh/issues/4487
- if ! _gd_rest GET "domains/$h/records/TXT/$_probe_sub"; then
- return 1
- fi
- if _startswith "$response" '\['; then
- _sub_domain="$_probe_sub"
- _domain="$h"
- return 0
- fi
-
- # Some accounts get UNKNOWN_DOMAIN when reading records of a valid zone
- # even though writes succeed (see issue #6517); fall back to the domain
- # details call for them.
if ! _gd_rest GET "domains/$h"; then
return 1
fi
- if _contains "$response" '"domainId"'; then
- _sub_domain="$_probe_sub"
+
+ if _contains "$response" '"code":"NOT_FOUND"'; then
+ _debug "$h not found"
+ else
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain="$h"
return 0
fi
-
- _debug "$h not found"
p="$i"
i=$(_math "$i" + 1)
done
diff --git a/dnsapi/dns_glesys.sh b/dnsapi/dns_glesys.sh
deleted file mode 100644
index 008abd12..00000000
--- a/dnsapi/dns_glesys.sh
+++ /dev/null
@@ -1,263 +0,0 @@
-#!/usr/bin/env sh
-# shellcheck disable=SC2034
-dns_glesys_info='Glesys
-Site: Glesys.se
-Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_glesys
-Options:
- GLESYS_API_KEY Generated API key.
- GLESYS_PROJECT_ID Project ID for the API key (e.g. cl12345).
- GLESYS_API API endpoint. Default "https://api.glesys.com/domain".
- GLESYS_TTL TXT record TTL. Default 120.
-Issues: https://github.com/acmesh-official/acme.sh/issues/7057
-Author: Toni Karppi
-'
-
-GLESYS_API_DEFAULT="https://api.glesys.com/domain"
-GLESYS_TTL_DEFAULT="120"
-
-######## Public functions #####################################################
-
-# Usage:
-# dns_glesys_add _acme-challenge.www.example.com "txt-value"
-dns_glesys_add() {
- fulldomain="$1"
- txtvalue="$2"
-
- _debug fulldomain "$fulldomain"
- _debug txtvalue "$txtvalue"
-
- _glesys_init || return 1
-
- if ! _glesys_get_root "$fulldomain"; then
- _err "Could not find root zone for $fulldomain"
- return 1
- fi
-
- _debug _domain "$_domain"
- _debug _sub_domain "$_sub_domain"
-
- host_value="${_sub_domain:-@}"
- _debug _host_value "$host_value"
-
- data="{\"domainname\":\"$_domain\",\"host\":\"$host_value\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":\"$GLESYS_TTL\"}"
-
- _debug2 data "$data"
-
- if ! _glesys_rest POST "/addrecord" "$data"; then
- _err "Failed to send HTTP request to add TXT record"
- return 1
- fi
-
- response_code=$(
- printf "%s" "$response" |
- tr -d '\r\n\t ' |
- _egrep_o '"code":"?[0-9]+' |
- _egrep_o '[0-9]+$'
- )
-
- _debug response_code "$response_code"
-
- if [ "$response_code" != "200" ]; then
- _err "GleSYS API responded with an unexpected status when attempting to add TXT record"
- _debug2 "API response" "$response"
- return 1
- fi
-
- _info "TXT record added"
-
- return 0
-}
-
-# Usage:
-# dns_glesys_rm _acme-challenge.www.example.com "txt-value"
-dns_glesys_rm() {
- fulldomain="$1"
- txtvalue="$2"
-
- _debug fulldomain "$fulldomain"
- _debug txtvalue "$txtvalue"
-
- _glesys_init || return 1
-
- if ! _glesys_get_root "$fulldomain"; then
- _err "Could not find root zone for $fulldomain"
- return 1
- fi
-
- if ! _glesys_find_record_id "$txtvalue"; then
- _info "TXT record not present, skip removal"
- return 0
- fi
-
- _debug _record_id "$_record_id"
-
- if ! _glesys_rest POST "/deleterecord" "{\"recordid\":$_record_id}"; then
- _err "Failed to send HTTP request to remove TXT record"
- return 1
- fi
-
- response_code=$(
- printf "%s" "$response" |
- tr -d '\r\n\t ' |
- _egrep_o '"code":"?[0-9]+' |
- _egrep_o '[0-9]+$'
- )
-
- _debug response_code "$response_code"
-
- if [ "$response_code" != "200" ]; then
- _err "GleSYS API responded with unexpected status when attempting to remove TXT record"
- _debug2 "API response" "$response"
- return 1
- fi
-
- _info "TXT record removed"
-
- return 0
-}
-
-######## Private functions ####################################################
-
-_glesys_find_record_id() {
- txtvalue="$1"
-
- _debug txtvalue "$txtvalue"
-
- if [ -z "$txtvalue" ]; then
- return 1
- fi
-
- _record_id=""
-
- _debug "Looking for TXT record with value" "$txtvalue"
-
- if ! _glesys_rest GET "/listrecords?domainname=$_domain"; then
- _err "Failed to list DNS records"
- return 1
- fi
-
- records="$(
- printf "%s" "$response" |
- tr -d '\r\n\t ' |
- sed 's/},{/}\
-{/g'
- )"
-
- _debug2 records "$records"
-
- expected_data="\"data\":\"$txtvalue\""
-
- _record_id="$(
- printf "%s\n" "$records" |
- while IFS= read -r record; do
- printf "%s" "$record" | grep -q '"type":"TXT"' || continue
- printf "%s" "$record" | grep -Fq "$expected_data" || continue
-
- printf "%s" "$record" |
- grep -E -o '"recordid":"?[0-9]+' |
- grep -E -o '[0-9]+$'
-
- break
- done
- )"
-
- _debug _record_id "$_record_id"
-
- if [ -z "$_record_id" ]; then
- return 1
- fi
-
- return 0
-}
-
-# Finds:
-# _domain example.com
-# _sub_domain _acme-challenge.www
-_glesys_get_root() {
- domain="$1"
- i=1
-
- while true; do
- h="$(printf "%s" "$domain" | cut -d . -f "$i"-100)"
-
- if [ -z "$h" ]; then
- return 1
- fi
-
- if _glesys_rest GET "/listrecords?domainname=$h"; then
- response_code=$(
- printf "%s" "$response" |
- tr -d '\r\n\t ' |
- _egrep_o '"code":"?[0-9]+' |
- _egrep_o '[0-9]+$'
- )
-
- _debug response_code "$response_code"
-
- if [ "$response_code" = "200" ]; then
- cut_len="$((${#domain} - ${#h} - 1))"
- _domain="$h"
- _sub_domain="$(printf "%s" "$domain" | cut -c "1-$cut_len")"
- return 0
- fi
- fi
-
- i="$((i + 1))"
- done
-}
-
-_glesys_init() {
- [ -z "$GLESYS_API" ] && GLESYS_API="$GLESYS_API_DEFAULT"
- [ -z "$GLESYS_TTL" ] && GLESYS_TTL="$GLESYS_TTL_DEFAULT"
-
- _debug GLESYS_API "$GLESYS_API"
- _debug GLESYS_TTL "$GLESYS_TTL"
-
- GLESYS_API_KEY="${GLESYS_API_KEY:-$(_readaccountconf_mutable GLESYS_API_KEY)}"
- GLESYS_PROJECT_ID="${GLESYS_PROJECT_ID:-$(_readaccountconf_mutable GLESYS_PROJECT_ID)}"
-
- if [ -z "$GLESYS_API_KEY" ] || [ -z "$GLESYS_PROJECT_ID" ]; then
- _err "GLESYS_API_KEY and GLESYS_PROJECT_ID must be set for this provider"
- return 1
- fi
-
- _secure_debug GLESYS_API_KEY "$GLESYS_API_KEY"
- _secure_debug GLESYS_PROJECT_ID "$GLESYS_PROJECT_ID"
-
- _glesys_basic_auth="$(printf "%s:%s" "$GLESYS_PROJECT_ID" "$GLESYS_API_KEY" | _base64)"
- _secure_debug2 _glesys_basic_auth "$_glesys_basic_auth"
-
- _saveaccountconf_mutable GLESYS_API_KEY "$GLESYS_API_KEY"
- _saveaccountconf_mutable GLESYS_PROJECT_ID "$GLESYS_PROJECT_ID"
-
- return 0
-}
-
-_glesys_rest() {
- method="$1"
- path="$2"
- data="$3"
-
- export _H1="Authorization: Basic $_glesys_basic_auth"
- export _H2="Content-Type: application/json"
- export _H3="Accept: application/json"
-
- url="$GLESYS_API$path"
- _debug "$method $url"
-
- if [ "$method" = "GET" ]; then
- response="$(_get "$url")"
- else
- response="$(_post "$data" "$url" "" "$method")"
- fi
-
- ret="$?"
- _debug2 response "$response"
- _debug ret "$ret"
-
- if [ "$ret" != "0" ]; then
- return 1
- fi
-
- return 0
-}
diff --git a/dnsapi/dns_hestiacp.sh b/dnsapi/dns_hestiacp.sh
deleted file mode 100644
index 13ee6caf..00000000
--- a/dnsapi/dns_hestiacp.sh
+++ /dev/null
@@ -1,198 +0,0 @@
-#!/usr/bin/env sh
-# shellcheck disable=SC2034
-dns_hestiacp_info='HestiaCP Server API
-Site: hestiacp.com
-Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_hestiacp
-Options:
- HESTIA_HOST Panel URL. E.g. "https://panel.example.com:8083"
- HESTIA_ACCESS API access key
- HESTIA_SECRET API secret key
- HESTIA_USER Username owning the DNS zones. Default "admin". Optional.
-Issues: github.com/acmesh-official/acme.sh/issues/6251
-Author: Radu Malica
-'
-
-######## Public functions #####################
-
-# Usage: dns_hestiacp_add fulldomain txtvalue
-dns_hestiacp_add() {
- fulldomain=$1
- txtvalue=$2
-
- if ! _hestia_init; then
- return 1
- fi
-
- _debug "Detecting the root zone for $fulldomain"
- if ! _hestia_get_root "$fulldomain"; then
- _err "Cannot find a DNS zone for $fulldomain under user $HESTIA_USER"
- return 1
- fi
- _debug _hestia_domain "$_hestia_domain"
- _debug _hestia_sub "$_hestia_sub"
-
- # _hestia_get_root left the zone record listing in _hestia_response
- if _hestia_find_records "$_hestia_sub" "TXT" | grep -F -- "$txtvalue" >/dev/null; then
- _info "The TXT record already exists, skipping"
- return 0
- fi
-
- _info "Adding TXT record for $fulldomain"
- if ! _hestia_rest "v-add-dns-record" "$HESTIA_USER" "$_hestia_domain" "$_hestia_sub" "TXT" "$txtvalue" "" "" "yes" "600"; then
- _err "Error adding TXT record: $_hestia_response"
- return 1
- fi
- _info "TXT record added successfully"
- return 0
-}
-
-# Usage: dns_hestiacp_rm fulldomain txtvalue
-dns_hestiacp_rm() {
- fulldomain=$1
- txtvalue=$2
-
- if ! _hestia_init; then
- return 1
- fi
-
- _debug "Detecting the root zone for $fulldomain"
- if ! _hestia_get_root "$fulldomain"; then
- _err "Cannot find a DNS zone for $fulldomain under user $HESTIA_USER"
- return 1
- fi
- _debug _hestia_domain "$_hestia_domain"
- _debug _hestia_sub "$_hestia_sub"
-
- _hestia_removed=0
- _hestia_failed=0
- while IFS='|' read -r _hestia_id _hestia_value || [ -n "$_hestia_id" ]; do
- if [ -z "$_hestia_id" ]; then
- continue
- fi
- if ! _contains "$_hestia_value" "$txtvalue"; then
- continue
- fi
- _info "Deleting TXT record $_hestia_id"
- if ! _hestia_rest "v-delete-dns-record" "$HESTIA_USER" "$_hestia_domain" "$_hestia_id" "yes"; then
- _err "Error deleting TXT record $_hestia_id: $_hestia_response"
- _hestia_failed=$(_math "$_hestia_failed" + 1)
- continue
- fi
- _hestia_removed=$(_math "$_hestia_removed" + 1)
- done <
-'
-
-HOSTINGER_Api="https://developers.hostinger.com/api/dns/v1/zones"
-
-######## Public functions #####################
-
-#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
-dns_hostinger_add() {
- fulldomain=$1
- txtvalue=$2
-
- HOSTINGER_Token="${HOSTINGER_Token:-$(_readaccountconf_mutable HOSTINGER_Token)}"
-
- if [ -z "$HOSTINGER_Token" ]; then
- HOSTINGER_Token=""
- _err "You didn't specify a Hostinger API Key yet."
- _err "Please read the documentation for the Hostinger API authentication at https://developers.hostinger.com/#description/authentication"
- return 1
- fi
- _saveaccountconf_mutable HOSTINGER_Token "$HOSTINGER_Token"
-
- _debug "First detect the root zone"
- if ! _get_root "$fulldomain"; then
- _err "invalid domain"
- return 1
- fi
- _debug _sub_domain "$_sub_domain"
- _debug _domain "$_domain"
-
- _debug "Getting existing records"
- _hostinger_rest GET "${_domain}"
-
- if [ -z "$response" ]; then
- _err "Error"
- return 1
- fi
-
- # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so
- # we can not use updating anymore.
- # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2)
- # _debug count "$count"
- # if [ "$count" = "0" ]; then
- _info "Adding record"
- if _hostinger_rest PUT "$_domain" "{\"zone\":[{\"name\": \"$_sub_domain\",\"records\": [{\"content\":\"$txtvalue\"}],\"type\":\"TXT\",\"ttl\":\"120\"}],\"overwrite\":false}"; then
- if _contains "$response" "Request accepted"; then
- _info "Added, OK"
- return 0
- elif _contains "$response" "DNS resource record is not valid or conflicts with another resource record" ||
- _contains "$response" 'DNS:4008'; then
- _info "Already exists, OK"
- return 0
- else
- _err "Add txt record error."
- return 1
- fi
- fi
- _err "Add txt record error."
- return 1
-
-}
-
-#fulldomain txtvalue
-dns_hostinger_rm() {
- fulldomain=$1
- txtvalue=$2
-
- HOSTINGER_Token="${HOSTINGER_Token:-$(_readaccountconf_mutable HOSTINGER_Token)}"
-
- if [ -z "$HOSTINGER_Token" ]; then
- HOSTINGER_Token=""
- _err "You didn't specify a Hostinger API Key yet."
- _err "Please read the documentation for the Hostinger API authentication at https://developers.hostinger.com/#description/authentication"
- return 1
- fi
- _saveaccountconf_mutable HOSTINGER_Token "$HOSTINGER_Token"
-
- _debug "First detect the root zone"
- if ! _get_root "$fulldomain"; then
- _err "invalid domain"
- return 1
- fi
- _debug _sub_domain "$_sub_domain"
- _debug _domain "$_domain"
-
- _debug "Getting existing records"
- _hostinger_rest GET "${_domain}"
-
- if [ -z "$response" ]; then
- _err "Error"
- return 1
- fi
-
- if _contains "$response" "\"name\":\"$_sub_domain\""; then
- # Match the record, and make certain it is a TXT record for the domain not another type. Then remove our target record from the list
- remaining_records=$(echo "$response" | _normalizeJson | _egrep_o '{"name":"'"$_sub_domain"'","records":\[[^]]+\],"ttl":[0-9]+,"type":"TXT"\}' | _egrep_o "\[.*\]" | sed -E 's#\{"content":"\\"'"$txtvalue"'\\"","is_disabled":false\},?##g')
- if [ "$remaining_records" != "[]" ]; then
- remaining_json=$(echo "$remaining_records" | _egrep_o '"content":"\\"[^}]+\\""' | sed -E 's/^(.*)$/{\1},/g' | tr -d '\n' | sed 's/,$//')
- # We need to set the remaining records back to Hostinger, as we can't partially delete
- _info "Removing $txtvalue from $_sub_domain by setting records to ${remaining_json}"
- if _hostinger_rest PUT "$_domain" "{\"zone\":[{\"name\": \"$_sub_domain\",\"records\": [${remaining_json}],\"type\":\"TXT\",\"ttl\":\"120\"}],\"overwrite\":true}"; then
- if _contains "$response" "Request accepted"; then
- _info "Updated remaining records, OK"
- return 0
- elif _contains "$response" "DNS resource record is not valid or conflicts with another resource record" ||
- _contains "$response" 'DNS:4008'; then
- _info "Already exists, OK"
- return 0
- else
- _err "Add txt record error."
- return 1
- fi
- fi
- # Otherwise delete the TXT record that matches the subdomain
- else
- if ! _hostinger_rest DELETE "$_domain" "{\"filters\":[{\"name\":\"$_sub_domain\",\"type\":\"TXT\"}]}"; then
- _err "Delete record error."
- return 1
- fi
- fi
- echo "$response" | grep "Request accepted" >/dev/null
- else
- _info "Don't need to remove."
- fi
-
-}
-
-#################### Private functions below ##################################
-#_acme-challenge.www.domain.com
-#returns
-# _sub_domain=_acme-challenge.www
-# _domain=domain.com
-_get_root() {
- domain=$1
- i=1
- p=1
-
- while true; do
- h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
- _debug h "$h"
- if [ -z "$h" ]; then
- #not valid
- return 1
- fi
-
- _hostinger_rest GET "$h"
- if _contains "$response" "records"; then
- if [ "$response" = "[]" ]; then
- _debug "Valid subdomains are not the root"
- else
- _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
- _domain=$h
- return 0
- fi
- fi
-
- p=$i
- i=$(_math "$i" + 1)
- done
- return 1
-}
-
-_hostinger_rest() {
- m=$1
- ep="$2"
- data="$3"
- _debug "$ep"
-
- token_trimmed=$(echo "$HOSTINGER_Token" | tr -d '"')
-
- export _H1="Content-Type: application/json"
- export _H2="Authorization: Bearer $token_trimmed"
-
- if [ "$m" != "GET" ]; then
- _debug data "$data"
- response="$(_post "$data" "$HOSTINGER_Api/$ep" "" "$m")"
- else
- response="$(_get "$HOSTINGER_Api/$ep")"
- fi
-
- if [ "$?" != "0" ]; then
- _err "error $ep"
- return 1
- fi
- _debug2 response "$response"
- return 0
-}
diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh
index a3d9174a..b3211069 100644
--- a/dnsapi/dns_hostup.sh
+++ b/dnsapi/dns_hostup.sh
@@ -6,13 +6,13 @@ Site: hostup.se
Docs: https://developer.hostup.se/
Options:
HOSTUP_API_KEY Required. HostUp API key with read:dns + write:dns + read:domains scopes.
- HOSTUP_API_BASE Optional. Override API base URL (default: https://cloud.hostup.se/api/v2).
+ HOSTUP_API_BASE Optional. Override API base URL (default: https://cloud.hostup.se/api).
HOSTUP_TTL Optional. TTL for TXT records (default: 60 seconds).
- HOSTUP_ZONE_ID Optional. Force a specific v2 zone ID (zone_...) and skip auto-detection.
+ HOSTUP_ZONE_ID Optional. Force a specific zone ID (skip auto-detection).
Author: HostUp (https://cloud.hostup.se/contact/en)
'
-HOSTUP_API_BASE_DEFAULT="https://cloud.hostup.se/api/v2"
+HOSTUP_API_BASE_DEFAULT="https://cloud.hostup.se/api"
HOSTUP_DEFAULT_TTL=60
# Public: add TXT record
@@ -20,7 +20,6 @@ HOSTUP_DEFAULT_TTL=60
dns_hostup_add() {
fulldomain="$1"
txtvalue="$2"
- hostup_add_txtvalue="$2"
_info "Using HostUp DNS API"
@@ -35,36 +34,33 @@ dns_hostup_add() {
record_name="$(_hostup_record_name "$fulldomain" "$HOSTUP_ZONE_DOMAIN")"
record_name="$(_hostup_sanitize_name "$record_name")"
- hostup_add_record_value="$(_hostup_json_escape "$hostup_add_txtvalue")"
+ record_value="$(_hostup_json_escape "$txtvalue")"
- raw_ttl="${HOSTUP_TTL:-$HOSTUP_DEFAULT_TTL}"
- ttl="$(_hostup_normalize_ttl "$raw_ttl")"
- if [ -z "$ttl" ]; then
- _err "HOSTUP_TTL must be a whole number between 60 and 86400 seconds."
- return 1
- fi
- if [ -n "$HOSTUP_TTL" ]; then
- HOSTUP_TTL="$ttl"
- _saveaccountconf_mutable HOSTUP_TTL "$HOSTUP_TTL"
- fi
+ ttl="${HOSTUP_TTL:-$HOSTUP_DEFAULT_TTL}"
_debug "zone_id" "$HOSTUP_ZONE_ID"
_debug "zone_domain" "$HOSTUP_ZONE_DOMAIN"
_debug "record_name" "$record_name"
_debug "ttl" "$ttl"
- record_name_fqdn="$(_hostup_fqdn "$fulldomain")"
- if _hostup_find_record "$HOSTUP_ZONE_ID" "$record_name_fqdn" "$hostup_add_txtvalue"; then
- _info "TXT record already exists for $fulldomain"
- return 0
+ request_body="{\"name\":\"$record_name\",\"type\":\"TXT\",\"value\":\"$record_value\",\"ttl\":$ttl}"
+
+ if ! _hostup_rest "POST" "/dns/zones/$HOSTUP_ZONE_ID/records" "$request_body"; then
+ return 1
fi
- request_body="{\"name\":\"$record_name\",\"type\":\"TXT\",\"value\":\"$hostup_add_record_value\",\"ttl\":$ttl}"
-
- if ! _hostup_rest "POST" "/dns-zones/$HOSTUP_ZONE_ID/records" "$request_body"; then
+ if ! _contains "$_hostup_response" '"success":true'; then
+ _err "HostUp DNS API: failed to create TXT record for $fulldomain"
+ _debug2 "_hostup_response" "$_hostup_response"
return 1
fi
+ record_id="$(_hostup_extract_record_id "$_hostup_response")"
+ if [ -n "$record_id" ]; then
+ _hostup_save_record_id "$HOSTUP_ZONE_ID" "$fulldomain" "$record_id"
+ _debug "hostup_saved_record_id" "$record_id"
+ fi
+
_info "Added TXT record for $fulldomain"
return 0
}
@@ -89,9 +85,20 @@ dns_hostup_rm() {
record_name_fqdn="$(_hostup_fqdn "$fulldomain")"
record_value="$txtvalue"
+ record_id_cached="$(_hostup_get_saved_record_id "$HOSTUP_ZONE_ID" "$fulldomain")"
+ if [ -n "$record_id_cached" ]; then
+ _debug "hostup_record_id_cached" "$record_id_cached"
+ if _hostup_delete_record_by_id "$HOSTUP_ZONE_ID" "$record_id_cached"; then
+ _info "Deleted TXT record $record_id_cached"
+ _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain"
+ HOSTUP_ZONE_ID=""
+ return 0
+ fi
+ fi
+
if ! _hostup_find_record "$HOSTUP_ZONE_ID" "$record_name_fqdn" "$record_value"; then
_info "TXT record not found for $record_name_fqdn. Skipping removal."
- _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain" "$record_value"
+ _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain"
return 0
fi
@@ -102,7 +109,7 @@ dns_hostup_rm() {
fi
_info "Deleted TXT record $HOSTUP_RECORD_ID"
- _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain" "$record_value"
+ _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain"
HOSTUP_ZONE_ID=""
return 0
}
@@ -120,18 +127,21 @@ _hostup_init() {
if [ -z "$HOSTUP_API_BASE" ]; then
HOSTUP_API_BASE="$HOSTUP_API_BASE_DEFAULT"
fi
- HOSTUP_API_BASE="$(_hostup_normalize_api_base "$HOSTUP_API_BASE")"
if [ -z "$HOSTUP_API_KEY" ]; then
HOSTUP_API_KEY=""
_err "HOSTUP_API_KEY is not set."
- _err "Please export your HostUp API key with read:dns, write:dns, and read:domains scopes."
+ _err "Please export your HostUp API key with read:dns and write:dns scopes."
return 1
fi
_saveaccountconf_mutable HOSTUP_API_KEY "$HOSTUP_API_KEY"
_saveaccountconf_mutable HOSTUP_API_BASE "$HOSTUP_API_BASE"
+ if [ -n "$HOSTUP_TTL" ]; then
+ _saveaccountconf_mutable HOSTUP_TTL "$HOSTUP_TTL"
+ fi
+
if [ -n "$HOSTUP_ZONE_ID" ]; then
_saveaccountconf_mutable HOSTUP_ZONE_ID "$HOSTUP_ZONE_ID"
fi
@@ -139,80 +149,11 @@ _hostup_init() {
return 0
}
-_hostup_normalize_api_base() {
- api_base="${1%/}"
-
- case "$api_base" in
- */api/v2)
- printf "%s" "$api_base"
- ;;
- */api)
- printf "%s/v2" "$api_base"
- ;;
- *)
- printf "%s" "$api_base"
- ;;
- esac
-}
-
-_hostup_normalize_ttl() {
- ttl_value="$1"
-
- case "$ttl_value" in
- "" | *[!0-9]*)
- return 1
- ;;
- esac
-
- while [ "${ttl_value#0}" != "$ttl_value" ]; do
- ttl_value="${ttl_value#0}"
- done
- [ -z "$ttl_value" ] && ttl_value=0
-
- case "$ttl_value" in
- ??????*)
- return 1
- ;;
- esac
-
- if [ "$ttl_value" -lt 60 ] || [ "$ttl_value" -gt 86400 ]; then
- return 1
- fi
-
- printf "%s" "$ttl_value"
-}
-
-_hostup_domain_in_zone() {
- host="$(printf "%s" "${1%.}" | _lower_case)"
- zone="$(printf "%s" "${2%.}" | _lower_case)"
-
- if [ -z "$host" ] || [ -z "$zone" ]; then
- return 1
- fi
-
- if [ "$host" = "$zone" ]; then
- return 0
- fi
-
- case "$host" in
- *."$zone")
- return 0
- ;;
- esac
-
- return 1
-}
-
_hostup_detect_zone() {
fulldomain="$1"
if [ -n "$HOSTUP_ZONE_ID" ] && [ -n "$HOSTUP_ZONE_DOMAIN" ]; then
- if _hostup_domain_in_zone "$fulldomain" "$HOSTUP_ZONE_DOMAIN"; then
- return 0
- fi
- _debug "hostup_cached_zone_mismatch" "$HOSTUP_ZONE_DOMAIN"
- HOSTUP_ZONE_ID=""
- HOSTUP_ZONE_DOMAIN=""
+ return 0
fi
HOSTUP_ZONE_DOMAIN=""
@@ -221,16 +162,16 @@ _hostup_detect_zone() {
if [ -n "$HOSTUP_ZONE_ID" ] && [ -z "$HOSTUP_ZONE_DOMAIN" ]; then
# Attempt to fetch domain name for provided zone ID
if _hostup_fetch_zone_details "$HOSTUP_ZONE_ID"; then
- if _hostup_domain_in_zone "$fulldomain" "$HOSTUP_ZONE_DOMAIN"; then
- return 0
- fi
- _debug "hostup_forced_zone_mismatch" "$HOSTUP_ZONE_DOMAIN"
+ return 0
fi
HOSTUP_ZONE_ID=""
- HOSTUP_ZONE_DOMAIN=""
fi
- _domain_candidate="$(printf "%s" "${fulldomain%.}" | _lower_case)"
+ if ! _hostup_load_zones; then
+ return 1
+ fi
+
+ _domain_candidate="$(printf "%s" "$fulldomain" | _lower_case)"
_debug "hostup_initial_candidate" "$_domain_candidate"
while [ -n "$_domain_candidate" ]; do
@@ -299,11 +240,11 @@ _hostup_fqdn() {
_hostup_fetch_zone_details() {
zone_id="$1"
- if ! _hostup_rest "GET" "/dns-zones/$zone_id/records" ""; then
+ if ! _hostup_rest "GET" "/dns/zones/$zone_id/records" ""; then
return 1
fi
- zonedomain="$(_hostup_json_extract "name" "$_hostup_response")"
+ zonedomain="$(printf "%s" "$_hostup_response" | _egrep_o '"domain":"[^"]*"' | sed -n '1p' | cut -d ':' -f 2 | tr -d '"')"
if [ -n "$zonedomain" ]; then
HOSTUP_ZONE_DOMAIN="$zonedomain"
return 0
@@ -313,7 +254,7 @@ _hostup_fetch_zone_details() {
}
_hostup_load_zones() {
- if ! _hostup_rest "GET" "/dns-zones?limit=1000" ""; then
+ if ! _hostup_rest "GET" "/dns/zones" ""; then
return 1
fi
@@ -322,9 +263,9 @@ _hostup_load_zones() {
while IFS= read -r line; do
case "$line" in
- *'"id"'*'"name"'*)
- zone_id="$(_hostup_json_extract "id" "$line")"
- zone_domain="$(_hostup_json_extract "name" "$line")"
+ *'"domain_id"'*'"domain"'*)
+ zone_id="$(printf "%s" "$line" | _hostup_json_extract "domain_id")"
+ zone_domain="$(printf "%s" "$line" | _hostup_json_extract "domain")"
if [ -n "$zone_id" ] && [ -n "$zone_domain" ]; then
HOSTUP_ZONES_CACHE="${HOSTUP_ZONES_CACHE}${zone_domain}|${zone_id}
"
@@ -349,30 +290,9 @@ _hostup_lookup_zone() {
_lookup_zone_id=""
_lookup_zone_domain=""
- encoded_domain="$(printf "%s" "$lookup_domain" | _url_encode)"
- if _hostup_rest "GET" "/dns-zones?name=$encoded_domain&limit=1" ""; then
- zone_id="$(_hostup_json_extract "id" "$_hostup_response")"
- zone_domain="$(_hostup_json_extract "name" "$_hostup_response")"
- if [ -n "$zone_id" ] && [ -n "$zone_domain" ]; then
- zone_domain_lower="$(printf "%s" "$zone_domain" | _lower_case)"
- if [ "$zone_domain_lower" = "$lookup_domain" ]; then
- _lookup_zone_domain="$zone_domain"
- _lookup_zone_id="$zone_id"
- HOSTUP_ZONE_DOMAIN="$zone_domain"
- HOSTUP_ZONE_ID="$zone_id"
- return 0
- fi
- fi
- fi
-
- if [ -z "$HOSTUP_ZONES_CACHE" ] && ! _hostup_load_zones; then
- return 1
- fi
-
while IFS='|' read -r domain zone_id; do
[ -z "$domain" ] && continue
- domain_lower="$(printf "%s" "$domain" | _lower_case)"
- if [ "$domain_lower" = "$lookup_domain" ]; then
+ if [ "$domain" = "$lookup_domain" ]; then
_lookup_zone_domain="$domain"
_lookup_zone_id="$zone_id"
HOSTUP_ZONE_DOMAIN="$domain"
@@ -387,50 +307,50 @@ EOF
}
_hostup_find_record() {
- _hostup_find_zone_id="$1"
- _hostup_find_fqdn="$2"
- _hostup_find_txtvalue="$3"
+ zone_id="$1"
+ fqdn="$2"
+ txtvalue="$3"
- _hostup_find_encoded_name="$(printf "%s" "$_hostup_find_fqdn" | _url_encode)"
- if ! _hostup_rest "GET" "/dns-zones/$_hostup_find_zone_id/records?type=TXT&name=$_hostup_find_encoded_name" ""; then
+ if ! _hostup_rest "GET" "/dns/zones/$zone_id/records" ""; then
return 1
fi
HOSTUP_RECORD_ID=""
- _hostup_find_records="$(printf "%s" "$_hostup_response" | tr '{' '\n')"
+ records="$(printf "%s" "$_hostup_response" | tr '{' '\n')"
- while IFS= read -r _hostup_find_line; do
+ while IFS= read -r line; do
# Normalize line to make TXT value matching reliable
- _hostup_find_line_clean="$(printf "%s" "$_hostup_find_line" | tr -d '\r\n')"
- _hostup_find_line_value_clean="$(printf "%s" "$_hostup_find_line_clean" | sed 's/\\"//g')"
+ line_clean="$(printf "%s" "$line" | tr -d '\r\n')"
+ line_value_clean="$(printf "%s" "$line_clean" | sed 's/\\"//g')"
- _hostup_find_record_type="$(_hostup_json_extract "type" "$_hostup_find_line_clean")"
- [ "$_hostup_find_record_type" != "TXT" ] && continue
+ case "$line_clean" in
+ *'"type":"TXT"'*'"name"'*'"value"'*)
+ name_value="$(_hostup_json_extract "name" "$line_clean")"
+ record_value="$(_hostup_json_extract "value" "$line_value_clean")"
- _hostup_find_name_value="$(_hostup_json_extract "name" "$_hostup_find_line_clean")"
- _hostup_find_record_value="$(_hostup_json_extract "value" "$_hostup_find_line_value_clean")"
-
- _debug "hostup_record_raw" "$_hostup_find_record_value"
- if [ "${_hostup_find_record_value#\"}" != "$_hostup_find_record_value" ] && [ "${_hostup_find_record_value%\"}" != "$_hostup_find_record_value" ]; then
- _hostup_find_record_value="${_hostup_find_record_value#\"}"
- _hostup_find_record_value="${_hostup_find_record_value%\"}"
- fi
- if [ "${_hostup_find_record_value#\'}" != "$_hostup_find_record_value" ] && [ "${_hostup_find_record_value%\'}" != "$_hostup_find_record_value" ]; then
- _hostup_find_record_value="${_hostup_find_record_value#\'}"
- _hostup_find_record_value="${_hostup_find_record_value%\'}"
- fi
- _hostup_find_record_value="$(printf "%s" "$_hostup_find_record_value" | tr -d '\r\n')"
- _debug "hostup_record_value" "$_hostup_find_record_value"
-
- if [ "$_hostup_find_name_value" = "$_hostup_find_fqdn" ] && [ "$_hostup_find_record_value" = "$_hostup_find_txtvalue" ]; then
- _hostup_find_record_id="$(_hostup_json_extract "id" "$_hostup_find_line_clean")"
- if [ -n "$_hostup_find_record_id" ]; then
- HOSTUP_RECORD_ID="$_hostup_find_record_id"
- return 0
+ _debug "hostup_record_raw" "$record_value"
+ if [ "${record_value#\"}" != "$record_value" ] && [ "${record_value%\"}" != "$record_value" ]; then
+ record_value="${record_value#\"}"
+ record_value="${record_value%\"}"
fi
- fi
+ if [ "${record_value#\'}" != "$record_value" ] && [ "${record_value%\'}" != "$record_value" ]; then
+ record_value="${record_value#\'}"
+ record_value="${record_value%\'}"
+ fi
+ record_value="$(printf "%s" "$record_value" | tr -d '\r\n')"
+ _debug "hostup_record_value" "$record_value"
+
+ if [ "$name_value" = "$fqdn" ] && [ "$record_value" = "$txtvalue" ]; then
+ record_id="$(_hostup_json_extract "id" "$line_clean")"
+ if [ -n "$record_id" ]; then
+ HOSTUP_RECORD_ID="$record_id"
+ return 0
+ fi
+ fi
+ ;;
+ esac
done <berlight.de" (the 'g' from
- # "" plus '.' matching '>'). See issue #5129.
- _hregex=$(printf "%s" "$h" | sed 's/\./\\./g')
- if _contains "$response" "$_hregex"; then
+ if _contains "$response" "$h"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain="$h"
return 0
fi
- # IDN fallback: INWX returns Unicode zone names; when $h is ACE/punycode,
- # encode each zone name via _idn() and compare -- no python dependency.
- if _contains "$h" "xn--"; then
- _zone_unicode=$(printf "%s" "$response" | _egrep_o '[^<]*' |
- sed 's/<[^>]*>//g' | while IFS= read -r _z; do
- if [ "$(_idn "$_z")" = "$h" ]; then
- printf "%s" "$_z"
- break
- fi
- done)
- if [ -n "$_zone_unicode" ]; then
- _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
- _domain="$_zone_unicode"
- return 0
- fi
- fi
p=$i
i=$(_math "$i" + 1)
done
diff --git a/dnsapi/dns_ionos.sh b/dnsapi/dns_ionos.sh
index 00662e82..9a464253 100755
--- a/dnsapi/dns_ionos.sh
+++ b/dnsapi/dns_ionos.sh
@@ -16,7 +16,7 @@ IONOS_TXT_TTL=60 # minimum accepted by API
IONOS_TXT_PRIO=10
dns_ionos_add() {
- fulldomain="$(echo "$1" | _lower_case)"
+ fulldomain=$1
txtvalue=$2
if ! _ionos_init; then
@@ -34,7 +34,7 @@ dns_ionos_add() {
}
dns_ionos_rm() {
- fulldomain="$(echo "$1" | _lower_case)"
+ fulldomain=$1
txtvalue=$2
if ! _ionos_init; then
@@ -146,7 +146,7 @@ _ionos_rest() {
if [ "$method" != "GET" ]; then
export _H2="Accept: application/json"
- export _H3=
+ export _H3="Content-Type: application/json"
_response="$(_post "$data" "$IONOS_API$route" "" "$method" "application/json")"
else
diff --git a/dnsapi/dns_ipprojects.sh b/dnsapi/dns_ipprojects.sh
deleted file mode 100644
index dadd05f0..00000000
--- a/dnsapi/dns_ipprojects.sh
+++ /dev/null
@@ -1,91 +0,0 @@
-#!/usr/bin/env sh
-# shellcheck disable=SC2034
-dns_ipprojects_info='IP-Projects DNS
-Site: ip-projects.de/
-Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_ipprojects
-Options:
- IPP_Apikey API Key
-Issues: github.com/acmesh-official/acme.sh/issues/6958
-Author: Markus Ebner
-'
-
-IPP_Apikey="${IPP_Apikey:-$(_readaccountconf_mutable IPP_Apikey)}"
-IPP_API="https://api.ip-projects.de/v1/dns/acme"
-
-######## Public functions ########
-
-dns_ipprojects_add() {
- fulldomain="$1"
- txtvalue="$2"
-
- _info "Using IP-Projects DNS API to add record"
- _debug fulldomain "$fulldomain"
- _debug txtvalue "$txtvalue"
-
- if ! _IPP_load_credentials; then
- return 1
- fi
-
- _IPP_api_request "add" "$fulldomain" "$txtvalue"
-}
-
-dns_ipprojects_rm() {
- fulldomain="$1"
- txtvalue="$2"
-
- _info "Using IP-Projects DNS API to remove record"
- _debug fulldomain "$fulldomain"
- _debug txtvalue "$txtvalue"
-
- if ! _IPP_load_credentials; then
- return 1
- fi
-
- _IPP_api_request "remove" "$fulldomain" "$txtvalue"
-}
-
-######## Private helpers ########
-
-_IPP_load_credentials() {
- IPP_Apikey="${IPP_Apikey:-$(_readaccountconf_mutable IPP_Apikey)}"
-
- if [ -z "$IPP_Apikey" ]; then
- _err "You must export IPP_Apikey"
- _err "e.g.: export IPP_Apikey=\"your_api_key\""
- return 1
- fi
-
- _saveaccountconf_mutable IPP_Apikey "$IPP_Apikey"
- return 0
-}
-
-_IPP_api_request() {
- action="$1"
- domain="$2"
- value="$3"
-
- url="$IPP_API/$action"
-
- data="{\"domain\":\"$domain\",\"key\":\"$domain\",\"value\":\"$value\"}"
- _debug url "$url"
- _debug data "$data"
- export _H1="X-API-Key: $IPP_Apikey"
-
- response="$(_post "$data" "$url" "" "POST" "application/json")"
- ret="$?"
- _ipprojects_last_http_code=$(grep "^HTTP" "${HTTP_HEADER}" | _tail_n 1 | cut -d " " -f 2 | tr -d '\r\n')
-
- _debug response "$response"
-
- if [ "$ret" != "0" ]; then
- _err "HTTP request failed"
- return 1
- fi
-
- if [ "$_ipprojects_last_http_code" != "200" ]; then
- _err "API returned an error [code: ${_ipprojects_last_http_code}]"
- return 1
- fi
-
- return 0
-}
diff --git a/dnsapi/dns_ispconfig.sh b/dnsapi/dns_ispconfig.sh
index bd6bfb28..edc789e1 100755
--- a/dnsapi/dns_ispconfig.sh
+++ b/dnsapi/dns_ispconfig.sh
@@ -136,7 +136,7 @@ _ISPC_getZoneInfo() {
curResult="$(_post "${curData}" "${ISPC_Api}?client_get_id")"
_debug "Calling _ISPC_ClientGetID: '${curData}' '${ISPC_Api}?client_get_id'"
_debug "Result of _ISPC_ClientGetID: '$curResult'"
- client_id=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2 | cut -d '[' -f 1 | tr -d '{}')
+ client_id=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2 | tr -d '{}')
_debug "Client ID: '${client_id}'"
case "${client_id}" in
'' | *[!0-9]*)
diff --git a/dnsapi/dns_joker.sh b/dnsapi/dns_joker.sh
index 0ad80327..401471be 100644
--- a/dnsapi/dns_joker.sh
+++ b/dnsapi/dns_joker.sh
@@ -35,28 +35,9 @@ dns_joker_add() {
return 1
fi
- # Joker's /nic/replace overwrites all TXT records at the label on every call,
- # and the API is not readable, so accumulate the values locally (keyed by the
- # full record name) and re-send the whole set each time. This is required so a
- # wildcard cert (base + *.domain both validating under the same
- # _acme-challenge label) does not overwrite its own first challenge value.
- _joker_conf_key=$(printf "%s" "JOKER_TXT_${fulldomain}" | tr '.-' '_')
- _joker_values=$(_readdomainconf "$_joker_conf_key")
- if [ -z "$_joker_values" ]; then
- _joker_values="$txtvalue"
- elif ! _contains " $_joker_values " " $txtvalue "; then
- _joker_values="$_joker_values $txtvalue"
- fi
-
- _joker_value_params=""
- for _joker_v in $_joker_values; do
- _joker_value_params="$_joker_value_params&value=$_joker_v"
- done
-
_info "Adding TXT record"
- if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT$_joker_value_params"; then
+ if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT&value=$txtvalue"; then
if _startswith "$response" "OK"; then
- _savedomainconf "$_joker_conf_key" "$_joker_values"
_info "Added, OK"
return 0
fi
@@ -78,36 +59,10 @@ dns_joker_rm() {
return 1
fi
- # Remove only this value from the accumulated set and replace the label with
- # whatever remains (an empty value clears the label's TXT records entirely).
- _joker_conf_key=$(printf "%s" "JOKER_TXT_${fulldomain}" | tr '.-' '_')
- _joker_values=$(_readdomainconf "$_joker_conf_key")
- _joker_remaining=""
- for _joker_v in $_joker_values; do
- if [ "$_joker_v" != "$txtvalue" ]; then
- _joker_remaining="$_joker_remaining $_joker_v"
- fi
- done
- _joker_remaining=$(printf "%s" "$_joker_remaining" | sed 's/^ *//')
-
- _joker_value_params=""
- for _joker_v in $_joker_remaining; do
- _joker_value_params="$_joker_value_params&value=$_joker_v"
- done
- if [ -z "$_joker_value_params" ]; then
- _joker_value_params="&value="
- fi
-
_info "Removing TXT record"
- # TXT record is removed by replacing the label with the remaining values
- # (or an empty value, which clears all TXT records at the label).
- if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT$_joker_value_params"; then
+ # TXT record is removed by setting its value to empty.
+ if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT&value="; then
if _startswith "$response" "OK"; then
- if [ -z "$_joker_remaining" ]; then
- _cleardomainconf "$_joker_conf_key"
- else
- _savedomainconf "$_joker_conf_key" "$_joker_remaining"
- fi
_info "Removed, OK"
return 0
fi
diff --git a/dnsapi/dns_knot.sh b/dnsapi/dns_knot.sh
index 2b6d8ef4..5636804a 100644
--- a/dnsapi/dns_knot.sh
+++ b/dnsapi/dns_knot.sh
@@ -5,8 +5,7 @@ Site: www.knot-dns.cz/docs/2.5/html/man_knsupdate.html
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_knot
Options:
KNOT_SERVER Server hostname. Default: "localhost".
- KNOT_KEY TSIG key data, not a file path. knsupdate "key" statement format: "[alg:]name secret". E.g. "hmac-sha256:acme_key BASE64SECRET="
- KNOT_ZONE Zone name. Optional, set it when the challenge record lives in a delegated subdomain zone. Default: the parent domain of the challenge record.
+ KNOT_KEY File path to TSIG key
'
# See also dns_nsupdate.sh
@@ -22,9 +21,6 @@ dns_knot_add() {
# save the dns server and key to the account.conf file.
_saveaccountconf KNOT_SERVER "${KNOT_SERVER}"
_saveaccountconf KNOT_KEY "${KNOT_KEY}"
- if [ -n "${KNOT_ZONE}" ]; then
- _saveaccountconf KNOT_ZONE "${KNOT_ZONE}"
- fi
if ! _get_root "$fulldomain"; then
_err "Domain does not exist."
@@ -88,13 +84,6 @@ EOF
# _domain=domain.com
_get_root() {
domain=$1
- # a delegated subdomain zone cannot be derived from the record name;
- # let the user name the zone explicitly (issue 2881)
- if [ -n "${KNOT_ZONE}" ]; then
- _domain="${KNOT_ZONE%.}"
- _debug "Using KNOT_ZONE zone" "${_domain}"
- return 0
- fi
i="$(echo "$fulldomain" | tr '.' ' ' | wc -w)"
i=$(_math "$i" - 1)
diff --git a/dnsapi/dns_laodc.sh b/dnsapi/dns_laodc.sh
deleted file mode 100644
index 9f2103b3..00000000
--- a/dnsapi/dns_laodc.sh
+++ /dev/null
@@ -1,197 +0,0 @@
-#!/usr/bin/env sh
-# shellcheck disable=SC2034
-dns_laodc_info='LaoDC DNS API Server
-Site: laodc.com
-Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_laodc
-Options:
- LaoDC_Key API Key
-Issues: github.com/acmesh-official/acme.sh/issues/6973
-Author: @laodc
-'
-
-# Usage:
-# export LaoDC_Key="your-api-key"
-# acme.sh --issue --dns dns_laodc -d example.la -d *.example.la --dnssleep 120
-#
-# The credentials will be saved in ~/.acme.sh/account.conf
-
-LAODC_VER="0.1.2"
-LAODC_API_ENDPOINT="https://dns.laodc.com/v1"
-
-######## Public functions #####################
-
-# Usage: dns_laodc_add _acme-challenge.example.la ZPXvna6tBhq7XQMH7_t2WC2sg0F-BdmtmmpUJiK6Ho
-dns_laodc_add() {
- fulldomain=$1
- txtvalue=$2
-
- _info "Using LaoDC DNS API"
-
- _laodc_validate_key || return 1
-
- _debug "Checking root zone exists for [$fulldomain]"
- if ! _get_root "$fulldomain"; then
- _err "Invalid domain"
- return 1
- fi
-
- domain_hash=$(echo "$response" | _egrep_o "\"hash\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
- _debug _domain "$_domain"
- _debug _sub_domain "$_sub_domain"
- _debug _domain_hash "$domain_hash"
-
- _info "Adding acme record"
- if _laodc_api "POST" "$domain_hash" "$_sub_domain" "$txtvalue"; then
- if [ "$_code" = "201" ]; then
- _info "Added, OK"
- return 0
- else
- _err "Add TXT record error, invalid code. Code: $_code"
- return 1
- fi
- fi
-
- _err "Add TXT record error."
- return 1
-}
-
-dns_laodc_rm() {
- fulldomain=$1
- txtvalue=$2
-
- _laodc_validate_key || return 1
-
- _debug "Checking root zone exists for [$fulldomain]"
- if ! _get_root "$fulldomain"; then
- _err "Invalid domain"
- return 1
- fi
-
- domain_hash=$(echo "$response" | _egrep_o "\"hash\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
- _debug _root_domain "$_domain"
- _debug _sub_domain "$_sub_domain"
- _debug _domain_hash "$domain_hash"
-
- _info "Deleting acme record"
- if _laodc_api "DELETE" "$domain_hash" "$_sub_domain" "$txtvalue"; then
- if [ "$_code" = "204" ]; then
- _info "Deleted, OK"
- return 0
- else
- _err "Delete TXT record error, invalid code. Code: $_code"
- return 1
- fi
- fi
-
- _err "Delete TXT record error."
- return 1
-}
-
-#################### Private functions below ##################################
-# _acme-challenge.www.domain.com
-# returns
-# _domain=domain.com
-# _sub_domain=www
-_get_root() {
- fqdn=$1
- p=1
- i=1
-
- while true; do
- h=$(printf "%s" "$fqdn" | cut -d . -f "$i"-100)
- if [ -z "$h" ]; then
- return 1 # not valid domain
- fi
-
- # Check API if domain exists
- if _laodc_api "GET" "$h"; then
- if [ "$_code" = "200" ]; then
- _domain="$h"
-
- # DNS alias mode - @ is alias for fqdn
- _sub_domain=$(printf "%s" "$fqdn" | cut -d . -f 1-"$p")
- if [ "$i" = "1" ]; then
- _sub_domain="@"
- fi
-
- return 0
- fi
- fi
-
- p="$i"
- i=$(_math "$i" + 1)
- done
-
- return 1
-}
-
-_laodc_validate_key() {
- LaoDC_Key="${LaoDC_Key:-$(_readaccountconf_mutable LaoDC_Key)}"
-
- if [ -z "$LaoDC_Key" ]; then
- LaoDC_Key=""
- _err "You didn't specify a LaoDC API Key yet."
- _err "Please export LaoDC_Key and try again."
- return 1
- fi
-
- # Save the api key to the account conf file.
- _saveaccountconf_mutable LaoDC_Key "$LaoDC_Key"
-}
-
-_laodc_api() {
- method=$1
- domain=$2
- subdomain=$3
- value=$4
-
- export _H1="Content-Type: application/json"
- export _H2="User-Agent: acme.sh/$VER laodc-dns-acme-sh/$LAODC_VER"
- export _H3="Authorization: Bearer $LaoDC_Key"
-
- case $method in
- GET)
- if [ -n "$subdomain" ]; then
- response="$(_get "$LAODC_API_ENDPOINT/$domain/$subdomain?type=TXT")"
- else
- response="$(_get "$LAODC_API_ENDPOINT/$domain")"
- fi
- ;;
- POST)
- # Sanitize value input
- value=$(printf '%s' "$value" | sed 's/\\/\\\\/g; s/"/\\"/g')
- data="{ \"type\": \"TXT\", \"value\": \"$value\", \"ttl\": \"60\" }"
- response="$(_post "$data" "$LAODC_API_ENDPOINT/$domain/$subdomain" "" "POST" "application/json")"
- ;;
- DELETE)
- # Sanitize value input
- value=$(printf '%s' "$value" | sed 's/\\/\\\\/g; s/"/\\"/g')
- data="{ \"type\": \"TXT\", \"value\": \"$value\" }"
- response="$(_post "$data" "$LAODC_API_ENDPOINT/$domain/$subdomain" "" "DELETE" "application/json")"
- ;;
- esac
-
- _ret=$?
-
- # Unset immediately after request to prevent leaks
- export _H1=
- export _H2=
- export _H3=
-
- if [ "$_ret" != "0" ]; then
- _err "Error $domain"
- return 1
- fi
-
- responseHeaders="$(cat "$HTTP_HEADER")"
-
- if echo "$responseHeaders" | grep -i "Content-Type: *application/json" >/dev/null 2>&1; then
- response="$(echo "$response" | _json_decode | _normalizeJson)"
- fi
-
- _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
-
- _debug "http response code $_code"
- _debug response "$response"
- return 0
-}
diff --git a/dnsapi/dns_level27.sh b/dnsapi/dns_level27.sh
deleted file mode 100644
index 3fbaf810..00000000
--- a/dnsapi/dns_level27.sh
+++ /dev/null
@@ -1,197 +0,0 @@
-#!/usr/bin/env sh
-# shellcheck disable=SC2034
-dns_level27_info='Level27
-Site: Level27.be
-Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_level27
-Options:
- LEVEL27_API_KEY API key. Get one from the Level27 control panel (https://app.level27.eu/account/profile/security).
-OptionsAlt:
- LEVEL27_API API base URL. Optional. Default "https://api.level27.eu/v1".
-Issues: github.com/acmesh-official/acme.sh/issues
-Author: Jeroen Moors
-'
-
-LEVEL27_API_DEFAULT="https://api.level27.eu/v1"
-
-######## Public functions #####################
-
-# Usage: dns_level27_add _acme-challenge.www.example.com "TXT-value"
-dns_level27_add() {
- fulldomain="$(_idn "$1")"
- txtvalue="$2"
-
- _info "Using Level27 to add a TXT record for $fulldomain"
-
- if ! _level27_init; then
- return 1
- fi
-
- _debug "First detect the root zone"
- if ! _get_root "$fulldomain"; then
- _err "Could not determine the root zone for $fulldomain at Level27."
- return 1
- fi
- _debug _domain_id "$_domain_id"
- _debug _sub_domain "$_sub_domain"
- _debug _domain "$_domain"
-
- _level27_data="{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\"}"
- if ! _level27_rest POST "domains/$_domain_id/records" "$_level27_data"; then
- _err "Could not add the TXT record."
- return 1
- fi
-
- if _contains "$response" "\"id\":"; then
- _info "TXT record added."
- return 0
- fi
-
- _err "Unexpected response while adding the TXT record."
- return 1
-}
-
-# Usage: dns_level27_rm _acme-challenge.www.example.com "TXT-value"
-dns_level27_rm() {
- fulldomain="$(_idn "$1")"
- txtvalue="$2"
-
- _info "Using Level27 to remove the TXT record for $fulldomain"
-
- if ! _level27_init; then
- return 1
- fi
-
- _debug "First detect the root zone"
- if ! _get_root "$fulldomain"; then
- _err "Could not determine the root zone for $fulldomain at Level27."
- return 1
- fi
- _debug _domain_id "$_domain_id"
- _debug _sub_domain "$_sub_domain"
- _debug _domain "$_domain"
-
- if ! _level27_rest GET "domains/$_domain_id/records?type=TXT"; then
- _err "Could not list the existing TXT records."
- return 1
- fi
-
- _record_id="$(_level27_find_record_id "$response" "$txtvalue")"
- if [ -z "$_record_id" ]; then
- _info "No matching TXT record found; nothing to remove."
- return 0
- fi
- _debug _record_id "$_record_id"
-
- if ! _level27_rest DELETE "domains/$_domain_id/records/$_record_id"; then
- _err "Could not remove the TXT record."
- return 1
- fi
-
- _info "TXT record removed."
- return 0
-}
-
-#################### Private functions below ##################################
-
-# Reads and validates the API credentials and endpoint, and stores them for renewals.
-_level27_init() {
- LEVEL27_API_KEY="${LEVEL27_API_KEY:-$(_readaccountconf_mutable LEVEL27_API_KEY)}"
- if [ -z "$LEVEL27_API_KEY" ]; then
- LEVEL27_API_KEY=""
- _err "You must export the variable LEVEL27_API_KEY before using the Level27 DNS API."
- _err "Get an API key from the Level27 control panel (https://app.level27.eu/account/profile/security)."
- return 1
- fi
- LEVEL27_API_KEY="$(echo "$LEVEL27_API_KEY" | tr -d '"')"
- _saveaccountconf_mutable LEVEL27_API_KEY "$LEVEL27_API_KEY"
-
- LEVEL27_API="${LEVEL27_API:-$(_readaccountconf_mutable LEVEL27_API)}"
- if [ -z "$LEVEL27_API" ]; then
- LEVEL27_API="$LEVEL27_API_DEFAULT"
- fi
- _saveaccountconf_mutable LEVEL27_API "$LEVEL27_API"
-
- # Remove a trailing slash so endpoints can be appended consistently.
- LEVEL27_API="$(echo "$LEVEL27_API" | sed 's#/$##')"
- return 0
-}
-
-# Usage: _get_root _acme-challenge.www.example.com
-# Splits the full domain into the registered zone and the subdomain part.
-# Sets: _domain, _domain_id, _sub_domain
-_get_root() {
- domain=$1
- i=1
- p=1
-
- while true; do
- h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
- _debug h "$h"
- if [ -z "$h" ]; then
- # not valid
- return 1
- fi
-
- if ! _level27_rest GET "domains?filter=$h"; then
- return 1
- fi
-
- _level27_zones="$(echo "$response" | _normalizeJson)"
- if _contains "$_level27_zones" "\"fullname\":\"$h\""; then
- _domain_line="$(echo "$_level27_zones" | sed 's/},{/}\n{/g' | grep "\"fullname\":\"$h\"" | _head_n 1)"
- _domain_id="$(echo "$_domain_line" | _egrep_o '"id":[0-9]*' | _head_n 1 | cut -d : -f 2)"
- if [ "$_domain_id" ]; then
- _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
- _domain=$h
- return 0
- fi
- return 1
- fi
- p=$i
- i=$(_math "$i" + 1)
- done
- return 1
-}
-
-# Usage: _level27_find_record_id "" ""
-# Prints the id of the TXT record whose content matches the value, or nothing.
-_level27_find_record_id() {
- _records="$(echo "$1" | _normalizeJson | sed 's/},{/}\n{/g')"
- _wanted="$2"
- _record_line="$(echo "$_records" | grep "\"content\":\"$_wanted\"" | _head_n 1)"
- if [ -z "$_record_line" ]; then
- # Some APIs store TXT content wrapped in quotes.
- _record_line="$(echo "$_records" | grep "\"content\":\"\\\\\"$_wanted\\\\\"\"" | _head_n 1)"
- fi
- if [ -z "$_record_line" ]; then
- return 0
- fi
- echo "$_record_line" | _egrep_o '"id":[0-9]*' | _head_n 1 | cut -d : -f 2
-}
-
-# Usage: _level27_rest [data]
-# Performs an authenticated API call and stores the body in $response.
-_level27_rest() {
- m="$1"
- ep="$2"
- data="$3"
- _debug "$ep"
-
- export _H1="Authorization: $LEVEL27_API_KEY"
- export _H2="Content-Type: application/json"
- export _H3="Accept: application/json"
-
- if [ "$m" != "GET" ]; then
- _debug2 data "$data"
- response="$(_post "$data" "$LEVEL27_API/$ep" "" "$m")"
- else
- response="$(_get "$LEVEL27_API/$ep")"
- fi
-
- if [ "$?" != "0" ]; then
- _err "Error querying the Level27 API endpoint: $ep"
- return 1
- fi
- _debug2 response "$response"
- return 0
-}
diff --git a/dnsapi/dns_linode.sh b/dnsapi/dns_linode.sh
new file mode 100755
index 00000000..d74d1fc8
--- /dev/null
+++ b/dnsapi/dns_linode.sh
@@ -0,0 +1,189 @@
+#!/usr/bin/env sh
+# shellcheck disable=SC2034
+dns_linode_info='Linode.com (Old)
+ Deprecated. Use dns_linode_v4
+Site: Linode.com
+Options:
+ LINODE_API_KEY API Key
+Author: Philipp Grosswiler
+'
+
+LINODE_API_URL="https://api.linode.com/?api_key=$LINODE_API_KEY&api_action="
+
+######## Public functions #####################
+
+#Usage: dns_linode_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_linode_add() {
+ fulldomain="${1}"
+ txtvalue="${2}"
+
+ if ! _Linode_API; then
+ return 1
+ fi
+
+ _info "Using Linode"
+ _debug "Calling: dns_linode_add() '${fulldomain}' '${txtvalue}'"
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "Domain does not exist."
+ return 1
+ fi
+ _debug _domain_id "$_domain_id"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _parameters="&DomainID=$_domain_id&Type=TXT&Name=$_sub_domain&Target=$txtvalue"
+
+ if _rest GET "domain.resource.create" "$_parameters" && [ -n "$response" ]; then
+ _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1)
+ _debug _resource_id "$_resource_id"
+
+ if [ -z "$_resource_id" ]; then
+ _err "Error adding the domain resource."
+ return 1
+ fi
+
+ _info "Domain resource successfully added."
+ return 0
+ fi
+
+ return 1
+}
+
+#Usage: dns_linode_rm _acme-challenge.www.domain.com
+dns_linode_rm() {
+ fulldomain="${1}"
+
+ if ! _Linode_API; then
+ return 1
+ fi
+
+ _info "Using Linode"
+ _debug "Calling: dns_linode_rm() '${fulldomain}'"
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "Domain does not exist."
+ return 1
+ fi
+ _debug _domain_id "$_domain_id"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _parameters="&DomainID=$_domain_id"
+
+ if _rest GET "domain.resource.list" "$_parameters" && [ -n "$response" ]; then
+ response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")"
+
+ resource="$(echo "$response" | _egrep_o "{.*\"NAME\":\s*\"$_sub_domain\".*}")"
+ if [ "$resource" ]; then
+ _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"RESOURCEID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
+ if [ "$_resource_id" ]; then
+ _debug _resource_id "$_resource_id"
+
+ _parameters="&DomainID=$_domain_id&ResourceID=$_resource_id"
+
+ if _rest GET "domain.resource.delete" "$_parameters" && [ -n "$response" ]; then
+ _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1)
+ _debug _resource_id "$_resource_id"
+
+ if [ -z "$_resource_id" ]; then
+ _err "Error deleting the domain resource."
+ return 1
+ fi
+
+ _info "Domain resource successfully deleted."
+ return 0
+ fi
+ fi
+
+ return 1
+ fi
+
+ return 0
+ fi
+
+ return 1
+}
+
+#################### Private functions below ##################################
+
+_Linode_API() {
+ if [ -z "$LINODE_API_KEY" ]; then
+ LINODE_API_KEY=""
+
+ _err "You didn't specify the Linode API key yet."
+ _err "Please create your key and try again."
+
+ return 1
+ fi
+
+ _saveaccountconf LINODE_API_KEY "$LINODE_API_KEY"
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=12345
+_get_root() {
+ domain=$1
+ i=2
+ p=1
+
+ if _rest GET "domain.list"; then
+ response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")"
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
+ _debug h "$h"
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+
+ hostedzone="$(echo "$response" | _egrep_o "{.*\"DOMAIN\":\s*\"$h\".*}")"
+ if [ "$hostedzone" ]; then
+ _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"DOMAINID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
+ if [ "$_domain_id" ]; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
+ _domain=$h
+ return 0
+ fi
+ return 1
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ fi
+ return 1
+}
+
+#method method action data
+_rest() {
+ mtd="$1"
+ ep="$2"
+ data="$3"
+
+ _debug mtd "$mtd"
+ _debug ep "$ep"
+
+ export _H1="Accept: application/json"
+ export _H2="Content-Type: application/json"
+
+ if [ "$mtd" != "GET" ]; then
+ # both POST and DELETE.
+ _debug data "$data"
+ response="$(_post "$data" "$LINODE_API_URL$ep" "" "$mtd")"
+ else
+ response="$(_get "$LINODE_API_URL$ep$data")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_me.sh b/dnsapi/dns_me.sh
index 0966c5f1..163fe8db 100644
--- a/dnsapi/dns_me.sh
+++ b/dnsapi/dns_me.sh
@@ -140,7 +140,7 @@ _me_rest() {
data="$3"
_debug "$ep"
- cdate=$(LC_ALL=C date -u +"%a, %d %b %Y %T %Z")
+ cdate=$(LANG=C date -u +"%a, %d %b %Y %T %Z")
hmac=$(printf "%s" "$cdate" | _hmac sha1 "$(printf "%s" "$ME_Secret" | _hex_dump | tr -d " ")" hex)
export _H1="x-dnsme-apiKey: $ME_Key"
diff --git a/dnsapi/dns_muumuu.sh b/dnsapi/dns_muumuu.sh
deleted file mode 100755
index 8ef0b8c8..00000000
--- a/dnsapi/dns_muumuu.sh
+++ /dev/null
@@ -1,167 +0,0 @@
-#!/usr/bin/env sh
-# shellcheck disable=SC2034
-dns_muumuu_info='muumuu-domain.com
-Site: muumuu-domain.com
-Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_muumuu
-Options:
- MUUMUU_PAT Personal Access Token (scopes: domains:read, dns:read, dns:write)
-Issues: github.com/acmesh-official/acme.sh/issues/7011
-'
-
-MUUMUU_API="https://muumuu-domain.com/api/v2"
-
-######## Public functions #####################
-
-dns_muumuu_add() {
- fulldomain="$(echo "$1" | _lower_case)"
- txtvalue="$2"
-
- _info "Using muumuu-domain.com DNS API"
- _debug fulldomain "$fulldomain"
- _debug txtvalue "$txtvalue"
-
- MUUMUU_PAT="${MUUMUU_PAT:-$(_readaccountconf_mutable MUUMUU_PAT)}"
- if [ -z "$MUUMUU_PAT" ]; then
- _err "MUUMUU_PAT is not set."
- _err "Please create a Personal Access Token at https://muumuu-domain.com"
- _err "with scopes: domains:read, dns:read, dns:write"
- return 1
- fi
- _saveaccountconf_mutable MUUMUU_PAT "$MUUMUU_PAT"
-
- if ! _muumuu_get_root "$fulldomain"; then
- _err "Unable to find the root domain for $fulldomain"
- return 1
- fi
- _debug _domain_id "$_domain_id"
- _debug _sub_domain "$_sub_domain"
- _debug _domain "$_domain"
-
- _info "Adding TXT record for ${fulldomain}"
- body="{\"fqdn\":\"${fulldomain}.\",\"type\":\"TXT\",\"value\":\"${txtvalue}\",\"ttl\":3600}"
- if _muumuu_rest POST "/me/domains/${_domain_id}/dns-records" "$body"; then
- if [ "$_muumuu_code" = "201" ]; then
- _info "TXT record added successfully"
- return 0
- fi
- fi
-
- _err "Failed to add TXT record (HTTP ${_muumuu_code})"
- return 1
-}
-
-dns_muumuu_rm() {
- fulldomain="$(echo "$1" | _lower_case)"
- txtvalue="$2"
-
- _info "Using muumuu-domain.com DNS API"
- _debug fulldomain "$fulldomain"
- _debug txtvalue "$txtvalue"
-
- MUUMUU_PAT="${MUUMUU_PAT:-$(_readaccountconf_mutable MUUMUU_PAT)}"
- if [ -z "$MUUMUU_PAT" ]; then
- _err "MUUMUU_PAT is not set."
- return 1
- fi
-
- if ! _muumuu_get_root "$fulldomain"; then
- _err "Unable to find the root domain for $fulldomain"
- return 1
- fi
- _debug _domain_id "$_domain_id"
-
- _info "Looking up TXT record for ${fulldomain}"
- if ! _muumuu_rest GET "/me/domains/${_domain_id}/dns-records?type=TXT&fqdn=${fulldomain}."; then
- _err "Failed to list TXT records"
- return 1
- fi
-
- record_id=$(echo "$response" | _egrep_o "\"id\":[0-9]+[^}]*\"value\":\"${txtvalue}\"" | _egrep_o "\"id\":[0-9]+" | _head_n 1 | cut -d: -f2)
- if [ -z "$record_id" ]; then
- _info "TXT record not found, nothing to remove"
- return 0
- fi
- _debug record_id "$record_id"
-
- if _muumuu_rest DELETE "/me/domains/${_domain_id}/dns-records/${record_id}"; then
- if [ "$_muumuu_code" = "204" ]; then
- _info "TXT record deleted successfully"
- return 0
- fi
- fi
-
- _err "Failed to delete TXT record (HTTP ${_muumuu_code})"
- return 1
-}
-
-#################### Private functions below ##################################
-
-# _acme-challenge.www.example.com
-# sets:
-# _domain_id MU00000001
-# _sub_domain _acme-challenge.www
-# _domain example.com
-_muumuu_get_root() {
- domain="$1"
- i=1
- p=0
- h=""
- while true; do
- h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
- if [ -z "$h" ]; then
- return 1
- fi
- if ! _muumuu_rest GET "/me/domains?fqdn=${h}&page-size=1"; then
- return 1
- fi
- if [ "$_muumuu_code" = "401" ] || [ "$_muumuu_code" = "403" ]; then
- _err "Authentication failed (HTTP ${_muumuu_code}). Check MUUMUU_PAT."
- return 1
- fi
- if _contains "$response" "\"fqdn\":\"${h}\""; then
- _domain_id=$(echo "$response" | _egrep_o "\"id\":\"MU[0-9]+\"" | _head_n 1 | cut -d: -f2 | tr -d '"')
- _domain="$h"
- if [ "$p" = "0" ]; then
- _sub_domain=""
- else
- _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
- fi
- return 0
- fi
- p="$i"
- i=$(_math "$i" + 1)
- done
-}
-
-_muumuu_rest() {
- _muumuu_method="$1"
- _muumuu_path="$2"
- _muumuu_data="$3"
- _muumuu_url="${MUUMUU_API}${_muumuu_path}"
-
- export _H1="Authorization: Bearer ${MUUMUU_PAT}"
- export _H2="Content-Type: application/json"
- export _H3="Accept: application/json"
- export _H4=""
- export _H5=""
-
- _secure_debug2 data "$_muumuu_data"
-
- if [ "$_muumuu_method" = "GET" ]; then
- response="$(_get "$_muumuu_url")"
- else
- response="$(_post "$_muumuu_data" "$_muumuu_url" "" "$_muumuu_method")"
- fi
- _muumuu_ret="$?"
- _muumuu_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
- _debug "HTTP code: ${_muumuu_code}"
- _secure_debug2 response "$response"
-
- if [ "$_muumuu_ret" != "0" ]; then
- _err "Error accessing ${_muumuu_url}"
- return 1
- fi
-
- response="$(printf "%s" "$response" | _normalizeJson)"
- return 0
-}
diff --git a/dnsapi/dns_mythic_beasts.sh b/dnsapi/dns_mythic_beasts.sh
index a49ab8ab..1529e1e7 100755
--- a/dnsapi/dns_mythic_beasts.sh
+++ b/dnsapi/dns_mythic_beasts.sh
@@ -186,7 +186,7 @@ _oauth2() {
_oauth2_std() {
# HTTP Basic Authentication
_H1="Authorization: Basic $(echo "$MB_AK:$MB_AS" | _base64)"
- _H2="Accept: application/json"
+ _H2="Accepts: application/json"
export _H1 _H2
body="grant_type=client_credentials"
@@ -210,7 +210,7 @@ _oauth2_std() {
}
_oauth2_github() {
- _H1="Accept: application/json"
+ _H1="Accepts: application/json"
export _H1
body="{\"login\":{\"handle\":\"$MB_AK\",\"pass\":\"$MB_AS\",\"floating\":1}}"
@@ -241,7 +241,7 @@ _mb_rest() {
fi
_H1="Authorization: Bearer $MB_TK"
- _H2="Accept: application/json"
+ _H2="Accepts: application/json"
export _H1 _H2
if [ "$data" ] || [ "$m" = "POST" ] || [ "$m" = "PUT" ] || [ "$m" = "DELETE" ]; then
# body url [needbase64] [POST|PUT|DELETE] [ContentType]
diff --git a/dnsapi/dns_namecheap.sh b/dnsapi/dns_namecheap.sh
index 7035640b..5527b357 100755
--- a/dnsapi/dns_namecheap.sh
+++ b/dnsapi/dns_namecheap.sh
@@ -104,9 +104,6 @@ _get_root_by_getList() {
return 1
fi
- _namecheap_domain_list=$(echo "$response" | _egrep_o ']*')
- _debug2 domain_list "$_namecheap_domain_list"
-
i=2
p=1
@@ -123,7 +120,7 @@ _get_root_by_getList() {
return 1
fi
- if ! _namecheap_is_our_dns "$h"; then
+ if ! _contains "$response" "$h"; then
_debug "$h not found"
else
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
@@ -136,29 +133,6 @@ _get_root_by_getList() {
return 1
}
-#Usage: _namecheap_is_our_dns
-#Succeeds only when domains.getList listed exactly AND that entry is
-#served by Namecheap's own DNS. A domain parked on Namecheap's webhosting DNS
-#is listed with IsOurDNS="false", and every dns.getHosts/setHosts call against
-#it is refused with error 2030288 "not using proper DNS servers". Accepting
-#such a domain as the root zone hides a subdomain that IS delegated to
-#Namecheap DNS and that the getHosts probe below would have found.
-#https://github.com/acmesh-official/acme.sh/issues/7178
-_namecheap_is_our_dns() {
- _namecheap_entry=$(echo "$_namecheap_domain_list" | grep -F " Name=\"$1\"" | _head_n 1)
- if [ -z "$_namecheap_entry" ]; then
- return 1
- fi
-
- _namecheap_ourdns=$(echo "$_namecheap_entry" | _egrep_o ' IsOurDNS="[^"]*' | cut -d '"' -f 2)
- _debug2 "$1 IsOurDNS" "$_namecheap_ourdns"
-
- if [ "$_namecheap_ourdns" = "true" ]; then
- return 0
- fi
- return 1
-}
-
_get_root_by_getHosts() {
i=100
p=99
@@ -290,16 +264,8 @@ _set_namecheap_TXT() {
_debug hosts "$hosts"
if [ -z "$hosts" ]; then
- # An empty host list is only acceptable when the API positively confirms
- # a successful getHosts reply: setHosts below REPLACES all records, so
- # proceeding on a malformed/unparsed response would wipe the whole zone.
- # https://github.com/acmesh-official/acme.sh/issues/6963
- if _contains "$response" "Status=\"OK\"" && _contains "$response" "DomainDNSGetHostsResult"; then
- _debug "No existing host records, adding the TXT record as the first one"
- else
- _err "Hosts not found"
- return 1
- fi
+ _err "Hosts not found"
+ return 1
fi
_namecheap_reset_hostList
diff --git a/dnsapi/dns_namecom.sh b/dnsapi/dns_namecom.sh
index bd7da0c2..1062c849 100755
--- a/dnsapi/dns_namecom.sh
+++ b/dnsapi/dns_namecom.sh
@@ -15,7 +15,7 @@ Namecom_API="https://api.name.com/v4"
#Usage: dns_namecom_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_namecom_add() {
- fulldomain=$(_idn "$1")
+ fulldomain=$1
txtvalue=$2
Namecom_Username="${Namecom_Username:-$(_readaccountconf_mutable Namecom_Username)}"
@@ -68,7 +68,7 @@ dns_namecom_add() {
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_namecom_rm() {
- fulldomain=$(_idn "$1")
+ fulldomain=$1
txtvalue=$2
Namecom_Username="${Namecom_Username:-$(_readaccountconf_mutable Namecom_Username)}"
@@ -153,9 +153,10 @@ _namecom_get_root() {
i=2
p=1
- # Probe each candidate with GetDomain (GET /v4/domains/{domainName}) instead
- # of listing all domains: the list is paginated at 1000 domains per page, so
- # larger accounts never found their domain on the first page.
+ if ! _namecom_rest GET "domains"; then
+ return 1
+ fi
+
# Need to exclude the last field (tld)
numfields=$(echo "$domain" | _egrep_o "\." | wc -l)
while [ "$i" -le "$numfields" ]; do
@@ -165,7 +166,7 @@ _namecom_get_root() {
return 1
fi
- if _namecom_rest GET "domains/$host" && _contains "$response" "\"domainName\":\"$host\""; then
+ if _contains "$response" "$host"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain="$host"
return 0
diff --git a/dnsapi/dns_namesilo.sh b/dnsapi/dns_namesilo.sh
index df5871cf..5d47a59a 100755
--- a/dnsapi/dns_namesilo.sh
+++ b/dnsapi/dns_namesilo.sh
@@ -65,7 +65,7 @@ dns_namesilo_rm() {
if _namesilo_rest GET "dnsListRecords?version=1&type=xml&key=$Namesilo_Key&domain=$_domain"; then
retcode=$(printf "%s\n" "$response" | _egrep_o "300")
if [ "$retcode" ]; then
- _record_id=$(echo "$response" | _egrep_o "([^<]*)TXT$_sub_domain$txtvalue" | _egrep_o "([^<]*)" | sed -r "s/([^<]*)<\/record_id>/\1/" | tail -n 1)
+ _record_id=$(echo "$response" | _egrep_o "([^<]*)TXT$fulldomain" | _egrep_o "([^<]*)" | sed -r "s/([^<]*)<\/record_id>/\1/" | tail -n 1)
_debug _record_id "$_record_id"
if [ "$_record_id" ]; then
_info "Successfully retrieved the record id for ACME challenge."
diff --git a/dnsapi/dns_netcup.sh b/dnsapi/dns_netcup.sh
index 3b291854..8609adf6 100644
--- a/dnsapi/dns_netcup.sh
+++ b/dnsapi/dns_netcup.sh
@@ -33,11 +33,9 @@ dns_netcup_add() {
exit=$(echo "$fulldomain" | tr -dc '.' | wc -c)
exit=$(_math "$exit" + 1)
i=$exit
- _nc_last=$(_nc_lastlevel "$i")
- _nc_found=""
while
- [ "$exit" -ge "$_nc_last" ]
+ [ "$exit" -gt 0 ]
do
tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit")
if [ "$(_math "$i" - "$exit")" -eq 0 ]; then
@@ -53,18 +51,12 @@ dns_netcup_add() {
_err "$msg"
return 1
else
- _nc_found=1
break
fi
fi
fi
exit=$(_math "$exit" - 1)
done
- if [ -z "$_nc_found" ]; then
- _err "$msg"
- _nc_nozone "$fulldomain"
- return 1
- fi
logout
}
@@ -78,11 +70,9 @@ dns_netcup_rm() {
exit=$(_math "$exit" + 1)
i=$exit
rec=""
- _nc_last=$(_nc_lastlevel "$i")
- _nc_found=""
while
- [ "$exit" -ge "$_nc_last" ]
+ [ "$exit" -gt 0 ]
do
tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit")
if [ "$(_math "$i" - "$exit")" -eq 0 ]; then
@@ -99,18 +89,12 @@ dns_netcup_rm() {
_err "$msg"
return 1
else
- _nc_found=1
break
fi
fi
fi
exit=$(_math "$exit" - 1)
done
- if [ -z "$_nc_found" ]; then
- _err "$msg"
- _nc_nozone "$fulldomain"
- return 1
- fi
ida=0000
idv=0001
@@ -141,27 +125,6 @@ dns_netcup_rm() {
logout
}
-# The zone is looked up by walking the challenge name from the right, one
-# label at a time. The leftmost label is the challenge prefix, so the full
-# name itself can never be a zone: asking netcup for it only returns 4013
-# "Validation Error", which would then mask the real 5028 "zone could not be
-# found". Stop one label short, unless the name is too short to have a
-# challenge prefix at all (manual invocation).
-# levels
-_nc_lastlevel() {
- if [ "$1" -ge 3 ]; then
- echo 2
- else
- echo 1
- fi
-}
-
-# fulldomain
-_nc_nozone() {
- _err "No DNS zone for $1 was found at netcup."
- _err "Check that the domain belongs to the account of the configured NC_CID and that its DNS is hosted at netcup."
-}
-
_login() {
tmp=$(_post "{\"action\": \"login\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apipassword\": \"$NC_Apipw\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST")
sid=$(echo "$tmp" | tr '{}' '\n' | grep apisessionid | cut -d '"' -f 4)
diff --git a/dnsapi/dns_nexdns.sh b/dnsapi/dns_nexdns.sh
deleted file mode 100755
index e4447c0e..00000000
--- a/dnsapi/dns_nexdns.sh
+++ /dev/null
@@ -1,244 +0,0 @@
-#!/usr/bin/env sh
-# shellcheck disable=SC2034
-dns_nexdns_info='NexDNS
-Site: nexdns.tech
-Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_nexdns
-Options:
- NEXDNS_Token API token. Can be created at https://nexdns.tech/settings/api-keys
- NEXDNS_Api API base url. Default "https://api.nexdns.tech/v1". Optional.
-Issues: github.com/acmesh-official/acme.sh/issues/7179
-Author: NexDNS
-'
-
-NEXDNS_Api_Default="https://api.nexdns.tech/v1"
-
-######## Public functions #####################
-
-#Usage: dns_nexdns_add _acme-challenge.www.example.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
-dns_nexdns_add() {
- fulldomain=$1
- txtvalue=$2
-
- if ! _nexdns_init; then
- return 1
- fi
-
- _saveaccountconf_mutable NEXDNS_Token "$NEXDNS_Token"
- if [ "$NEXDNS_Api" != "$NEXDNS_Api_Default" ]; then
- _saveaccountconf_mutable NEXDNS_Api "$NEXDNS_Api"
- else
- _clearaccountconf_mutable NEXDNS_Api
- fi
-
- _debug "First detect the root zone"
- if ! _get_root "$fulldomain"; then
- _err "Cannot find the zone of $fulldomain in this NexDNS account."
- return 1
- fi
- _debug _sub_domain "$_sub_domain"
- _debug _domain "$_domain"
- _debug _domain_id "$_domain_id"
-
- _info "Adding the TXT record for $fulldomain"
- if ! _nexdns_rest POST "zones/$_domain_id/records" "{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
- return 1
- fi
-
- _info "The TXT record has been added."
- return 0
-}
-
-#Usage: dns_nexdns_rm _acme-challenge.www.example.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
-dns_nexdns_rm() {
- fulldomain=$1
- txtvalue=$2
-
- if ! _nexdns_init; then
- return 1
- fi
-
- _debug "First detect the root zone"
- if ! _get_root "$fulldomain"; then
- _err "Cannot find the zone of $fulldomain in this NexDNS account."
- return 1
- fi
- _debug _sub_domain "$_sub_domain"
- _debug _domain "$_domain"
- _debug _domain_id "$_domain_id"
-
- _info "Removing the TXT record for $fulldomain"
- if ! _nexdns_rest GET "zones/$_domain_id/records?type=TXT&name=$_sub_domain"; then
- return 1
- fi
-
- #All the challenge records share one name and one type, so the value is the
- #only thing that tells them apart. A certificate covering example.com and
- #*.example.com puts two of them at the same name at the same time.
- _record_id="$(echo "$response" | tr '{' "\n" | grep -- "$txtvalue" | _egrep_o '"id":"[^"]*"' | _head_n 1 | cut -d '"' -f 4)"
- _debug _record_id "$_record_id"
-
- if [ -z "$_record_id" ]; then
- _info "The TXT record is already gone, nothing to remove."
- return 0
- fi
-
- if ! _nexdns_rest DELETE "zones/$_domain_id/records/$_record_id"; then
- return 1
- fi
-
- _info "The TXT record has been removed."
- return 0
-}
-
-#################### Private functions below ##################################
-
-#Reads the token and the api url, and applies the default url.
-_nexdns_init() {
- NEXDNS_Token="${NEXDNS_Token:-$(_readaccountconf_mutable NEXDNS_Token)}"
- NEXDNS_Api="${NEXDNS_Api:-$(_readaccountconf_mutable NEXDNS_Api)}"
-
- if [ -z "$NEXDNS_Token" ]; then
- _err "You have not set NEXDNS_Token yet."
- _err "Create one at https://nexdns.tech/settings/api-keys, on a plan that includes API access, then:"
- _err "export NEXDNS_Token=\"your-api-token\""
- return 1
- fi
-
- if [ -z "$NEXDNS_Api" ]; then
- NEXDNS_Api="$NEXDNS_Api_Default"
- fi
- #A trailing slash would make every request path begin with a double slash.
- NEXDNS_Api="$(echo "$NEXDNS_Api" | sed 's|/*$||')"
- _debug NEXDNS_Api "$NEXDNS_Api"
-
- return 0
-}
-
-#_acme-challenge.www.example.com
-#returns
-# _sub_domain=_acme-challenge.www
-# _domain=example.com
-# _domain_id=Zm9vYmFy
-_get_root() {
- domain=$1
- i=1
- p=1
-
- while true; do
- h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
- _debug h "$h"
- if [ -z "$h" ]; then
- #not valid
- return 1
- fi
-
- if ! _nexdns_rest GET "zones?search=$h&per_page=100"; then
- return 1
- fi
-
- #search matches on a substring, so the page can also hold zones that merely
- #contain h. Take the id of the one whose name is exactly h.
- _domain_id="$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o '"id":"[^"]*"' | _head_n 1 | cut -d '"' -f 4)"
- if [ "$_domain_id" ]; then
- _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
- _domain=$h
- return 0
- fi
-
- p=$i
- i=$(_math "$i" + 1)
- done
-}
-
-#Usage: _nexdns_rest GET|POST|DELETE path [body] [attempt]
-_nexdns_rest() {
- m=$1
- ep=$2
- data=$3
- attempt=${4:-1}
- _debug "$ep"
-
- export _H1="Authorization: Bearer $NEXDNS_Token"
- export _H2="Content-Type: application/json"
- export _H3="Accept: application/json"
-
- if [ "$m" = "GET" ]; then
- response="$(_get "$NEXDNS_Api/$ep")"
- else
- _debug2 data "$data"
- response="$(_post "$data" "$NEXDNS_Api/$ep" "" "$m" "application/json")"
- fi
-
- if [ "$?" != "0" ]; then
- _err "error $ep"
- return 1
- fi
-
- #A single certificate costs a handful of requests, but a renewal sweep over
- #many of them meets the account's per-minute budget, and that run is
- #unattended. Retry-After is treated as a floor: an api may report the time one
- #token needs at an average rate and name a second when nothing frees for a
- #minute, so the wait grows on its own across attempts.
- if [ "$(grep "^HTTP" "$HTTP_HEADER" 2>/dev/null | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" = "429" ]; then
- if [ "$attempt" -ge 4 ]; then
- _err "$m $ep failed: rate limited, and the wait budget is spent"
- return 1
- fi
-
- _retry_after="$(grep -i "^Retry-After" "$HTTP_HEADER" 2>/dev/null | _tail_n 1 | cut -d : -f 2 | tr -d " \r\n")"
- _backoff="$(_math "$attempt" \* 15)"
- #The header may also carry an http date. Anything but a plain count of
- #seconds falls through to the backoff rather than being parsed: guessing
- #wrong about a date is worse than waiting a known interval, and comparing a
- #date numerically would abort the hook outright.
- case "$_retry_after" in
- "" | *[!0-9]*) _retry_after="$_backoff" ;;
- *)
- if [ "$_retry_after" -lt "$_backoff" ]; then
- _retry_after="$_backoff"
- fi
- ;;
- esac
-
- #A wait longer than this is a refusal rather than a schedule, and sleeping
- #it out would hold the hook for the length of the window. Hand the run back
- #instead, so the next cron pass picks it up.
- if [ "$_retry_after" -gt 120 ]; then
- _err "$m $ep failed: rate limited for ${_retry_after}s, longer than this hook will wait"
- return 1
- fi
-
- _info "Rate limited by the NexDNS API; retrying in $_retry_after seconds."
- _sleep "$_retry_after"
-
- _nexdns_rest "$m" "$ep" "$data" "$(_math "$attempt" + 1)"
- return $?
- fi
-
- #Whitespace between a key and its value would defeat every match made on the
- #body, here and in the callers.
- response="$(echo "$response" | _normalizeJson)"
- _debug2 response "$response"
-
- #The status line decides success, not the body: a delete answers 204 with no
- #body at all, and a record whose own content contains "error": would otherwise
- #turn a stored value into a reported failure. The body is read only for the
- #message once the status says the request was rejected.
- _code="$(grep "^HTTP" "$HTTP_HEADER" 2>/dev/null | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
- _debug2 _code "$_code"
- case "$_code" in
- "" | 2*)
- return 0
- ;;
- esac
-
- #A rejected request carries {"error":{"code":..,"message":..}}, so say what the
- #api says went wrong.
- _message="$(echo "$response" | _egrep_o '"message":"[^"]*"' | _head_n 1 | cut -d '"' -f 4)"
- if [ -z "$_message" ]; then
- _message="status $_code"
- fi
- _err "$m $ep failed: $_message"
-
- return 1
-}
diff --git a/dnsapi/dns_njalla.sh b/dnsapi/dns_njalla.sh
index 6ce51380..c410447d 100644
--- a/dnsapi/dns_njalla.sh
+++ b/dnsapi/dns_njalla.sh
@@ -98,7 +98,7 @@ dns_njalla_rm() {
echo "$records" | while read -r record; do
record_name=$(echo "$record" | _egrep_o "\"name\":\s?\"[^\"]*\"" | cut -d : -f 2 | tr -d " " | tr -d \")
record_content=$(echo "$record" | _egrep_o "\"content\":\s?\"[^\"]*\"" | cut -d : -f 2 | tr -d " " | tr -d \")
- record_id=$(echo "$record" | _egrep_o "\"id\":\s?\"?[^\",}]*" | cut -d : -f 2 | tr -d " " | tr -d \")
+ record_id=$(echo "$record" | _egrep_o "\"id\":\s?[0-9]+" | cut -d : -f 2 | tr -d " " | tr -d \")
if [ "$_sub_domain" = "$record_name" ]; then
if [ "$txtvalue" = "$record_content" ]; then
_debug "record_id" "$record_id"
diff --git a/dnsapi/dns_oci.sh b/dnsapi/dns_oci.sh
index e1aa3dd9..c76a4565 100644
--- a/dnsapi/dns_oci.sh
+++ b/dnsapi/dns_oci.sh
@@ -115,15 +115,12 @@ _oci_config() {
_clearaccountconf_mutable OCI_CLI_PROFILE
fi
- if [ -z "$OCI_CLI_TENANCY" ] && [ -f "$OCI_CLI_CONFIG_FILE" ]; then
- _debug "Reading OCI_CLI_TENANCY value from: $OCI_CLI_CONFIG_FILE"
- OCI_CLI_TENANCY=$(_readini "$OCI_CLI_CONFIG_FILE" tenancy "$OCI_CLI_PROFILE")
- fi
- if [ -z "$OCI_CLI_TENANCY" ]; then
- OCI_CLI_TENANCY=$(_readaccountconf_mutable OCI_CLI_TENANCY)
- fi
+ OCI_CLI_TENANCY="${OCI_CLI_TENANCY:-$(_readaccountconf_mutable OCI_CLI_TENANCY)}"
if [ "$OCI_CLI_TENANCY" ]; then
_saveaccountconf_mutable OCI_CLI_TENANCY "$OCI_CLI_TENANCY"
+ elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
+ _debug "Reading OCI_CLI_TENANCY value from: $OCI_CLI_CONFIG_FILE"
+ OCI_CLI_TENANCY="${OCI_CLI_TENANCY:-$(_readini "$OCI_CLI_CONFIG_FILE" tenancy "$OCI_CLI_PROFILE")}"
fi
if [ -z "$OCI_CLI_TENANCY" ]; then
@@ -131,47 +128,41 @@ _oci_config() {
return 1
fi
- if [ -z "$OCI_CLI_USER" ] && [ -f "$OCI_CLI_CONFIG_FILE" ]; then
- _debug "Reading OCI_CLI_USER value from: $OCI_CLI_CONFIG_FILE"
- OCI_CLI_USER=$(_readini "$OCI_CLI_CONFIG_FILE" user "$OCI_CLI_PROFILE")
- fi
- if [ -z "$OCI_CLI_USER" ]; then
- OCI_CLI_USER=$(_readaccountconf_mutable OCI_CLI_USER)
- fi
+ OCI_CLI_USER="${OCI_CLI_USER:-$(_readaccountconf_mutable OCI_CLI_USER)}"
if [ "$OCI_CLI_USER" ]; then
_saveaccountconf_mutable OCI_CLI_USER "$OCI_CLI_USER"
+ elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
+ _debug "Reading OCI_CLI_USER value from: $OCI_CLI_CONFIG_FILE"
+ OCI_CLI_USER="${OCI_CLI_USER:-$(_readini "$OCI_CLI_CONFIG_FILE" user "$OCI_CLI_PROFILE")}"
fi
if [ -z "$OCI_CLI_USER" ]; then
_err "Error: unable to read OCI_CLI_USER from config file or environment variable."
return 1
fi
- if [ -z "$OCI_CLI_REGION" ] && [ -f "$OCI_CLI_CONFIG_FILE" ]; then
- _debug "Reading OCI_CLI_REGION value from: $OCI_CLI_CONFIG_FILE"
- OCI_CLI_REGION=$(_readini "$OCI_CLI_CONFIG_FILE" region "$OCI_CLI_PROFILE")
- fi
- if [ -z "$OCI_CLI_REGION" ]; then
- OCI_CLI_REGION=$(_readaccountconf_mutable OCI_CLI_REGION)
- fi
+ OCI_CLI_REGION="${OCI_CLI_REGION:-$(_readaccountconf_mutable OCI_CLI_REGION)}"
if [ "$OCI_CLI_REGION" ]; then
_saveaccountconf_mutable OCI_CLI_REGION "$OCI_CLI_REGION"
+ elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
+ _debug "Reading OCI_CLI_REGION value from: $OCI_CLI_CONFIG_FILE"
+ OCI_CLI_REGION="${OCI_CLI_REGION:-$(_readini "$OCI_CLI_CONFIG_FILE" region "$OCI_CLI_PROFILE")}"
fi
if [ -z "$OCI_CLI_REGION" ]; then
_err "Error: unable to read OCI_CLI_REGION from config file or environment variable."
return 1
fi
- if [ -z "$OCI_CLI_KEY_FILE" ] && [ -f "$OCI_CLI_CONFIG_FILE" ]; then
- OCI_CLI_KEY_FILE=$(_readini "$OCI_CLI_CONFIG_FILE" key_file "$OCI_CLI_PROFILE")
- fi
- if [ "$OCI_CLI_KEY" ]; then
- _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY"
- elif [ "$OCI_CLI_KEY_FILE" ] && [ -f "$OCI_CLI_KEY_FILE" ]; then
- _debug "Reading OCI_CLI_KEY value from: $OCI_CLI_KEY_FILE"
- OCI_CLI_KEY=$(_base64 <"$OCI_CLI_KEY_FILE")
- _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY"
+ OCI_CLI_KEY="${OCI_CLI_KEY:-$(_readaccountconf_mutable OCI_CLI_KEY)}"
+ if [ -z "$OCI_CLI_KEY" ]; then
+ _clearaccountconf_mutable OCI_CLI_KEY
+ OCI_CLI_KEY_FILE="${OCI_CLI_KEY_FILE:-$(_readini "$OCI_CLI_CONFIG_FILE" key_file "$OCI_CLI_PROFILE")}"
+ if [ "$OCI_CLI_KEY_FILE" ] && [ -f "$OCI_CLI_KEY_FILE" ]; then
+ _debug "Reading OCI_CLI_KEY value from: $OCI_CLI_KEY_FILE"
+ OCI_CLI_KEY=$(_base64 <"$OCI_CLI_KEY_FILE")
+ _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY"
+ fi
else
- OCI_CLI_KEY=$(_readaccountconf_mutable OCI_CLI_KEY)
+ _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY"
fi
if [ -z "$OCI_CLI_KEY_FILE" ] && [ -z "$OCI_CLI_KEY" ]; then
diff --git a/dnsapi/dns_ovh.sh b/dnsapi/dns_ovh.sh
index df2b184d..9f2cd23f 100755
--- a/dnsapi/dns_ovh.sh
+++ b/dnsapi/dns_ovh.sh
@@ -224,7 +224,7 @@ _ovh_authentication() {
_H3=""
_H4=""
- _ovhdata='{"accessRules": [{"method": "GET","path": "/auth/time"},{"method": "GET","path": "/domain"},{"method": "GET","path": "/domain/zone/*"},{"method": "GET","path": "/domain/zone/*/record"},{"method": "GET","path": "/domain/zone/*/record/*"},{"method": "POST","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/refresh"},{"method": "PUT","path": "/domain/zone/*/record/*"},{"method": "DELETE","path": "/domain/zone/*/record/*"}],"redirection":"'$ovh_success'"}'
+ _ovhdata='{"accessRules": [{"method": "GET","path": "/auth/time"},{"method": "GET","path": "/domain"},{"method": "GET","path": "/domain/zone/*"},{"method": "GET","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/refresh"},{"method": "PUT","path": "/domain/zone/*/record/*"},{"method": "DELETE","path": "/domain/zone/*/record/*"}],"redirection":"'$ovh_success'"}'
response="$(_post "$_ovhdata" "$OVH_API/auth/credential")"
_debug3 response "$response"
diff --git a/dnsapi/dns_pdns.sh b/dnsapi/dns_pdns.sh
index 72a58af0..ec19ad25 100755
--- a/dnsapi/dns_pdns.sh
+++ b/dnsapi/dns_pdns.sh
@@ -50,9 +50,6 @@ dns_pdns_add() {
PDNS_Ttl="$DEFAULT_PDNS_TTL"
fi
- # Ensure PDNS_Url has no trailing slash ('/')
- PDNS_Url="${PDNS_Url%/}"
-
#save the api addr and key to the account conf file.
_saveaccountconf_mutable PDNS_Url "$PDNS_Url"
_saveaccountconf_mutable PDNS_ServerId "$PDNS_ServerId"
@@ -189,23 +186,19 @@ _get_root() {
domain=$1
i=1
+ if _pdns_rest "GET" "/api/v1/servers/$PDNS_ServerId/zones"; then
+ _zones_response=$(echo "$response" | _normalizeJson)
+ fi
+
while true; do
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
- # Probe each candidate zone with the server-side name filter instead of
- # listing every zone: with large installations (100k zones) the
- # unfiltered list takes minutes. Servers that ignore the parameter
- # return the full list, which the check below still handles.
- # https://doc.powerdns.com/authoritative/http-api/zone.html
- if _pdns_rest "GET" "/api/v1/servers/$PDNS_ServerId/zones?zone=$h."; then
- _zones_response=$(echo "$response" | _normalizeJson)
- if _contains "$_zones_response" "\"name\":\"$h.\""; then
- _domain="$h."
- if [ -z "$h" ]; then
- _domain="=2E"
- fi
- return 0
+ if _contains "$_zones_response" "\"name\":\"$h.\""; then
+ _domain="$h."
+ if [ -z "$h" ]; then
+ _domain="=2E"
fi
+ return 0
fi
if [ -z "$h" ]; then
diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh
index 176f329d..465bcc60 100644
--- a/dnsapi/dns_pleskxml.sh
+++ b/dnsapi/dns_pleskxml.sh
@@ -151,8 +151,8 @@ dns_pleskxml_rm() {
# Extracting the id of the TXT record for the full domain (NOT case-sensitive) and corresponding value
recid="$(
_value "$reclist" |
- grep -Fi "${fulldomain}." |
- grep -F "${txtvalue}" |
+ grep -i "${fulldomain}." |
+ grep "${txtvalue}" |
sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/'
)"
@@ -419,7 +419,7 @@ _pleskxml_get_root_domain() {
_debug "Checking if '$root_domain_name' is managed by the Plesk server..."
- root_domain_id="$(_value "$output" | grep -F "$root_domain_name" | _head_n 1 | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/')"
+ root_domain_id="$(_value "$output" | grep "$root_domain_name" | _head_n 1 | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/')"
if [ -n "$root_domain_id" ]; then
# Found a match
diff --git a/dnsapi/dns_poweradmin.sh b/dnsapi/dns_poweradmin.sh
deleted file mode 100644
index a4c81835..00000000
--- a/dnsapi/dns_poweradmin.sh
+++ /dev/null
@@ -1,238 +0,0 @@
-#!/usr/bin/env sh
-
-# shellcheck disable=SC2034
-
-# Credits to the authors of dnsapi/dns_pdns.sh as this reuses much of that code.
-
-dns_poweradmin_info='Poweradmin API
-Site: https://www.poweradmin.org/
-Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_poweradmin
-Options:
-POWERADMIN_URL API URL (with scheme). E.g. "https://poweradmin.example.com" or "http://192.168.0.10:8080"
-POWERADMIN_API_KEY API Token "pwa_xxxx"
-POWERADMIN_API_VERSION Optionally override Poweradmin API version.
-Issues: https://github.com/acmesh-official/acme.sh/issues/6912
-Author: Jakob Næss
-'
-
-######## Public functions ####################
-
-# Usage: dns_poweradmin_add _acme-challenge.www.domain.com "123456789ABCDEF"
-# fulldomain
-# txtvalue
-dns_poweradmin_add() {
- fulldomain=$1
- txtvalue=$2
-
- POWERADMIN_URL="${POWERADMIN_URL:-$(_readaccountconf_mutable POWERADMIN_URL)}"
- POWERADMIN_API_KEY="${POWERADMIN_API_KEY:-$(_readaccountconf_mutable POWERADMIN_API_KEY)}"
- POWERADMIN_API_VERSION="${POWERADMIN_API_VERSION:-$(_readaccountconf_mutable POWERADMIN_API_VERSION)}"
- POWERADMIN_API_VERSION="${POWERADMIN_API_VERSION:-2}"
-
- if [ -z "$POWERADMIN_URL" ]; then
- POWERADMIN_URL=""
- _err "You didn't specify Poweradmin URL."
- _err "Please set POWERADMIN_URL and try again."
- return 1
- fi
-
- if [ -z "$POWERADMIN_API_KEY" ]; then
- POWERADMIN_API_KEY=""
- _err "You didn't specify Poweradmin token."
- _err "Please set POWERADMIN_API_KEY and try again."
- return 1
- fi
-
- # Save the api addr, key, and version to the account conf file.
- _saveaccountconf_mutable POWERADMIN_URL "$POWERADMIN_URL"
- _saveaccountconf_mutable POWERADMIN_API_KEY "$POWERADMIN_API_KEY"
- _saveaccountconf_mutable POWERADMIN_API_VERSION "$POWERADMIN_API_VERSION"
-
- _debug "Detect root zone"
- if ! _get_root "$fulldomain"; then
- _err "invalid domain"
- return 1
- fi
-
- _debug _domain "$_domain"
- _debug _zone_id "$_zone_id"
-
- if ! _set_record "$fulldomain" "$txtvalue"; then
- return 1
- fi
-
- return 0
-}
-
-# Usage: dns_poweradmin_rm _acme-challenge.www.domain.com "123456789ABCDEF"
-# fulldomain
-# txtvalue
-dns_poweradmin_rm() {
- fulldomain=$1
- txtvalue=$2
-
- POWERADMIN_URL="${POWERADMIN_URL:-$(_readaccountconf_mutable POWERADMIN_URL)}"
- POWERADMIN_API_KEY="${POWERADMIN_API_KEY:-$(_readaccountconf_mutable POWERADMIN_API_KEY)}"
- POWERADMIN_API_VERSION="${POWERADMIN_API_VERSION:-$(_readaccountconf_mutable POWERADMIN_API_VERSION)}"
- POWERADMIN_API_VERSION="${POWERADMIN_API_VERSION:-2}"
-
- _debug "Detect root zone"
- if ! _get_root "$fulldomain"; then
- _err "invalid domain"
- return 1
- fi
-
- _debug _domain "$_domain"
- _debug _zone_id "$_zone_id"
-
- if ! _rm_record "$fulldomain" "$txtvalue"; then
- return 1
- fi
-
- return 0
-}
-
-######## Private functions below #####################
-
-_set_record() {
- _info "Adding TXT record"
- full=$1
- new_challenge=$2
-
- data='{"name":"'$full'","type":"TXT","content":"'$new_challenge'","ttl":60}'
-
- if ! _poweradmin_rest "POST" "/api/v${POWERADMIN_API_VERSION}/zones/$_zone_id/records" "$data" "application/json"; then
- _err "Failed to add TXT record"
- return 1
- fi
-
- return 0
-}
-
-_rm_record() {
- _info "Remove TXT record"
- full=$1
- txtvalue=$2
-
- if ! _poweradmin_rest "GET" "/api/v${POWERADMIN_API_VERSION}/zones/$_zone_id/records"; then
- _err "Failed to retrieve records"
- return 1
- fi
-
- # The API returns: {"success":true,"data":[{"id":..., "name":"...", "type":"TXT", "content":"...", ...}]}
- _txt_record_obj=$(
- printf '%s\n' "$response" |
- sed 's/^.*"data":\[//; s/\],"message":.*$//' |
- awk '{ gsub(/},{/, "}\n{"); print }' |
- grep -F "\"name\":\"$full\"" |
- grep -F "\"type\":\"TXT\"" |
- grep -F "\"content\":\"$txtvalue\"" |
- _head_n 1
- )
-
- if [ -z "$_txt_record_obj" ]; then
- _info "TXT record not found for $full with content $txtvalue"
- return 0
- fi
-
- record_id=$(printf '%s\n' "$_txt_record_obj" | sed -n 's/.*"id":\([0-9][0-9]*\).*/\1/p' | _head_n 1)
- record_type=$(printf '%s\n' "$_txt_record_obj" | sed -n 's/.*"type":"\([^"]*\)".*/\1/p' | _head_n 1)
- record_name=$(printf '%s\n' "$_txt_record_obj" | sed -n 's/.*"name":"\([^"]*\)".*/\1/p' | _head_n 1)
- record_content=$(printf '%s\n' "$_txt_record_obj" | sed -n 's/.*"content":"\([^"]*\)".*/\1/p' | _head_n 1)
-
- _debug2 "_txt_record_obj=$_txt_record_obj"
- _debug2 "record id: $record_id"
- _debug2 "record type: $record_type"
- _debug2 "record name: $record_name"
- _debug2 "record content: $record_content"
-
- if [ "$record_type" != "TXT" ]; then
- _err "Refusing to delete non-TXT record id=$record_id type=$record_type name=$full"
- return 1
- fi
-
- if ! _poweradmin_rest "DELETE" "/api/v${POWERADMIN_API_VERSION}/zones/$_zone_id/records/$record_id"; then
- _err "Failed to delete TXT record"
- return 1
- fi
-
- _info "Record deleted successfully"
- return 0
-}
-
-# _acme-challenge.www.domain.com
-# returns
-# _domain=domain.com
-# _zone_id=220
-_get_root() {
- domain=$1
- i=1
-
- if ! _poweradmin_rest "GET" "/api/v${POWERADMIN_API_VERSION}/zones"; then
- _err "Failed to retrieve zones"
- return 1
- fi
-
- _zones_response="$response"
-
- while true; do
- h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
-
- if [ -z "$h" ]; then
- _debug "Root domain not found for $domain"
- return 1
- fi
-
- zone_obj=$(
- printf '%s' "$_zones_response" |
- sed 's/},{/}\n{/g' |
- grep -F "\"name\":\"$h\"" |
- _head_n 1
- )
-
- if [ -n "$zone_obj" ]; then
- _zone_id=$(printf '%s' "$zone_obj" | _egrep_o '"id":[0-9][0-9]*' | _head_n 1 | cut -d: -f2)
- _domain="$h"
- _debug "Found zone: $_domain with id: $_zone_id"
- return 0
- fi
-
- i=$(_math "$i" + 1)
- done
-}
-
-_poweradmin_rest() {
- method=$1
- ep=$2
- data=$3
- ct=$4
-
- export _H1="X-API-Key: $POWERADMIN_API_KEY"
-
- if [ "$method" = "GET" ]; then
- response="$(_get "$POWERADMIN_URL$ep")"
- else
- _debug "API call: $method $ep"
- _debug "Content-Type: $ct"
- _debug "Payload: $data"
- response="$(_post "$data" "$POWERADMIN_URL$ep" "" "$method" "$ct")"
- fi
-
- # Clear _H1 variable
- unset -v _H1
-
- if [ "$?" != "0" ]; then
- _err "API error on $method $ep"
- _debug "Response: $response"
- return 1
- fi
-
- if printf '%s' "$response" | grep -q '"success"[ ]*:[ ]*false'; then
- _err "API reported failure on $method $ep"
- _debug "Response: $response"
- return 1
- fi
-
- _debug2 "API Response: $response"
- return 0
-}
diff --git a/dnsapi/dns_rage4.sh b/dnsapi/dns_rage4.sh
index b9abff17..c27fbc5f 100755
--- a/dnsapi/dns_rage4.sh
+++ b/dnsapi/dns_rage4.sh
@@ -71,7 +71,7 @@ dns_rage4_rm() {
_debug "Getting txt records"
_rage4_rest "getrecords/?id=${_domain_id}"
- _record_id=$(echo "$response" | tr '{' '\n' | grep '"TXT"' | grep "\"$txtvalue" | sed -n 's/.*"id":\([0-9][0-9]*\),.*/\1/p')
+ _record_id=$(echo "$response" | tr '{' '\n' | grep '"TXT"' | grep "\"$txtvalue" | sed -rn 's/.*"id":([[:digit:]]+),.*/\1/p')
if [ -z "$_record_id" ]; then
_err "error retrieving the record_id of the new TXT record in order to delete it, got: '$_record_id'."
return 1
diff --git a/dnsapi/dns_regru.sh b/dnsapi/dns_regru.sh
index edf8b464..be5ae117 100644
--- a/dnsapi/dns_regru.sh
+++ b/dnsapi/dns_regru.sh
@@ -96,8 +96,8 @@ _get_root() {
for ITEM in ${domains_list}; do
IDN_ITEM=${ITEM}
- case ".${domain}" in
- *.${IDN_ITEM}*)
+ case "${domain}" in
+ *${IDN_ITEM}*)
_domain="$(_idn "${ITEM}")"
_debug _domain "${_domain}"
return 0
diff --git a/dnsapi/dns_rltx.sh b/dnsapi/dns_rltx.sh
deleted file mode 100644
index 065ac177..00000000
--- a/dnsapi/dns_rltx.sh
+++ /dev/null
@@ -1,145 +0,0 @@
-#!/usr/bin/env sh
-# shellcheck disable=SC2034
-dns_rltx_info='Realtox Media Cloudpanel DNS API
-Site: realtoxmedia.de
-Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_rltx
-Options:
- RLTX_Key API Key
- RLTX_OrganizationID Organization ID
-'
-
-######## Public functions #####################
-
-#Usage: dns_rltx_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
-dns_rltx_add() {
- fulldomain=$1
- txtvalue=$2
-
- _info "Using Realtox Media Cloudpanel DNS API"
- _debug fulldomain "$fulldomain"
- _debug txtvalue "$txtvalue"
-
- if ! _rltx_init; then
- return 1
- fi
-
- if ! _get_root "$fulldomain"; then
- _err "Could not find matching DNS zone for $fulldomain"
- return 1
- fi
-
- _debug _domain_id "$_domain_id"
- _debug _domain "$_domain"
- _debug _sub_domain "$_sub_domain"
-
- data="{\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\",\"ttl\":120}"
- if ! _rltx_rest POST "domains/$_domain_id/dns/acme-txt" "$data"; then
- _err "Add TXT record request failed"
- return 1
- fi
- if _contains "$response" '"status":"added"'; then
- _info "Added TXT record, OK"
- return 0
- fi
- _err "Add TXT record failed: $response"
- return 1
-}
-
-#Usage: fulldomain txtvalue
-#Remove the txt record after validation.
-dns_rltx_rm() {
- fulldomain=$1
- txtvalue=$2
-
- _info "Using Realtox Media Cloudpanel DNS API"
- _debug fulldomain "$fulldomain"
- _debug txtvalue "$txtvalue"
-
- if ! _rltx_init; then
- return 1
- fi
-
- if ! _get_root "$fulldomain"; then
- _err "Could not find matching DNS zone for $fulldomain"
- return 1
- fi
-
- _debug _domain_id "$_domain_id"
- _debug _domain "$_domain"
- _debug _sub_domain "$_sub_domain"
-
- data="{\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\",\"ttl\":120}"
- if ! _rltx_rest DELETE "domains/$_domain_id/dns/acme-txt" "$data"; then
- _err "Remove TXT record request failed"
- return 1
- fi
- if _contains "$response" '"status":"removed"'; then
- _info "Removed TXT record, OK"
- return 0
- fi
- _err "Remove TXT record failed: $response"
- return 1
-}
-
-#################### Private functions below ##################################
-
-_rltx_init() {
- RLTX_Key="${RLTX_Key:-$(_readaccountconf_mutable RLTX_Key)}"
- RLTX_OrganizationID="${RLTX_OrganizationID:-$(_readaccountconf_mutable RLTX_OrganizationID)}"
-
- if [ -z "$RLTX_Key" ] || [ -z "$RLTX_OrganizationID" ]; then
- RLTX_Key=""
- RLTX_OrganizationID=""
- _err "Please specify RLTX_Key and RLTX_OrganizationID."
- _err "You can export them and retry: export RLTX_Key=... RLTX_OrganizationID=..."
- return 1
- fi
-
- _saveaccountconf_mutable RLTX_Key "$RLTX_Key"
- _saveaccountconf_mutable RLTX_OrganizationID "$RLTX_OrganizationID"
-}
-
-_get_root() {
- domain=$1
- fqdn_encoded="$(printf "%s" "$domain" | _url_encode)"
- if ! _rltx_rest GET "domains/dns/acme-zone?fqdn=$fqdn_encoded"; then
- return 1
- fi
- if ! _contains "$response" '"domain_id":"'; then
- return 1
- fi
-
- _domain_id="$(printf "%s" "$response" | _egrep_o '"domain_id":"[^"]*"' | cut -d : -f 2 | tr -d '"' | _head_n 1)"
- _domain="$(printf "%s" "$response" | _egrep_o '"zone":"[^"]*"' | cut -d : -f 2 | tr -d '"' | _head_n 1)"
- _sub_domain="$(printf "%s" "$response" | _egrep_o '"record_name":"[^"]*"' | cut -d : -f 2 | tr -d '"' | _head_n 1)"
-
- if [ -z "$_domain_id" ] || [ -z "$_domain" ] || [ -z "$_sub_domain" ]; then
- return 1
- fi
- return 0
-}
-
-_rltx_rest() {
- m=$1
- ep="$2"
- data="$3"
- _debug "$ep"
-
- export _H1="X-API-Key: $RLTX_Key"
- export _H2="X-Organization-ID: $RLTX_OrganizationID"
- export _H3="Content-Type: application/json"
-
- if [ "$m" = "GET" ]; then
- response="$(_get "https://api.ccp.realtoxmedia.de/api/$ep")"
- else
- _debug2 data "$data"
- response="$(_post "$data" "https://api.ccp.realtoxmedia.de/api/$ep" "" "$m")"
- fi
-
- if [ "$?" != "0" ]; then
- _err "Realtox Media Cloudpanel API request failed: $ep"
- return 1
- fi
- _debug2 response "$response"
- return 0
-}
diff --git a/dnsapi/dns_selectel.sh b/dnsapi/dns_selectel.sh
index 8ba9a4fb..565f541b 100644
--- a/dnsapi/dns_selectel.sh
+++ b/dnsapi/dns_selectel.sh
@@ -368,7 +368,7 @@ _get_auth_token() {
_data_auth="{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":{\"user\":{\"name\":\"${SL_Login_Name}\",\"domain\":{\"name\":\"${SL_Login_ID}\"},\"password\":\"${SL_Pswd}\"}}},\"scope\":{\"project\":{\"name\":\"${SL_Project_Name}\",\"domain\":{\"name\":\"${SL_Login_ID}\"}}}}}"
export _H1="Content-Type: application/json"
_result=$(_post "$_data_auth" "$auth_uri")
- _token_keystone=$(grep 'x-subject-token' "$HTTP_HEADER" | cut -d ':' -f 2- | tr -d ' \t\r')
+ _token_keystone=$(grep 'x-subject-token' "$HTTP_HEADER" | sed -nE "s/[[:space:]]*x-subject-token:[[:space:]]*([[:print:]]*)(\r*)/\1/p")
_dt_curr=$(date +%s)
SL_Token_V2="${SL_Login_Name}${_sl_sep}${_token_keystone}${_sl_sep}${SL_Login_ID}${_sl_sep}${SL_Project_Name}${_sl_sep}${_dt_curr}"
_saveaccountconf_mutable SL_Token_V2 "$SL_Token_V2"
diff --git a/dnsapi/dns_selfhost.sh b/dnsapi/dns_selfhost.sh
index 25130146..4912dfdf 100644
--- a/dnsapi/dns_selfhost.sh
+++ b/dnsapi/dns_selfhost.sh
@@ -7,7 +7,6 @@ Options:
SELFHOSTDNS_USERNAME Username
SELFHOSTDNS_PASSWORD Password
SELFHOSTDNS_MAP Subdomain name
- SELFHOSTDNS_UPDATE_URL API url. Optional. Default "https://account.selfhost.de/cgi-bin/api.pl"
Issues: github.com/acmesh-official/acme.sh/issues/4291
Author: Marvin Edeler
'
@@ -19,11 +18,9 @@ dns_selfhost_add() {
_debug fulldomain "$fulldomain"
_debug txtvalue "$txt"
- DEFAULT_SELFHOSTDNS_UPDATE_URL="https://account.selfhost.de/cgi-bin/api.pl"
+ SELFHOSTDNS_UPDATE_URL="https://selfhost.de/cgi-bin/api.pl"
# Get values, but don't save until we successfully validated
- SELFHOSTDNS_UPDATE_URL="${SELFHOSTDNS_UPDATE_URL:-$(_readaccountconf_mutable SELFHOSTDNS_UPDATE_URL)}"
- SELFHOSTDNS_UPDATE_URL="${SELFHOSTDNS_UPDATE_URL:-$DEFAULT_SELFHOSTDNS_UPDATE_URL}"
SELFHOSTDNS_USERNAME="${SELFHOSTDNS_USERNAME:-$(_readaccountconf_mutable SELFHOSTDNS_USERNAME)}"
SELFHOSTDNS_PASSWORD="${SELFHOSTDNS_PASSWORD:-$(_readaccountconf_mutable SELFHOSTDNS_PASSWORD)}"
# These values are domain dependent, so read them from there
@@ -42,10 +39,7 @@ dns_selfhost_add() {
# only match full domains (at the beginning of the string or with a leading whitespace),
# e.g. don't match mytest.example.com or sub.test.example.com for test.example.com
# if the domain is defined multiple times only the last occurance will be matched
- # prepend a space to each line so "start of line" and "after whitespace"
- # can both be matched as "after a space/tab" (portable BRE, no ERE (^|..))
- _selfhost_tab="$(printf '\t')"
- mapEntry=$(echo "$SELFHOSTDNS_MAP" | sed 's/^/ /' | sed -n "s/.*[ $_selfhost_tab]\($fulldomain:[0-9][0-9]*:\{0,1\}[0-9]*\).*/\1/p")
+ mapEntry=$(echo "$SELFHOSTDNS_MAP" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain)(:[[:digit:]]+)([:]?[[:digit:]]*)(.*)/\2\3\4/p")
_debug2 mapEntry "$mapEntry"
if test -z "$mapEntry"; then
_err "SELFHOSTDNS_MAP must contain the fulldomain incl. prefix and at least one RID"
@@ -57,7 +51,7 @@ dns_selfhost_add() {
rid2=$(echo "$mapEntry" | cut -d: -f3)
# read last used rid domain
- lastUsedRidForDomainEntry=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed 's/^/ /' | sed -n "s/.*[ $_selfhost_tab]\($fulldomain:[0-9][0-9]*\).*/\1/p")
+ lastUsedRidForDomainEntry=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain:[[:digit:]]+)(.*)/\2/p")
_debug2 lastUsedRidForDomainEntry "$lastUsedRidForDomainEntry"
lastUsedRidForDomain=$(echo "$lastUsedRidForDomainEntry" | cut -d: -f2)
@@ -90,11 +84,6 @@ dns_selfhost_add() {
fi
fi
- # Save api url if different from default
- if [ "$DEFAULT_SELFHOSTDNS_UPDATE_URL" != "$SELFHOSTDNS_UPDATE_URL" ]; then
- _saveaccountconf_mutable SELFHOSTDNS_UPDATE_URL "$SELFHOSTDNS_UPDATE_URL"
- fi
-
# Now that we know the values are good, save them
_saveaccountconf_mutable SELFHOSTDNS_USERNAME "$SELFHOSTDNS_USERNAME"
_saveaccountconf_mutable SELFHOSTDNS_PASSWORD "$SELFHOSTDNS_PASSWORD"
diff --git a/dnsapi/dns_simply.sh b/dnsapi/dns_simply.sh
index 74e891ad..e0ad16e2 100644
--- a/dnsapi/dns_simply.sh
+++ b/dnsapi/dns_simply.sh
@@ -8,7 +8,11 @@ Options:
SIMPLY_ApiKey API Key
'
-SIMPLY_Api="https://api.simply.com/2"
+#SIMPLY_Api="https://api.simply.com/2/"
+SIMPLY_Api_Default="https://api.simply.com/2"
+
+#This is used for determining success of REST call
+SIMPLY_SUCCESS_CODE='"status":200'
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
@@ -68,16 +72,7 @@ dns_simply_rm() {
return 1
fi
- case "$_simply_http_code" in
- 2*) ;;
- *)
- _err "Failed to fetch DNS records (HTTP $_simply_http_code)"
- _err "$response"
- return 1
- ;;
- esac
-
- records=$(echo "$response" | tr '{' "\n" | grep -E 'record_id|type|data|name' | sed 's/\"record_id/;\"record_id/' | tr "\n" ' ' | tr -d ' ' | tr ';' ' ')
+ records=$(echo "$response" | tr '{' "\n" | grep 'record_id\|type\|data\|\name' | sed 's/\"record_id/;\"record_id/' | tr "\n" ' ' | tr -d ' ' | tr ';' ' ')
nr_of_deleted_records=0
_info "Fetching txt record"
@@ -100,7 +95,7 @@ dns_simply_rm() {
if [ "$record_id" -gt 0 ]; then
- if ! _simply_delete_record "$_domain" "$record_id"; then
+ if ! _simply_delete_record "$_domain" "$_sub_domain" "$record_id"; then
_err "Record with id $record_id could not be deleted"
return 1
fi
@@ -127,9 +122,14 @@ dns_simply_rm() {
#################### Private functions below ##################################
_simply_load_config() {
+ SIMPLY_Api="${SIMPLY_Api:-$(_readaccountconf_mutable SIMPLY_Api)}"
SIMPLY_AccountName="${SIMPLY_AccountName:-$(_readaccountconf_mutable SIMPLY_AccountName)}"
SIMPLY_ApiKey="${SIMPLY_ApiKey:-$(_readaccountconf_mutable SIMPLY_ApiKey)}"
+ if [ -z "$SIMPLY_Api" ]; then
+ SIMPLY_Api="$SIMPLY_Api_Default"
+ fi
+
if [ -z "$SIMPLY_AccountName" ] || [ -z "$SIMPLY_ApiKey" ]; then
SIMPLY_AccountName=""
SIMPLY_ApiKey=""
@@ -144,6 +144,9 @@ _simply_load_config() {
}
_simply_save_config() {
+ if [ "$SIMPLY_Api" != "$SIMPLY_Api_Default" ]; then
+ _saveaccountconf_mutable SIMPLY_Api "$SIMPLY_Api"
+ fi
_saveaccountconf_mutable SIMPLY_AccountName "$SIMPLY_AccountName"
_saveaccountconf_mutable SIMPLY_ApiKey "$SIMPLY_ApiKey"
}
@@ -160,39 +163,26 @@ _simply_get_all_records() {
_get_root() {
domain=$1
-
- if ! _simply_rest GET "my/products/"; then
- return 1
- fi
-
- case "$_simply_http_code" in
- 2*) ;;
- *)
- _err "Failed to fetch product list (HTTP $_simply_http_code)"
- _err "$response"
- return 1
- ;;
- esac
-
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
if [ -z "$h" ]; then
+ #not valid
return 1
fi
- _domain=$(printf "%s" "$response" | tr '}' '\n' |
- grep -F -e "\"object\":\"$h\"" -e "\"name\":\"$h\"" -e "\"name_idn\":\"$h\"" |
- sed -n 's/.*"object":"\([^"]*\)".*/\1/p' |
- _head_n 1)
-
- if [ -n "$_domain" ]; then
- _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
- return 0
+ if ! _simply_rest GET "my/products/$h/dns/"; then
+ return 1
fi
- _debug "No Simply.com product found for $h"
+ if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then
+ _debug "$h not found"
+ else
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
+ _domain="$h"
+ return 0
+ fi
p="$i"
i=$(_math "$i" + 1)
done
@@ -204,44 +194,39 @@ _simply_add_record() {
sub_domain=$2
txtval=$3
- data="{\"name\": \"$sub_domain\", \"type\":\"TXT\", \"data\": \"$txtval\", \"priority\":0, \"ttl\": 120}"
+ data="{\"name\": \"$sub_domain\", \"type\":\"TXT\", \"data\": \"$txtval\", \"priority\":0, \"ttl\": 3600}"
if ! _simply_rest POST "my/products/$domain/dns/records/" "$data"; then
- _err "Adding record not successful!"
+ _err "Adding record not successfull!"
return 1
fi
- case "$_simply_http_code" in
- 2*) ;;
- *)
- _err "Call to API not successful (HTTP $_simply_http_code), see below message for more details"
+ if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then
+ _err "Call to API not sucessfull, see below message for more details"
_err "$response"
return 1
- ;;
- esac
+ fi
return 0
}
_simply_delete_record() {
domain=$1
- record_id=$2
+ sub_domain=$2
+ record_id=$3
_debug record_id "Delete record with id $record_id"
if ! _simply_rest DELETE "my/products/$domain/dns/records/$record_id/"; then
- _err "Deleting record not successful!"
+ _err "Deleting record not successfull!"
return 1
fi
- case "$_simply_http_code" in
- 2*) ;;
- *)
- _err "Call to API not successful (HTTP $_simply_http_code), see below message for more details"
+ if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then
+ _err "Call to API not sucessfull, see below message for more details"
_err "$response"
return 1
- ;;
- esac
+ fi
return 0
}
@@ -263,24 +248,17 @@ _simply_rest() {
export _H2="Content-Type: application/json"
- : >"$HTTP_HEADER"
-
if [ "$m" != "GET" ]; then
response="$(_post "$data" "$SIMPLY_Api/$ep" "" "$m")"
else
response="$(_get "$SIMPLY_Api/$ep")"
fi
- _ret="$?"
- unset _H1 _H2
-
- if [ "$_ret" != "0" ]; then
+ if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
- _simply_http_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d' ' -f2 | tr -d '\r\n')"
-
response="$(echo "$response" | _normalizeJson)"
_debug2 response "$response"
diff --git a/dnsapi/dns_udr.sh b/dnsapi/dns_udr.sh
index dbc959d6..656a0557 100644
--- a/dnsapi/dns_udr.sh
+++ b/dnsapi/dns_udr.sh
@@ -145,8 +145,8 @@ _udr_rest() {
_debug data "${data}"
response="$(_post "${data}" "${UDR_API}?s_login=${UDR_USER}&s_pw=${UDR_PASS}" "" "POST")"
- _code=$(echo "$response" | _egrep_o "code = ([0-9]+)" | _head_n 1 | cut -d = -f 2 | tr -d ' \t\r')
- _description=$(echo "$response" | _egrep_o "description = .*" | _head_n 1 | cut -d = -f 2 | tr -d '\r' | sed -e 's/^[ ]*//' -e 's/[ ]*$//')
+ _code=$(echo "$response" | _egrep_o "code = ([0-9]+)" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
+ _description=$(echo "$response" | _egrep_o "description = .*" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
_debug response_code "$_code"
_debug response_description "$_description"
diff --git a/dnsapi/dns_volcengine.sh b/dnsapi/dns_volcengine.sh
deleted file mode 100755
index 2cc805d5..00000000
--- a/dnsapi/dns_volcengine.sh
+++ /dev/null
@@ -1,297 +0,0 @@
-#!/usr/bin/env sh
-# shellcheck disable=SC2034
-dns_volcengine_info='Volcano Engine DNS API
-Site: https://www.volcengine.com/docs/6758/155086
-Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_volcengine
-Options:
- Volcengine_ACCESS_KEY_ID API Key ID
- Volcengine_SECRET_ACCESS_KEY API Secret
- Volcengine_SESSION_TOKEN Session Token. Optional, only needed when using temporary STS credentials.
-Issues: github.com/acmesh-official/acme.sh/issues/7064
-'
-
-Volcengine_HOST="dns.volcengineapi.com"
-Volcengine_URL="https://$Volcengine_HOST"
-
-######## Public functions #####################
-
-#fulldomain txtvalue
-dns_volcengine_add() {
- fulldomain=$1
- txtvalue=$2
- _record_id=""
-
- Volcengine_ACCESS_KEY_ID="${Volcengine_ACCESS_KEY_ID:-$(_readaccountconf_mutable Volcengine_ACCESS_KEY_ID)}"
- Volcengine_SECRET_ACCESS_KEY="${Volcengine_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable Volcengine_SECRET_ACCESS_KEY)}"
-
- if [ -z "$Volcengine_ACCESS_KEY_ID" ] || [ -z "$Volcengine_SECRET_ACCESS_KEY" ]; then
- Volcengine_ACCESS_KEY_ID=""
- Volcengine_SECRET_ACCESS_KEY=""
- _err "You haven't specified the volcengine dns api key id and api key secret yet."
- return 1
- fi
-
- #save the api key and email to the account conf file.
- _saveaccountconf_mutable Volcengine_ACCESS_KEY_ID "$Volcengine_ACCESS_KEY_ID"
- _saveaccountconf_mutable Volcengine_SECRET_ACCESS_KEY "$Volcengine_SECRET_ACCESS_KEY"
-
- _debug "First detect the root zone"
- if ! _get_root "$fulldomain"; then
- _err "invalid domain"
- _sleep 1
- return 1
- fi
- _debug _domain_id "$_domain_id"
- _debug _sub_domain "$_sub_domain"
- _debug _domain "$_domain"
-
- # _info "Getting existing records for $fulldomain"
- if ! volcengine_rest POST "" "Action=ListRecords&Version=2018-08-01" "{\"ZID\":$_domain_id,\"Host\":\"$_sub_domain\",\"Type\":\"TXT\",\"Value\":\"$txtvalue\",\"SearchMode\":\"exact\"}"; then
- _sleep 1
- return 1
- fi
-
- # ListRecords already filtered by ZID + Host + Value + SearchMode:exact,
- # so any returned record is our target. Don't match on FQDN: Volcengine
- # lowercases the Host/FQDN in the response, which would break a
- # case-sensitive string compare against $fulldomain.
- _record_id="$(echo "$response" | _egrep_o "\"RecordID\":\"[0-9]+\"," | cut -d: -f2 | cut -d, -f1 | tr -d '"')"
- _debug "_record_id" "$_record_id"
-
- if [ "$_record_id" ] && _contains "$response" "$txtvalue"; then
- _info "The TXT record already exists. Skipping."
- _sleep 1
- return 0
- fi
-
- _debug "Adding records"
-
- if volcengine_rest POST "" "Action=CreateRecord&Version=2018-08-01" "{\"ZID\":$_domain_id,\"Host\":\"$_sub_domain\",\"Type\":\"TXT\",\"Value\":\"$txtvalue\",\"Remark\":\"acme.sh\"}"; then
- _info "TXT record updated successfully."
- _sleep 1
- return 0
- fi
-
- _sleep 1
- return 1
-}
-
-#fulldomain txtvalue
-dns_volcengine_rm() {
- fulldomain=$1
- txtvalue=$2
- _record_id=""
-
- Volcengine_ACCESS_KEY_ID="${Volcengine_ACCESS_KEY_ID:-$(_readaccountconf_mutable Volcengine_ACCESS_KEY_ID)}"
- Volcengine_SECRET_ACCESS_KEY="${Volcengine_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable Volcengine_SECRET_ACCESS_KEY)}"
-
- _debug "First detect the root zone"
- if ! _get_root "$fulldomain"; then
- _err "invalid domain"
- _sleep 1
- return 1
- fi
- _debug _domain_id "$_domain_id"
- _debug _sub_domain "$_sub_domain"
- _debug _domain "$_domain"
-
- _info "Getting existing records for $fulldomain"
-
- if ! volcengine_rest POST "" "Action=ListRecords&Version=2018-08-01" "{\"ZID\":$_domain_id,\"Host\":\"$_sub_domain\",\"Type\":\"TXT\",\"Value\":\"$txtvalue\",\"SearchMode\":\"exact\"}"; then
- _sleep 1
- return 1
- fi
-
- # ListRecords already filtered by ZID + Host + Value + SearchMode:exact,
- # so any returned record is our target. Don't match on FQDN: Volcengine
- # lowercases the Host/FQDN in the response, which would break a
- # case-sensitive string compare against $fulldomain.
- _record_id="$(echo "$response" | _egrep_o "\"RecordID\":\"[0-9]+\"," | cut -d: -f2 | cut -d, -f1 | tr -d '"')"
- _debug "_record_id" "$_record_id"
-
- if [ -z "$_record_id" ]; then
- _debug "no records exist, skip"
- _sleep 1
- return 0
- fi
-
- if volcengine_rest POST "" "Action=DeleteRecord&Version=2018-08-01" "{\"RecordID\":\"$_record_id\"}"; then
- _info "TXT record deleted successfully."
- _sleep 1
- return 0
- fi
- _sleep 1
- return 1
-}
-
-#################### Private functions below ##################################
-
-_get_root() {
- domain=$1
- i=1
- p=1
-
- # iterate over names (a.b.c.d -> b.c.d -> c.d -> d)
- while true; do
- h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
- _debug "Checking domain: $h"
- if [ -z "$h" ]; then
- _err "invalid domain"
- return 1
- fi
-
- # iterate over paginated result for list_hosted_zones
- if ! volcengine_rest POST "" "Action=ListZones&Version=2018-08-01" "{\"Key\":\"$h\",\"SearchMode\":\"exact\"}"; then
- return 1
- fi
- if _contains "$response" "\"ZoneName\":\"$h\""; then
- _domain_id=$(printf "%s" "$response" | _egrep_o "\"ZID\":[0-9]+," | cut -d: -f2 | cut -d, -f1)
- if [ "$_domain_id" ]; then
- _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
- _domain=$h
- return 0
- fi
- _err "Can't find domain with id: $h"
- return 1
- fi
- p=$i
- i=$(_math "$i" + 1)
- done
- return 1
-}
-
-#method uri qstr data
-volcengine_rest() {
- mtd="$1"
- ep="$2"
- qsr="$3"
- data="$4"
-
- _debug mtd "$mtd"
- _debug ep "$ep"
- _debug qsr "$qsr"
- _debug data "$data"
-
- # clear any header state left over from a previous request so that
- # conditionally-set headers (e.g. x-content-sha256, x-security-token)
- # can't leak into the next request
- _H1=""
- _H2=""
- _H3=""
- _H4=""
- _H5=""
-
- CanonicalURI="/$ep"
- _debug2 CanonicalURI "$CanonicalURI"
-
- CanonicalQueryString="$qsr"
- _debug2 CanonicalQueryString "$CanonicalQueryString"
-
- RequestDate="$(date -u +"%Y%m%dT%H%M%SZ")"
- _debug2 RequestDate "$RequestDate"
-
- Hash="sha256"
-
- _H1="X-Date: $RequestDate"
- _debug2 _H1 "$_H1"
-
- volcengine_host="$Volcengine_HOST"
- CanonicalHeaders="host:$volcengine_host\n"
- SignedHeaders="host"
-
- if [ -n "$data" ]; then
- XContentSha256="$(printf "%s" "$data" | _digest "$Hash" hex)"
- _H4="x-content-sha256: $XContentSha256"
- _debug2 _H4 "$_H4"
-
- CanonicalHeaders="${CanonicalHeaders}x-content-sha256:$XContentSha256\n"
- SignedHeaders="${SignedHeaders};x-content-sha256"
- fi
-
- CanonicalHeaders="${CanonicalHeaders}x-date:$RequestDate\n"
- SignedHeaders="${SignedHeaders};x-date"
-
- if [ -n "$Volcengine_SESSION_TOKEN" ]; then
- _H3="x-security-token: $Volcengine_SESSION_TOKEN"
- CanonicalHeaders="${CanonicalHeaders}x-security-token:$Volcengine_SESSION_TOKEN\n"
- SignedHeaders="${SignedHeaders};x-security-token"
- fi
-
- _debug2 CanonicalHeaders "$CanonicalHeaders"
- _debug2 SignedHeaders "$SignedHeaders"
-
- RequestPayload="$data"
- _debug2 RequestPayload "$RequestPayload"
-
- CanonicalRequest="$mtd\n$CanonicalURI\n$CanonicalQueryString\n$CanonicalHeaders\n$SignedHeaders\n$(printf "%s" "$RequestPayload" | _digest "$Hash" hex)"
- _debug2 CanonicalRequest "$CanonicalRequest"
-
- HashedCanonicalRequest="$(printf '%b' "$CanonicalRequest" | _digest "$Hash" hex)"
- _debug2 HashedCanonicalRequest "$HashedCanonicalRequest"
-
- Algorithm="HMAC-SHA256"
- _debug2 Algorithm "$Algorithm"
-
- RequestDateOnly="$(echo "$RequestDate" | cut -c 1-8)"
- _debug2 RequestDateOnly "$RequestDateOnly"
-
- Region="cn-beijing"
- Service="dns"
-
- CredentialScope="$RequestDateOnly/$Region/$Service/request"
- _debug2 CredentialScope "$CredentialScope"
-
- StringToSign="$Algorithm\n$RequestDate\n$CredentialScope\n$HashedCanonicalRequest"
-
- _debug2 StringToSign "$StringToSign"
-
- kSecret="$Volcengine_SECRET_ACCESS_KEY"
-
- _secure_debug2 kSecret "$kSecret"
-
- kSecretH="$(printf "%s" "$kSecret" | _hex_dump | tr -d " ")"
- _secure_debug2 kSecretH "$kSecretH"
-
- kDateH="$(printf "%s" "$RequestDateOnly" | _hmac "$Hash" "$kSecretH" hex)"
- _debug2 kDateH "$kDateH"
-
- kRegionH="$(printf "%s" "$Region" | _hmac "$Hash" "$kDateH" hex)"
- _debug2 kRegionH "$kRegionH"
-
- kServiceH="$(printf "%s" "$Service" | _hmac "$Hash" "$kRegionH" hex)"
- _debug2 kServiceH "$kServiceH"
-
- kSigningH="$(printf "%s" "request" | _hmac "$Hash" "$kServiceH" hex)"
- _debug2 kSigningH "$kSigningH"
-
- signature="$(printf '%b' "$StringToSign" | _hmac "$Hash" "$kSigningH" hex)"
- _debug2 signature "$signature"
-
- Authorization="$Algorithm Credential=$Volcengine_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature"
- _debug2 Authorization "$Authorization"
-
- _H2="Authorization: $Authorization"
- _debug2 _H2 "$_H2"
-
- url="$Volcengine_URL/$ep"
- if [ "$qsr" ]; then
- url="$Volcengine_URL/$ep?$qsr"
- fi
-
- if [ "$mtd" = "GET" ]; then
- response="$(_get "$url")"
- else
- response="$(_post "$data" "$url" "" "POST" "application/json")"
- fi
-
- _ret="$?"
- _debug response "$response"
- if [ "$_ret" = "0" ]; then
- if _contains "$response" "\"Error\":{"; then
- _err "Response error:$response"
- return 1
- fi
- fi
-
- return "$_ret"
-}
diff --git a/dnsapi/dns_wedos.sh b/dnsapi/dns_wedos.sh
deleted file mode 100644
index d1f353e2..00000000
--- a/dnsapi/dns_wedos.sh
+++ /dev/null
@@ -1,217 +0,0 @@
-#!/usr/bin/env sh
-# shellcheck disable=SC2034
-dns_wedos_info='WEDOS.com
-Site: wedos.com
-Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_wedos
-Options:
- WEDOS_Username WAPI login (account email)
- WEDOS_Wapipass WAPI password
-Issues: github.com/acmesh-official/acme.sh/issues/7071
-Author: Jan Forman
-'
-
-WEDOS_Api="https://api.wedos.com/wapi/json"
-
-######## Public functions #####################
-
-#Usage: dns_wedos_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
-dns_wedos_add() {
- fulldomain=$(echo "$1" | _lower_case)
- txtvalue=$2
-
- if ! _wedos_init; then
- return 1
- fi
-
- _debug "Detecting root zone for $fulldomain"
- if ! _get_root "$fulldomain"; then
- _err "Cannot determine root zone for: $fulldomain"
- return 1
- fi
- _debug _domain "$_domain"
- _debug _sub_domain "$_sub_domain"
-
- _info "Adding TXT record: $_sub_domain.$_domain"
- if ! _wedos_request "dns-row-add" "{\"domain\":\"$_domain\",\"name\":\"$_sub_domain\",\"ttl\":\"300\",\"type\":\"TXT\",\"rdata\":\"$txtvalue\"}"; then
- _err "Failed to add TXT record"
- return 1
- fi
-
- _info "Committing DNS changes for $_domain"
- if ! _wedos_request "dns-domain-commit" "{\"name\":\"$_domain\"}"; then
- _err "Failed to commit DNS changes"
- return 1
- fi
-
- return 0
-}
-
-#Usage: dns_wedos_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
-dns_wedos_rm() {
- fulldomain=$(echo "$1" | _lower_case)
- txtvalue=$2
-
- if ! _wedos_init; then
- return 1
- fi
-
- _debug "Detecting root zone for $fulldomain"
- if ! _get_root "$fulldomain"; then
- _err "Cannot determine root zone for: $fulldomain"
- return 1
- fi
- _debug _domain "$_domain"
- _debug _sub_domain "$_sub_domain"
-
- # _get_root leaves the dns-rows-list response for $_domain in $response
- _debug "Looking up row IDs for TXT value: $txtvalue"
- _row_ids=$(echo "$response" | tr '{' '\n' | grep -F -- "\"rdata\":\"$txtvalue\"" | grep -F -- "\"name\":\"$_sub_domain\"" | _egrep_o '"ID": *"[0-9]*"' | tr -dc '0-9\n')
- _debug _row_ids "$_row_ids"
-
- if [ -z "$_row_ids" ]; then
- _info "TXT record not found, nothing to remove"
- return 0
- fi
-
- for _row_id in $_row_ids; do
- _info "Removing TXT record ID $_row_id from $_domain"
- if ! _wedos_request "dns-row-delete" "{\"domain\":\"$_domain\",\"row_id\":\"$_row_id\"}"; then
- _err "Failed to delete TXT record"
- return 1
- fi
- done
-
- _info "Committing DNS changes for $_domain"
- if ! _wedos_request "dns-domain-commit" "{\"name\":\"$_domain\"}"; then
- _err "Failed to commit DNS changes"
- return 1
- fi
-
- return 0
-}
-
-#################### Private functions below ##################################
-
-_wedos_init() {
- WEDOS_Username="${WEDOS_Username:-$(_readaccountconf_mutable WEDOS_Username)}"
- WEDOS_Wapipass="${WEDOS_Wapipass:-$(_readaccountconf_mutable WEDOS_Wapipass)}"
-
- if [ -z "$WEDOS_Username" ] || [ -z "$WEDOS_Wapipass" ]; then
- WEDOS_Username=""
- WEDOS_Wapipass=""
- _err "You didn't specify the WEDOS WAPI credentials yet."
- _err "Please export WEDOS_Username and WEDOS_Wapipass and try again."
- return 1
- fi
-
- _saveaccountconf_mutable WEDOS_Username "$WEDOS_Username"
- _saveaccountconf_mutable WEDOS_Wapipass "$WEDOS_Wapipass"
- return 0
-}
-
-# WAPI auth token: sha1(login + sha1(password) + hour), where the hour is
-# the current hour on the WEDOS servers (Europe/Prague timezone).
-# The POSIX TZ string is used so no tzdata is required on the client.
-_wedos_auth() {
- if [ "$_wedos_utc" ]; then
- # fallback: WAPI accepts 1 hour of skew, UTC+1 fits both CET and CEST
- _wedos_hour=$(date -u +%H)
- _wedos_hour=$(printf '%02d' "$(((${_wedos_hour#0} + 1) % 24))")
- else
- _wedos_hour=$(TZ='CET-1CEST,M3.5.0,M10.5.0/3' date +%H)
- fi
- _wedos_phash=$(printf '%s' "$WEDOS_Wapipass" | _digest sha1 hex)
- printf '%s' "${WEDOS_Username}${_wedos_phash}${_wedos_hour}" | _digest sha1 hex
-}
-
-#Usage: _wedos_request
-#Returns 0 and sets $response on WAPI code 1000, returns 1 otherwise.
-_wedos_request() {
- _wedos_cmd="$1"
- _wedos_data="$2"
-
- _wedos_token=$(_wedos_auth)
- _secure_debug _wedos_token "$_wedos_token"
-
- _wedos_json="{\"request\":{\"user\":\"$WEDOS_Username\",\"auth\":\"$_wedos_token\",\"command\":\"$_wedos_cmd\",\"data\":$_wedos_data}}"
- _debug2 "WAPI command: $_wedos_cmd"
- _debug2 "WAPI data: $_wedos_data"
-
- # _post sends the global _H1.._H5 headers with every request; clear them so
- # headers from earlier API calls are not leaked to the WAPI endpoint.
- export _H1=""
- export _H2=""
- export _H3=""
- export _H4=""
- export _H5=""
-
- _wedos_body="request=$(printf '%s' "$_wedos_json" | _url_encode)"
- response=$(_post "$_wedos_body" "$WEDOS_Api" "" "POST" "application/x-www-form-urlencoded")
- if [ "$?" != "0" ]; then
- _err "WAPI request failed for command '$_wedos_cmd'"
- return 1
- fi
- _debug2 "WAPI response: $response"
-
- _wedos_code=$(echo "$response" | _egrep_o '"code": *[0-9]*' | _head_n 1 | tr -dc '0-9')
- _debug2 "WAPI result code: $_wedos_code"
- if [ "$_wedos_code" = "1000" ]; then
- return 0
- fi
-
- # some systems ignore the TZ variable (Haiku), sending a wrong auth hour;
- # retry once with the UTC fallback in _wedos_auth
- if [ "$_wedos_code" = "2050" ] && [ -z "$_wedos_utc" ]; then
- _wedos_utc=1
- _wedos_request "$_wedos_cmd" "$_wedos_data"
- return $?
- fi
-
- # 2050 = bad credentials, 2051 = IP not whitelisted, 2052 = IP blocked
- if [ "$_wedos_code" = "2050" ] || [ "$_wedos_code" = "2051" ] || [ "$_wedos_code" = "2052" ]; then
- _wedos_result=$(echo "$response" | _egrep_o '"result": *"[^"]*"' | _head_n 1 | cut -d '"' -f 4)
- _err "WAPI authentication error $_wedos_code: $_wedos_result"
- _err "Check WEDOS_Username, WEDOS_Wapipass and the WAPI IP whitelist."
- _wedos_autherr=1
- return 1
- fi
-
- _debug "WAPI error for command '$_wedos_cmd': $response"
- return 1
-}
-
-# Determine the registered domain (_domain) and subdomain prefix (_sub_domain)
-# by walking up the labels and calling dns-rows-list until WAPI accepts one.
-# _acme-challenge.www.example.co.uk
-# -> _sub_domain=_acme-challenge.www _domain=example.co.uk
-# The full domain itself is tried first, so a zone apex (e.g. DNS alias mode
-# pointing at the registered domain) resolves to an empty _sub_domain.
-_get_root() {
- _gr_full="$1"
- _gr_i=1
- _wedos_autherr=""
- while true; do
- _gr_candidate=$(printf '%s' "$_gr_full" | cut -d . -f "${_gr_i}"-100)
- _debug2 "Checking zone candidate: $_gr_candidate"
- if [ -z "$_gr_candidate" ]; then
- return 1
- fi
-
- if _wedos_request "dns-rows-list" "{\"domain\":\"$_gr_candidate\"}"; then
- _domain="$_gr_candidate"
- if [ "$_gr_i" = "1" ]; then
- _sub_domain=""
- else
- _sub_domain=$(printf '%s' "$_gr_full" | cut -d . -f 1-"$((_gr_i - 1))")
- fi
- return 0
- fi
-
- # auth error hits every candidate, stop the walk
- if [ "$_wedos_autherr" ]; then
- return 1
- fi
-
- _gr_i=$((_gr_i + 1))
- done
-}
diff --git a/dnsapi/dns_world4you.sh b/dnsapi/dns_world4you.sh
index 0a1cda6b..f59715ac 100644
--- a/dnsapi/dns_world4you.sh
+++ b/dnsapi/dns_world4you.sh
@@ -61,7 +61,7 @@ dns_world4you_add() {
if _contains "$res" "successfully"; then
return 0
else
- msg=$(_w4y_alert_msg "$res")
+ msg=$(echo "$res" | grep -A 20 'alert-notification' | grep 'class="weak-title">[^<]' | sed 's/<[^>]*>//g;s/^\s*//g')
if [ "$msg" = '' ]; then
_err "Unable to add record: Unknown error"
echo "$ret" >'error-01.html'
@@ -125,7 +125,7 @@ dns_world4you_rm() {
if _contains "$res" "successfully"; then
return 0
else
- msg=$(_w4y_alert_msg "$res")
+ msg=$(echo "$res" | grep -A 20 'alert-notification' | grep 'class="weak-title">[^<]' | sed 's/<[^>]*>//g;s/^\s*//g')
if [ "$msg" = '' ]; then
_err "Unable to remove record: Unknown error"
echo "$ret" >'error-01.html'
@@ -145,17 +145,6 @@ dns_world4you_rm() {
################ Private functions ################
-# Usage: _w4y_alert_msg
-# Extracts the error text out of the alert box of a DNS page.
-# "grep -A" is not portable (Solaris /usr/bin/grep: "illegal option -- A"),
-# so select from the alert to EOF and keep the same number of lines.
-# "\s" is a GNU sed extension, use an explicit space/tab bracket instead.
-_w4y_alert_msg() {
- _w4y_tab=$(printf '\t')
- echo "$1" | sed -n '/alert-notification/,$p' | _head_n 21 |
- grep 'class="weak-title">[^<]' | sed "s/<[^>]*>//g;s/^[ $_w4y_tab]*//"
-}
-
# Usage: _login
_login() {
WORLD4YOU_USERNAME="${WORLD4YOU_USERNAME:-$(_readaccountconf_mutable WORLD4YOU_USERNAME)}"
diff --git a/dnsapi/dns_yandex360.sh b/dnsapi/dns_yandex360.sh
index 98841d6b..18d01361 100644
--- a/dnsapi/dns_yandex360.sh
+++ b/dnsapi/dns_yandex360.sh
@@ -149,7 +149,7 @@ _check_variables() {
org_response="$(echo "$org_response" | _normalizeJson)"
YANDEX360_ORG_ID=$(
echo "$org_response" |
- _egrep_o '"id":[ ]*[0-9]+' |
+ _egrep_o '"id":[[:space:]]*[0-9]+' |
cut -d':' -f2
)
_debug 'Automatically retrieved YANDEX360_ORG_ID' "$YANDEX360_ORG_ID"
@@ -216,7 +216,7 @@ _get_token() {
interval=$(
echo "$response" |
- _egrep_o '"interval":[ ]*[0-9]+' |
+ _egrep_o '"interval":[[:space:]]*[0-9]+' |
cut -d':' -f2
)
_debug 'Polling interval' "$interval"
diff --git a/dnsapi/dns_yc.sh b/dnsapi/dns_yc.sh
index cacb5977..36c49ce4 100644
--- a/dnsapi/dns_yc.sh
+++ b/dnsapi/dns_yc.sh
@@ -22,32 +22,21 @@ dns_yc_add() {
fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name
txtvalue=$2
- # YC_SA_Key_File_PEM_b64/Path are always persisted to the domain conf below,
- # so they must be recovered from there first (account conf is only a
- # fallback for the YC_Folder_ID case, see the SA_ID/SA_Key_ID save below).
- YC_SA_Key_File_PEM_b64="${YC_SA_Key_File_PEM_b64:-$(_readdomainconf YC_SA_Key_File_PEM_b64)}"
YC_SA_Key_File_PEM_b64="${YC_SA_Key_File_PEM_b64:-$(_readaccountconf_mutable YC_SA_Key_File_PEM_b64)}"
- YC_SA_Key_File_Path="${YC_SA_Key_File_Path:-$(_readdomainconf YC_SA_Key_File_Path)}"
YC_SA_Key_File_Path="${YC_SA_Key_File_Path:-$(_readaccountconf_mutable YC_SA_Key_File_Path)}"
if [ "$YC_SA_Key_File_PEM_b64" ]; then
echo "$YC_SA_Key_File_PEM_b64" | _dbase64 >private.key
YC_SA_Key_File="private.key"
- _yc_key_is_temp=1
_savedomainconf YC_SA_Key_File_PEM_b64 "$YC_SA_Key_File_PEM_b64"
else
YC_SA_Key_File="$YC_SA_Key_File_Path"
- _yc_key_is_temp=""
_savedomainconf YC_SA_Key_File_Path "$YC_SA_Key_File_Path"
fi
- YC_Zone_ID="${YC_Zone_ID:-$(_readdomainconf YC_Zone_ID)}"
YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}"
- YC_Folder_ID="${YC_Folder_ID:-$(_readdomainconf YC_Folder_ID)}"
YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}"
- YC_SA_ID="${YC_SA_ID:-$(_readdomainconf YC_SA_ID)}"
YC_SA_ID="${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}"
- YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readdomainconf YC_SA_Key_ID)}"
YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}"
if [ "$YC_SA_ID" ] && [ "$YC_SA_Key_ID" ] && [ "$YC_SA_Key_File" ]; then
@@ -76,21 +65,11 @@ dns_yc_add() {
return 1
fi
else
- # Clear both possible stores -- YC_Zone_ID/YC_Folder_ID/key material are
- # persisted to the domain conf, while YC_SA_ID/YC_SA_Key_ID may have been
- # saved account-wide (Folder_ID mode), so a plain _clearaccountconf alone
- # would leave stale values behind in whichever store wasn't touched.
- _cleardomainconf YC_Zone_ID
_clearaccountconf YC_Zone_ID
- _cleardomainconf YC_Folder_ID
_clearaccountconf YC_Folder_ID
- _cleardomainconf YC_SA_ID
- _clearaccountconf_mutable YC_SA_ID
- _cleardomainconf YC_SA_Key_ID
- _clearaccountconf_mutable YC_SA_Key_ID
- _cleardomainconf YC_SA_Key_File_PEM_b64
+ _clearaccountconf YC_SA_ID
+ _clearaccountconf YC_SA_Key_ID
_clearaccountconf YC_SA_Key_File_PEM_b64
- _cleardomainconf YC_SA_Key_File_Path
_clearaccountconf YC_SA_Key_File_Path
_err "You didn't specify a YC_SA_ID or YC_SA_Key_ID or YC_SA_Key_File."
return 1
@@ -131,30 +110,11 @@ dns_yc_rm() {
fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name
txtvalue=$2
- YC_Zone_ID="${YC_Zone_ID:-$(_readdomainconf YC_Zone_ID)}"
YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}"
- YC_Folder_ID="${YC_Folder_ID:-$(_readdomainconf YC_Folder_ID)}"
YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}"
- YC_SA_ID="${YC_SA_ID:-$(_readdomainconf YC_SA_ID)}"
YC_SA_ID="${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}"
- YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readdomainconf YC_SA_Key_ID)}"
YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}"
- # See dns_yc_add() for why domain conf is checked before account conf.
- YC_SA_Key_File_PEM_b64="${YC_SA_Key_File_PEM_b64:-$(_readdomainconf YC_SA_Key_File_PEM_b64)}"
- YC_SA_Key_File_PEM_b64="${YC_SA_Key_File_PEM_b64:-$(_readaccountconf_mutable YC_SA_Key_File_PEM_b64)}"
- YC_SA_Key_File_Path="${YC_SA_Key_File_Path:-$(_readdomainconf YC_SA_Key_File_Path)}"
- YC_SA_Key_File_Path="${YC_SA_Key_File_Path:-$(_readaccountconf_mutable YC_SA_Key_File_Path)}"
-
- if [ "$YC_SA_Key_File_PEM_b64" ]; then
- echo "$YC_SA_Key_File_PEM_b64" | _dbase64 >private.key
- YC_SA_Key_File="private.key"
- _yc_key_is_temp=1
- else
- YC_SA_Key_File="$YC_SA_Key_File_Path"
- _yc_key_is_temp=""
- fi
-
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
@@ -164,10 +124,16 @@ dns_yc_rm() {
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
- # upsertRecordSets.deletions removes only the given value from the rrset,
- # leaving any other values at the same name (e.g. base + wildcard domain)
- # intact -- no need to read the current data set and recompute it.
- if _yc_rest POST "zones/$_domain_id:upsertRecordSets" "{\"deletions\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":[\"$txtvalue\"]}]}"; then
+ _debug "Getting txt records"
+ if _yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain"; then
+ exists_txtvalue=$(echo "$response" | _normalizeJson | _egrep_o "\"data\".*\][^,]*" | _egrep_o "[^:]*$")
+ _debug exists_txtvalue "$exists_txtvalue"
+ else
+ _err "Error: $response"
+ return 1
+ fi
+
+ if _yc_rest POST "zones/$_domain_id:updateRecordSets" "{\"deletions\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":$exists_txtvalue}]}"; then
if _contains "$response" "\"done\": true"; then
_info "Delete, OK"
return 0
@@ -228,7 +194,7 @@ _get_root() {
return 1
fi
if _contains "$response" "\"zone\": \"$h\""; then
- _domain_id=$(echo "$response" | _normalizeJson | _egrep_o "[^{]*\"zone\":\"$h\"[^}]*" | _egrep_o "\"id\"[^,]*" | _egrep_o "[^:][^:]*$" | tr -d '"')
+ _domain_id=$(echo "$response" | _normalizeJson | _egrep_o "[^{]*\"zone\":\"$h\"[^}]*" | _egrep_o "\"id\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"')
_debug _domain_id "$_domain_id"
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
@@ -289,9 +255,7 @@ _yc_login() {
_signature=$(printf "%s.%s" "$header" "$payload" | _sign "$YC_SA_Key_File" "sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1" | _url_replace)
_debug2 _signature "$_signature"
- if [ "$_yc_key_is_temp" ]; then
- rm -f "$YC_SA_Key_File"
- fi
+ rm -rf "$YC_SA_Key_File"
_jwt=$(printf "{\"jwt\": \"%s.%s.%s\"}" "$header" "$payload" "$_signature")
_debug2 _jwt "$_jwt"
@@ -300,7 +264,7 @@ _yc_login() {
_iam_response="$(_post "$_jwt" "https://iam.api.cloud.yandex.net/iam/v1/tokens" "" "POST")"
_debug3 _iam_response "$(echo "$_iam_response" | _normalizeJson)"
- YC_Token="$(echo "$_iam_response" | _normalizeJson | _egrep_o "\"iamToken\"[^,]*" | _egrep_o "[^:][^:]*$" | tr -d '"')"
+ YC_Token="$(echo "$_iam_response" | _normalizeJson | _egrep_o "\"iamToken\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"')"
_debug3 YC_Token
return 0
diff --git a/dnsapi/dns_zonomi.sh b/dnsapi/dns_zonomi.sh
index c783a221..ee817381 100644
--- a/dnsapi/dns_zonomi.sh
+++ b/dnsapi/dns_zonomi.sh
@@ -5,11 +5,9 @@ Site: zonomi.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_zonomi
Options:
ZM_Key API Key
-OptionsAlt:
- ZM_Api API endpoint. Default: "https://zonomi.com/app/dns/dyndns.jsp". For RimuHosting use "https://rimuhosting.com/dns/dyndns.jsp".
'
-ZM_Api_Default="https://zonomi.com/app/dns/dyndns.jsp"
+ZM_Api="https://zonomi.com/app/dns/dyndns.jsp"
######## Public functions #####################
@@ -30,8 +28,6 @@ dns_zonomi_add() {
#save the api key to the account conf file.
_saveaccountconf_mutable ZM_Key "$ZM_Key"
- _zm_init_api
-
_info "Get existing txt records for $fulldomain"
if ! _zm_request "action=QUERY&name=$fulldomain"; then
_err "error"
@@ -68,27 +64,11 @@ dns_zonomi_rm() {
return 1
fi
- _zm_init_api
-
_zm_request "action=DELETE&type=TXT&name=$fulldomain"
}
#################### Private functions below ##################################
-
-# resolve the API endpoint: zonomi by default, overridable for providers
-# sharing the same API on another host (e.g. RimuHosting)
-_zm_init_api() {
- ZM_Api="${ZM_Api:-$(_readaccountconf_mutable ZM_Api)}"
- if [ -z "$ZM_Api" ]; then
- ZM_Api="$ZM_Api_Default"
- fi
- _debug2 ZM_Api "$ZM_Api"
- if [ "$ZM_Api" != "$ZM_Api_Default" ]; then
- _saveaccountconf_mutable ZM_Api "$ZM_Api"
- fi
-}
-
#qstr
_zm_request() {
qstr="$1"
diff --git a/notify/aws_ses.sh b/notify/aws_ses.sh
index 735e6204..07e0c48c 100644
--- a/notify/aws_ses.sh
+++ b/notify/aws_ses.sh
@@ -83,43 +83,7 @@ aws_ses_send() {
response="$(aws_rest POST "" "" "$_data")"
}
-_use_container_role() {
- # automatically set if running inside ECS
- if [ -z "$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" ]; then
- _debug "No ECS environment variable detected"
- return 1
- fi
- _use_metadata "169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
-}
-
-_use_instance_role() {
- _instance_role_name_url="http://169.254.169.254/latest/meta-data/iam/security-credentials/"
-
- if _get "$_instance_role_name_url" true 1 | _head_n 1 | grep -Fq 401; then
- _debug "Using IMDSv2"
- _token_url="http://169.254.169.254/latest/api/token"
- export _H1="X-aws-ec2-metadata-token-ttl-seconds: 21600"
- _token="$(_post "" "$_token_url" "" "PUT")"
- _secure_debug3 "_token" "$_token"
- if [ -z "$_token" ]; then
- _debug "Unable to fetch IMDSv2 token from instance metadata"
- return 1
- fi
- export _H1="X-aws-ec2-metadata-token: $_token"
- fi
-
- if ! _get "$_instance_role_name_url" true 1 | _head_n 1 | grep -Fq 200; then
- _debug "Unable to fetch IAM role from instance metadata"
- return 1
- fi
-
- _instance_role_name=$(_get "$_instance_role_name_url" "" 1)
- _debug "_instance_role_name" "$_instance_role_name"
- _use_metadata "$_instance_role_name_url$_instance_role_name" "$_token"
-}
-
_use_metadata() {
- export _H1="X-aws-ec2-metadata-token: $2"
_aws_creds="$(
_get "$1" "" 1 |
_normalizeJson |
diff --git a/notify/customscript.sh b/notify/customscript.sh
deleted file mode 100644
index ba8b07cb..00000000
--- a/notify/customscript.sh
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/env sh
-
-# Support calling a custom script for notifications
-#
-# export CUSTOMSCRIPT_PATH="/usr/local/bin/acme-notification.sh"
-#
-# The script is called with three arguments:
-# $1 subject
-# $2 content
-# $3 status code (0: success, 1: error, 2: skipped)
-
-customscript_send() {
- _subject="$1"
- _content="$2"
- _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
- _debug "_subject" "$_subject"
- _debug "_content" "$_content"
- _debug "_statusCode" "$_statusCode"
-
- CUSTOMSCRIPT_PATH="${CUSTOMSCRIPT_PATH:-$(_readaccountconf_mutable CUSTOMSCRIPT_PATH)}"
- if [ -z "$CUSTOMSCRIPT_PATH" ]; then
- _err "You didn't specify the custom script path CUSTOMSCRIPT_PATH yet."
- return 1
- fi
- if ! _exists "$CUSTOMSCRIPT_PATH"; then
- _err "The custom script $CUSTOMSCRIPT_PATH does not exist or is not executable."
- return 1
- fi
- _saveaccountconf_mutable CUSTOMSCRIPT_PATH "$CUSTOMSCRIPT_PATH"
-
- # Invoke directly, never through eval: the subject and content contain
- # domain names and CA messages, eval would allow command injection.
- _customscript_result="$("$CUSTOMSCRIPT_PATH" "$_subject" "$_content" "$_statusCode" 2>&1)"
- _customscript_rc="$?"
- _debug2 "_customscript_result" "$_customscript_result"
-
- if [ "$_customscript_rc" != "0" ]; then
- _err "custom script execution error ($_customscript_rc): $_customscript_result"
- return 1
- fi
-
- _info "custom script executed successfully."
- return 0
-}
diff --git a/notify/smtp.sh b/notify/smtp.sh
index a7318692..f5ebebca 100644
--- a/notify/smtp.sh
+++ b/notify/smtp.sh
@@ -200,7 +200,6 @@ _smtp_send_curl() {
set -- "$@" \
--upload-file - \
- --crlf \
--mail-from "$SMTP_FROM" \
--max-time "$SMTP_TIMEOUT"
diff --git a/notify/waha.sh b/notify/waha.sh
deleted file mode 100755
index 573295e9..00000000
--- a/notify/waha.sh
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/usr/bin/env sh
-
-#Support WAHA (WhatsApp HTTP API) - free, self-hosted WhatsApp API
-#https://waha.devlike.pro/
-
-#Required:
-#WAHA_URL="http://localhost:3000"
-#WAHA_CHAT_ID="1234567890@c.us"
-
-#Optional:
-#WAHA_API_KEY=""
-#WAHA_SESSION="default"
-
-waha_send() {
- _subject="$1"
- _content="$2"
- _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
- _debug "_subject" "$_subject"
- _debug "_content" "$_content"
- _debug "_statusCode" "$_statusCode"
-
- WAHA_URL="${WAHA_URL:-$(_readaccountconf_mutable WAHA_URL)}"
- if [ -z "$WAHA_URL" ]; then
- WAHA_URL=""
- _err "You didn't specify the WAHA server url WAHA_URL yet."
- _err "Example: export WAHA_URL=\"http://localhost:3000\""
- return 1
- fi
- _saveaccountconf_mutable WAHA_URL "$WAHA_URL"
-
- WAHA_CHAT_ID="${WAHA_CHAT_ID:-$(_readaccountconf_mutable WAHA_CHAT_ID)}"
- if [ -z "$WAHA_CHAT_ID" ]; then
- WAHA_CHAT_ID=""
- _err "You didn't specify the WhatsApp chat id WAHA_CHAT_ID yet."
- _err "Example: export WAHA_CHAT_ID=\"1234567890@c.us\""
- return 1
- fi
- _saveaccountconf_mutable WAHA_CHAT_ID "$WAHA_CHAT_ID"
-
- WAHA_API_KEY="${WAHA_API_KEY:-$(_readaccountconf_mutable WAHA_API_KEY)}"
- if [ "$WAHA_API_KEY" ]; then
- _saveaccountconf_mutable WAHA_API_KEY "$WAHA_API_KEY"
- fi
-
- WAHA_SESSION="${WAHA_SESSION:-$(_readaccountconf_mutable WAHA_SESSION)}"
- if [ -z "$WAHA_SESSION" ]; then
- WAHA_SESSION="default"
- else
- _saveaccountconf_mutable WAHA_SESSION "$WAHA_SESSION"
- fi
-
- _content=$(printf "*%s*\n%s" "$_subject" "$_content" | _json_encode)
-
- _data="{\"chatId\": \"$WAHA_CHAT_ID\", "
- _data="$_data\"text\": \"$_content\", "
- _data="$_data\"session\": \"$WAHA_SESSION\"}"
-
- _debug "_data" "$_data"
-
- if [ "$WAHA_API_KEY" ]; then
- export _H1="X-Api-Key: $WAHA_API_KEY"
- fi
-
- _waha_url="${WAHA_URL}/api/sendText"
- response="$(_post "$_data" "$_waha_url" "" "POST" "application/json")"
-
- if [ "$?" = "0" ] && _contains "$response" "\"id\""; then
- _info "waha send success."
- return 0
- fi
- _err "waha send error."
- _err "$response"
- return 1
-}