mirror of
https://github.com/acmesh-official/acme.sh.git
synced 2026-05-18 20:03:55 +08:00
221 lines
6.4 KiB
Bash
Executable File
221 lines
6.4 KiB
Bash
Executable File
#!/usr/bin/env sh
|
|
# shellcheck disable=SC2034
|
|
dns_sitehost_info='SiteHost
|
|
Site: sitehost.nz
|
|
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_sitehost
|
|
Options:
|
|
SITEHOST_API_KEY API Key
|
|
SITEHOST_CLIENT_ID Client ID. The numeric client ID for your SiteHost account.
|
|
Issues: github.com/acmesh-official/acme.sh/issues/6892
|
|
Author: Jordan Russell <jordanbrussell@gmail.com>
|
|
'
|
|
|
|
SITEHOST_API="https://api.sitehost.nz/1.5"
|
|
|
|
######## Public functions #####################
|
|
|
|
# Usage: dns_sitehost_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
|
dns_sitehost_add() {
|
|
fulldomain=$1
|
|
txtvalue=$2
|
|
|
|
if ! _sitehost_load_creds; then
|
|
return 1
|
|
fi
|
|
|
|
_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"
|
|
|
|
# SiteHost expects the full record name as the name parameter
|
|
_info "Adding TXT record for ${fulldomain}"
|
|
if _sitehost_rest POST "dns/add_record.json" "client_id=$(printf '%s' "${SITEHOST_CLIENT_ID}" | _url_encode)&domain=$(printf '%s' "${_domain}" | _url_encode)&type=TXT&name=$(printf '%s' "${fulldomain}" | _url_encode)&content=$(printf '%s' "${txtvalue}" | _url_encode)"; then
|
|
if _contains "$response" '"status":true'; then
|
|
_info "TXT record added successfully."
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
_err "Could not add TXT record for ${fulldomain}"
|
|
_err "$response"
|
|
return 1
|
|
}
|
|
|
|
# Usage: dns_sitehost_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
|
# Remove the txt record after validation.
|
|
dns_sitehost_rm() {
|
|
fulldomain=$1
|
|
txtvalue=$2
|
|
|
|
if ! _sitehost_load_creds; then
|
|
return 1
|
|
fi
|
|
|
|
_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 TXT records for ${_domain}"
|
|
if ! _sitehost_rest GET "dns/list_records.json" "client_id=$(printf '%s' "${SITEHOST_CLIENT_ID}" | _url_encode)&domain=$(printf '%s' "${_domain}" | _url_encode)"; then
|
|
_err "Could not list DNS records"
|
|
_err "$response"
|
|
return 1
|
|
fi
|
|
|
|
if ! _contains "$response" '"status":true'; then
|
|
_err "Error listing DNS records"
|
|
_err "$response"
|
|
return 1
|
|
fi
|
|
|
|
# Extract record ID matching our fulldomain, type TXT, and txtvalue
|
|
# Response format: {"return":[{"id":"123","name":"...","type":"TXT","content":"..."},...]}
|
|
# SiteHost returns flat single-line JSON objects in the records array
|
|
# Escape regex metacharacters in values before grep matching
|
|
_fulldomain_grep="$(printf "%s" "$fulldomain" | sed 's/[][\\.^$*]/\\&/g')"
|
|
_txtvalue_grep="$(printf "%s" "$txtvalue" | sed 's/[][\\.^$*]/\\&/g')"
|
|
# Use field-specific matching to avoid false positives from substring matches
|
|
_record_id="$(echo "$response" | _egrep_o '\{[^}]*\}' | grep '"name" *: *"'"${_fulldomain_grep}"'"' | grep '"type" *: *"TXT"' | grep '"content" *: *"'"${_txtvalue_grep}"'"' | _head_n 1 | _egrep_o '"id" *: *"?[0-9]+"?' | _egrep_o '[0-9]+')"
|
|
|
|
if [ -z "$_record_id" ]; then
|
|
_info "TXT record not found, nothing to remove."
|
|
return 0
|
|
fi
|
|
|
|
_debug _record_id "$_record_id"
|
|
|
|
_info "Deleting TXT record ${_record_id} for ${fulldomain}"
|
|
if _sitehost_rest POST "dns/delete_record.json" "client_id=$(printf '%s' "${SITEHOST_CLIENT_ID}" | _url_encode)&domain=$(printf '%s' "${_domain}" | _url_encode)&record_id=$(printf '%s' "${_record_id}" | _url_encode)"; then
|
|
if _contains "$response" '"status":true'; then
|
|
_info "TXT record deleted successfully."
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
_err "Could not delete TXT record for ${fulldomain}"
|
|
_err "$response"
|
|
return 1
|
|
}
|
|
|
|
#################### Private functions below ##################################
|
|
|
|
_sitehost_load_creds() {
|
|
SITEHOST_API_KEY="${SITEHOST_API_KEY:-$(_readaccountconf_mutable SITEHOST_API_KEY)}"
|
|
SITEHOST_CLIENT_ID="${SITEHOST_CLIENT_ID:-$(_readaccountconf_mutable SITEHOST_CLIENT_ID)}"
|
|
|
|
if [ -z "$SITEHOST_API_KEY" ] || [ -z "$SITEHOST_CLIENT_ID" ]; then
|
|
SITEHOST_API_KEY=""
|
|
SITEHOST_CLIENT_ID=""
|
|
_err "You didn't specify SITEHOST_API_KEY and/or SITEHOST_CLIENT_ID."
|
|
_err "Please export them and try again."
|
|
return 1
|
|
fi
|
|
|
|
_saveaccountconf_mutable SITEHOST_API_KEY "$SITEHOST_API_KEY"
|
|
_saveaccountconf_mutable SITEHOST_CLIENT_ID "$SITEHOST_CLIENT_ID"
|
|
return 0
|
|
}
|
|
|
|
#_acme-challenge.www.domain.com
|
|
#returns
|
|
# _sub_domain=_acme-challenge.www
|
|
# _domain=domain.com
|
|
_get_root() {
|
|
domain=$1
|
|
|
|
_debug "Getting domain list"
|
|
|
|
# Fetch ALL pages of domains first so we can match the most specific zone
|
|
# (a more specific zone on a later page must take precedence over a broader match)
|
|
_all_domains=""
|
|
_page=1
|
|
|
|
while true; do
|
|
if ! _sitehost_rest GET "dns/list_domains.json" "client_id=$(printf '%s' "${SITEHOST_CLIENT_ID}" | _url_encode)&filters%5Bpage_number%5D=${_page}"; then
|
|
_err "Could not list domains"
|
|
return 1
|
|
fi
|
|
|
|
if ! _contains "$response" '"status":true'; then
|
|
_err "Error listing domains"
|
|
_err "$response"
|
|
return 1
|
|
fi
|
|
|
|
_all_domains="${_all_domains} ${response}"
|
|
|
|
_total_pages=$(echo "$response" | _egrep_o '"total_pages" *: *[0-9]+' | _egrep_o '[0-9]+')
|
|
if [ -z "$_total_pages" ] || [ "$_page" -ge "$_total_pages" ]; then
|
|
break
|
|
fi
|
|
|
|
_page=$(_math "$_page" + 1)
|
|
done
|
|
|
|
# Try each subdomain level, most specific first
|
|
_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
|
|
|
|
if echo "$_all_domains" | grep -F "\"${h}\"" >/dev/null 2>&1; then
|
|
if [ "$_i" = "1" ]; then
|
|
# DNS alias mode - fulldomain is the zone itself
|
|
_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
|
|
}
|
|
|
|
# Usage: _sitehost_rest method endpoint data
|
|
_sitehost_rest() {
|
|
m="$1"
|
|
ep="$2"
|
|
data="$3"
|
|
url="${SITEHOST_API}/${ep}"
|
|
|
|
_debug url "$url"
|
|
|
|
_apikey="$(printf "%s" "${SITEHOST_API_KEY}" | _url_encode)"
|
|
|
|
if [ "$m" = "GET" ]; then
|
|
response="$(_get "${url}?apikey=${_apikey}&${data}")"
|
|
else
|
|
_debug2 data "$data"
|
|
response="$(_post "apikey=${_apikey}&${data}" "$url")"
|
|
fi
|
|
|
|
if [ "$?" != "0" ]; then
|
|
_err "error ${ep}"
|
|
return 1
|
|
fi
|
|
|
|
response="$(printf '%s' "$response" | tr -d '\r')"
|
|
|
|
_debug2 response "$response"
|
|
return 0
|
|
}
|