mirror of
https://github.com/acmesh-official/acme.sh.git
synced 2026-05-18 11:53:56 +08:00
204
.github/copilot-instructions.md
vendored
204
.github/copilot-instructions.md
vendored
@@ -1,67 +1,181 @@
|
|||||||
# GitHub Copilot Shell Scripting (sh) Review Instructions
|
# GitHub Copilot Shell Scripting (sh) Review Instructions for acme.sh
|
||||||
|
|
||||||
## 🎯 Overall Goal
|
## Overall Goal
|
||||||
|
|
||||||
Your role is to act as a rigorous yet helpful senior engineer, reviewing Shell script code (`.sh` files). Ensure the code exhibits the highest levels of robustness, security, and portability.
|
Your role is to act as a rigorous yet helpful senior engineer, reviewing Shell script code (`.sh` files) for the [acme.sh](https://github.com/acmesh-official/acme.sh) project. Ensure the code exhibits the highest levels of robustness, security, and portability.
|
||||||
The review must focus on risks unique to Shell scripting, such as proper quoting, robust error handling, and the secure execution of external commands.
|
The review must focus on risks unique to Shell scripting, such as proper quoting, robust error handling, and the secure execution of external commands.
|
||||||
|
|
||||||
## 📝 Required Output Format
|
## Required Output Format
|
||||||
|
|
||||||
Please adhere to the previous format: organize the feedback into a single, structured report, using the three-level marking system:
|
Organize the feedback into a single, structured report, using the three-level marking system:
|
||||||
|
|
||||||
1. **🔴 Critical Issues (Must Fix Before Merge)**
|
1. **Critical Issues (Must Fix Before Merge)**
|
||||||
2. **🟡 Suggestions (Improvements to Consider)**
|
2. **Suggestions (Improvements to Consider)**
|
||||||
3. **✅ Good Practices (Points to Commend)**
|
3. **Good Practices (Points to Commend)**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔍 Focus Areas and Rules for Shell
|
## Shell Compatibility
|
||||||
|
|
||||||
### 1. Robustness and Error Handling
|
- **POSIX sh only** -- all scripts must target `sh`, not `bash`. No bash-isms allowed.
|
||||||
|
- **Shebang**: always use `#!/usr/bin/env sh` (not `#!/bin/sh`, not `#!/usr/bin/env bash`).
|
||||||
* **Shebang:** Check that the script starts with the correct Shebang, must be "#!/usr/bin/env sh".
|
- **Use `return`, never `exit`** -- scripts are sourced, not executed as subprocesses. `exit` would kill the parent shell.
|
||||||
* **Startup Options:** **(🔴 Critical)** Enforce the use of the following combination at the start of the script for safety and robustness:
|
- **Cross-platform**: code must work on Linux, macOS, FreeBSD, Solaris, and BusyBox environments.
|
||||||
* `set -e`: Exit immediately if a command exits with a non-zero status.
|
|
||||||
* `set -u`: Treat unset variables as an error and exit.
|
|
||||||
* `set -o pipefail`: Ensure the whole pipeline fails if any command in the pipe fails.
|
|
||||||
* **Exit Codes:** Ensure functions and the main script use `exit 0` for success and a non-zero exit code upon failure.
|
|
||||||
* **Temporary Files:** Check for the use of `mktemp` when creating temporary files to prevent race conditions and security risks.
|
|
||||||
|
|
||||||
### 2. Security and Quoting
|
|
||||||
|
|
||||||
* **Variable Quoting:** **(🔴 Critical)** Check that all variable expansions (like `$VAR` and `$(COMMAND)`) are properly enclosed in **double quotes** (i.e., `"$VAR"` and `"$(COMMAND)"`) to prevent **Word Splitting** and **Globbing**.
|
|
||||||
* **Hardcoded Secrets:** **(🔴 Critical)** Find and flag any hardcoded passwords, keys, tokens, or authentication details.
|
|
||||||
* **Untrusted Input:** Verify that all user input, command-line arguments (`$1`, `$2`, etc.), or environment variables are rigorously validated and sanitized before use.
|
|
||||||
* **Avoid `eval`:** Warn against and suggest alternatives to using `eval`, as it can lead to arbitrary code execution.
|
|
||||||
|
|
||||||
### 3. Readability and Maintainability
|
|
||||||
|
|
||||||
* **Function Usage:** Recommend wrapping complex or reusable logic within clearly named functions.
|
|
||||||
* **Local Variables:** Check that variables inside functions are declared using the `local` keyword to avoid unintentionally modifying global state.
|
|
||||||
* **Naming Convention:** Variable names should use uppercase letters and underscores (e.g., `MY_VARIABLE`), or follow established project conventions.
|
|
||||||
* **Test Conditions:** Encourage the use of Bash's **double brackets `[[ ... ]]`** for conditional tests, as it is generally safer and more powerful (e.g., supports pattern matching and avoids Word Splitting) than single brackets `[ ... ]`.
|
|
||||||
* **Command Substitution:** Encourage using `$(command)` over backticks `` `command` `` for command substitution, as it is easier to nest and improves readability.
|
|
||||||
|
|
||||||
### 4. External Commands and Environment
|
|
||||||
|
|
||||||
* **`for` Loops:** Warn against patterns like `for i in $(cat file)` or `for i in $(ls)` and recommend the more robust `while IFS= read -r line` pattern for safely processing file contents or filenames that might contain spaces.
|
|
||||||
* **Use existing acme.sh functions whenever possible.** For example: do not use `tr '[:upper:]' '[:lower:]'`, use `_lower_case` instead.
|
|
||||||
* **Do not use `head -n`.** Use the `_head_n()` function instead.
|
|
||||||
* **Do not use `curl` or `wget`.** Use the `_post()` and `_get()` functions instead.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 5. Review Rules for Files Under `dnsapi/`:
|
## Robustness and Error Handling
|
||||||
|
|
||||||
* **Each file must contain a `{filename}_add` function** for adding DNS TXT records. It should use `_readaccountconf_mutable` to read the API key and `_saveaccountconf_mutable` to save it. Do not use `_saveaccountconf` or `_readaccountconf`.
|
- **(Critical)** Enforce the use of the following combination at the start of the script for safety and robustness:
|
||||||
|
- `set -e`: Exit immediately if a command exits with a non-zero status.
|
||||||
|
- `set -u`: Treat unset variables as an error and exit.
|
||||||
|
- `set -o pipefail`: Ensure the whole pipeline fails if any command in the pipe fails.
|
||||||
|
- **Always check return values** of function calls. If an error occurs, there must be a way to stop execution.
|
||||||
|
- **Return 1** after `_err` messages:
|
||||||
|
```sh
|
||||||
|
if [ -z "$VARIABLE" ]; then
|
||||||
|
_err "VARIABLE is required"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
- Check for the use of `mktemp` when creating temporary files to prevent race conditions and security risks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## ❌ Things to Avoid
|
## Security and Quoting
|
||||||
|
|
||||||
* Do not comment on purely stylistic issues like spacing or indentation, which should be handled by tools like ShellCheck or Prettier.
|
- **(Critical)** Check that all variable expansions (like `$VAR` and `$(COMMAND)`) are properly enclosed in **double quotes** (i.e., `"$VAR"` and `"$(COMMAND)"`) to prevent **Word Splitting** and **Globbing**.
|
||||||
* Do not be overly verbose unless a significant issue is found. Keep feedback concise and actionable.
|
- **(Critical)** Find and flag any hardcoded passwords, keys, tokens, or authentication details.
|
||||||
|
- Verify that all user input, command-line arguments (`$1`, `$2`, etc.), or environment variables are rigorously validated and sanitized before use.
|
||||||
|
- Avoid `eval` -- warn against and suggest alternatives, as it can lead to arbitrary code execution.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use Built-in Helper Functions
|
||||||
|
|
||||||
|
Never use raw shell commands when acme.sh provides a wrapper function. This is the most critical rule for portability.
|
||||||
|
|
||||||
|
| Instead of | Use |
|
||||||
|
|---|---|
|
||||||
|
| `tr '[:upper:]' '[:lower:]'` | `_lower_case()` |
|
||||||
|
| `head -n 1` | `_head_n 1` |
|
||||||
|
| `openssl dgst` / `openssl` | `_digest()` / `_hmac()` |
|
||||||
|
| `date` | `_utc_date()` with `sed`/`tr` |
|
||||||
|
| `curl` / `wget` | `_get()` or `_post()` |
|
||||||
|
| `sleep` | `_sleep` |
|
||||||
|
| `base64` / `openssl base64` | `_base64()` |
|
||||||
|
| `$(( ))` arithmetic | `_math()` |
|
||||||
|
| `grep -E` / `grep -Po` | `_egrep_o()` |
|
||||||
|
| `printf` | `echo` |
|
||||||
|
| `idn` command | `_idn()` / `_is_idn()` |
|
||||||
|
|
||||||
|
When fixing a pattern issue, fix **all instances** in the file, not just the one highlighted.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Forbidden External Tools
|
||||||
|
|
||||||
|
Do not use these commands -- they are not portable across all target platforms:
|
||||||
|
|
||||||
|
- `jq` (parse JSON with built-in string manipulation)
|
||||||
|
- `grep -A` (removed throughout the project)
|
||||||
|
- `grep -Po` (Perl regex not available everywhere)
|
||||||
|
- `rev`, `xargs`, `iconv`
|
||||||
|
- If you must depend on an external tool, check with `_exists` first:
|
||||||
|
```sh
|
||||||
|
if ! _exists jq; then
|
||||||
|
_err "jq is required"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
- Warn against patterns like `for i in $(cat file)` or `for i in $(ls)` and recommend the more robust `while IFS= read -r line` pattern for safely processing file contents or filenames that might contain spaces.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration Management
|
||||||
|
|
||||||
|
Use the correct save/read functions depending on hook type:
|
||||||
|
|
||||||
|
- **DNS hooks**: `_readaccountconf_mutable` to read API keys, `_saveaccountconf_mutable` to save them. Do not use `_saveaccountconf` or `_readaccountconf`.
|
||||||
|
- **Deploy hooks**: `_savedeployconf` / `_getdeployconf`
|
||||||
|
- **Notification hooks**: use account conf functions.
|
||||||
|
- Save operations should only happen in the correct lifecycle function (e.g., `_issue()`).
|
||||||
|
- Use environment variables for all configurable values -- do not introduce hardcoded config files.
|
||||||
|
- Do not clear account conf without a clear reason.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## DNS API Conventions
|
||||||
|
|
||||||
|
- Read the [DNS API Dev Guide](https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide) before writing a DNS plugin.
|
||||||
|
- Each file under `dnsapi/` must contain a `{filename}_add` function for adding DNS TXT records.
|
||||||
|
- The `_get_root()` loop counter `i` must start from `1` (not `2`) to support DNS alias mode.
|
||||||
|
- The `dns_*_rm()` function must remove records **by TXT value**, not by replacing/updating. See [#1261](https://github.com/acmesh-official/acme.sh/issues/1261).
|
||||||
|
- Preserve the `dns_*_info` metadata variable block in each DNS script header.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Variable Naming
|
||||||
|
|
||||||
|
- Use CamelCase with provider prefix: `KINGHOST_Username` (not `KINGHOST_username`).
|
||||||
|
- Variable names should use uppercase letters and underscores (e.g., `MY_VARIABLE`), or follow established project conventions.
|
||||||
|
- Avoid confusingly similar names. Prefer one variable with comma-separated values over multiple variables (e.g., `CZ_Zones` with comma support instead of separate `CZ_Zone` and `CZ_Zones`).
|
||||||
|
- Do not define variables with the same name in different scopes.
|
||||||
|
- Variables inside functions should be declared using the `local` keyword to avoid unintentionally modifying global state.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
- Use `shfmt` for formatting -- CI enforces it.
|
||||||
|
- Reduce indentation where possible.
|
||||||
|
- Single space, not double spaces.
|
||||||
|
- No trailing semicolons after `return` statements.
|
||||||
|
- Add a newline at the end of every file.
|
||||||
|
- Use `$(command)` over backticks `` `command` `` for command substitution.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Simplicity
|
||||||
|
|
||||||
|
- Prefer hardcoded sensible defaults over unnecessary configuration variables (e.g., use `3600` for TTL instead of a `DESEC_TTL` variable).
|
||||||
|
- Reject over-engineered solutions. If it can be done in one line, do it in one line.
|
||||||
|
- Follow existing patterns in the codebase -- new hooks should look like existing hooks.
|
||||||
|
- Respect user choices: do not `chmod` files that already exist; the user's permissions take priority.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentation Requirements
|
||||||
|
|
||||||
|
Before a PR can be merged, the following documentation must be provided:
|
||||||
|
|
||||||
|
- **Wiki page**: add or update the relevant page:
|
||||||
|
- DNS APIs: [dnsapi](https://github.com/acmesh-official/acme.sh/wiki/dnsapi) or [dnsapi2](https://github.com/acmesh-official/acme.sh/wiki/dnsapi2)
|
||||||
|
- Deploy hooks: [deployhooks](https://github.com/acmesh-official/acme.sh/wiki/deployhooks)
|
||||||
|
- Notification hooks: [notify](https://github.com/acmesh-official/acme.sh/wiki/notify)
|
||||||
|
- Options: [Options-and-Params](https://github.com/acmesh-official/acme.sh/wiki/Options-and-Params)
|
||||||
|
- **In-code usage**: add usage examples in the help text of `acme.sh` itself.
|
||||||
|
- **README**: add website URLs for new DNS providers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CI and Merge Hygiene
|
||||||
|
|
||||||
|
- All CI checks must pass before merge.
|
||||||
|
- Rebase to the latest `dev` branch frequently -- do not use merge commits.
|
||||||
|
- Enable GitHub Actions on your fork to catch errors early.
|
||||||
|
- Run the [DNS API Test](https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test) workflow for DNS plugins.
|
||||||
|
- For Docker changes, ensure the Dockerfile includes any required dependencies.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Debug Logging
|
||||||
|
|
||||||
|
- Use `_debug2` (not `_debug3` or other levels) unless there is a specific reason for a different level.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Things to Avoid in Reviews
|
||||||
|
|
||||||
|
- Do not comment on purely stylistic issues like spacing or indentation, which should be handled by tools like ShellCheck or `shfmt`.
|
||||||
|
- Do not be overly verbose unless a significant issue is found. Keep feedback concise and actionable.
|
||||||
|
|||||||
33
acme.sh
33
acme.sh
@@ -5285,7 +5285,7 @@ $_authorizations_map"
|
|||||||
_info "Order status is 'ready', let's sleep and retry."
|
_info "Order status is 'ready', let's sleep and retry."
|
||||||
_retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *:" | cut -d : -f 2 | tr -d ' ' | tr -d '\r')
|
_retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *:" | cut -d : -f 2 | tr -d ' ' | tr -d '\r')
|
||||||
_debug "_retryafter" "$_retryafter"
|
_debug "_retryafter" "$_retryafter"
|
||||||
if [ "$_retryafter" ]; then
|
if [ "$_retryafter" ] && [ $_retryafter -gt 0 ]; then
|
||||||
_info "Sleeping for $_retryafter seconds then retrying"
|
_info "Sleeping for $_retryafter seconds then retrying"
|
||||||
_sleep $_retryafter
|
_sleep $_retryafter
|
||||||
else
|
else
|
||||||
@@ -5295,7 +5295,7 @@ $_authorizations_map"
|
|||||||
_info "Order status is 'processing', let's sleep and retry."
|
_info "Order status is 'processing', let's sleep and retry."
|
||||||
_retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *:" | cut -d : -f 2 | tr -d ' ' | tr -d '\r')
|
_retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *:" | cut -d : -f 2 | tr -d ' ' | tr -d '\r')
|
||||||
_debug "_retryafter" "$_retryafter"
|
_debug "_retryafter" "$_retryafter"
|
||||||
if [ "$_retryafter" ]; then
|
if [ "$_retryafter" ] && [ $_retryafter -gt 0 ]; then
|
||||||
_info "Sleeping for $_retryafter seconds then retrying"
|
_info "Sleeping for $_retryafter seconds then retrying"
|
||||||
_sleep $_retryafter
|
_sleep $_retryafter
|
||||||
else
|
else
|
||||||
@@ -5555,16 +5555,17 @@ renew() {
|
|||||||
. "$DOMAIN_CONF"
|
. "$DOMAIN_CONF"
|
||||||
_debug Le_API "$Le_API"
|
_debug Le_API "$Le_API"
|
||||||
|
|
||||||
case "$Le_API" in
|
#don't switch it back
|
||||||
"$CA_LETSENCRYPT_V2_TEST")
|
# case "$Le_API" in
|
||||||
_info "Switching back to $CA_LETSENCRYPT_V2"
|
# "$CA_LETSENCRYPT_V2_TEST")
|
||||||
Le_API="$CA_LETSENCRYPT_V2"
|
# _info "Switching back to $CA_LETSENCRYPT_V2"
|
||||||
;;
|
# Le_API="$CA_LETSENCRYPT_V2"
|
||||||
"$CA_GOOGLE_TEST")
|
# ;;
|
||||||
_info "Switching back to $CA_GOOGLE"
|
# "$CA_GOOGLE_TEST")
|
||||||
Le_API="$CA_GOOGLE"
|
# _info "Switching back to $CA_GOOGLE"
|
||||||
;;
|
# Le_API="$CA_GOOGLE"
|
||||||
esac
|
# ;;
|
||||||
|
# esac
|
||||||
|
|
||||||
if [ "$_server" ]; then
|
if [ "$_server" ]; then
|
||||||
Le_API="$_server"
|
Le_API="$_server"
|
||||||
@@ -5764,6 +5765,9 @@ ${_skipped_msg}
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$_TREAT_SKIP_AS_SUCCESS" ] && [ "$_ret" = "$RENEW_SKIP" ]; then
|
||||||
|
_ret=0
|
||||||
|
fi
|
||||||
return "$_ret"
|
return "$_ret"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6982,6 +6986,7 @@ cron() {
|
|||||||
|
|
||||||
_info "Automatically upgraded to: $VER"
|
_info "Automatically upgraded to: $VER"
|
||||||
fi
|
fi
|
||||||
|
_TREAT_SKIP_AS_SUCCESS="1"
|
||||||
renewAll
|
renewAll
|
||||||
_ret="$?"
|
_ret="$?"
|
||||||
_ACME_IN_CRON=""
|
_ACME_IN_CRON=""
|
||||||
@@ -7229,6 +7234,7 @@ Parameters:
|
|||||||
--local-address <ip> Specifies the standalone/tls server listening address, in case you have multiple ip addresses.
|
--local-address <ip> Specifies the standalone/tls server listening address, in case you have multiple ip addresses.
|
||||||
--listraw Only used for '--list' command, list the certs in raw format.
|
--listraw Only used for '--list' command, list the certs in raw format.
|
||||||
-se, --stop-renew-on-error Only valid for '--renew-all' command. Stop if one cert has error in renewal.
|
-se, --stop-renew-on-error Only valid for '--renew-all' command. Stop if one cert has error in renewal.
|
||||||
|
--treat-skip-as-success Only valid for '--renew-all' command. Treat skipped certs as success, return 0 instead of $RENEW_SKIP.
|
||||||
--insecure Do not check the server certificate, in some devices, the api server's certificate may not be trusted.
|
--insecure Do not check the server certificate, in some devices, the api server's certificate may not be trusted.
|
||||||
--ca-bundle <file> Specifies the path to the CA certificate bundle to verify api server's certificate.
|
--ca-bundle <file> Specifies the path to the CA certificate bundle to verify api server's certificate.
|
||||||
--ca-path <directory> Specifies directory containing CA certificates in PEM format, used by wget or curl.
|
--ca-path <directory> Specifies directory containing CA certificates in PEM format, used by wget or curl.
|
||||||
@@ -7709,6 +7715,9 @@ _process() {
|
|||||||
-f | --force)
|
-f | --force)
|
||||||
FORCE="1"
|
FORCE="1"
|
||||||
;;
|
;;
|
||||||
|
--treat-skip-as-success | --treatskipassuccess)
|
||||||
|
_TREAT_SKIP_AS_SUCCESS="1"
|
||||||
|
;;
|
||||||
--staging | --test)
|
--staging | --test)
|
||||||
STAGE="1"
|
STAGE="1"
|
||||||
;;
|
;;
|
||||||
|
|||||||
202
dnsapi/dns_bh.sh
Executable file
202
dnsapi/dns_bh.sh
Executable file
@@ -0,0 +1,202 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
dns_bh_info='Best-Hosting.cz
|
||||||
|
Site: best-hosting.cz
|
||||||
|
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_bh
|
||||||
|
Options:
|
||||||
|
BH_API_USER API User identifier.
|
||||||
|
BH_API_KEY API Secret key.
|
||||||
|
Issues: github.com/acmesh-official/acme.sh/issues/6854
|
||||||
|
Author: @heximcz
|
||||||
|
'
|
||||||
|
|
||||||
|
BH_Api="https://best-hosting.cz/api/v1"
|
||||||
|
|
||||||
|
######## Public functions #####################
|
||||||
|
|
||||||
|
# Usage: dns_bh_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||||
|
dns_bh_add() {
|
||||||
|
fulldomain=$1
|
||||||
|
txtvalue=$2
|
||||||
|
|
||||||
|
# --- 1. Credentials ---
|
||||||
|
BH_API_USER="${BH_API_USER:-$(_readaccountconf_mutable BH_API_USER)}"
|
||||||
|
BH_API_KEY="${BH_API_KEY:-$(_readaccountconf_mutable BH_API_KEY)}"
|
||||||
|
|
||||||
|
if [ -z "$BH_API_USER" ] || [ -z "$BH_API_KEY" ]; then
|
||||||
|
BH_API_USER=""
|
||||||
|
BH_API_KEY=""
|
||||||
|
_err "You must specify BH_API_USER and BH_API_KEY."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_saveaccountconf_mutable BH_API_USER "$BH_API_USER"
|
||||||
|
_saveaccountconf_mutable BH_API_KEY "$BH_API_KEY"
|
||||||
|
|
||||||
|
# --- 2. Add TXT record ---
|
||||||
|
_info "Adding TXT record for $fulldomain"
|
||||||
|
|
||||||
|
json_payload="{\"fulldomain\":\"$fulldomain\",\"txtvalue\":\"$txtvalue\"}"
|
||||||
|
if ! _bh_rest POST "dns" "$json_payload"; then
|
||||||
|
_err "Failed to add DNS record."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_norm_add=$(printf "%s" "$response" | tr -d '[:space:]')
|
||||||
|
if ! _contains "$_norm_add" '"status":"success"'; then
|
||||||
|
_err "API error: $response"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
record_id=$(printf "%s" "$_norm_add" | _egrep_o '"id":[0-9]+' | cut -d':' -f2)
|
||||||
|
_debug record_id "$record_id"
|
||||||
|
|
||||||
|
if [ -z "$record_id" ]; then
|
||||||
|
_err "Could not parse record ID from response."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Sanitize key — replace dots and hyphens with underscores
|
||||||
|
_conf_key=$(printf "%s" "BH_record_ids_${fulldomain}" | tr '.-' '_')
|
||||||
|
|
||||||
|
# Wildcard support: store space-separated list of IDs
|
||||||
|
# First call stores "111", second call stores "111 222"
|
||||||
|
_existing_ids=$(_readdomainconf "$_conf_key")
|
||||||
|
if [ -z "$_existing_ids" ]; then
|
||||||
|
_savedomainconf "$_conf_key" "$record_id"
|
||||||
|
else
|
||||||
|
_savedomainconf "$_conf_key" "$_existing_ids $record_id"
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info "DNS TXT record added successfully."
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Usage: dns_bh_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||||
|
dns_bh_rm() {
|
||||||
|
fulldomain=$1
|
||||||
|
txtvalue=$2
|
||||||
|
|
||||||
|
# --- 1. Credentials ---
|
||||||
|
BH_API_USER="${BH_API_USER:-$(_readaccountconf_mutable BH_API_USER)}"
|
||||||
|
BH_API_KEY="${BH_API_KEY:-$(_readaccountconf_mutable BH_API_KEY)}"
|
||||||
|
|
||||||
|
if [ -z "$BH_API_USER" ] || [ -z "$BH_API_KEY" ]; then
|
||||||
|
BH_API_USER=""
|
||||||
|
BH_API_KEY=""
|
||||||
|
_err "You must specify BH_API_USER and BH_API_KEY."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Sanitize key — same as in add
|
||||||
|
_conf_key=$(printf "%s" "BH_record_ids_${fulldomain}" | tr '.-' '_')
|
||||||
|
|
||||||
|
# --- 2. Load stored record ID(s) ---
|
||||||
|
_existing_ids=$(_readdomainconf "$_conf_key")
|
||||||
|
_debug _existing_ids "$_existing_ids"
|
||||||
|
|
||||||
|
if [ -z "$_existing_ids" ]; then
|
||||||
|
_err "Could not find record ID for $fulldomain."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
record_id=""
|
||||||
|
_remaining_ids=""
|
||||||
|
|
||||||
|
# Find the record ID that matches both the name and txtvalue
|
||||||
|
for _id in $_existing_ids; do
|
||||||
|
if ! _bh_rest GET "dns/$_id"; then
|
||||||
|
_debug "Failed to query record id $_id, skipping."
|
||||||
|
|
||||||
|
# Keep it in the list so a later run can try again
|
||||||
|
if [ -z "$_remaining_ids" ]; then
|
||||||
|
_remaining_ids="$_id"
|
||||||
|
else
|
||||||
|
_remaining_ids="$_remaining_ids $_id"
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
_match_name=0
|
||||||
|
_match_content=0
|
||||||
|
_norm_response=$(printf "%s" "$response" | tr -d '[:space:]')
|
||||||
|
|
||||||
|
case "$_norm_response" in
|
||||||
|
*"\"name\":\"$fulldomain\""*)
|
||||||
|
_match_name=1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
case "$_norm_response" in
|
||||||
|
*"\"content\":\"$txtvalue\""*)
|
||||||
|
_match_content=1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "$_match_name" -eq 1 ] && [ "$_match_content" -eq 1 ]; then
|
||||||
|
record_id="$_id"
|
||||||
|
_debug "Matched record id" "$record_id"
|
||||||
|
# Do not add this ID to _remaining_ids; it will be deleted
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Not a match — keep ID for potential future cleanups
|
||||||
|
if [ -z "$_remaining_ids" ]; then
|
||||||
|
_remaining_ids="$_id"
|
||||||
|
else
|
||||||
|
_remaining_ids="$_remaining_ids $_id"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$record_id" ]; then
|
||||||
|
_err "Could not find matching TXT record for $fulldomain with the given value."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 3. Delete record ---
|
||||||
|
_info "Removing TXT record for $fulldomain"
|
||||||
|
|
||||||
|
if ! _bh_rest DELETE "dns/$record_id"; then
|
||||||
|
_err "Failed to remove DNS record."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update stored list — remove used ID
|
||||||
|
if [ -z "$_remaining_ids" ]; then
|
||||||
|
_cleardomainconf "$_conf_key"
|
||||||
|
else
|
||||||
|
_savedomainconf "$_conf_key" "$_remaining_ids"
|
||||||
|
fi
|
||||||
|
|
||||||
|
_info "DNS TXT record removed successfully."
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#################### Private functions #####################
|
||||||
|
|
||||||
|
_bh_rest() {
|
||||||
|
m="$1"
|
||||||
|
ep="$2"
|
||||||
|
data="$3"
|
||||||
|
_debug "$ep"
|
||||||
|
|
||||||
|
_credentials="$(printf "%s:%s" "$BH_API_USER" "$BH_API_KEY" | _base64)"
|
||||||
|
|
||||||
|
export _H1="Authorization: Basic $_credentials"
|
||||||
|
export _H2="Content-Type: application/json"
|
||||||
|
export _H3="Accept: application/json"
|
||||||
|
|
||||||
|
if [ "$m" = "GET" ]; then
|
||||||
|
response="$(_get "$BH_Api/$ep")"
|
||||||
|
else
|
||||||
|
_debug data "$data"
|
||||||
|
response="$(_post "$data" "$BH_Api/$ep" "" "$m")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$?" != "0" ]; then
|
||||||
|
_err "Error calling $m $BH_Api/$ep"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_debug2 response "$response"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
201
dnsapi/dns_czechia.sh
Normal file
201
dnsapi/dns_czechia.sh
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# dns_czechia.sh - CZECHIA.COM/ZONER DNS API for acme.sh (DNS-01)
|
||||||
|
#
|
||||||
|
# Documentation: https://api.czechia.com/swagger/index.html
|
||||||
|
|
||||||
|
#shellcheck disable=SC2034
|
||||||
|
dns_czechia_info='[
|
||||||
|
{"name":"CZ_AuthorizationToken","usage":"Your API token from CZECHIA.COM/Zoner administration.","required":"1"},
|
||||||
|
{"name":"CZ_Zones","usage":"Managed zones separated by comma or space (e.g. \"example.com\").","required":"1"},
|
||||||
|
{"name":"CZ_API_BASE","usage":"Defaults to https://api.czechia.com","required":"0"}
|
||||||
|
]'
|
||||||
|
|
||||||
|
dns_czechia_add() {
|
||||||
|
fulldomain="$1"
|
||||||
|
txtvalue="$2"
|
||||||
|
|
||||||
|
_debug "dns_czechia_add fulldomain='$fulldomain'"
|
||||||
|
|
||||||
|
if [ -z "$fulldomain" ] || [ -z "$txtvalue" ]; then
|
||||||
|
_err "dns_czechia_add: missing fulldomain or txtvalue"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_czechia_load_conf || return 1
|
||||||
|
|
||||||
|
_current_zone=$(_czechia_pick_zone "$fulldomain")
|
||||||
|
if [ -z "$_current_zone" ]; then
|
||||||
|
_err "No matching zone found for $fulldomain. Please check CZ_Zones."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_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."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_url="$CZ_API_BASE/api/DNS/$_cz/TXT"
|
||||||
|
_fd=$(printf "%s" "$fulldomain" | _lower_case | sed 's/\.$//')
|
||||||
|
|
||||||
|
if [ "$_fd" = "$_cz" ]; then
|
||||||
|
_h="@"
|
||||||
|
else
|
||||||
|
# Remove the literal ".<zone>" suffix from _fd, if present
|
||||||
|
_h=${_fd%."$_cz"}
|
||||||
|
[ "$_h" = "$_fd" ] && _h="@"
|
||||||
|
fi
|
||||||
|
[ -z "$_h" ] && _h="@"
|
||||||
|
|
||||||
|
_info "Adding TXT record for $_h in zone $_cz"
|
||||||
|
|
||||||
|
_h_esc=$(printf "%s" "$_h" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
||||||
|
_txt_esc=$(printf "%s" "$txtvalue" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
||||||
|
_body="{\"hostName\":\"$_h_esc\",\"text\":\"$_txt_esc\",\"ttl\":300,\"publishZone\":1}"
|
||||||
|
|
||||||
|
_debug "URL: $_url"
|
||||||
|
_debug "Body: $_body"
|
||||||
|
|
||||||
|
export _H1="Content-Type: application/json"
|
||||||
|
export _H2="AuthorizationToken: $_tk"
|
||||||
|
|
||||||
|
_res="$(_post "$_body" "$_url" "" "POST")"
|
||||||
|
_post_exit="$?"
|
||||||
|
_debug2 "Response: $_res"
|
||||||
|
|
||||||
|
if [ "$_post_exit" -ne 0 ]; then
|
||||||
|
_err "API request failed. exit code $_post_exit"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if _contains "$_res" "already exists"; then
|
||||||
|
_info "Record already exists, skipping."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
_nres="$(_normalizeJson "$_res")"
|
||||||
|
if [ "$?" -ne 0 ] || [ -z "$_nres" ]; then
|
||||||
|
_nres="$_res"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if _contains "$_nres" "\"status\":4" || _contains "$_nres" "\"status\":5" || _contains "$_nres" "\"errors\""; then
|
||||||
|
_err "API error: $_res"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
dns_czechia_rm() {
|
||||||
|
fulldomain="$1"
|
||||||
|
txtvalue="$2"
|
||||||
|
|
||||||
|
_debug "dns_czechia_rm fulldomain='$fulldomain'"
|
||||||
|
|
||||||
|
if [ -z "$fulldomain" ] || [ -z "$txtvalue" ]; then
|
||||||
|
_err "dns_czechia_rm: missing fulldomain or txtvalue"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_czechia_load_conf || return 1
|
||||||
|
|
||||||
|
_current_zone=$(_czechia_pick_zone "$fulldomain")
|
||||||
|
if [ -z "$_current_zone" ]; then
|
||||||
|
_err "No matching zone found for $fulldomain. Please check CZ_Zones configuration."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_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."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_url="$CZ_API_BASE/api/DNS/$_cz/TXT"
|
||||||
|
_fd=$(printf "%s" "$fulldomain" | _lower_case | sed 's/\.$//')
|
||||||
|
|
||||||
|
if [ "$_fd" = "$_cz" ]; then
|
||||||
|
_h="@"
|
||||||
|
else
|
||||||
|
_h=$(printf "%s" "$_fd" | sed "s/\.$_cz$//")
|
||||||
|
[ "$_h" = "$_fd" ] && _h="@"
|
||||||
|
fi
|
||||||
|
[ -z "$_h" ] && _h="@"
|
||||||
|
|
||||||
|
_h_esc=$(printf "%s" "$_h" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
||||||
|
_txt_esc=$(printf "%s" "$txtvalue" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
||||||
|
_body="{\"hostName\":\"$_h_esc\",\"text\":\"$_txt_esc\",\"ttl\":300,\"publishZone\":1}"
|
||||||
|
|
||||||
|
_debug "URL: $_url"
|
||||||
|
_debug "Body: $_body"
|
||||||
|
|
||||||
|
export _H1="Content-Type: application/json"
|
||||||
|
export _H2="AuthorizationToken: $_tk"
|
||||||
|
|
||||||
|
_res="$(_post "$_body" "$_url" "" "DELETE")"
|
||||||
|
_post_exit="$?"
|
||||||
|
_debug2 "Response: $_res"
|
||||||
|
|
||||||
|
if [ "$_post_exit" -ne 0 ]; then
|
||||||
|
_err "CZECHIA DNS API DELETE request failed for $_fd: exit code $_post_exit, response: $_res"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_res_normalized=$(printf '%s' "$_res" | _normalizeJson)
|
||||||
|
|
||||||
|
if _contains "$_res_normalized" '"isError":true'; then
|
||||||
|
_err "CZECHIA DNS API reported an error while deleting TXT for $_fd: $_res"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_czechia_load_conf() {
|
||||||
|
CZ_AuthorizationToken="${CZ_AuthorizationToken:-$(_readaccountconf_mutable CZ_AuthorizationToken)}"
|
||||||
|
if [ -z "$CZ_AuthorizationToken" ]; then
|
||||||
|
_err "Missing CZ_AuthorizationToken"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CZ_Zones="${CZ_Zones:-$(_readaccountconf_mutable CZ_Zones)}"
|
||||||
|
if [ -z "$CZ_Zones" ]; then
|
||||||
|
_err "Missing CZ_Zones"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CZ_API_BASE="${CZ_API_BASE:-$(_readaccountconf_mutable CZ_API_BASE)}"
|
||||||
|
[ -z "$CZ_API_BASE" ] && CZ_API_BASE="https://api.czechia.com"
|
||||||
|
|
||||||
|
_saveaccountconf_mutable CZ_AuthorizationToken "$CZ_AuthorizationToken"
|
||||||
|
_saveaccountconf_mutable CZ_Zones "$CZ_Zones"
|
||||||
|
_saveaccountconf_mutable CZ_API_BASE "$CZ_API_BASE"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_czechia_pick_zone() {
|
||||||
|
_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/[[:space:]]//g; s/\.$//')
|
||||||
|
[ -z "$_clean_z" ] && continue
|
||||||
|
|
||||||
|
case "$_fd" in
|
||||||
|
"$_clean_z" | *."$_clean_z")
|
||||||
|
if [ ${#_clean_z} -gt ${#_best_zone} ]; then
|
||||||
|
_best_zone="$_clean_z"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
printf "%s" "$_best_zone"
|
||||||
|
}
|
||||||
220
dnsapi/dns_subreg.sh
Normal file
220
dnsapi/dns_subreg.sh
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
dns_subreg_info='Subreg.cz
|
||||||
|
Site: subreg.cz
|
||||||
|
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_subreg
|
||||||
|
Options:
|
||||||
|
SUBREG_API_USERNAME API username
|
||||||
|
SUBREG_API_PASSWORD API password
|
||||||
|
Issues: github.com/acmesh-official/acme.sh/issues/6835
|
||||||
|
Author: Tomas Pavlic <https://github.com/tomaspavlic>
|
||||||
|
'
|
||||||
|
|
||||||
|
# Subreg SOAP API
|
||||||
|
# https://subreg.cz/manual/
|
||||||
|
|
||||||
|
SUBREG_API_URL="https://soap.subreg.cz/cmd.php"
|
||||||
|
|
||||||
|
######## Public functions #####################
|
||||||
|
|
||||||
|
# Usage: dns_subreg_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||||
|
dns_subreg_add() {
|
||||||
|
fulldomain=$1
|
||||||
|
txtvalue=$2
|
||||||
|
|
||||||
|
SUBREG_API_USERNAME="${SUBREG_API_USERNAME:-$(_readaccountconf_mutable SUBREG_API_USERNAME)}"
|
||||||
|
SUBREG_API_PASSWORD="${SUBREG_API_PASSWORD:-$(_readaccountconf_mutable SUBREG_API_PASSWORD)}"
|
||||||
|
if [ -z "$SUBREG_API_USERNAME" ] || [ -z "$SUBREG_API_PASSWORD" ]; then
|
||||||
|
_err "SUBREG_API_USERNAME and SUBREG_API_PASSWORD are not set."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_saveaccountconf_mutable SUBREG_API_USERNAME "$SUBREG_API_USERNAME"
|
||||||
|
_saveaccountconf_mutable SUBREG_API_PASSWORD "$SUBREG_API_PASSWORD"
|
||||||
|
|
||||||
|
if ! _subreg_login; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! _get_root "$fulldomain"; then
|
||||||
|
_err "Cannot determine root domain for: $fulldomain"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_debug _sub_domain "$_sub_domain"
|
||||||
|
_debug _domain "$_domain"
|
||||||
|
|
||||||
|
_subreg_soap "Add_DNS_Record" "<domain>$_domain</domain><record><name>$_sub_domain</name><type>TXT</type><content>$txtvalue</content><prio>0</prio><ttl>120</ttl></record>"
|
||||||
|
if _subreg_ok; then
|
||||||
|
_record_id="$(_subreg_map_get record_id)"
|
||||||
|
|
||||||
|
if [ -z "$_record_id" ]; then
|
||||||
|
_err "Subreg API did not return a record_id for TXT record on $fulldomain"
|
||||||
|
_err "$response"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_savedomainconf "$(_subreg_record_id_key "$txtvalue")" "$_record_id"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
_err "Failed to add TXT record."
|
||||||
|
_err "$response"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Usage: dns_subreg_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||||
|
dns_subreg_rm() {
|
||||||
|
fulldomain=$1
|
||||||
|
txtvalue=$2
|
||||||
|
|
||||||
|
SUBREG_API_USERNAME="${SUBREG_API_USERNAME:-$(_readaccountconf_mutable SUBREG_API_USERNAME)}"
|
||||||
|
SUBREG_API_PASSWORD="${SUBREG_API_PASSWORD:-$(_readaccountconf_mutable SUBREG_API_PASSWORD)}"
|
||||||
|
if [ -z "$SUBREG_API_USERNAME" ] || [ -z "$SUBREG_API_PASSWORD" ]; then
|
||||||
|
_err "SUBREG_API_USERNAME and SUBREG_API_PASSWORD are not set."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! _subreg_login; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! _get_root "$fulldomain"; then
|
||||||
|
_err "Cannot determine root domain for: $fulldomain"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_debug _sub_domain "$_sub_domain"
|
||||||
|
_debug _domain "$_domain"
|
||||||
|
|
||||||
|
_record_id="$(_readdomainconf "$(_subreg_record_id_key "$txtvalue")")"
|
||||||
|
if [ -z "$_record_id" ]; then
|
||||||
|
_err "Could not find saved record ID for $fulldomain"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_debug "Deleting record ID: $_record_id"
|
||||||
|
_subreg_soap "Delete_DNS_Record" "<domain>$_domain</domain><record><id>$_record_id</id></record>"
|
||||||
|
if _subreg_ok; then
|
||||||
|
|
||||||
|
_cleardomainconf "$(_subreg_record_id_key "$txtvalue")"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
_err "Failed to delete TXT record."
|
||||||
|
_err "$response"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#################### Private functions #####################
|
||||||
|
|
||||||
|
# Build a domain-conf key for storing the record ID of a given TXT value.
|
||||||
|
# Base64url chars include '-' which is invalid in shell variable names, so replace with '_'.
|
||||||
|
_subreg_record_id_key() {
|
||||||
|
printf 'SUBREG_RECORD_ID_%s' "$(printf '%s' "$1" | tr '-' '_')"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if the current $response contains a successful status in the ns2:Map format:
|
||||||
|
# <item><key ...>status</key><value ...>ok</value></item>
|
||||||
|
_subreg_ok() {
|
||||||
|
[ "$(_subreg_map_get status)" = "ok" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract the value for a given key from the ns2:Map response.
|
||||||
|
# Usage: _subreg_map_get keyname
|
||||||
|
# Reads from $response
|
||||||
|
_subreg_map_get() {
|
||||||
|
_key="$1"
|
||||||
|
echo "$response" | tr -d '\n\r' | _egrep_o ">${_key}</key><value[^>]*>[^<]*</value>" | sed 's/.*<value[^>]*>//;s/<\/value>//'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Login and store session token in _subreg_ssid
|
||||||
|
_subreg_login() {
|
||||||
|
_debug "Logging in to Subreg API as $SUBREG_API_USERNAME"
|
||||||
|
_subreg_soap_noauth "Login" "<login>$SUBREG_API_USERNAME</login><password>$SUBREG_API_PASSWORD</password>"
|
||||||
|
if ! _subreg_ok; then
|
||||||
|
_err "Subreg login failed."
|
||||||
|
_err "$response"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
_subreg_ssid="$(_subreg_map_get ssid)"
|
||||||
|
if [ -z "$_subreg_ssid" ]; then
|
||||||
|
_err "Subreg login: could not extract session token (ssid)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
_debug "Subreg login: session token (ssid) obtained"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# _get_root _acme-challenge.www.domain.com
|
||||||
|
# returns _sub_domain and _domain
|
||||||
|
_get_root() {
|
||||||
|
domain=$1
|
||||||
|
i=1
|
||||||
|
p=1
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||||
|
if [ -z "$h" ]; then
|
||||||
|
_err "Unable to retrieve DNS zone matching domain: $domain"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_subreg_soap "Get_DNS_Zone" "<domain>$h</domain>"
|
||||||
|
|
||||||
|
if _subreg_ok; then
|
||||||
|
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||||
|
_domain="$h"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
p=$i
|
||||||
|
i=$(_math "$i" + 1)
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send a SOAP request without authentication (used for Login)
|
||||||
|
# _subreg_soap_noauth command inner_xml
|
||||||
|
_subreg_build_soap() {
|
||||||
|
_cmd="$1"
|
||||||
|
_data_inner="$2"
|
||||||
|
|
||||||
|
_soap_body="<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
||||||
|
<SOAP-ENV:Envelope
|
||||||
|
xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"
|
||||||
|
xmlns:ns1=\"http://soap.subreg.cz/soap\"
|
||||||
|
xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"
|
||||||
|
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
|
||||||
|
xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\"
|
||||||
|
SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">
|
||||||
|
<SOAP-ENV:Body>
|
||||||
|
<ns1:${_cmd}>
|
||||||
|
<data>
|
||||||
|
${_data_inner}
|
||||||
|
</data>
|
||||||
|
</ns1:${_cmd}>
|
||||||
|
</SOAP-ENV:Body>
|
||||||
|
</SOAP-ENV:Envelope>"
|
||||||
|
|
||||||
|
export _H1="Content-Type: text/xml"
|
||||||
|
export _H2="SOAPAction: http://soap.subreg.cz/soap#${_cmd}"
|
||||||
|
response="$(_post "$_soap_body" "$SUBREG_API_URL" "" "POST" "text/xml")"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send an authenticated SOAP request (requires _subreg_ssid to be set)
|
||||||
|
# _subreg_soap command inner_xml
|
||||||
|
_subreg_soap_noauth() {
|
||||||
|
_cmd="$1"
|
||||||
|
_inner="$2"
|
||||||
|
|
||||||
|
_subreg_build_soap "$_cmd" "$_inner"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send an authenticated SOAP request (requires _subreg_ssid to be set)
|
||||||
|
# _subreg_soap command inner_xml
|
||||||
|
_subreg_soap() {
|
||||||
|
_cmd="$1"
|
||||||
|
_inner="$2"
|
||||||
|
_inner_with_ssid="<ssid>${_subreg_ssid}</ssid>${_inner}"
|
||||||
|
|
||||||
|
_subreg_build_soap "$_cmd" "$_inner_with_ssid"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user