You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
513 lines
13 KiB
513 lines
13 KiB
#!/usr/bin/env sh
|
|
|
|
dns_baidu_info='Baidu Cloud BCD DNS
|
|
Site: cloud.baidu.com
|
|
Docs: https://cloud.baidu.com/doc/BCD/
|
|
Signature: https://cloud.baidu.com/doc/Reference/s/njwvz1yfu
|
|
Options:
|
|
Baidu_AK AccessKeyId
|
|
Baidu_SK SecretAccessKey
|
|
OptionsAlt:
|
|
Baidu_BCD_Host API host, default: bcd.baidubce.com
|
|
Baidu_BCD_Version API version number, default: 1
|
|
Baidu_BCD_Expire Signature expiration seconds, default: 3600
|
|
Baidu_View Resolve view, default: DEFAULT
|
|
Baidu_TTL Resolve ttl seconds, default: 600
|
|
Baidu_RM_Max Max records to delete in one run, default: 20
|
|
'
|
|
: "${dns_baidu_info}"
|
|
|
|
BAIDU_BCD_DEFAULT_HOST="bcd.baidubce.com"
|
|
|
|
# --- Public API ---
|
|
dns_baidu_add() {
|
|
fulldomain=$(_idn "$1")
|
|
txtvalue=$2
|
|
|
|
if ! _baidu_prepare_record "$fulldomain"; then
|
|
return 1
|
|
fi
|
|
|
|
case "$_record_domain" in
|
|
_acme-challenge* | acmetest*) ;;
|
|
*)
|
|
_err "Only _acme-challenge* or acmetest* TXT is allowed to add"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
if ! _existing_ids="$(_baidu_find_record_ids "$_zone_name" "$_record_domain" "TXT" "$txtvalue")"; then
|
|
return 1
|
|
fi
|
|
if [ "$_existing_ids" ]; then
|
|
if _baidu_should_debug2; then
|
|
_debug2 "baidu_bcd" "txt exists, skip add: $_record_domain.$_zone_name"
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
_ttl="${Baidu_TTL:-600}"
|
|
_ttl="$(_baidu_trim_ws "$_ttl")"
|
|
case "$_ttl" in
|
|
"" | *[!0-9]*)
|
|
_ttl="600"
|
|
;;
|
|
esac
|
|
_view="$(_baidu_trim_ws "${Baidu_View:-DEFAULT}")"
|
|
txtvalue="$(_baidu_trim_ws "$txtvalue")"
|
|
_record_domain="$(_baidu_trim_ws "$_record_domain")"
|
|
_zone_name="$(_baidu_trim_ws "$_zone_name")"
|
|
|
|
_body="$(_baidu_payload_add_txt "$_zone_name" "$_record_domain" "$txtvalue" "$_ttl" "$_view")"
|
|
|
|
if ! _baidu_bcd_post "/domain/resolve/add" "$_body"; then
|
|
return 1
|
|
fi
|
|
|
|
if _baidu_is_api_error "$response"; then
|
|
_err "$response"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
dns_baidu_rm() {
|
|
fulldomain=$(_idn "$1")
|
|
txtvalue=$2
|
|
|
|
if ! _baidu_prepare_record "$fulldomain"; then
|
|
return 1
|
|
fi
|
|
|
|
case "$_record_domain" in
|
|
_acme-challenge* | acmetest*) ;;
|
|
*)
|
|
_err "Only _acme-challenge* or acmetest* TXT is allowed to delete"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
if ! _ids="$(_baidu_find_record_ids "$_zone_name" "$_record_domain" "TXT" "$txtvalue")"; then
|
|
return 1
|
|
fi
|
|
if [ -z "$_ids" ]; then
|
|
if _baidu_should_debug2; then
|
|
_debug2 "baidu_bcd" "no matching txt to delete: $_record_domain.$_zone_name"
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
_rm_max="${Baidu_RM_Max:-20}"
|
|
_rm_max="$(_baidu_trim_ws "$_rm_max")"
|
|
case "$_rm_max" in
|
|
"" | *[!0-9]*)
|
|
_rm_max="20"
|
|
;;
|
|
esac
|
|
_rm_cnt="$(printf "%s\n" "$_ids" | sed '/^$/d' | wc -l | tr -d ' ')"
|
|
if [ "$_rm_cnt" ] && [ "$_rm_cnt" -gt "$_rm_max" ]; then
|
|
_err "Refusing to delete $_rm_cnt records (limit: $_rm_max)"
|
|
return 1
|
|
fi
|
|
|
|
for _rid in $_ids; do
|
|
_body="$(_baidu_payload_delete "$_zone_name" "$_rid")"
|
|
if ! _baidu_bcd_post "/domain/resolve/delete" "$_body"; then
|
|
return 1
|
|
fi
|
|
if _baidu_is_api_error "$response"; then
|
|
_err "$response"
|
|
return 1
|
|
fi
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
# --- Config / Record Context ---
|
|
_baidu_load_credentials() {
|
|
Baidu_AK="${Baidu_AK:-$(_readaccountconf_mutable Baidu_AK)}"
|
|
Baidu_SK="${Baidu_SK:-$(_readaccountconf_mutable Baidu_SK)}"
|
|
|
|
Baidu_AK="$(_baidu_trim_ws "$Baidu_AK")"
|
|
Baidu_SK="$(_baidu_trim_ws "$Baidu_SK")"
|
|
|
|
if [ -z "$Baidu_AK" ] || [ -z "$Baidu_SK" ]; then
|
|
_err "Baidu_AK and Baidu_SK are required"
|
|
return 1
|
|
fi
|
|
|
|
_saveaccountconf_mutable Baidu_AK "$Baidu_AK"
|
|
_saveaccountconf_mutable Baidu_SK "$Baidu_SK"
|
|
|
|
BAIDU_BCD_HOST="${Baidu_BCD_Host:-$BAIDU_BCD_DEFAULT_HOST}"
|
|
BAIDU_BCD_VERSION="${Baidu_BCD_Version:-1}"
|
|
|
|
return 0
|
|
}
|
|
|
|
_baidu_prepare_record() {
|
|
_fulldomain="$1"
|
|
if ! _baidu_load_credentials; then
|
|
return 1
|
|
fi
|
|
if ! _baidu_get_root "$_fulldomain"; then
|
|
_err "Could not find zone for $_fulldomain"
|
|
return 1
|
|
fi
|
|
_record_domain="$_sub_domain"
|
|
_zone_name="$_domain"
|
|
return 0
|
|
}
|
|
|
|
# --- Zone / Records ---
|
|
_baidu_get_root() {
|
|
domain=$1
|
|
i=1
|
|
p=1
|
|
|
|
while true; do
|
|
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
|
if [ -z "$h" ]; then
|
|
return 1
|
|
fi
|
|
|
|
if ! _baidu_bcd_post "/domain/resolve/list" "$(_baidu_payload_list "$h" 1 1)"; then
|
|
return 1
|
|
fi
|
|
if ! _baidu_is_api_error "$response" && (_contains "$response" "\"totalCount\"" || _contains "$response" "\"result\""); then
|
|
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
|
_domain=$h
|
|
if [ "$_sub_domain" = "$_domain" ]; then
|
|
_sub_domain="@"
|
|
fi
|
|
if _baidu_should_debug2; then
|
|
_debug2 "baidu_bcd" "zone matched: $_domain (host: $_sub_domain)"
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
p=$i
|
|
i=$(_math "$i" + 1)
|
|
done
|
|
}
|
|
|
|
_baidu_find_record_ids() {
|
|
_zone_name="$1"
|
|
_record_domain="$2"
|
|
_rdtype="$3"
|
|
_rdata="$4"
|
|
|
|
_zone_name_e="$(_baidu_json_escape "$_zone_name")"
|
|
_record_domain_e="$(_baidu_json_escape "$_record_domain")"
|
|
_rdtype_e="$(_baidu_json_escape "$_rdtype")"
|
|
_rdata_e="$(_baidu_json_escape "$_rdata")"
|
|
|
|
_page=1
|
|
_page_size=100
|
|
_ids=""
|
|
|
|
_max_page=""
|
|
while true; do
|
|
if ! _baidu_bcd_post "/domain/resolve/list" "$(_baidu_payload_list "$_zone_name" "$_page" "$_page_size")"; then
|
|
return 1
|
|
fi
|
|
|
|
if _baidu_is_api_error "$response"; then
|
|
return 1
|
|
fi
|
|
|
|
_normalized="$(
|
|
printf "%s" "$response" | _normalizeJson
|
|
)"
|
|
|
|
if [ -z "$_max_page" ]; then
|
|
_total="$(_baidu_parse_totalcount "$_normalized")"
|
|
_max_page="$(_baidu_calc_max_page "$_total" "$_page_size")"
|
|
fi
|
|
|
|
_records=$(printf "%s" "$_normalized" | sed 's/},{/}\n{/g')
|
|
while IFS= read -r _line; do
|
|
_id="$(_baidu_match_record_id "$_line" "$_record_domain_e" "$_rdtype_e" "$_rdata_e")"
|
|
if [ "$_id" ]; then
|
|
_ids="$_ids $_id"
|
|
fi
|
|
done <<EOF
|
|
$_records
|
|
EOF
|
|
|
|
if [ "$_page" -ge "$_max_page" ]; then
|
|
break
|
|
fi
|
|
_page=$(_math "$_page" + 1)
|
|
done
|
|
|
|
printf "%s" "$_ids" | tr -s " " | tr " " "\n" | sed '/^$/d'
|
|
}
|
|
|
|
# --- HTTP ---
|
|
_baidu_bcd_post() {
|
|
_api_path="$1"
|
|
_payload="$2"
|
|
|
|
# BCD API requires JSON payload. Some call sites build fragments; normalize defensively.
|
|
_payload="$(_baidu_normalize_payload "$_payload")"
|
|
|
|
_ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
_expire="${Baidu_BCD_Expire:-3600}"
|
|
_content_type="application/json; charset=utf-8"
|
|
_payload_hash="$(printf "%s" "$_payload" | _digest sha256 hex)"
|
|
|
|
_uri="/v${BAIDU_BCD_VERSION}${_api_path}"
|
|
_auth="$(_baidu_bce_auth "POST" "$_uri" "" "$BAIDU_BCD_HOST" "$_ts" "$_expire" "$_content_type" "$_payload_hash")"
|
|
if [ -z "$_auth" ]; then
|
|
return 1
|
|
fi
|
|
|
|
_H1="Authorization: $_auth"
|
|
_H2="x-bce-date: $_ts"
|
|
_H3="x-bce-content-sha256: $_payload_hash"
|
|
_H4="Host: $BAIDU_BCD_HOST"
|
|
_H5=""
|
|
|
|
_url="https://${BAIDU_BCD_HOST}${_uri}"
|
|
if _baidu_should_debug2; then
|
|
_signed_headers_dbg="$(printf "%s" "$_auth" | cut -d / -f 5)"
|
|
_debug2 "baidu_bcd" "POST ${_uri}"
|
|
_debug2 "baidu_bcd.signedHeaders" "$_signed_headers_dbg"
|
|
_debug2 "baidu_bcd.payload_sha256" "$_payload_hash"
|
|
if _baidu_should_debug3; then
|
|
_debug2 "baidu_bcd.payload" "$(_baidu_dbg_trim "$(_baidu_redact_txt "$_payload")")"
|
|
fi
|
|
fi
|
|
response="$(_post "$_payload" "$_url" "" "POST" "$_content_type")"
|
|
_ret="$?"
|
|
if _baidu_should_debug2; then
|
|
_debug2 "baidu_bcd.ret" "$_ret"
|
|
_req_id="$(_baidu_json_get_str "$response" "requestId")"
|
|
_code="$(_baidu_json_get_str "$response" "code")"
|
|
_msg="$(_baidu_json_get_str "$response" "message")"
|
|
_debug2 "baidu_bcd.response" "requestId=${_req_id:-"-"} code=${_code:-"-"} message=$(_baidu_dbg_trim "${_msg:-"-"}")"
|
|
fi
|
|
if [ "$_ret" != "0" ]; then
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# --- Auth / Signing ---
|
|
_baidu_bce_auth() {
|
|
# Signing algorithm (bce-auth-v1):
|
|
# - SigningKey = HMAC-SHA256-HEX(sk, authStringPrefix)
|
|
# - Signature = HMAC-SHA256-HEX(SigningKey, CanonicalRequest)
|
|
# Reference: https://cloud.baidu.com/doc/Reference/s/njwvz1yfu
|
|
_method="$1"
|
|
_uri="$2"
|
|
_query="$3"
|
|
_host="$4"
|
|
_ts="$5"
|
|
_expire="$6"
|
|
_ct="$7"
|
|
_payload_hash="$8"
|
|
|
|
_auth_prefix="bce-auth-v1/${Baidu_AK}/${_ts}/${_expire}"
|
|
|
|
_signed_headers="content-type;host;x-bce-content-sha256;x-bce-date"
|
|
_canonical_uri="$(_baidu_bce_encode_path "$_uri")"
|
|
_canonical_query=""
|
|
|
|
_host_v="$(_baidu_trim_ws "$_host")"
|
|
_date_v="$(_baidu_trim_ws "$_ts")"
|
|
_ct_v="$(_baidu_trim_ws "$_ct")"
|
|
_host_e="$(printf "%s" "$_host_v" | _url_encode upper-hex)"
|
|
_date_e="$(printf "%s" "$_date_v" | _url_encode upper-hex)"
|
|
_ct_e="$(printf "%s" "$_ct_v" | _url_encode upper-hex)"
|
|
_hash_e="$(printf "%s" "$_payload_hash" | _url_encode upper-hex)"
|
|
|
|
_canonical_headers="content-type:${_ct_e}
|
|
host:${_host_e}
|
|
x-bce-content-sha256:${_hash_e}
|
|
x-bce-date:${_date_e}"
|
|
|
|
_canonical_request="${_method}
|
|
${_canonical_uri}
|
|
${_canonical_query}
|
|
${_canonical_headers}"
|
|
|
|
_sk_hex="$(printf "%s" "$Baidu_SK" | _hex_dump | tr -d " ")"
|
|
_signing_key="$(_baidu_hmac_sha256_hexkey "$_sk_hex" "$_auth_prefix")"
|
|
_signature="$(_baidu_hmac_sha256_strkey "$_signing_key" "$_canonical_request")"
|
|
|
|
if _baidu_should_debug3; then
|
|
_debug2 "bce_auth"
|
|
_debug2 "auth_prefix" "bce-auth-v1/[ak]/${_ts}/${_expire}/$_signed_headers/[signature]"
|
|
_debug2 "canonical_request_l" "$(printf "%s" "$_canonical_request" | sed -n 'l')"
|
|
_debug2 "signature" "$(printf "%s" "$_signature" | cut -c 1-16)..."
|
|
fi
|
|
|
|
printf "%s" "${_auth_prefix}/${_signed_headers}/${_signature}"
|
|
}
|
|
|
|
_baidu_bce_encode_path() {
|
|
_p="$1"
|
|
_out=""
|
|
if [ "${_p#"/"}" != "$_p" ]; then
|
|
_out="/"
|
|
fi
|
|
|
|
_rest="${_p#/}"
|
|
while [ -n "$_rest" ]; do
|
|
_seg="${_rest%%/*}"
|
|
if [ "$_seg" ]; then
|
|
if [ -z "$_out" ] || [ "$_out" = "/" ]; then
|
|
_out="${_out}$(printf "%s" "$_seg" | _url_encode upper-hex)"
|
|
else
|
|
_out="${_out}/$(printf "%s" "$_seg" | _url_encode upper-hex)"
|
|
fi
|
|
fi
|
|
if [ "${_rest#*/}" = "$_rest" ]; then
|
|
break
|
|
fi
|
|
_rest="${_rest#*/}"
|
|
done
|
|
|
|
if [ -z "$_out" ]; then
|
|
_out="/"
|
|
fi
|
|
printf "%s" "$_out"
|
|
}
|
|
|
|
# --- Utils ---
|
|
_baidu_trim() {
|
|
printf "%s" "$1" | sed 's/^ *//;s/ *$//'
|
|
}
|
|
|
|
_baidu_trim_ws() {
|
|
printf "%s" "$1" | tr '\r\n\t' ' ' | tr -s ' ' | sed 's/^ *//;s/ *$//'
|
|
}
|
|
|
|
_baidu_dbg_trim() {
|
|
printf "%s" "$1" | tr '\r\n' ' ' | cut -c 1-800
|
|
}
|
|
|
|
_baidu_should_debug2() {
|
|
# Match acme.sh default logging rules: debug2 prints when any debug2 channel is enabled.
|
|
[ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_2" ] ||
|
|
[ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG_2" ] ||
|
|
[ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_2" ]
|
|
}
|
|
|
|
_baidu_should_debug3() {
|
|
# Match acme.sh default logging rules: debug3 is more verbose and includes signing details.
|
|
[ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_3" ] ||
|
|
[ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG_3" ] ||
|
|
[ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_3" ]
|
|
}
|
|
|
|
_baidu_is_api_error() {
|
|
_contains "$1" "\"code\"" && _contains "$1" "\"message\""
|
|
}
|
|
|
|
_baidu_normalize_payload() {
|
|
_p="$(_baidu_trim "$(printf "%s" "$1" | tr -d '\r')")"
|
|
if [ -z "$_p" ]; then
|
|
printf "%s" ""
|
|
return 0
|
|
fi
|
|
case "$_p" in
|
|
\{* | \[*)
|
|
printf "%s" "$_p"
|
|
;;
|
|
*)
|
|
printf "%s" "{$_p}"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_baidu_redact_txt() {
|
|
printf "%s" "$1" | sed 's/"rdata" *: *"[^"]*"/"rdata":"[redacted]"/g'
|
|
}
|
|
|
|
_baidu_json_get_str() {
|
|
_json="$1"
|
|
_key="$2"
|
|
printf "%s" "$_json" | _normalizeJson | sed -n "s/.*\"${_key}\" *: *\"\\([^\"]*\\)\".*/\\1/p" | _head_n 1
|
|
}
|
|
|
|
_baidu_json_escape() {
|
|
_s="$1"
|
|
_s="$(printf "%s" "$_s" | tr -d '\r\n')"
|
|
printf "%s" "$_s" |
|
|
sed 's/\\/\\\\/g; s/ /\\t/g' |
|
|
_json_encode |
|
|
sed 's/\\n$//'
|
|
}
|
|
|
|
_baidu_payload_list() {
|
|
_domain="$(_baidu_json_escape "$1")"
|
|
_pageNo="$2"
|
|
_pageSize="$3"
|
|
printf "%s" "{\"domain\":\"${_domain}\",\"pageNo\":${_pageNo},\"pageSize\":${_pageSize}}"
|
|
}
|
|
|
|
_baidu_payload_add_txt() {
|
|
_zoneName="$(_baidu_json_escape "$1")"
|
|
_domain="$(_baidu_json_escape "$2")"
|
|
_rdata="$(_baidu_json_escape "$3")"
|
|
_ttl="$4"
|
|
_view="$(_baidu_json_escape "$5")"
|
|
printf "%s" "{\"domain\":\"${_domain}\",\"view\":\"${_view}\",\"rdType\":\"TXT\",\"ttl\":${_ttl},\"rdata\":\"${_rdata}\",\"zoneName\":\"${_zoneName}\"}"
|
|
}
|
|
|
|
_baidu_payload_delete() {
|
|
_zoneName="$(_baidu_json_escape "$1")"
|
|
_recordId="$2"
|
|
printf "%s" "{\"zoneName\":\"${_zoneName}\",\"recordId\":${_recordId}}"
|
|
}
|
|
|
|
_baidu_parse_totalcount() {
|
|
_json="$1"
|
|
printf "%s" "$_json" | _egrep_o "\"totalCount\": *[0-9]*" | _head_n 1 | cut -d : -f 2 | tr -d " "
|
|
}
|
|
|
|
_baidu_calc_max_page() {
|
|
_total="$1"
|
|
_page_size="$2"
|
|
if [ -z "$_total" ]; then
|
|
printf "%s" "1"
|
|
return 0
|
|
fi
|
|
_max=$(((_total + _page_size - 1) / _page_size))
|
|
if [ "$_max" -lt 1 ]; then
|
|
_max=1
|
|
fi
|
|
printf "%s" "$_max"
|
|
}
|
|
|
|
_baidu_match_record_id() {
|
|
_line="$1"
|
|
_domain_e="$2"
|
|
_rdtype_e="$3"
|
|
_rdata_e="$4"
|
|
if _contains "$_line" "\"recordId\"" &&
|
|
_contains "$_line" "\"rdtype\":\"$_rdtype_e\"" &&
|
|
_contains "$_line" "\"domain\":\"$_domain_e\"" &&
|
|
_contains "$_line" "\"rdata\":\"$_rdata_e\""; then
|
|
printf "%s" "$_line" | _egrep_o "\"recordId\": *[0-9]*" | _head_n 1 | cut -d : -f 2 | tr -d " "
|
|
fi
|
|
}
|
|
|
|
_baidu_hmac_sha256_hexkey() {
|
|
_key_hex="$1"
|
|
_msg="$2"
|
|
printf "%s" "$_msg" | _hmac sha256 "$_key_hex" hex
|
|
}
|
|
|
|
_baidu_hmac_sha256_strkey() {
|
|
_key_str="$1"
|
|
_msg="$2"
|
|
_key_hex="$(printf "%s" "$_key_str" | _hex_dump | tr -d " ")"
|
|
printf "%s" "$_msg" | _hmac sha256 "$_key_hex" hex
|
|
}
|