From 077868604d275b9051baa8f27f2a66654e4f0785 Mon Sep 17 00:00:00 2001 From: ARNik Date: Wed, 15 Jan 2025 09:40:31 +0300 Subject: [PATCH 1/5] dnsapi: Add Beget.com DNS API --- dnsapi/dns_beget.sh | 225 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100755 dnsapi/dns_beget.sh diff --git a/dnsapi/dns_beget.sh b/dnsapi/dns_beget.sh new file mode 100755 index 00000000..2df41e2e --- /dev/null +++ b/dnsapi/dns_beget.sh @@ -0,0 +1,225 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_beget_info='Beget.com +Site: Beget.com +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_beget +Options: + BEGET_User API user + BEGET_Password API password +Issues: github.com/acmesh-official/acme.sh/issues/6200 +Author: ARNik arnik@arnik.ru +' + +Beget_Api="https://api.beget.com/api" + +######## Public functions ##################### + +# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Used to add txt record +dns_beget_add() { + fulldomain=$1 + txtvalue=$2 + _debug "dns_beget_add $fulldomain $txtvalue" + + Beget_Username="${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}" + Beget_Password="${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}" + + if [ -z "$Beget_Username" ] || [ -z "$Beget_Password" ]; then + Beget_Username="" + Beget_Password="" + _err "You must export variables: Beget_Username, and Beget_Password" + return 1 + fi + + #save the credentials to the account conf file. + _saveaccountconf_mutable Beget_Username "$Beget_Username" + _saveaccountconf_mutable Beget_Password "$Beget_Password" + + _info "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + if [ -n "$_sub_domain" ]; then + _debug "Create subdomen for the record" + data="{\"subdomain\":\"$_sub_domain\",\"domain_id\":$_domain_id}" + res=$(_api_call "$Beget_Api/domain/addSubdomainVirtual" "$data") + + if _contains "$res" "^{\"status\":\"success\",\"answer\":{\"status\":\"success\",\"result\":[0-9]*}}$"; then + _debug "Cleanup subdomen records" + data="{\"fqdn\":\"$fulldomain\",\"records\":{}}" + res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") + data="{\"fqdn\":\"www.$fulldomain\",\"records\":{}}" + res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") + fi + fi + + _info "Get current domain records" + data="{\"fqdn\":\"$fulldomain\"}" + res=$(_api_call "$Beget_Api/dns/getData" "$data") + + if ! _contains "$res" "^{\"status\":\"success\",\"answer\":{\"status\":\"success\",\"result\":{.*}}}$"; then + _err "Can't get domain records." + return 1 + fi + + _info "Add new TXT record" + data="{\"fqdn\":\"$fulldomain\",\"records\":{" + data=${data}$(_parce_records "$res" "A") + data=${data}$(_parce_records "$res" "AAAA") + data=${data}$(_parce_records "$res" "CAA") + data=${data}$(_parce_records "$res" "MX") + data=${data}$(_parce_records "$res" "SRV") + data=${data}$(_parce_records "$res" "TXT") + data=$(echo "$data" | sed 's/,$//') + data=${data}'}}' + + str=$(_txt_to_dns_json "$txtvalue") + data=$(_add_record "$data" "TXT" "$str") + + res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") + + return 0 +} + +# Usage: fulldomain txtvalue +# Used to remove the txt record after validation +dns_beget_rm() { + fulldomain=$1 + txtvalue=$2 + _debug "dns_beget_rm $fulldomain $txtvalue" + + Beget_Username="${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}" + Beget_Password="${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}" + + _info "Get current domain records" + data="{\"fqdn\":\"$fulldomain\"}" + res=$(_api_call "$Beget_Api/dns/getData" "$data") + + if ! _contains "$res" "^{\"status\":\"success\",\"answer\":{\"status\":\"success\",\"result\":{.*}}}$"; then + _err Can\'t get domain records. + return 1 + fi + + _info "Remove TXT record" + data="{\"fqdn\":\"$fulldomain\",\"records\":{" + data=${data}$(_parce_records "$res" "A") + data=${data}$(_parce_records "$res" "AAAA") + data=${data}$(_parce_records "$res" "CAA") + data=${data}$(_parce_records "$res" "MX") + data=${data}$(_parce_records "$res" "SRV") + data=${data}$(_parce_records "$res" "TXT") + data=$(echo "$data" | sed 's/,$//') + data=${data}'}}' + + str=$(_txt_to_dns_json "$txtvalue") + data=$(_rm_record "$data" "$str") + + res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") + + return 0 +} + +#################### Private functions below ################################## + +# Usage: _get_root _acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=32436365 +_get_root() { + fulldomain=$1 + i=1 + p=1 + + _debug "Request domain list" + res=$(_api_call "$Beget_Api/domain/getList") + + while true; do + h=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100) + _debug h "$h" + + if [ -z "$h" ]; then + return 1 + fi + + if _contains "$res" "$h"; then + _domain_id=$(echo "$res" | _egrep_o "\"id\":[0-9]*,\"fqdn\":\"$h\"" | cut -d , -f1 | cut -d : -f2) + if [ "$_domain_id" ]; then + if [ "$h" != "$fulldomain" ]; then + _sub_domain=$(echo "$fulldomain" | cut -d . -f 1-"$p") + else + _sub_domain="" + fi + _domain=$h + return 0 + fi + return 1 + fi + p="$i" + i=$(_math "$i" + 1) + done + return 1 +} + +# Parce DNS records from json string +# Usage: _parce_records [j_str] [record_name] +_parce_records() { + j_str=$1 + record_name=$2 + res="\"$record_name\":[" + res=${res}$(echo "$j_str" | _egrep_o "\"$record_name\":\[.*" | cut -d '[' -f2 | cut -d ']' -f1) + res=${res}"]," + echo "$res" +} + +# Usage: _add_record [data] [record_name] [record_data] +_add_record() { + data=$1 + record_name=$2 + record_data=$3 + echo "$data" | sed "s/\"$record_name\":\[/\"$record_name\":\[$record_data,/" | sed "s/,\]/\]/" +} + +# Usage: _rm_record [data] [record_data] +_rm_record() { + data=$1 + record_data=$2 + echo "$data" | sed "s/$record_data,//" | sed "s/,$record_data//" | sed "s/$record_data//" +} + +_txt_to_dns_json() { + echo "{\"ttl\":600,\"txtdata\":\"$1\"}" +} + +# Usage: _api_call [api_url] [input_data] +_api_call() { + api_url="$1" + input_data="$2" + + _debug "_api_call $api_url" + _debug "Request: $input_data" + + # res=$(curl -s -L -D ./http.header \ + # "$api_url" \ + # --data-urlencode login=$Beget_Username \ + # --data-urlencode passwd=$Beget_Password \ + # --data-urlencode input_format=json \ + # --data-urlencode output_format=json \ + # --data-urlencode "input_data=$input_data") + + url="$api_url?login=$Beget_Username&passwd=$Beget_Password&input_format=json&output_format=json" + if [ -n "$input_data" ]; then + url=${url}"&input_data=" + url=${url}$(echo "$input_data" | _url_encode) + fi + + res=$(_get "$url") + + _debug "Reply: $res" + echo "$res" +} From 66dd51a09a0baadcf4f481a28e58c17881cff6ae Mon Sep 17 00:00:00 2001 From: ARNik Date: Thu, 16 Jan 2025 16:28:12 +0300 Subject: [PATCH 2/5] dnsapi: check Beget.com API replies --- dnsapi/dns_beget.sh | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/dnsapi/dns_beget.sh b/dnsapi/dns_beget.sh index 2df41e2e..3e738e3f 100755 --- a/dnsapi/dns_beget.sh +++ b/dnsapi/dns_beget.sh @@ -19,7 +19,7 @@ Beget_Api="https://api.beget.com/api" dns_beget_add() { fulldomain=$1 txtvalue=$2 - _debug "dns_beget_add $fulldomain $txtvalue" + _debug "dns_beget_add() $fulldomain $txtvalue" Beget_Username="${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}" Beget_Password="${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}" @@ -49,20 +49,27 @@ dns_beget_add() { data="{\"subdomain\":\"$_sub_domain\",\"domain_id\":$_domain_id}" res=$(_api_call "$Beget_Api/domain/addSubdomainVirtual" "$data") - if _contains "$res" "^{\"status\":\"success\",\"answer\":{\"status\":\"success\",\"result\":[0-9]*}}$"; then + if _is_api_reply_ok "$res"; then _debug "Cleanup subdomen records" data="{\"fqdn\":\"$fulldomain\",\"records\":{}}" res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") + if ! _is_api_reply_ok "$res"; then + _err "Can't cleanup subdomain records." + return 1 + fi data="{\"fqdn\":\"www.$fulldomain\",\"records\":{}}" res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") + if ! _is_api_reply_ok "$res"; then + _err "Can't cleanup subdomain records." + return 1 + fi fi fi _info "Get current domain records" data="{\"fqdn\":\"$fulldomain\"}" res=$(_api_call "$Beget_Api/dns/getData" "$data") - - if ! _contains "$res" "^{\"status\":\"success\",\"answer\":{\"status\":\"success\",\"result\":{.*}}}$"; then + if ! _is_api_reply_ok "$res"; then _err "Can't get domain records." return 1 fi @@ -82,6 +89,10 @@ dns_beget_add() { data=$(_add_record "$data" "TXT" "$str") res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") + if ! _is_api_reply_ok "$res"; then + _err "Can't change domain records." + return 1 + fi return 0 } @@ -91,7 +102,7 @@ dns_beget_add() { dns_beget_rm() { fulldomain=$1 txtvalue=$2 - _debug "dns_beget_rm $fulldomain $txtvalue" + _debug "dns_beget_rm() $fulldomain $txtvalue" Beget_Username="${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}" Beget_Password="${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}" @@ -99,9 +110,8 @@ dns_beget_rm() { _info "Get current domain records" data="{\"fqdn\":\"$fulldomain\"}" res=$(_api_call "$Beget_Api/dns/getData" "$data") - - if ! _contains "$res" "^{\"status\":\"success\",\"answer\":{\"status\":\"success\",\"result\":{.*}}}$"; then - _err Can\'t get domain records. + if ! _is_api_reply_ok "$res"; then + _err "Can't get domain records." return 1 fi @@ -120,6 +130,10 @@ dns_beget_rm() { data=$(_rm_record "$data" "$str") res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") + if ! _is_api_reply_ok "$res"; then + _err "Can't change domain records." + return 1 + fi return 0 } @@ -138,6 +152,10 @@ _get_root() { _debug "Request domain list" res=$(_api_call "$Beget_Api/domain/getList") + if ! _is_api_reply_ok "$res"; then + _err "Can't get domain list." + return 1 + fi while true; do h=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100) @@ -217,9 +235,13 @@ _api_call() { url=${url}"&input_data=" url=${url}$(echo "$input_data" | _url_encode) fi - res=$(_get "$url") _debug "Reply: $res" echo "$res" } + +# Usage: _is_api_reply_ok [api_reply] +_is_api_reply_ok() { + _contains "$1" '^{"status":"success","answer":{"status":"success","result":.*}}$' +} From 2fef0ebea83a792f437123fd90128d3192d240c8 Mon Sep 17 00:00:00 2001 From: ARNik Date: Thu, 16 Jan 2025 20:14:07 +0300 Subject: [PATCH 3/5] dnsapi: Beget.com subdomain preparation rework --- dnsapi/dns_beget.sh | 94 ++++++++++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/dnsapi/dns_beget.sh b/dnsapi/dns_beget.sh index 3e738e3f..0213e3ef 100755 --- a/dnsapi/dns_beget.sh +++ b/dnsapi/dns_beget.sh @@ -12,7 +12,7 @@ Author: ARNik arnik@arnik.ru Beget_Api="https://api.beget.com/api" -######## Public functions ##################### +#################### Public functions #################### # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Used to add txt record @@ -35,38 +35,13 @@ dns_beget_add() { _saveaccountconf_mutable Beget_Username "$Beget_Username" _saveaccountconf_mutable Beget_Password "$Beget_Password" - _info "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" + _info "Prepare subdomain." + if ! _prepare_subdomain "$fulldomain"; then + _err "Can't prepare subdomain." return 1 fi - _debug _domain_id "$_domain_id" - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - if [ -n "$_sub_domain" ]; then - _debug "Create subdomen for the record" - data="{\"subdomain\":\"$_sub_domain\",\"domain_id\":$_domain_id}" - res=$(_api_call "$Beget_Api/domain/addSubdomainVirtual" "$data") - - if _is_api_reply_ok "$res"; then - _debug "Cleanup subdomen records" - data="{\"fqdn\":\"$fulldomain\",\"records\":{}}" - res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") - if ! _is_api_reply_ok "$res"; then - _err "Can't cleanup subdomain records." - return 1 - fi - data="{\"fqdn\":\"www.$fulldomain\",\"records\":{}}" - res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") - if ! _is_api_reply_ok "$res"; then - _err "Can't cleanup subdomain records." - return 1 - fi - fi - fi - - _info "Get current domain records" + _info "Get domain records" data="{\"fqdn\":\"$fulldomain\"}" res=$(_api_call "$Beget_Api/dns/getData" "$data") if ! _is_api_reply_ok "$res"; then @@ -138,7 +113,62 @@ dns_beget_rm() { return 0 } -#################### Private functions below ################################## +#################### Private functions below #################### + +# Create subdomain if needed +# Usage: _prepare_subdomain [fulldomain] +_prepare_subdomain() { + fulldomain=$1 + + _info "Detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + if [ -z "$_sub_domain" ]; then + _debug "$fulldomain is a root domain." + return 0 + fi + + _info "Get subdomain list" + res=$(_api_call "$Beget_Api/domain/getSubdomainList") + if ! _is_api_reply_ok "$res"; then + _err "Can't get subdomain list." + return 1 + fi + + if _contains "$res" "\"fqdn\":\"$fulldomain\""; then + _debug "Subdomain $fulldomain already exist." + return 0 + fi + + _info "Subdomain $fulldomain does not exist. Let's create one." + data="{\"subdomain\":\"$_sub_domain\",\"domain_id\":$_domain_id}" + res=$(_api_call "$Beget_Api/domain/addSubdomainVirtual" "$data") + if ! _is_api_reply_ok "$res"; then + _err "Can't create subdomain." + return 1 + fi + + _debug "Cleanup subdomen records" + data="{\"fqdn\":\"$fulldomain\",\"records\":{}}" + res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") + if ! _is_api_reply_ok "$res"; then + _debug "Can't cleanup $fulldomain records." + fi + + data="{\"fqdn\":\"www.$fulldomain\",\"records\":{}}" + res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") + if ! _is_api_reply_ok "$res"; then + _debug "Can't cleanup www.$fulldomain records." + fi + + return 0 +} # Usage: _get_root _acme-challenge.www.domain.com #returns @@ -150,7 +180,7 @@ _get_root() { i=1 p=1 - _debug "Request domain list" + _debug "Get domain list" res=$(_api_call "$Beget_Api/domain/getList") if ! _is_api_reply_ok "$res"; then _err "Can't get domain list." From aa6feb4b62b269c335e15a19ef65ab42dcd6a6be Mon Sep 17 00:00:00 2001 From: ARNik Date: Thu, 16 Jan 2025 19:25:56 +0300 Subject: [PATCH 4/5] dnsapi: Beget.com fix rm record --- dnsapi/dns_beget.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dnsapi/dns_beget.sh b/dnsapi/dns_beget.sh index 0213e3ef..e570fa10 100755 --- a/dnsapi/dns_beget.sh +++ b/dnsapi/dns_beget.sh @@ -237,7 +237,9 @@ _add_record() { _rm_record() { data=$1 record_data=$2 - echo "$data" | sed "s/$record_data,//" | sed "s/,$record_data//" | sed "s/$record_data//" + echo "$data" | sed "s/$record_data//g" | sed "s/,\+/,/g" | + sed "s/{,/{/g" | sed "s/,}/}/g" | + sed "s/\[,/\[/g" | sed "s/,\]/\]/g" } _txt_to_dns_json() { From 26a5d48f634a49036a4de6e1a78962fa066179a9 Mon Sep 17 00:00:00 2001 From: ARNik Date: Thu, 16 Jan 2025 21:49:31 +0300 Subject: [PATCH 5/5] dnsapi: Beget.com fix case-sensitive domain names --- dnsapi/dns_beget.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dnsapi/dns_beget.sh b/dnsapi/dns_beget.sh index e570fa10..aa43caed 100755 --- a/dnsapi/dns_beget.sh +++ b/dnsapi/dns_beget.sh @@ -20,6 +20,7 @@ dns_beget_add() { fulldomain=$1 txtvalue=$2 _debug "dns_beget_add() $fulldomain $txtvalue" + fulldomain=$(echo "$fulldomain" | _lower_case) Beget_Username="${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}" Beget_Password="${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}" @@ -78,6 +79,7 @@ dns_beget_rm() { fulldomain=$1 txtvalue=$2 _debug "dns_beget_rm() $fulldomain $txtvalue" + fulldomain=$(echo "$fulldomain" | _lower_case) Beget_Username="${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}" Beget_Password="${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}"