1 changed files with 513 additions and 0 deletions
@ -0,0 +1,513 @@ |
|||
#!/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 |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue