mirror of
https://github.com/acmesh-official/acme.sh.git
synced 2026-08-13 12:33:30 +02:00
Some checks are pending
Apache / Apache (push) Waiting to run
DNS / CheckToken (push) Waiting to run
DNS / Fail (push) Blocked by required conditions
DNS / Docker (push) Blocked by required conditions
DNS / MacOS (push) Blocked by required conditions
DNS / Windows (push) Blocked by required conditions
DNS / FreeBSD (push) Blocked by required conditions
DNS / GhostBSD (push) Blocked by required conditions
DNS / OpenBSD (push) Blocked by required conditions
DNS / NetBSD (push) Blocked by required conditions
DNS / DragonFlyBSD (push) Blocked by required conditions
DNS / MidnightBSD (push) Blocked by required conditions
DNS / Solaris (push) Blocked by required conditions
DNS / Omnios (push) Blocked by required conditions
DNS / OpenIndiana (push) Blocked by required conditions
DNS / Tribblix (push) Blocked by required conditions
DNS / Haiku (push) Blocked by required conditions
DragonFlyBSD / DragonFlyBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
FreeBSD / FreeBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
FreeBSD / FreeBSD (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
GhostBSD / GhostBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
GhostBSD / GhostBSD (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
Haiku / Haiku (, , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
Haiku / Haiku (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
Linux / Linux (almalinux:latest) (push) Waiting to run
Linux / Linux (alpine:latest) (push) Waiting to run
Linux / Linux (archlinux:latest) (push) Waiting to run
Linux / Linux (debian:latest) (push) Waiting to run
Linux / Linux (fedora:latest) (push) Waiting to run
Linux / Linux (gentoo/stage3) (push) Waiting to run
Linux / Linux (kalilinux/kali) (push) Waiting to run
Linux / Linux (opensuse/leap:latest) (push) Waiting to run
Linux / Linux (oraclelinux:8) (push) Waiting to run
Linux / Linux (ubuntu:latest) (push) Waiting to run
MacOS / MacOS (, , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
MidnightBSD / MidnightBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
NetBSD / NetBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
Nginx / Nginx (push) Waiting to run
Omnios / Omnios (, , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
Omnios / Omnios (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
OpenBSD / OpenBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
OpenBSD / OpenBSD (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
OpenIndiana / OpenIndiana (, , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
OpenIndiana / OpenIndiana (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
PebbleStrict / PebbleStrict (push) Waiting to run
PebbleStrict / PebbleStrict_IPCert (push) Waiting to run
Solaris / Solaris (, , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
Solaris / Solaris (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
Tribblix / Tribblix (, , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
Tribblix / Tribblix (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
Ubuntu / Ubuntu (, , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
Ubuntu / Ubuntu (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
Ubuntu / Ubuntu (Smallstep Intermediate CA, Smallstep Intermediate CA, , 1, https://localhost:9000/acme/acme/directory, ) (push) Waiting to run
Ubuntu / Ubuntu (Smallstep Intermediate CA, Smallstep Intermediate CA, , 1, https://localhost:9000/acme/acme/directory, 1, , 172.17.0.1) (push) Waiting to run
Ubuntu / Ubuntu (ZeroSSL RSA DV SSL CA 2, ZeroSSL ECC DV SSL CA 2, githubtest@acme.sh, ZeroSSL.com, ) (push) Waiting to run
Windows / Windows (, , , LetsEncrypt.org_test, (STAGING)) (push) Waiting to run
Build DockerHub / CheckToken (push) Waiting to run
Build DockerHub / build (push) Blocked by required conditions
Shellcheck / ShellCheck (push) Waiting to run
Shellcheck / shfmt (push) Waiting to run
dnsapi/deploy: remove POSIX character classes from sed/grep patterns Solaris /usr/bin/sed and /usr/bin/grep parse [[:space:]] etc. as a literal bracket set and silently mis-match. Replace with [ ]* for JSON matching, a printf-tab bracket for user-input trimming, and [0-9] for digits; also drop GNU-only sed -r/-E in rage4, selfhost and selectel, and reuse _strip_blank_lines in byteplus_alb.
373 lines
10 KiB
Bash
373 lines
10 KiB
Bash
#!/usr/bin/env sh
|
|
|
|
# shellcheck disable=SC2034
|
|
dns_bhosted_info='bHosted.nl DNS API
|
|
Site: bHosted.nl
|
|
Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_bhosted
|
|
Options:
|
|
BHOSTED_Username API username
|
|
BHOSTED_Password API password (MD5 hash like bHosted web services example)
|
|
BHOSTED_TTL TTL for TXT record (default: 300)
|
|
BHOSTED_SLD Optional override (useful for multi-part TLDs like co.uk)
|
|
BHOSTED_TLD Optional override (useful for multi-part TLDs like co.uk)
|
|
Notes:
|
|
- Plugin uses addrecord + delrecord for DNS-01 challenge
|
|
- Record ID is retrieved from addrecord XML response and cached for cleanup
|
|
'
|
|
|
|
BHOSTED_API_ROOT="https://webservices.bhosted.com/dns"
|
|
|
|
############ Public functions #####################
|
|
|
|
# Usage: dns_bhosted_add _acme-challenge.www.example.com "txt-value"
|
|
dns_bhosted_add() {
|
|
fulldomain="$1"
|
|
txtvalue="$2"
|
|
|
|
_debug "fulldomain" "$fulldomain"
|
|
_debug "txtvalue" "$txtvalue"
|
|
|
|
_bhosted_load_credentials || return 1
|
|
_bhosted_get_root "$fulldomain" || return 1
|
|
|
|
_info "Adding TXT record: ${_bhosted_name}.${_domain}"
|
|
|
|
BHOSTED_TTL="${BHOSTED_TTL:-$(_readaccountconf_mutable BHOSTED_TTL)}"
|
|
BHOSTED_TTL="${BHOSTED_TTL:-300}"
|
|
_saveaccountconf_mutable BHOSTED_TTL "$BHOSTED_TTL"
|
|
|
|
_bhosted_api_add_txt "$_bhosted_sld" "$_bhosted_tld" "$_bhosted_name" "$txtvalue" "$BHOSTED_TTL" || return 1
|
|
|
|
# Extract and cache record id in-memory for cleanup in this run
|
|
_rec_id="$(_bhosted_extract_id "$response")"
|
|
if [ -n "$_rec_id" ]; then
|
|
_hash="$(_bhosted_cache_hash "$fulldomain" "$txtvalue")"
|
|
_debug "_hash" "$_hash"
|
|
_debug "_rec_id" "$_rec_id"
|
|
_bhosted_mem_set_id "$_hash" "$_rec_id"
|
|
else
|
|
_err "TXT record added but no record id found in response."
|
|
_err "Cleanup may fail unless bHosted addrecord returns <id>...</id>."
|
|
_debug2 "add response" "$response"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Usage: dns_bhosted_rm _acme-challenge.www.example.com "txt-value"
|
|
dns_bhosted_rm() {
|
|
fulldomain="$1"
|
|
txtvalue="$2"
|
|
|
|
_debug "fulldomain" "$fulldomain"
|
|
_debug "txtvalue" "$txtvalue"
|
|
|
|
_bhosted_load_credentials || return 1
|
|
_bhosted_get_root "$fulldomain" || return 1
|
|
|
|
_hash="$(_bhosted_cache_hash "$fulldomain" "$txtvalue")"
|
|
_rec_id="$(_bhosted_mem_get_id "$_hash")"
|
|
|
|
if [ -z "$_rec_id" ]; then
|
|
_err "No cached bHosted record id found for cleanup."
|
|
_err "Please delete TXT manually in bHosted DNS for: ${_bhosted_name}.${_domain}"
|
|
return 1
|
|
fi
|
|
|
|
_info "Removing TXT record id=${_rec_id}: ${_bhosted_name}.${_domain}"
|
|
_bhosted_api_del_record "$_bhosted_sld" "$_bhosted_tld" "$_rec_id" || return 1
|
|
|
|
return 0
|
|
}
|
|
|
|
######## Private functions #####################
|
|
|
|
_bhosted_load_credentials() {
|
|
BHOSTED_Username="${BHOSTED_Username:-$(_readaccountconf_mutable BHOSTED_Username)}"
|
|
BHOSTED_Password="${BHOSTED_Password:-$(_readaccountconf_mutable BHOSTED_Password)}"
|
|
|
|
if [ -z "$BHOSTED_Username" ] || [ -z "$BHOSTED_Password" ]; then
|
|
BHOSTED_Username=""
|
|
BHOSTED_Password=""
|
|
_err "You didn't specify bHosted credentials."
|
|
_err "Please export BHOSTED_Username and BHOSTED_Password (MD5 hash)."
|
|
return 1
|
|
fi
|
|
|
|
_saveaccountconf_mutable BHOSTED_Username "$BHOSTED_Username"
|
|
_saveaccountconf_mutable BHOSTED_Password "$BHOSTED_Password"
|
|
|
|
return 0
|
|
}
|
|
|
|
# Determine root zone and host part
|
|
# Supports simple domains automatically (example.com, example.nl)
|
|
# For multi-part TLDs (example.co.uk), set:
|
|
# BHOSTED_SLD=example
|
|
# BHOSTED_TLD=co.uk
|
|
_bhosted_get_root() {
|
|
domain="$1"
|
|
|
|
BHOSTED_SLD="${BHOSTED_SLD:-$(_readdomainconf BHOSTED_SLD)}"
|
|
BHOSTED_TLD="${BHOSTED_TLD:-$(_readdomainconf BHOSTED_TLD)}"
|
|
|
|
if [ -n "$BHOSTED_SLD" ] && [ -n "$BHOSTED_TLD" ]; then
|
|
_savedomainconf BHOSTED_SLD "$BHOSTED_SLD"
|
|
_savedomainconf BHOSTED_TLD "$BHOSTED_TLD"
|
|
|
|
_domain="${BHOSTED_SLD}.${BHOSTED_TLD}"
|
|
case "$domain" in
|
|
*."$_domain") ;;
|
|
"$_domain") ;;
|
|
*)
|
|
_err "BHOSTED_SLD/BHOSTED_TLD do not match requested domain: $domain"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
_bhosted_sld="$BHOSTED_SLD"
|
|
_bhosted_tld="$BHOSTED_TLD"
|
|
_bhosted_name="${domain%."$_domain"}"
|
|
if [ "$_bhosted_name" = "$domain" ]; then
|
|
_bhosted_name=""
|
|
fi
|
|
|
|
[ -n "$_bhosted_name" ] || _bhosted_name="@"
|
|
|
|
_debug "_domain" "$_domain"
|
|
_debug "_bhosted_sld" "$_bhosted_sld"
|
|
_debug "_bhosted_tld" "$_bhosted_tld"
|
|
_debug "_bhosted_name" "$_bhosted_name"
|
|
return 0
|
|
fi
|
|
|
|
# Auto-parse: assume last label = tld, label before = sld
|
|
# Works for .nl / .com / .org etc.
|
|
_bhosted_tld="$(printf "%s" "$domain" | awk -F. '{print $NF}')"
|
|
_bhosted_sld="$(printf "%s" "$domain" | awk -F. '{print $(NF-1)}')"
|
|
|
|
if [ -z "$_bhosted_sld" ] || [ -z "$_bhosted_tld" ]; then
|
|
_err "Could not parse SLD/TLD from domain: $domain"
|
|
return 1
|
|
fi
|
|
|
|
_domain="${_bhosted_sld}.${_bhosted_tld}"
|
|
_bhosted_name="${domain%."$_domain"}"
|
|
if [ "$_bhosted_name" = "$domain" ]; then
|
|
_bhosted_name=""
|
|
fi
|
|
|
|
[ -n "$_bhosted_name" ] || _bhosted_name="@"
|
|
|
|
_debug "_domain" "$_domain"
|
|
_debug "_bhosted_sld" "$_bhosted_sld"
|
|
_debug "_bhosted_tld" "$_bhosted_tld"
|
|
_debug "_bhosted_name" "$_bhosted_name"
|
|
|
|
return 0
|
|
}
|
|
|
|
_bhosted_api_add_txt() {
|
|
_sld="$1"
|
|
_tld="$2"
|
|
_name="$3"
|
|
_content="$4"
|
|
_ttl="$5"
|
|
|
|
_u_user="$(printf "%s" "$BHOSTED_Username" | _url_encode)"
|
|
_u_pass="$(printf "%s" "$BHOSTED_Password" | _url_encode)"
|
|
_u_sld="$(printf "%s" "$_sld" | _url_encode)"
|
|
_u_tld="$(printf "%s" "$_tld" | _url_encode)"
|
|
_u_name="$(printf "%s" "$_name" | _url_encode)"
|
|
_u_content="$(printf "%s" "$_content" | _url_encode)"
|
|
_u_ttl="$(printf "%s" "$_ttl" | _url_encode)"
|
|
|
|
_data="user=${_u_user}&password=${_u_pass}&tld=${_u_tld}&sld=${_u_sld}&type=TXT&name=${_u_name}&content=${_u_content}&ttl=${_u_ttl}"
|
|
|
|
_debug "bHosted add endpoint" "${BHOSTED_API_ROOT}/addrecord"
|
|
response="$(_post "$_data" "${BHOSTED_API_ROOT}/addrecord")"
|
|
_ret="$?"
|
|
|
|
_debug2 "bHosted add response" "$response"
|
|
|
|
if [ "$_ret" != "0" ]; then
|
|
_err "bHosted addrecord request failed"
|
|
return 1
|
|
fi
|
|
|
|
if _bhosted_response_has_error "$response"; then
|
|
_err "bHosted addrecord returned an error"
|
|
_debug2 "response" "$response"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
_bhosted_api_del_record() {
|
|
_sld="$1"
|
|
_tld="$2"
|
|
_id="$3"
|
|
|
|
_u_user="$(printf "%s" "$BHOSTED_Username" | _url_encode)"
|
|
_u_pass="$(printf "%s" "$BHOSTED_Password" | _url_encode)"
|
|
_u_sld="$(printf "%s" "$_sld" | _url_encode)"
|
|
_u_tld="$(printf "%s" "$_tld" | _url_encode)"
|
|
_u_id="$(printf "%s" "$_id" | _url_encode)"
|
|
|
|
_url="${BHOSTED_API_ROOT}/delrecord"
|
|
_data="user=${_u_user}&password=${_u_pass}&tld=${_u_tld}&sld=${_u_sld}&id=${_u_id}"
|
|
|
|
_debug "bHosted delete endpoint" "$_url"
|
|
response="$(_post "$_data" "$_url")"
|
|
_ret="$?"
|
|
|
|
_debug2 "bHosted delete response" "$response"
|
|
|
|
if [ "$_ret" != "0" ]; then
|
|
_err "bHosted delrecord request failed"
|
|
return 1
|
|
fi
|
|
|
|
if _bhosted_response_has_error "$response"; then
|
|
_err "bHosted delrecord returned an error"
|
|
_debug2 "response" "$response"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Extract XML tag value from response, e.g. <id>12345</id>
|
|
_bhosted_xml_value() {
|
|
_tag="$1"
|
|
_resp="$2"
|
|
|
|
# Flatten response to simplify parsing
|
|
_flat="$(printf "%s" "$_resp" | tr -d '\r\n\t')"
|
|
printf "%s" "$_flat" | sed -n "s:.*<${_tag}>\\([^<]*\\)</${_tag}>.*:\\1:p" | _head_n 1
|
|
}
|
|
|
|
# Return code convention:
|
|
# return 0 => response HAS error
|
|
# return 1 => response has NO error (success)
|
|
_bhosted_response_has_error() {
|
|
_resp="$1"
|
|
|
|
# Empty response = error
|
|
if [ -z "$_resp" ]; then
|
|
_debug "Empty API response"
|
|
return 0
|
|
fi
|
|
|
|
# Prefer explicit bHosted XML response fields
|
|
if _contains "$_resp" "<response>"; then
|
|
_errors="$(_bhosted_xml_value "errors" "$_resp")"
|
|
_done="$(_bhosted_xml_value "done" "$_resp")"
|
|
_subcommand="$(_bhosted_xml_value "subcommand" "$_resp")"
|
|
_id="$(_bhosted_xml_value "id" "$_resp")"
|
|
|
|
_debug "bHosted XML subcommand" "$_subcommand"
|
|
_debug "bHosted XML id" "$_id"
|
|
_debug "bHosted XML errors" "$_errors"
|
|
_debug "bHosted XML done" "$_done"
|
|
|
|
# Success according to provided format
|
|
if [ "$_errors" = "0" ] && [ "$_done" = "true" ]; then
|
|
return 1
|
|
fi
|
|
|
|
_debug "bHosted XML indicates failure"
|
|
return 0
|
|
fi
|
|
|
|
# Fallback for unexpected/non-XML responses
|
|
_resp_lc="$(_lower_case "$_resp")"
|
|
|
|
if _contains "$_resp_lc" "error"; then
|
|
_debug "Detected 'error' in response"
|
|
return 0
|
|
fi
|
|
if _contains "$_resp_lc" "fout"; then
|
|
_debug "Detected 'fout' in response"
|
|
return 0
|
|
fi
|
|
if _contains "$_resp_lc" "invalid"; then
|
|
_debug "Detected 'invalid' in response"
|
|
return 0
|
|
fi
|
|
if _contains "$_resp_lc" "failed"; then
|
|
_debug "Detected 'failed' in response"
|
|
return 0
|
|
fi
|
|
if _contains "$_resp_lc" "denied"; then
|
|
_debug "Detected 'denied' in response"
|
|
return 0
|
|
fi
|
|
|
|
# If no explicit error markers found, assume success
|
|
return 1
|
|
}
|
|
|
|
# Extract record id from response
|
|
# Supports bHosted XML first, then generic fallbacks
|
|
_bhosted_extract_id() {
|
|
_resp="$1"
|
|
|
|
# bHosted XML: <id>12345</id>
|
|
_id="$(_bhosted_xml_value "id" "$_resp" | tr -cd '0-9')"
|
|
if [ -n "$_id" ]; then
|
|
printf "%s" "$_id"
|
|
return 0
|
|
fi
|
|
|
|
# JSON: "id":12345
|
|
_id="$(printf "%s" "$_resp" | _egrep_o '"id"[ ]*:[ ]*[0-9]+' | _head_n 1 | tr -cd '0-9')"
|
|
if [ -n "$_id" ]; then
|
|
printf "%s" "$_id"
|
|
return 0
|
|
fi
|
|
|
|
# key=value: id=12345
|
|
_id="$(printf "%s" "$_resp" | _egrep_o '(^|[^0-9a-zA-Z])id[ ]*=[ ]*[0-9]+' | _head_n 1 | tr -cd '0-9')"
|
|
if [ -n "$_id" ]; then
|
|
printf "%s" "$_id"
|
|
return 0
|
|
fi
|
|
|
|
# "record id 12345" / "recordid 12345"
|
|
_id="$(printf "%s" "$_resp" | _egrep_o '(record[ ]*id|recordid)[^0-9]*[0-9]+' | _head_n 1 | tr -cd '0-9')"
|
|
if [ -n "$_id" ]; then
|
|
printf "%s" "$_id"
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# Create a unique config key for cached record ids
|
|
_bhosted_cache_hash() {
|
|
_fd="$1"
|
|
_tv="$2"
|
|
# md5 hex of fulldomain|txtvalue
|
|
printf "%s|%s" "$_fd" "$_tv" | _digest md5 hex
|
|
}
|
|
|
|
_bhosted_cache_key() {
|
|
_hash="$1"
|
|
printf "%s" "BHOSTED_TXT_ID_${_hash}"
|
|
}
|
|
|
|
_bhosted_mem_set_id() {
|
|
_hash="$1"
|
|
_id="$2"
|
|
_key="$(_bhosted_cache_key "$_hash")"
|
|
_savedomainconf "$_key" "$_id"
|
|
}
|
|
|
|
_bhosted_mem_get_id() {
|
|
_hash="$1"
|
|
_key="$(_bhosted_cache_key "$_hash")"
|
|
_readdomainconf "$_key"
|
|
}
|