committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 373 additions and 0 deletions
@ -0,0 +1,373 @@ |
|||
#!/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"[[:space:]]*:[[:space:]]*[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 '(^|[[:space:][:punct:]])id[[:space:]]*=[[:space:]]*[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[[:space:]]*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" |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue