diff --git a/dnsapi/dns_baidu.sh b/dnsapi/dns_baidu.sh new file mode 100644 index 00000000..a382e308 --- /dev/null +++ b/dnsapi/dns_baidu.sh @@ -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 <