| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- #!/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 <[email protected]>
- '
- 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
- }
|