|')"
+ _debug2 table "${table}"
+ names=$(echo "${table}" | _egrep_o 'id="[0-9]+\.name">[^<]*' | sed 's|||; s|.*>||')
+ ids=$(echo "${table}" | _egrep_o 'id="[0-9]+\.name">[^<]*' | sed 's|\.name">.*||; s|id="||')
+ types=$(echo "${table}" | _egrep_o 'id="[0-9]+\.type">[^<]*' | sed 's|||; s|.*>||')
+ values=$(echo "${table}" | _egrep_o 'id="[0-9]+\.content">[^<]*' | sed 's|||; s|.*>||')
+
+ _debug2 names "${names}"
+ _debug2 ids "${ids}"
+ _debug2 types "${types}"
+ _debug2 values "${values}"
+
+ # look for line whose name is ${full_domain}, whose type is TXT, and whose value is ${txt_value}
+ line_num="$(echo "${values}" | grep -F -n -- "${txt_value}" | _head_n 1 | cut -d ':' -f 1)"
+ _debug2 line_num "${line_num}"
+ found_id=
+ if [ -n "$line_num" ]; then
+ type=$(echo "${types}" | sed -n "${line_num}p")
+ name=$(echo "${names}" | sed -n "${line_num}p")
+ id=$(echo "${ids}" | sed -n "${line_num}p")
+
+ _debug2 type "$type"
+ _debug2 name "$name"
+ _debug2 id "$id"
+ _debug2 full_domain "$full_domain"
+
+ if [ "${type}" = "TXT" ] && [ "${name}" = "${full_domain}" ]; then
+ found_id=${id}
+ fi
+ fi
+
+ if [ "${found_id}" = "" ]; then
+ _err "Can not find record id."
+ return 0
+ fi
+
+ # Remove the record
+ body="id=${zone_id}&record_id=${found_id}"
+ response=$(do_post "$body" "https://www.geoscaling.com/dns2/ajax/delete_record.php")
+ exit_code="$?"
+ if [ "$exit_code" -eq 0 ]; then
+ _info "Record removed successfully."
+ else
+ _err "Could not clean (remove) up the record. Please go to Geoscaling administration interface and clean it by hand."
+ fi
+ do_logout
+ return "${exit_code}"
+}
+
+########################## PRIVATE FUNCTIONS ###########################
+
+do_get() {
+ _url=$1
+ export _H1="Cookie: $geoscaling_phpsessid_cookie"
+ _get "${_url}"
+}
+
+do_post() {
+ _body=$1
+ _url=$2
+ export _H1="Cookie: $geoscaling_phpsessid_cookie"
+ _post "${_body}" "${_url}"
+}
+
+do_login() {
+
+ _info "Logging in..."
+
+ username_encoded="$(printf "%s" "${GEOSCALING_Username}" | _url_encode)"
+ password_encoded="$(printf "%s" "${GEOSCALING_Password}" | _url_encode)"
+ body="username=${username_encoded}&password=${password_encoded}"
+
+ response=$(_post "$body" "https://www.geoscaling.com/dns2/index.php?module=auth")
+ _debug2 response "${response}"
+
+ #retcode=$(grep '^HTTP[^ ]*' "${HTTP_HEADER}" | _head_n 1 | _egrep_o '[0-9]+$')
+ retcode=$(grep '^HTTP[^ ]*' "${HTTP_HEADER}" | _head_n 1 | cut -d ' ' -f 2)
+
+ if [ "$retcode" != "302" ]; then
+ _err "Geoscaling login failed for user ${GEOSCALING_Username}. Check ${HTTP_HEADER} file"
+ return 1
+ fi
+
+ geoscaling_phpsessid_cookie="$(grep -i '^set-cookie:' "${HTTP_HEADER}" | _egrep_o 'PHPSESSID=[^;]*;' | tr -d ';')"
+ return 0
+
+}
+
+do_logout() {
+ _info "Logging out."
+ response="$(do_get "https://www.geoscaling.com/dns2/index.php?module=auth")"
+ _debug2 response "$response"
+ return 0
+}
+
+find_zone() {
+ domain="$1"
+
+ # do login
+ do_login || return 1
+
+ # get zones
+ response="$(do_get "https://www.geoscaling.com/dns2/index.php?module=domains")"
+
+ table="$(echo "${response}" | tr -d '\n' | sed 's|.*
Your domains
|')"
+ _debug2 table "${table}"
+ zone_names="$(echo "${table}" | _egrep_o '
[^<]*' | sed 's|
||;s|||')"
+ _debug2 _matches "${zone_names}"
+ # Zone names and zone IDs are in same order
+ zone_ids=$(echo "${table}" | _egrep_o '
' | sed 's|.*id=||;s|. .*||')
+
+ _debug2 "These are the zones on this Geoscaling account:"
+ _debug2 "zone_names" "${zone_names}"
+ _debug2 "And these are their respective IDs:"
+ _debug2 "zone_ids" "${zone_ids}"
+ if [ -z "${zone_names}" ] || [ -z "${zone_ids}" ]; then
+ _err "Can not get zone names or IDs."
+ return 1
+ fi
+ # Walk through all possible zone names
+ strip_counter=1
+ while true; do
+ attempted_zone=$(echo "${domain}" | cut -d . -f ${strip_counter}-)
+
+ # All possible zone names have been tried
+ if [ -z "${attempted_zone}" ]; then
+ _err "No zone for domain '${domain}' found."
+ return 1
+ fi
+
+ _debug "Looking for zone '${attempted_zone}'"
+
+ line_num="$(echo "${zone_names}" | grep -n "^${attempted_zone}\$" | _head_n 1 | cut -d : -f 1)"
+ _debug2 line_num "${line_num}"
+ if [ "$line_num" ]; then
+ zone_id=$(echo "${zone_ids}" | sed -n "${line_num}p")
+ zone_name=$(echo "${zone_names}" | sed -n "${line_num}p")
+ if [ -z "${zone_id}" ]; then
+ _err "Can not find zone id."
+ return 1
+ fi
+ _debug "Found relevant zone '${attempted_zone}' with id '${zone_id}' - will be used for domain '${domain}'."
+ return 0
+ fi
+
+ _debug "Zone '${attempted_zone}' doesn't exist, let's try a less specific zone."
+ strip_counter=$(_math "${strip_counter}" + 1)
+ done
+}
+# vim: et:ts=2:sw=2:
diff --git a/dnsapi/dns_googledomains.sh b/dnsapi/dns_googledomains.sh
new file mode 100755
index 00000000..63e3073b
--- /dev/null
+++ b/dnsapi/dns_googledomains.sh
@@ -0,0 +1,173 @@
+#!/usr/bin/env sh
+
+# Author: Alex Leigh
+# Created: 2023-03-02
+
+#GOOGLEDOMAINS_ACCESS_TOKEN="xxxx"
+#GOOGLEDOMAINS_ZONE="xxxx"
+GOOGLEDOMAINS_API="https://acmedns.googleapis.com/v1/acmeChallengeSets"
+
+######## Public functions ########
+
+#Usage: dns_googledomains_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_googledomains_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _info "Invoking Google Domains ACME DNS API."
+
+ if ! _dns_googledomains_setup; then
+ return 1
+ fi
+
+ zone="$(_dns_googledomains_get_zone "$fulldomain")"
+ if [ -z "$zone" ]; then
+ _err "Could not find a Google Domains-managed zone containing the requested domain."
+ return 1
+ fi
+
+ _debug zone "$zone"
+ _debug txtvalue "$txtvalue"
+
+ _info "Adding TXT record for $fulldomain."
+ if _dns_googledomains_api "$zone" ":rotateChallenges" "{\"accessToken\":\"$GOOGLEDOMAINS_ACCESS_TOKEN\",\"recordsToAdd\":[{\"fqdn\":\"$fulldomain\",\"digest\":\"$txtvalue\"}],\"keepExpiredRecords\":true}"; then
+ if _contains "$response" "$txtvalue"; then
+ _info "TXT record added."
+ return 0
+ else
+ _err "Error adding TXT record."
+ return 1
+ fi
+ fi
+
+ _err "Error adding TXT record."
+ return 1
+}
+
+#Usage: dns_googledomains_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_googledomains_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _info "Invoking Google Domains ACME DNS API."
+
+ if ! _dns_googledomains_setup; then
+ return 1
+ fi
+
+ zone="$(_dns_googledomains_get_zone "$fulldomain")"
+ if [ -z "$zone" ]; then
+ _err "Could not find a Google Domains-managed domain based on request."
+ return 1
+ fi
+
+ _debug zone "$zone"
+ _debug txtvalue "$txtvalue"
+
+ _info "Removing TXT record for $fulldomain."
+ if _dns_googledomains_api "$zone" ":rotateChallenges" "{\"accessToken\":\"$GOOGLEDOMAINS_ACCESS_TOKEN\",\"recordsToRemove\":[{\"fqdn\":\"$fulldomain\",\"digest\":\"$txtvalue\"}],\"keepExpiredRecords\":true}"; then
+ if _contains "$response" "$txtvalue"; then
+ _err "Error removing TXT record."
+ return 1
+ else
+ _info "TXT record removed."
+ return 0
+ fi
+ fi
+
+ _err "Error removing TXT record."
+ return 1
+}
+
+######## Private functions ########
+
+_dns_googledomains_setup() {
+ if [ -n "$GOOGLEDOMAINS_SETUP_COMPLETED" ]; then
+ return 0
+ fi
+
+ GOOGLEDOMAINS_ACCESS_TOKEN="${GOOGLEDOMAINS_ACCESS_TOKEN:-$(_readaccountconf_mutable GOOGLEDOMAINS_ACCESS_TOKEN)}"
+ GOOGLEDOMAINS_ZONE="${GOOGLEDOMAINS_ZONE:-$(_readaccountconf_mutable GOOGLEDOMAINS_ZONE)}"
+
+ if [ -z "$GOOGLEDOMAINS_ACCESS_TOKEN" ]; then
+ GOOGLEDOMAINS_ACCESS_TOKEN=""
+ _err "Google Domains access token was not specified."
+ _err "Please visit Google Domains Security settings to provision an ACME DNS API access token."
+ return 1
+ fi
+
+ if [ "$GOOGLEDOMAINS_ZONE" ]; then
+ _savedomainconf GOOGLEDOMAINS_ACCESS_TOKEN "$GOOGLEDOMAINS_ACCESS_TOKEN"
+ _savedomainconf GOOGLEDOMAINS_ZONE "$GOOGLEDOMAINS_ZONE"
+ else
+ _saveaccountconf_mutable GOOGLEDOMAINS_ACCESS_TOKEN "$GOOGLEDOMAINS_ACCESS_TOKEN"
+ _clearaccountconf_mutable GOOGLEDOMAINS_ZONE
+ _clearaccountconf GOOGLEDOMAINS_ZONE
+ fi
+
+ _debug GOOGLEDOMAINS_ACCESS_TOKEN "$GOOGLEDOMAINS_ACCESS_TOKEN"
+ _debug GOOGLEDOMAINS_ZONE "$GOOGLEDOMAINS_ZONE"
+
+ GOOGLEDOMAINS_SETUP_COMPLETED=1
+ return 0
+}
+
+_dns_googledomains_get_zone() {
+ domain=$1
+
+ # Use zone directly if provided
+ if [ "$GOOGLEDOMAINS_ZONE" ]; then
+ if ! _dns_googledomains_api "$GOOGLEDOMAINS_ZONE"; then
+ return 1
+ fi
+
+ echo "$GOOGLEDOMAINS_ZONE"
+ return 0
+ fi
+
+ i=2
+ while true; do
+ curr=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug curr "$curr"
+
+ if [ -z "$curr" ]; then
+ return 1
+ fi
+
+ if _dns_googledomains_api "$curr"; then
+ echo "$curr"
+ return 0
+ fi
+
+ i=$(_math "$i" + 1)
+ done
+
+ return 1
+}
+
+_dns_googledomains_api() {
+ zone=$1
+ apimethod=$2
+ data="$3"
+
+ if [ -z "$data" ]; then
+ response="$(_get "$GOOGLEDOMAINS_API/$zone$apimethod")"
+ else
+ _debug data "$data"
+ export _H1="Content-Type: application/json"
+ response="$(_post "$data" "$GOOGLEDOMAINS_API/$zone$apimethod")"
+ fi
+
+ _debug response "$response"
+
+ if [ "$?" != "0" ]; then
+ _err "Error"
+ return 1
+ fi
+
+ if _contains "$response" "\"error\": {"; then
+ return 1
+ fi
+
+ return 0
+}
diff --git a/dnsapi/dns_he.sh b/dnsapi/dns_he.sh
index ef09fa0a..bf4a5030 100755
--- a/dnsapi/dns_he.sh
+++ b/dnsapi/dns_he.sh
@@ -85,7 +85,7 @@ dns_he_rm() {
_debug "The txt record is not found, just skip"
return 0
fi
- _record_id="$(echo "$response" | tr -d "#" | sed "s//dev/null
+ else
+ _post "${_post_body}" "${dns_api}/v2/zones/${zoneid}/recordsets/${_record_id}" false "PUT" >/dev/null
+ fi
+ _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+ if [ "$_code" != "202" ]; then
+ _err "dns_huaweicloud: http code ${_code}"
+ return 1
+ fi
+ return 0
+}
+
+# _rm_record $token $zoneid $recordid
+# assume ${dns_api} exist
+# no output
+# return 0
+_rm_record() {
+ _token=$1
+ _zone_id=$2
+ _record_id=$3
+
+ export _H2="Content-Type: application/json"
+ export _H1="X-Auth-Token: ${_token}"
+
+ _post "" "${dns_api}/v2/zones/${_zone_id}/recordsets/${_record_id}" false "DELETE" >/dev/null
+ return $?
+}
+
+_get_token() {
+ _username=$1
+ _password=$2
+ _domain_name=$3
+
+ _debug "Getting Token"
+ body="{
+ \"auth\": {
+ \"identity\": {
+ \"methods\": [
+ \"password\"
+ ],
+ \"password\": {
+ \"user\": {
+ \"name\": \"${_username}\",
+ \"password\": \"${_password}\",
+ \"domain\": {
+ \"name\": \"${_domain_name}\"
+ }
+ }
+ }
+ },
+ \"scope\": {
+ \"project\": {
+ \"name\": \"ap-southeast-1\"
+ }
+ }
+ }
+ }"
+ export _H1="Content-Type: application/json;charset=utf8"
+ _post "${body}" "${iam_api}/v3/auth/tokens" >/dev/null
+ _code=$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")
+ _token=$(grep "^X-Subject-Token" "$HTTP_HEADER" | cut -d " " -f 2-)
+ _secure_debug "${_code}"
+ printf "%s" "${_token}"
+ return 0
+}
diff --git a/dnsapi/dns_infoblox.sh b/dnsapi/dns_infoblox.sh
index 4cbb2146..6bfd36ee 100644
--- a/dnsapi/dns_infoblox.sh
+++ b/dnsapi/dns_infoblox.sh
@@ -9,7 +9,6 @@ dns_infoblox_add() {
## Nothing to see here, just some housekeeping
fulldomain=$1
txtvalue=$2
- baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=$Infoblox_View"
_info "Using Infoblox API"
_debug fulldomain "$fulldomain"
@@ -19,12 +18,13 @@ dns_infoblox_add() {
if [ -z "$Infoblox_Creds" ] || [ -z "$Infoblox_Server" ]; then
Infoblox_Creds=""
Infoblox_Server=""
- _err "You didn't specify the credentials, server or infoblox view yet (Infoblox_Creds, Infoblox_Server and Infoblox_View)."
- _err "Please set them via EXPORT ([username:password], [ip or hostname]) and try again."
+ _err "You didn't specify the Infoblox credentials or server (Infoblox_Creds; Infoblox_Server)."
+ _err "Please set them via EXPORT Infoblox_Creds=username:password or EXPORT Infoblox_server=ip/hostname and try again."
return 1
fi
if [ -z "$Infoblox_View" ]; then
+ _info "No Infoblox_View set, using fallback value 'default'"
Infoblox_View="default"
fi
@@ -33,6 +33,9 @@ dns_infoblox_add() {
_saveaccountconf Infoblox_Server "$Infoblox_Server"
_saveaccountconf Infoblox_View "$Infoblox_View"
+ ## URLencode Infoblox View to deal with e.g. spaces
+ Infoblox_ViewEncoded=$(printf "%b" "$Infoblox_View" | _url_encode)
+
## Base64 encode the credentials
Infoblox_CredsEncoded=$(printf "%b" "$Infoblox_Creds" | _base64)
@@ -40,11 +43,14 @@ dns_infoblox_add() {
export _H1="Accept-Language:en-US"
export _H2="Authorization: Basic $Infoblox_CredsEncoded"
+ ## Construct the request URL
+ baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=${Infoblox_ViewEncoded}"
+
## Add the challenge record to the Infoblox grid member
result="$(_post "" "$baseurlnObject" "" "POST")"
## Let's see if we get something intelligible back from the unit
- if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/$Infoblox_View")" ]; then
+ if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/${Infoblox_ViewEncoded}")" ]; then
_info "Successfully created the txt record"
return 0
else
@@ -65,6 +71,9 @@ dns_infoblox_rm() {
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
+ ## URLencode Infoblox View to deal with e.g. spaces
+ Infoblox_ViewEncoded=$(printf "%b" "$Infoblox_View" | _url_encode)
+
## Base64 encode the credentials
Infoblox_CredsEncoded="$(printf "%b" "$Infoblox_Creds" | _base64)"
@@ -73,18 +82,18 @@ dns_infoblox_rm() {
export _H2="Authorization: Basic $Infoblox_CredsEncoded"
## Does the record exist? Let's check.
- baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=$Infoblox_View&_return_type=xml-pretty"
+ baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=${Infoblox_ViewEncoded}&_return_type=xml-pretty"
result="$(_get "$baseurlnObject")"
## Let's see if we get something intelligible back from the grid
- if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/$Infoblox_View")" ]; then
+ if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/${Infoblox_ViewEncoded}")" ]; then
## Extract the object reference
- objRef="$(printf "%b" "$result" | _egrep_o "record:txt/.*:.*/$Infoblox_View")"
+ objRef="$(printf "%b" "$result" | _egrep_o "record:txt/.*:.*/${Infoblox_ViewEncoded}")"
objRmUrl="https://$Infoblox_Server/wapi/v2.2.2/$objRef"
## Delete them! All the stale records!
rmResult="$(_post "" "$objRmUrl" "" "DELETE")"
## Let's see if that worked
- if [ "$(echo "$rmResult" | _egrep_o "record:txt/.*:.*/$Infoblox_View")" ]; then
+ if [ "$(echo "$rmResult" | _egrep_o "record:txt/.*:.*/${Infoblox_ViewEncoded}")" ]; then
_info "Successfully deleted $objRef"
return 0
else
diff --git a/dnsapi/dns_infomaniak.sh b/dnsapi/dns_infomaniak.sh
new file mode 100755
index 00000000..a005132c
--- /dev/null
+++ b/dnsapi/dns_infomaniak.sh
@@ -0,0 +1,199 @@
+#!/usr/bin/env sh
+
+###############################################################################
+# Infomaniak API integration
+#
+# To use this API you need visit the API dashboard of your account
+# once logged into https://manager.infomaniak.com add /api/dashboard to the URL
+#
+# Please report bugs to
+# https://github.com/acmesh-official/acme.sh/issues/3188
+#
+# Note: the URL looks like this:
+# https://manager.infomaniak.com/v3//api/dashboard
+# Then generate a token with the scope Domain
+# this is given as an environment variable INFOMANIAK_API_TOKEN
+###############################################################################
+
+# base variables
+
+DEFAULT_INFOMANIAK_API_URL="https://api.infomaniak.com"
+DEFAULT_INFOMANIAK_TTL=300
+
+######## Public functions #####################
+
+#Usage: dns_infomaniak_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_infomaniak_add() {
+
+ INFOMANIAK_API_TOKEN="${INFOMANIAK_API_TOKEN:-$(_readaccountconf_mutable INFOMANIAK_API_TOKEN)}"
+ INFOMANIAK_API_URL="${INFOMANIAK_API_URL:-$(_readaccountconf_mutable INFOMANIAK_API_URL)}"
+ INFOMANIAK_TTL="${INFOMANIAK_TTL:-$(_readaccountconf_mutable INFOMANIAK_TTL)}"
+
+ if [ -z "$INFOMANIAK_API_TOKEN" ]; then
+ INFOMANIAK_API_TOKEN=""
+ _err "Please provide a valid Infomaniak API token in variable INFOMANIAK_API_TOKEN"
+ return 1
+ fi
+
+ if [ -z "$INFOMANIAK_API_URL" ]; then
+ INFOMANIAK_API_URL="$DEFAULT_INFOMANIAK_API_URL"
+ fi
+
+ if [ -z "$INFOMANIAK_TTL" ]; then
+ INFOMANIAK_TTL="$DEFAULT_INFOMANIAK_TTL"
+ fi
+
+ #save the token to the account conf file.
+ _saveaccountconf_mutable INFOMANIAK_API_TOKEN "$INFOMANIAK_API_TOKEN"
+
+ if [ "$INFOMANIAK_API_URL" != "$DEFAULT_INFOMANIAK_API_URL" ]; then
+ _saveaccountconf_mutable INFOMANIAK_API_URL "$INFOMANIAK_API_URL"
+ fi
+
+ if [ "$INFOMANIAK_TTL" != "$DEFAULT_INFOMANIAK_TTL" ]; then
+ _saveaccountconf_mutable INFOMANIAK_TTL "$INFOMANIAK_TTL"
+ fi
+
+ export _H1="Authorization: Bearer $INFOMANIAK_API_TOKEN"
+ export _H2="Content-Type: application/json"
+
+ fulldomain="$1"
+ txtvalue="$2"
+
+ _info "Infomaniak DNS API"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ fqdn=${fulldomain#_acme-challenge.}
+
+ # guess which base domain to add record to
+ zone_and_id=$(_find_zone "$fqdn")
+ if [ -z "$zone_and_id" ]; then
+ _err "cannot find zone to modify"
+ return 1
+ fi
+ zone=${zone_and_id% *}
+ domain_id=${zone_and_id#* }
+
+ # extract first part of domain
+ key=${fulldomain%."$zone"}
+
+ _debug "zone:$zone id:$domain_id key:$key"
+
+ # payload
+ data="{\"type\": \"TXT\", \"source\": \"$key\", \"target\": \"$txtvalue\", \"ttl\": $INFOMANIAK_TTL}"
+
+ # API call
+ response=$(_post "$data" "${INFOMANIAK_API_URL}/1/domain/$domain_id/dns/record")
+ if [ -n "$response" ] && echo "$response" | _contains '"result":"success"'; then
+ _info "Record added"
+ _debug "Response: $response"
+ return 0
+ fi
+ _err "could not create record"
+ _debug "Response: $response"
+ return 1
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_infomaniak_rm() {
+
+ INFOMANIAK_API_TOKEN="${INFOMANIAK_API_TOKEN:-$(_readaccountconf_mutable INFOMANIAK_API_TOKEN)}"
+ INFOMANIAK_API_URL="${INFOMANIAK_API_URL:-$(_readaccountconf_mutable INFOMANIAK_API_URL)}"
+ INFOMANIAK_TTL="${INFOMANIAK_TTL:-$(_readaccountconf_mutable INFOMANIAK_TTL)}"
+
+ if [ -z "$INFOMANIAK_API_TOKEN" ]; then
+ INFOMANIAK_API_TOKEN=""
+ _err "Please provide a valid Infomaniak API token in variable INFOMANIAK_API_TOKEN"
+ return 1
+ fi
+
+ if [ -z "$INFOMANIAK_API_URL" ]; then
+ INFOMANIAK_API_URL="$DEFAULT_INFOMANIAK_API_URL"
+ fi
+
+ if [ -z "$INFOMANIAK_TTL" ]; then
+ INFOMANIAK_TTL="$DEFAULT_INFOMANIAK_TTL"
+ fi
+
+ #save the token to the account conf file.
+ _saveaccountconf_mutable INFOMANIAK_API_TOKEN "$INFOMANIAK_API_TOKEN"
+
+ if [ "$INFOMANIAK_API_URL" != "$DEFAULT_INFOMANIAK_API_URL" ]; then
+ _saveaccountconf_mutable INFOMANIAK_API_URL "$INFOMANIAK_API_URL"
+ fi
+
+ if [ "$INFOMANIAK_TTL" != "$DEFAULT_INFOMANIAK_TTL" ]; then
+ _saveaccountconf_mutable INFOMANIAK_TTL "$INFOMANIAK_TTL"
+ fi
+
+ export _H1="Authorization: Bearer $INFOMANIAK_API_TOKEN"
+ export _H2="ContentType: application/json"
+
+ fulldomain=$1
+ txtvalue=$2
+ _info "Infomaniak DNS API"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ fqdn=${fulldomain#_acme-challenge.}
+
+ # guess which base domain to add record to
+ zone_and_id=$(_find_zone "$fqdn")
+ if [ -z "$zone_and_id" ]; then
+ _err "cannot find zone to modify"
+ return 1
+ fi
+ zone=${zone_and_id% *}
+ domain_id=${zone_and_id#* }
+
+ # extract first part of domain
+ key=${fulldomain%."$zone"}
+
+ _debug "zone:$zone id:$domain_id key:$key"
+
+ # find previous record
+ # shellcheck disable=SC1004
+ record_id=$(_get "${INFOMANIAK_API_URL}/1/domain/$domain_id/dns/record" | sed 's/.*"data":\[\(.*\)\]}/\1/; s/},{/}\
+{/g' | sed -n 's/.*"id":"*\([0-9]*\)"*.*"source_idn":"'"$fulldomain"'".*"target_idn":"'"$txtvalue"'".*/\1/p')
+ if [ -z "$record_id" ]; then
+ _err "could not find record to delete"
+ return 1
+ fi
+ _debug "record_id: $record_id"
+
+ # API call
+ response=$(_post "" "${INFOMANIAK_API_URL}/1/domain/$domain_id/dns/record/$record_id" "" DELETE)
+ if [ -n "$response" ] && echo "$response" | _contains '"result":"success"'; then
+ _info "Record deleted"
+ return 0
+ fi
+ _err "could not delete record"
+ return 1
+}
+
+#################### Private functions below ##################################
+
+_get_domain_id() {
+ domain="$1"
+
+ # shellcheck disable=SC1004
+ _get "${INFOMANIAK_API_URL}/1/product?service_name=domain&customer_name=$domain" | sed 's/.*"data":\[{\(.*\)}\]}/\1/; s/,/\
+/g' | sed -n 's/^"id":\(.*\)/\1/p'
+}
+
+_find_zone() {
+ zone="$1"
+
+ # find domain in list, removing . parts sequentialy
+ while _contains "$zone" '\.'; do
+ _debug "testing $zone"
+ id=$(_get_domain_id "$zone")
+ if [ -n "$id" ]; then
+ echo "$zone $id"
+ return
+ fi
+ zone=${zone#*.}
+ done
+}
diff --git a/dnsapi/dns_ionos.sh b/dnsapi/dns_ionos.sh
new file mode 100755
index 00000000..e4ad3318
--- /dev/null
+++ b/dnsapi/dns_ionos.sh
@@ -0,0 +1,171 @@
+#!/usr/bin/env sh
+
+# Supports IONOS DNS API v1.0.1
+#
+# Usage:
+# Export IONOS_PREFIX and IONOS_SECRET before calling acme.sh:
+#
+# $ export IONOS_PREFIX="..."
+# $ export IONOS_SECRET="..."
+#
+# $ acme.sh --issue --dns dns_ionos ...
+
+IONOS_API="https://api.hosting.ionos.com/dns"
+IONOS_ROUTE_ZONES="/v1/zones"
+
+IONOS_TXT_TTL=60 # minimum accepted by API
+IONOS_TXT_PRIO=10
+
+dns_ionos_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ if ! _ionos_init; then
+ return 1
+ fi
+
+ _body="[{\"name\":\"$_sub_domain.$_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":$IONOS_TXT_TTL,\"prio\":$IONOS_TXT_PRIO,\"disabled\":false}]"
+
+ if _ionos_rest POST "$IONOS_ROUTE_ZONES/$_zone_id/records" "$_body" && [ "$_code" = "201" ]; then
+ _info "TXT record has been created successfully."
+ return 0
+ fi
+
+ return 1
+}
+
+dns_ionos_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ if ! _ionos_init; then
+ return 1
+ fi
+
+ if ! _ionos_get_record "$fulldomain" "$_zone_id" "$txtvalue"; then
+ _err "Could not find _acme-challenge TXT record."
+ return 1
+ fi
+
+ if _ionos_rest DELETE "$IONOS_ROUTE_ZONES/$_zone_id/records/$_record_id" && [ "$_code" = "200" ]; then
+ _info "TXT record has been deleted successfully."
+ return 0
+ fi
+
+ return 1
+}
+
+_ionos_init() {
+ IONOS_PREFIX="${IONOS_PREFIX:-$(_readaccountconf_mutable IONOS_PREFIX)}"
+ IONOS_SECRET="${IONOS_SECRET:-$(_readaccountconf_mutable IONOS_SECRET)}"
+
+ if [ -z "$IONOS_PREFIX" ] || [ -z "$IONOS_SECRET" ]; then
+ _err "You didn't specify an IONOS api prefix and secret yet."
+ _err "Read https://beta.developer.hosting.ionos.de/docs/getstarted to learn how to get a prefix and secret."
+ _err ""
+ _err "Then set them before calling acme.sh:"
+ _err "\$ export IONOS_PREFIX=\"...\""
+ _err "\$ export IONOS_SECRET=\"...\""
+ _err "\$ acme.sh --issue -d ... --dns dns_ionos"
+ return 1
+ fi
+
+ _saveaccountconf_mutable IONOS_PREFIX "$IONOS_PREFIX"
+ _saveaccountconf_mutable IONOS_SECRET "$IONOS_SECRET"
+
+ if ! _get_root "$fulldomain"; then
+ _err "Cannot find this domain in your IONOS account."
+ return 1
+ fi
+}
+
+_get_root() {
+ domain=$1
+ i=1
+ p=1
+
+ if _ionos_rest GET "$IONOS_ROUTE_ZONES"; then
+ _response="$(echo "$_response" | tr -d "\n")"
+
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ if [ -z "$h" ]; then
+ return 1
+ fi
+
+ _zone="$(echo "$_response" | _egrep_o "\"name\":\"$h\".*\}")"
+ if [ "$_zone" ]; then
+ _zone_id=$(printf "%s\n" "$_zone" | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"')
+ if [ "$_zone_id" ]; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain=$h
+
+ return 0
+ fi
+
+ return 1
+ fi
+
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ fi
+
+ return 1
+}
+
+_ionos_get_record() {
+ fulldomain=$1
+ zone_id=$2
+ txtrecord=$3
+
+ if _ionos_rest GET "$IONOS_ROUTE_ZONES/$zone_id?recordName=$fulldomain&recordType=TXT"; then
+ _response="$(echo "$_response" | tr -d "\n")"
+
+ _record="$(echo "$_response" | _egrep_o "\"name\":\"$fulldomain\"[^\}]*\"type\":\"TXT\"[^\}]*\"content\":\"\\\\\"$txtrecord\\\\\"\".*\}")"
+ if [ "$_record" ]; then
+ _record_id=$(printf "%s\n" "$_record" | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"')
+
+ return 0
+ fi
+ fi
+
+ return 1
+}
+
+_ionos_rest() {
+ method="$1"
+ route="$2"
+ data="$3"
+
+ IONOS_API_KEY="$(printf "%s.%s" "$IONOS_PREFIX" "$IONOS_SECRET")"
+
+ export _H1="X-API-Key: $IONOS_API_KEY"
+
+ # clear headers
+ : >"$HTTP_HEADER"
+
+ if [ "$method" != "GET" ]; then
+ export _H2="Accept: application/json"
+ export _H3="Content-Type: application/json"
+
+ _response="$(_post "$data" "$IONOS_API$route" "" "$method" "application/json")"
+ else
+ export _H2="Accept: */*"
+ export _H3=
+
+ _response="$(_get "$IONOS_API$route")"
+ fi
+
+ _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+
+ if [ "$?" != "0" ]; then
+ _err "Error $route: $_response"
+ return 1
+ fi
+
+ _debug2 "_response" "$_response"
+ _debug2 "_code" "$_code"
+
+ return 0
+}
diff --git a/dnsapi/dns_ipv64.sh b/dnsapi/dns_ipv64.sh
new file mode 100755
index 00000000..54470119
--- /dev/null
+++ b/dnsapi/dns_ipv64.sh
@@ -0,0 +1,157 @@
+#!/usr/bin/env sh
+
+#Created by Roman Lumetsberger, to use ipv64.net's API to add/remove text records
+#2022/11/29
+
+# Pass credentials before "acme.sh --issue --dns dns_ipv64 ..."
+# --
+# export IPv64_Token="aaaaaaaaaaaaaaaaaaaaaaaaaa"
+# --
+#
+
+IPv64_API="https://ipv64.net/api"
+
+######## Public functions ######################
+
+#Usage: dns_ipv64_add _acme-challenge.domain.ipv64.net "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_ipv64_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ IPv64_Token="${IPv64_Token:-$(_readaccountconf_mutable IPv64_Token)}"
+ if [ -z "$IPv64_Token" ]; then
+ _err "You must export variable: IPv64_Token"
+ _err "The API Key for your IPv64 account is necessary."
+ _err "You can look it up in your IPv64 account."
+ return 1
+ fi
+
+ # Now save the credentials.
+ _saveaccountconf_mutable IPv64_Token "$IPv64_Token"
+
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain" "$fulldomain"
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ # convert to lower case
+ _domain="$(echo "$_domain" | _lower_case)"
+ _sub_domain="$(echo "$_sub_domain" | _lower_case)"
+ # Now add the TXT record
+ _info "Trying to add TXT record"
+ if _ipv64_rest "POST" "add_record=$_domain&praefix=$_sub_domain&type=TXT&content=$txtvalue"; then
+ _info "TXT record has been successfully added."
+ return 0
+ else
+ _err "Errors happened during adding the TXT record, response=$_response"
+ return 1
+ fi
+
+}
+
+#Usage: fulldomain txtvalue
+#Usage: dns_ipv64_rm _acme-challenge.domain.ipv64.net "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+#Remove the txt record after validation.
+dns_ipv64_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ IPv64_Token="${IPv64_Token:-$(_readaccountconf_mutable IPv64_Token)}"
+ if [ -z "$IPv64_Token" ]; then
+ _err "You must export variable: IPv64_Token"
+ _err "The API Key for your IPv64 account is necessary."
+ _err "You can look it up in your IPv64 account."
+ return 1
+ fi
+
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain" "$fulldomain"
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ # convert to lower case
+ _domain="$(echo "$_domain" | _lower_case)"
+ _sub_domain="$(echo "$_sub_domain" | _lower_case)"
+ # Now delete the TXT record
+ _info "Trying to delete TXT record"
+ if _ipv64_rest "DELETE" "del_record=$_domain&praefix=$_sub_domain&type=TXT&content=$txtvalue"; then
+ _info "TXT record has been successfully deleted."
+ return 0
+ else
+ _err "Errors happened during deleting the TXT record, response=$_response"
+ return 1
+ fi
+
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ domain="$1"
+ i=1
+ p=1
+
+ _ipv64_get "get_domains"
+ domain_data=$_response
+
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+
+ #if _contains "$domain_data" "\""$h"\"\:"; then
+ if _contains "$domain_data" "\"""$h""\"\:"; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
+ _domain="$h"
+ return 0
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+#send get request to api
+# $1 has to set the api-function
+_ipv64_get() {
+ url="$IPv64_API?$1"
+ export _H1="Authorization: Bearer $IPv64_Token"
+
+ _response=$(_get "$url")
+ _response="$(echo "$_response" | _normalizeJson)"
+
+ if _contains "$_response" "429 Too Many Requests"; then
+ _info "API throttled, sleeping to reset the limit"
+ _sleep 10
+ _response=$(_get "$url")
+ _response="$(echo "$_response" | _normalizeJson)"
+ fi
+}
+
+_ipv64_rest() {
+ url="$IPv64_API"
+ export _H1="Authorization: Bearer $IPv64_Token"
+ export _H2="Content-Type: application/x-www-form-urlencoded"
+ _response=$(_post "$2" "$url" "" "$1")
+
+ if _contains "$_response" "429 Too Many Requests"; then
+ _info "API throttled, sleeping to reset the limit"
+ _sleep 10
+ _response=$(_post "$2" "$url" "" "$1")
+ fi
+
+ if ! _contains "$_response" "\"info\":\"success\""; then
+ return 1
+ fi
+ _debug2 response "$_response"
+ return 0
+}
diff --git a/dnsapi/dns_ispconfig.sh b/dnsapi/dns_ispconfig.sh
index bd1e0391..560f073e 100755
--- a/dnsapi/dns_ispconfig.sh
+++ b/dnsapi/dns_ispconfig.sh
@@ -32,7 +32,11 @@ dns_ispconfig_rm() {
#################### Private functions below ##################################
_ISPC_credentials() {
- if [ -z "${ISPC_User}" ] || [ -z "$ISPC_Password" ] || [ -z "${ISPC_Api}" ] || [ -z "${ISPC_Api_Insecure}" ]; then
+ ISPC_User="${ISPC_User:-$(_readaccountconf_mutable ISPC_User)}"
+ ISPC_Password="${ISPC_Password:-$(_readaccountconf_mutable ISPC_Password)}"
+ ISPC_Api="${ISPC_Api:-$(_readaccountconf_mutable ISPC_Api)}"
+ ISPC_Api_Insecure="${ISPC_Api_Insecure:-$(_readaccountconf_mutable ISPC_Api_Insecure)}"
+ if [ -z "${ISPC_User}" ] || [ -z "${ISPC_Password}" ] || [ -z "${ISPC_Api}" ] || [ -z "${ISPC_Api_Insecure}" ]; then
ISPC_User=""
ISPC_Password=""
ISPC_Api=""
@@ -40,10 +44,10 @@ _ISPC_credentials() {
_err "You haven't specified the ISPConfig Login data, URL and whether you want check the ISPC SSL cert. Please try again."
return 1
else
- _saveaccountconf ISPC_User "${ISPC_User}"
- _saveaccountconf ISPC_Password "${ISPC_Password}"
- _saveaccountconf ISPC_Api "${ISPC_Api}"
- _saveaccountconf ISPC_Api_Insecure "${ISPC_Api_Insecure}"
+ _saveaccountconf_mutable ISPC_User "${ISPC_User}"
+ _saveaccountconf_mutable ISPC_Password "${ISPC_Password}"
+ _saveaccountconf_mutable ISPC_Api "${ISPC_Api}"
+ _saveaccountconf_mutable ISPC_Api_Insecure "${ISPC_Api_Insecure}"
# Set whether curl should use secure or insecure mode
export HTTPS_INSECURE="${ISPC_Api_Insecure}"
fi
@@ -75,7 +79,7 @@ _ISPC_getZoneInfo() {
# suffix . needed for zone -> domain.tld.
curData="{\"session_id\":\"${sessionID}\",\"primary_id\":{\"origin\":\"${curZone}.\"}}"
curResult="$(_post "${curData}" "${ISPC_Api}?dns_zone_get")"
- _debug "Calling _ISPC_getZoneInfo: '${curData}' '${ISPC_Api}?login'"
+ _debug "Calling _ISPC_getZoneInfo: '${curData}' '${ISPC_Api}?dns_zone_get'"
_debug "Result of _ISPC_getZoneInfo: '$curResult'"
if _contains "${curResult}" '"id":"'; then
zoneFound=true
@@ -110,18 +114,32 @@ _ISPC_getZoneInfo() {
;;
*) _info "Retrieved Zone ID" ;;
esac
- client_id=$(echo "${curResult}" | _egrep_o "sys_userid.*" | cut -d ':' -f 2 | cut -d '"' -f 2)
- _debug "Client ID: '${client_id}'"
- case "${client_id}" in
+ sys_userid=$(echo "${curResult}" | _egrep_o "sys_userid.*" | cut -d ':' -f 2 | cut -d '"' -f 2)
+ _debug "SYS User ID: '${sys_userid}'"
+ case "${sys_userid}" in
'' | *[!0-9]*)
- _err "Client ID is not numeric."
+ _err "SYS User ID is not numeric."
return 1
;;
- *) _info "Retrieved Client ID." ;;
+ *) _info "Retrieved SYS User ID." ;;
esac
zoneFound=""
zoneEnd=""
fi
+ # Need to get client_id as it is different from sys_userid
+ curData="{\"session_id\":\"${sessionID}\",\"sys_userid\":\"${sys_userid}\"}"
+ curResult="$(_post "${curData}" "${ISPC_Api}?client_get_id")"
+ _debug "Calling _ISPC_ClientGetID: '${curData}' '${ISPC_Api}?client_get_id'"
+ _debug "Result of _ISPC_ClientGetID: '$curResult'"
+ client_id=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2 | tr -d '{}')
+ _debug "Client ID: '${client_id}'"
+ case "${client_id}" in
+ '' | *[!0-9]*)
+ _err "Client ID is not numeric."
+ return 1
+ ;;
+ *) _info "Retrieved Client ID." ;;
+ esac
}
_ISPC_addTxt() {
diff --git a/dnsapi/dns_kas.sh b/dnsapi/dns_kas.sh
index 2cb0b439..1253cf27 100755
--- a/dnsapi/dns_kas.sh
+++ b/dnsapi/dns_kas.sh
@@ -5,51 +5,81 @@
# Environment variables:
#
# - $KAS_Login (Kasserver API login name)
-# - $KAS_Authtype (Kasserver API auth type. Default: sha1)
+# - $KAS_Authtype (Kasserver API auth type. Default: plain)
# - $KAS_Authdata (Kasserver API auth data.)
#
-# Author: Martin Kammerlander, Phlegx Systems OG
-# Updated by: Marc-Oliver Lange
-# Credits: Inspired by dns_he.sh. Thanks a lot man!
-# Git repo: https://github.com/phlegx/acme.sh
-# TODO: Better Error handling
+# Last update: squared GmbH
+# Credits:
+# - dns_he.sh. Thanks a lot man!
+# - Martin Kammerlander, Phlegx Systems OG
+# - Marc-Oliver Lange
+# - https://github.com/o1oo11oo/kasapi.sh
########################################################################
-KAS_Api="https://kasapi.kasserver.com/dokumentation/formular.php"
+KAS_Api_GET="$(_get "https://kasapi.kasserver.com/soap/wsdl/KasApi.wsdl")"
+KAS_Api="$(echo "$KAS_Api_GET" | tr -d ' ' | grep -i "//g")"
+_info "[KAS] -> API URL $KAS_Api"
+
+KAS_Auth_GET="$(_get "https://kasapi.kasserver.com/soap/wsdl/KasAuth.wsdl")"
+KAS_Auth="$(echo "$KAS_Auth_GET" | tr -d ' ' | grep -i "//g")"
+_info "[KAS] -> AUTH URL $KAS_Auth"
+
+KAS_default_ratelimit=5 # TODO - Every response delivers a ratelimit (seconds) where KASAPI is blocking a request.
+
######## Public functions #####################
dns_kas_add() {
_fulldomain=$1
_txtvalue=$2
- _info "Using DNS-01 All-inkl/Kasserver hook"
- _info "Adding $_fulldomain DNS TXT entry on All-inkl/Kasserver"
- _info "Check and Save Props"
+
+ _info "[KAS] -> Using DNS-01 All-inkl/Kasserver hook"
+ _info "[KAS] -> Check and Save Props"
_check_and_save
- _info "Checking Zone and Record_Name"
+
+ _info "[KAS] -> Adding $_fulldomain DNS TXT entry on all-inkl.com/Kasserver"
+ _info "[KAS] -> Retriving Credential Token"
+ _get_credential_token
+
+ _info "[KAS] -> Checking Zone and Record_Name"
_get_zone_and_record_name "$_fulldomain"
- _info "Getting Record ID"
+
+ _info "[KAS] -> Checking for existing Record entries"
_get_record_id
- _info "Creating TXT DNS record"
- params="?kas_login=$KAS_Login"
- params="$params&kas_auth_type=$KAS_Authtype"
- params="$params&kas_auth_data=$KAS_Authdata"
- params="$params&var1=record_name"
- params="$params&wert1=$_record_name"
- params="$params&var2=record_type"
- params="$params&wert2=TXT"
- params="$params&var3=record_data"
- params="$params&wert3=$_txtvalue"
- params="$params&var4=record_aux"
- params="$params&wert4=0"
- params="$params&kas_action=add_dns_settings"
- params="$params&var5=zone_host"
- params="$params&wert5=$_zone"
- _debug2 "Wait for 10 seconds by default before calling KAS API."
- _sleep 10
- response="$(_get "$KAS_Api$params")"
- _debug2 "response" "$response"
+ # If there is a record_id, delete the entry
+ if [ -n "$_record_id" ]; then
+ _info "[KAS] -> Existing records found. Now deleting old entries"
+ for i in $_record_id; do
+ _delete_RecordByID "$i"
+ done
+ else
+ _info "[KAS] -> No record found."
+ fi
- if ! _contains "$response" "TRUE"; then
- _err "An unkown error occurred, please check manually."
+ _info "[KAS] -> Creating TXT DNS record"
+ action="add_dns_settings"
+ kasReqParam="\"record_name\":\"$_record_name\""
+ kasReqParam="$kasReqParam,\"record_type\":\"TXT\""
+ kasReqParam="$kasReqParam,\"record_data\":\"$_txtvalue\""
+ kasReqParam="$kasReqParam,\"record_aux\":\"0\""
+ kasReqParam="$kasReqParam,\"zone_host\":\"$_zone\""
+ response="$(_callAPI "$action" "$kasReqParam")"
+ _debug2 "[KAS] -> Response" "$response"
+
+ if [ -z "$response" ]; then
+ _info "[KAS] -> Response was empty, please check manually."
+ return 1
+ elif _contains "$response" ""; then
+ faultstring="$(echo "$response" | tr -d '\n\r' | sed "s//\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")"
+ case "${faultstring}" in
+ "record_already_exists")
+ _info "[KAS] -> The record already exists, which must not be a problem. Please check manually."
+ ;;
+ *)
+ _err "[KAS] -> An error =>$faultstring<= occurred, please check manually."
+ return 1
+ ;;
+ esac
+ elif ! _contains "$response" "- ReturnStringTRUE
"; then
+ _err "[KAS] -> An unknown error occurred, please check manually."
return 1
fi
return 0
@@ -58,45 +88,62 @@ dns_kas_add() {
dns_kas_rm() {
_fulldomain=$1
_txtvalue=$2
- _info "Using DNS-01 All-inkl/Kasserver hook"
- _info "Cleaning up after All-inkl/Kasserver hook"
- _info "Removing $_fulldomain DNS TXT entry on All-inkl/Kasserver"
- _info "Check and Save Props"
+ _info "[KAS] -> Using DNS-01 All-inkl/Kasserver hook"
+ _info "[KAS] -> Check and Save Props"
_check_and_save
- _info "Checking Zone and Record_Name"
+
+ _info "[KAS] -> Cleaning up after All-inkl/Kasserver hook"
+ _info "[KAS] -> Removing $_fulldomain DNS TXT entry on All-inkl/Kasserver"
+ _info "[KAS] -> Retriving Credential Token"
+ _get_credential_token
+
+ _info "[KAS] -> Checking Zone and Record_Name"
_get_zone_and_record_name "$_fulldomain"
- _info "Getting Record ID"
+
+ _info "[KAS] -> Getting Record ID"
_get_record_id
+ _info "[KAS] -> Removing entries with ID: $_record_id"
# If there is a record_id, delete the entry
if [ -n "$_record_id" ]; then
- params="?kas_login=$KAS_Login"
- params="$params&kas_auth_type=$KAS_Authtype"
- params="$params&kas_auth_data=$KAS_Authdata"
- params="$params&kas_action=delete_dns_settings"
-
for i in $_record_id; do
- params2="$params&var1=record_id"
- params2="$params2&wert1=$i"
- _debug2 "Wait for 10 seconds by default before calling KAS API."
- _sleep 10
- response="$(_get "$KAS_Api$params2")"
- _debug2 "response" "$response"
- if ! _contains "$response" "TRUE"; then
- _err "Either the txt record is not found or another error occurred, please check manually."
- return 1
- fi
+ _delete_RecordByID "$i"
done
else # Cannot delete or unkown error
- _err "No record_id found that can be deleted. Please check manually."
- return 1
+ _info "[KAS] -> No record_id found that can be deleted. Please check manually."
fi
return 0
}
########################## PRIVATE FUNCTIONS ###########################
+# Delete Record ID
+_delete_RecordByID() {
+ recId=$1
+ action="delete_dns_settings"
+ kasReqParam="\"record_id\":\"$recId\""
+ response="$(_callAPI "$action" "$kasReqParam")"
+ _debug2 "[KAS] -> Response" "$response"
+ if [ -z "$response" ]; then
+ _info "[KAS] -> Response was empty, please check manually."
+ return 1
+ elif _contains "$response" ""; then
+ faultstring="$(echo "$response" | tr -d '\n\r' | sed "s//\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")"
+ case "${faultstring}" in
+ "record_id_not_found")
+ _info "[KAS] -> The record was not found, which perhaps is not a problem. Please check manually."
+ ;;
+ *)
+ _err "[KAS] -> An error =>$faultstring<= occurred, please check manually."
+ return 1
+ ;;
+ esac
+ elif ! _contains "$response" "- ReturnStringTRUE
"; then
+ _err "[KAS] -> An unknown error occurred, please check manually."
+ return 1
+ fi
+}
# Checks for the ENV variables and saves them
_check_and_save() {
KAS_Login="${KAS_Login:-$(_readaccountconf_mutable KAS_Login)}"
@@ -107,7 +154,7 @@ _check_and_save() {
KAS_Login=
KAS_Authtype=
KAS_Authdata=
- _err "No auth details provided. Please set user credentials using the \$KAS_Login, \$KAS_Authtype, and \$KAS_Authdata environment variables."
+ _err "[KAS] -> No auth details provided. Please set user credentials using the \$KAS_Login, \$KAS_Authtype, and \$KAS_Authdata environment variables."
return 1
fi
_saveaccountconf_mutable KAS_Login "$KAS_Login"
@@ -119,50 +166,116 @@ _check_and_save() {
# Gets back the base domain/zone and record name.
# See: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide
_get_zone_and_record_name() {
- params="?kas_login=$KAS_Login"
- params="?kas_login=$KAS_Login"
- params="$params&kas_auth_type=$KAS_Authtype"
- params="$params&kas_auth_data=$KAS_Authdata"
- params="$params&kas_action=get_domains"
+ action="get_domains"
+ response="$(_callAPI "$action")"
+ _debug2 "[KAS] -> Response" "$response"
- _debug2 "Wait for 10 seconds by default before calling KAS API."
- _sleep 10
- response="$(_get "$KAS_Api$params")"
- _debug2 "response" "$response"
- _zonen="$(echo "$response" | tr -d "\n\r" | tr -d " " | tr '[]' '<>' | sed "s/=>Array/\n=> Array/g" | tr ' ' '\n' | grep "domain_name" | tr '<' '\n' | grep "domain_name" | sed "s/domain_name>=>//g")"
- _domain="$1"
- _temp_domain="$(echo "$1" | sed 's/\.$//')"
- _rootzone="$_domain"
- for i in $_zonen; do
- l1=${#_rootzone}
+ if [ -z "$response" ]; then
+ _info "[KAS] -> Response was empty, please check manually."
+ return 1
+ elif _contains "$response" ""; then
+ faultstring="$(echo "$response" | tr -d '\n\r' | sed "s//\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")"
+ _err "[KAS] -> Either no domains were found or another error =>$faultstring<= occurred, please check manually."
+ return 1
+ fi
+
+ zonen="$(echo "$response" | sed 's/- /\n/g' | sed -r 's/(.*domain_name<\/key>)(.*)(<\/value.*)/\2/' | sed '/^ Zone:" "$_zone"
+ _debug "[KAS] -> Domain:" "$domain"
+ _debug "[KAS] -> Record_Name:" "$_record_name"
return 0
}
# Retrieve the DNS record ID
_get_record_id() {
- params="?kas_login=$KAS_Login"
- params="$params&kas_auth_type=$KAS_Authtype"
- params="$params&kas_auth_data=$KAS_Authdata"
- params="$params&kas_action=get_dns_settings"
- params="$params&var1=zone_host"
- params="$params&wert1=$_zone"
+ action="get_dns_settings"
+ kasReqParam="\"zone_host\":\"$_zone\""
+ response="$(_callAPI "$action" "$kasReqParam")"
+ _debug2 "[KAS] -> Response" "$response"
- _debug2 "Wait for 10 seconds by default before calling KAS API."
- _sleep 10
- response="$(_get "$KAS_Api$params")"
- _debug2 "response" "$response"
- _record_id="$(echo "$response" | tr -d "\n\r" | tr -d " " | tr '[]' '<>' | sed "s/=>Array/\n=> Array/g" | tr ' ' '\n' | grep "=>$_record_name<" | grep '>TXT<' | tr '<' '\n' | grep record_id | sed "s/record_id>=>//g")"
- _debug2 _record_id "$_record_id"
+ if [ -z "$response" ]; then
+ _info "[KAS] -> Response was empty, please check manually."
+ return 1
+ elif _contains "$response" ""; then
+ faultstring="$(echo "$response" | tr -d '\n\r' | sed "s//\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")"
+ _err "[KAS] -> Either no domains were found or another error =>$faultstring<= occurred, please check manually."
+ return 1
+ fi
+
+ _record_id="$(echo "$response" | tr -d '\n\r' | sed "s/
- /\n/g" | grep -i "$_record_name" | grep -i ">TXT<" | sed "s/
- record_id<\/key>/=>/g" | grep -i "$_txtvalue" | sed "s/<\/value><\/item>/\n/g" | grep "=>" | sed "s/=>//g")"
+ _debug "[KAS] -> Record Id: " "$_record_id"
return 0
}
+
+# Retrieve credential token
+_get_credential_token() {
+ baseParamAuth="\"kas_login\":\"$KAS_Login\""
+ baseParamAuth="$baseParamAuth,\"kas_auth_type\":\"$KAS_Authtype\""
+ baseParamAuth="$baseParamAuth,\"kas_auth_data\":\"$KAS_Authdata\""
+ baseParamAuth="$baseParamAuth,\"session_lifetime\":600"
+ baseParamAuth="$baseParamAuth,\"session_update_lifetime\":\"Y\""
+
+ data='{'
+ data="$data$baseParamAuth}"
+
+ _debug "[KAS] -> Be friendly and wait $KAS_default_ratelimit seconds by default before calling KAS API."
+ _sleep $KAS_default_ratelimit
+
+ contentType="text/xml"
+ export _H1="SOAPAction: urn:xmethodsKasApiAuthentication#KasAuth"
+ response="$(_post "$data" "$KAS_Auth" "" "POST" "$contentType")"
+ _debug2 "[KAS] -> Response" "$response"
+
+ if [ -z "$response" ]; then
+ _info "[KAS] -> Response was empty, please check manually."
+ return 1
+ elif _contains "$response" ""; then
+ faultstring="$(echo "$response" | tr -d '\n\r' | sed "s//\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")"
+ _err "[KAS] -> Could not retrieve login token or antoher error =>$faultstring<= occurred, please check manually."
+ return 1
+ fi
+
+ _credential_token="$(echo "$response" | tr '\n' ' ' | sed 's/.*return xsi:type="xsd:string">\(.*\)<\/return>/\1/' | sed 's/<\/ns1:KasAuthResponse\(.*\)Envelope>.*//')"
+ _debug "[KAS] -> Credential Token: " "$_credential_token"
+ return 0
+}
+
+_callAPI() {
+ kasaction=$1
+ kasReqParams=$2
+
+ baseParamAuth="\"kas_login\":\"$KAS_Login\""
+ baseParamAuth="$baseParamAuth,\"kas_auth_type\":\"session\""
+ baseParamAuth="$baseParamAuth,\"kas_auth_data\":\"$_credential_token\""
+
+ data='{'
+ data="$data$baseParamAuth,\"kas_action\":\"$kasaction\""
+ if [ -n "$kasReqParams" ]; then
+ data="$data,\"KasRequestParams\":{$kasReqParams}"
+ fi
+ data="$data}"
+
+ _debug2 "[KAS] -> Request" "$data"
+
+ _debug "[KAS] -> Be friendly and wait $KAS_default_ratelimit seconds by default before calling KAS API."
+ _sleep $KAS_default_ratelimit
+
+ contentType="text/xml"
+ export _H1="SOAPAction: urn:xmethodsKasApi#KasApi"
+ response="$(_post "$data" "$KAS_Api" "" "POST" "$contentType")"
+ _debug2 "[KAS] -> Response" "$response"
+ echo "$response"
+}
diff --git a/dnsapi/dns_kinghost.sh b/dnsapi/dns_kinghost.sh
index 6253c71d..f640242f 100644
--- a/dnsapi/dns_kinghost.sh
+++ b/dnsapi/dns_kinghost.sh
@@ -2,7 +2,7 @@
############################################################
# KingHost API support #
-# http://api.kinghost.net/doc/ #
+# https://api.kinghost.net/doc/ #
# #
# Author: Felipe Keller Braz #
# Report Bugs here: https://github.com/kinghost/acme.sh #
diff --git a/dnsapi/dns_knot.sh b/dnsapi/dns_knot.sh
index 094a6981..729a89cb 100644
--- a/dnsapi/dns_knot.sh
+++ b/dnsapi/dns_knot.sh
@@ -19,8 +19,9 @@ dns_knot_add() {
_info "Adding ${fulldomain}. 60 TXT \"${txtvalue}\""
- knsupdate -y "${KNOT_KEY}" <
#Utilize leaseweb.com API to finish dns-01 verifications.
#Requires a Leaseweb API Key (export LSW_Key="Your Key")
-#See http://developer.leaseweb.com for more information.
+#See https://developer.leaseweb.com for more information.
######## Public functions #####################
-LSW_API="https://api.leaseweb.com/hosting/v2/domains/"
+LSW_API="https://api.leaseweb.com/hosting/v2/domains"
#Usage: dns_leaseweb_add _acme-challenge.www.domain.com
dns_leaseweb_add() {
diff --git a/dnsapi/dns_linode_v4.sh b/dnsapi/dns_linode_v4.sh
index c2bebc57..9504afbf 100755
--- a/dnsapi/dns_linode_v4.sh
+++ b/dnsapi/dns_linode_v4.sh
@@ -106,6 +106,7 @@ dns_linode_v4_rm() {
#################### Private functions below ##################################
_Linode_API() {
+ LINODE_V4_API_KEY="${LINODE_V4_API_KEY:-$(_readaccountconf_mutable LINODE_V4_API_KEY)}"
if [ -z "$LINODE_V4_API_KEY" ]; then
LINODE_V4_API_KEY=""
@@ -115,7 +116,7 @@ _Linode_API() {
return 1
fi
- _saveaccountconf LINODE_V4_API_KEY "$LINODE_V4_API_KEY"
+ _saveaccountconf_mutable LINODE_V4_API_KEY "$LINODE_V4_API_KEY"
}
#################### Private functions below ##################################
diff --git a/dnsapi/dns_loopia.sh b/dnsapi/dns_loopia.sh
index 7760b53e..60d072e0 100644
--- a/dnsapi/dns_loopia.sh
+++ b/dnsapi/dns_loopia.sh
@@ -32,8 +32,12 @@ dns_loopia_add() {
_info "Adding record"
- _loopia_add_sub_domain "$_domain" "$_sub_domain"
- _loopia_add_record "$_domain" "$_sub_domain" "$txtvalue"
+ if ! _loopia_add_sub_domain "$_domain" "$_sub_domain"; then
+ return 1
+ fi
+ if ! _loopia_add_record "$_domain" "$_sub_domain" "$txtvalue"; then
+ return 1
+ fi
}
@@ -70,12 +74,13 @@ dns_loopia_rm() {
%s
- ' "$LOOPIA_User" "$LOOPIA_Password" "$_domain" "$_sub_domain")
+ ' "$LOOPIA_User" "$Encoded_Password" "$_domain" "$_sub_domain")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
if ! _contains "$response" "OK"; then
- _err "Error could not get txt records"
+ err_response=$(echo "$response" | sed 's/.*\(.*\)<\/string>.*/\1/')
+ _err "Error could not get txt records: $err_response"
return 1
fi
}
@@ -101,6 +106,12 @@ _loopia_load_config() {
return 1
fi
+ if _contains "$LOOPIA_Password" "'" || _contains "$LOOPIA_Password" '"'; then
+ _err "Password contains a quotation mark or double quotation marks and this is not supported by dns_loopia.sh"
+ return 1
+ fi
+
+ Encoded_Password=$(_xml_encode "$LOOPIA_Password")
return 0
}
@@ -133,11 +144,12 @@ _loopia_get_records() {
%s
- ' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain")
+ ' "$LOOPIA_User" "$Encoded_Password" "$domain" "$sub_domain")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
if ! _contains "$response" ""; then
- _err "Error"
+ err_response=$(echo "$response" | sed 's/.*\(.*\)<\/string>.*/\1/')
+ _err "Error: $err_response"
return 1
fi
return 0
@@ -162,7 +174,7 @@ _get_root() {
%s
- ' $LOOPIA_User $LOOPIA_Password)
+ ' "$LOOPIA_User" "$Encoded_Password")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
while true; do
@@ -206,32 +218,35 @@ _loopia_add_record() {
%s
-
-
- type
- TXT
-
-
- priority
- 0
-
-
- ttl
- 300
-
-
- rdata
- %s
-
-
+
+
+
+ type
+ TXT
+
+
+ priority
+ 0
+
+
+ ttl
+ 300
+
+
+ rdata
+ %s
+
+
+
- ' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain" "$txtval")
+ ' "$LOOPIA_User" "$Encoded_Password" "$domain" "$sub_domain" "$txtval")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
if ! _contains "$response" "OK"; then
- _err "Error"
+ err_response=$(echo "$response" | sed 's/.*\(.*\)<\/string>.*/\1/')
+ _err "Error: $err_response"
return 1
fi
return 0
@@ -255,7 +270,7 @@ _sub_domain_exists() {
%s
- ' $LOOPIA_User $LOOPIA_Password "$domain")
+ ' "$LOOPIA_User" "$Encoded_Password" "$domain")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
@@ -290,13 +305,22 @@ _loopia_add_sub_domain() {
%s
- ' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain")
+ ' "$LOOPIA_User" "$Encoded_Password" "$domain" "$sub_domain")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
if ! _contains "$response" "OK"; then
- _err "Error"
+ err_response=$(echo "$response" | sed 's/.*\(.*\)<\/string>.*/\1/')
+ _err "Error: $err_response"
return 1
fi
return 0
}
+
+_xml_encode() {
+ encoded_string=$1
+ encoded_string=$(echo "$encoded_string" | sed 's/&/\&/')
+ encoded_string=$(echo "$encoded_string" | sed 's/\</')
+ encoded_string=$(echo "$encoded_string" | sed 's/>/\>/')
+ printf "%s" "$encoded_string"
+}
diff --git a/dnsapi/dns_miab.sh b/dnsapi/dns_miab.sh
index 7e697704..dad69bde 100644
--- a/dnsapi/dns_miab.sh
+++ b/dnsapi/dns_miab.sh
@@ -163,6 +163,7 @@ _retrieve_miab_env() {
_saveaccountconf_mutable MIAB_Username "$MIAB_Username"
_saveaccountconf_mutable MIAB_Password "$MIAB_Password"
_saveaccountconf_mutable MIAB_Server "$MIAB_Server"
+ return 0
}
#Useage: _miab_rest "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" "custom/_acme-challenge.www.domain.com/txt "POST"
diff --git a/dnsapi/dns_mydevil.sh b/dnsapi/dns_mydevil.sh
index 2f398959..953290af 100755
--- a/dnsapi/dns_mydevil.sh
+++ b/dnsapi/dns_mydevil.sh
@@ -74,7 +74,7 @@ dns_mydevil_rm() {
validRecords="^${num}${w}${fulldomain}${w}TXT${w}${any}${txtvalue}$"
for id in $(devil dns list "$domain" | tail -n+2 | grep "${validRecords}" | cut -w -s -f 1); do
_info "Removing record $id from domain $domain"
- devil dns del "$domain" "$id" || _err "Could not remove DNS record."
+ echo "y" | devil dns del "$domain" "$id" || _err "Could not remove DNS record."
done
}
@@ -87,7 +87,9 @@ mydevil_get_domain() {
domain=""
for domain in $(devil dns list | cut -w -s -f 1 | tail -n+2); do
+ _debug "Checking domain: $domain"
if _endswith "$fulldomain" "$domain"; then
+ _debug "Fulldomain '$fulldomain' matches '$domain'"
printf -- "%s" "$domain"
return 0
fi
diff --git a/dnsapi/dns_mydnsjp.sh b/dnsapi/dns_mydnsjp.sh
index aab2aabf..13866f70 100755
--- a/dnsapi/dns_mydnsjp.sh
+++ b/dnsapi/dns_mydnsjp.sh
@@ -150,7 +150,7 @@ _get_root() {
_mydnsjp_retrieve_domain() {
_debug "Login to MyDNS.JP"
- response="$(_post "masterid=$MYDNSJP_MasterID&masterpwd=$MYDNSJP_Password" "$MYDNSJP_API/?MENU=100")"
+ response="$(_post "MENU=100&masterid=$MYDNSJP_MasterID&masterpwd=$MYDNSJP_Password" "$MYDNSJP_API/members/")"
cookie="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)"
# If cookies is not empty then logon successful
@@ -159,22 +159,8 @@ _mydnsjp_retrieve_domain() {
return 1
fi
- _debug "Retrieve DOMAIN INFO page"
-
- export _H1="Cookie:${cookie}"
-
- response="$(_get "$MYDNSJP_API/?MENU=300")"
-
- if [ "$?" != "0" ]; then
- _err "Fail to retrieve DOMAIN INFO."
- return 1
- fi
-
_root_domain=$(echo "$response" | grep "DNSINFO\[domainname\]" | sed 's/^.*value="\([^"]*\)".*/\1/')
- # Logout
- response="$(_get "$MYDNSJP_API/?MENU=090")"
-
_debug _root_domain "$_root_domain"
if [ -z "$_root_domain" ]; then
diff --git a/dnsapi/dns_mythic_beasts.sh b/dnsapi/dns_mythic_beasts.sh
new file mode 100755
index 00000000..294ae84c
--- /dev/null
+++ b/dnsapi/dns_mythic_beasts.sh
@@ -0,0 +1,261 @@
+#!/usr/bin/env sh
+# Mythic Beasts is a long-standing UK service provider using standards-based OAuth2 authentication
+# To test: ./acme.sh --dns dns_mythic_beasts --test --debug 1 --output-insecure --issue --domain domain.com
+# Cannot retest once cert is issued
+# OAuth2 tokens only valid for 300 seconds so we do not store
+# NOTE: This will remove all TXT records matching the fulldomain, not just the added ones (_acme-challenge.www.domain.com)
+
+# Test OAuth2 credentials
+#MB_AK="aaaaaaaaaaaaaaaa"
+#MB_AS="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
+
+# URLs
+MB_API='https://api.mythic-beasts.com/dns/v2/zones'
+MB_AUTH='https://auth.mythic-beasts.com/login'
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_mythic_beasts_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _info "MYTHIC BEASTS Adding record $fulldomain = $txtvalue"
+ if ! _initAuth; then
+ return 1
+ fi
+
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+
+ # method path body_data
+ if _mb_rest POST "$_domain/records/$_sub_domain/TXT" "$txtvalue"; then
+
+ if _contains "$response" "1 records added"; then
+ _info "Added, verifying..."
+ # Max 120 seconds to publish
+ for i in $(seq 1 6); do
+ # Retry on error
+ if ! _mb_rest GET "$_domain/records/$_sub_domain/TXT?verify"; then
+ _sleep 20
+ else
+ _info "Record published!"
+ return 0
+ fi
+ done
+
+ else
+ _err "\n$response"
+ fi
+
+ fi
+ _err "Add txt record error."
+ return 1
+}
+
+#Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_mythic_beasts_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _info "MYTHIC BEASTS Removing record $fulldomain = $txtvalue"
+ if ! _initAuth; then
+ return 1
+ fi
+
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+
+ # method path body_data
+ if _mb_rest DELETE "$_domain/records/$_sub_domain/TXT" "$txtvalue"; then
+ _info "Record removed"
+ return 0
+ fi
+ _err "Remove txt record error."
+ return 1
+}
+
+#################### Private functions below ##################################
+
+#Possible formats:
+# _acme-challenge.www.example.com
+# _acme-challenge.example.com
+# _acme-challenge.example.co.uk
+# _acme-challenge.www.example.co.uk
+# _acme-challenge.sub1.sub2.www.example.co.uk
+# sub1.sub2.example.co.uk
+# example.com
+# example.co.uk
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ domain=$1
+ i=1
+ p=1
+
+ _debug "Detect the root zone"
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ if [ -z "$h" ]; then
+ _err "Domain exhausted"
+ return 1
+ fi
+
+ # Use the status errors to find the domain, continue on 403 Access denied
+ # method path body_data
+ _mb_rest GET "$h/records"
+ ret="$?"
+ if [ "$ret" -eq 0 ]; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="$h"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+ return 0
+ elif [ "$ret" -eq 1 ]; then
+ return 1
+ fi
+
+ p=$i
+ i=$(_math "$i" + 1)
+
+ if [ "$i" -gt 50 ]; then
+ break
+ fi
+ done
+ _err "Domain too long"
+ return 1
+}
+
+_initAuth() {
+ MB_AK="${MB_AK:-$(_readaccountconf_mutable MB_AK)}"
+ MB_AS="${MB_AS:-$(_readaccountconf_mutable MB_AS)}"
+
+ if [ -z "$MB_AK" ] || [ -z "$MB_AS" ]; then
+ MB_AK=""
+ MB_AS=""
+ _err "Please specify an OAuth2 Key & Secret"
+ return 1
+ fi
+
+ _saveaccountconf_mutable MB_AK "$MB_AK"
+ _saveaccountconf_mutable MB_AS "$MB_AS"
+
+ if ! _oauth2; then
+ return 1
+ fi
+
+ _info "Checking authentication"
+ _secure_debug access_token "$MB_TK"
+ _sleep 1
+
+ # GET a list of zones
+ # method path body_data
+ if ! _mb_rest GET ""; then
+ _err "The token is invalid"
+ return 1
+ fi
+ _info "Token OK"
+ return 0
+}
+
+# Github appears to use an outbound proxy for requests which means subsequent requests may not have the same
+# source IP. The standard Mythic Beasts OAuth2 tokens are tied to an IP, meaning github test requests fail
+# authentication. This is a work around using an undocumented MB API to obtain a token not tied to an
+# IP just for the github tests.
+_oauth2() {
+ if [ "$GITHUB_ACTIONS" = "true" ]; then
+ _oauth2_github
+ else
+ _oauth2_std
+ fi
+ return $?
+}
+
+_oauth2_std() {
+ # HTTP Basic Authentication
+ _H1="Authorization: Basic $(echo "$MB_AK:$MB_AS" | _base64)"
+ _H2="Accepts: application/json"
+ export _H1 _H2
+ body="grant_type=client_credentials"
+
+ _info "Getting OAuth2 token..."
+ # body url [needbase64] [POST|PUT|DELETE] [ContentType]
+ response="$(_post "$body" "$MB_AUTH" "" "POST" "application/x-www-form-urlencoded")"
+ if _contains "$response" "\"token_type\":\"bearer\""; then
+ MB_TK="$(echo "$response" | _egrep_o "access_token\":\"[^\"]*\"" | cut -d : -f 2 | tr -d '"')"
+ if [ -z "$MB_TK" ]; then
+ _err "Unable to get access_token"
+ _err "\n$response"
+ return 1
+ fi
+ else
+ _err "OAuth2 token_type not Bearer"
+ _err "\n$response"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
+
+_oauth2_github() {
+ _H1="Accepts: application/json"
+ export _H1
+ body="{\"login\":{\"handle\":\"$MB_AK\",\"pass\":\"$MB_AS\",\"floating\":1}}"
+
+ _info "Getting Floating token..."
+ # body url [needbase64] [POST|PUT|DELETE] [ContentType]
+ response="$(_post "$body" "$MB_AUTH" "" "POST" "application/json")"
+ MB_TK="$(echo "$response" | _egrep_o "\"token\":\"[^\"]*\"" | cut -d : -f 2 | tr -d '"')"
+ if [ -z "$MB_TK" ]; then
+ _err "Unable to get token"
+ _err "\n$response"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
+
+# method path body_data
+_mb_rest() {
+ # URL encoded body for single API operations
+ m="$1"
+ ep="$2"
+ data="$3"
+
+ if [ -z "$ep" ]; then
+ _mb_url="$MB_API"
+ else
+ _mb_url="$MB_API/$ep"
+ fi
+
+ _H1="Authorization: Bearer $MB_TK"
+ _H2="Accepts: application/json"
+ export _H1 _H2
+ if [ "$data" ] || [ "$m" = "POST" ] || [ "$m" = "PUT" ] || [ "$m" = "DELETE" ]; then
+ # body url [needbase64] [POST|PUT|DELETE] [ContentType]
+ response="$(_post "data=$data" "$_mb_url" "" "$m" "application/x-www-form-urlencoded")"
+ else
+ response="$(_get "$_mb_url")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "Request error"
+ return 1
+ fi
+
+ header="$(cat "$HTTP_HEADER")"
+ status="$(echo "$header" | _egrep_o "^HTTP[^ ]* .*$" | cut -d " " -f 2-100 | tr -d "\f\n")"
+ code="$(echo "$status" | _egrep_o "^[0-9]*")"
+ if [ "$code" -ge 400 ] || _contains "$response" "\"error\"" || _contains "$response" "invalid_client"; then
+ _err "error $status"
+ _err "\n$response"
+ _debug "\n$header"
+ return 2
+ fi
+
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_namecheap.sh b/dnsapi/dns_namecheap.sh
index 2e389265..a5f667a9 100755
--- a/dnsapi/dns_namecheap.sh
+++ b/dnsapi/dns_namecheap.sh
@@ -82,7 +82,7 @@ _get_root() {
_debug "Failed domain lookup via domains.getList api call. Trying domain lookup via domains.dns.getHosts api."
# The above "getList" api will only return hosts *owned* by the calling user. However, if the calling
# user is not the owner, but still has administrative rights, we must query the getHosts api directly.
- # See this comment and the official namecheap response: http://disq.us/p/1q6v9x9
+ # See this comment and the official namecheap response: https://disq.us/p/1q6v9x9
if ! _get_root_by_getHosts "$fulldomain"; then
return 1
fi
@@ -157,7 +157,7 @@ _namecheap_set_publicip() {
if [ -z "$NAMECHEAP_SOURCEIP" ]; then
_err "No Source IP specified for Namecheap API."
- _err "Use your public ip address or an url to retrieve it (e.g. https://ipconfig.co/ip) and export it as NAMECHEAP_SOURCEIP"
+ _err "Use your public ip address or an url to retrieve it (e.g. https://ifconfig.co/ip) and export it as NAMECHEAP_SOURCEIP"
return 1
else
_saveaccountconf NAMECHEAP_SOURCEIP "$NAMECHEAP_SOURCEIP"
@@ -175,7 +175,7 @@ _namecheap_set_publicip() {
_publicip=$(_get "$addr")
else
_err "No Source IP specified for Namecheap API."
- _err "Use your public ip address or an url to retrieve it (e.g. https://ipconfig.co/ip) and export it as NAMECHEAP_SOURCEIP"
+ _err "Use your public ip address or an url to retrieve it (e.g. https://ifconfig.co/ip) and export it as NAMECHEAP_SOURCEIP"
return 1
fi
fi
@@ -208,7 +208,7 @@ _namecheap_parse_host() {
_hostid=$(echo "$_host" | _egrep_o ' HostId="[^"]*' | cut -d '"' -f 2)
_hostname=$(echo "$_host" | _egrep_o ' Name="[^"]*' | cut -d '"' -f 2)
_hosttype=$(echo "$_host" | _egrep_o ' Type="[^"]*' | cut -d '"' -f 2)
- _hostaddress=$(echo "$_host" | _egrep_o ' Address="[^"]*' | cut -d '"' -f 2)
+ _hostaddress=$(echo "$_host" | _egrep_o ' Address="[^"]*' | cut -d '"' -f 2 | _xml_decode)
_hostmxpref=$(echo "$_host" | _egrep_o ' MXPref="[^"]*' | cut -d '"' -f 2)
_hostttl=$(echo "$_host" | _egrep_o ' TTL="[^"]*' | cut -d '"' -f 2)
@@ -259,7 +259,7 @@ _set_namecheap_TXT() {
_debug hosts "$hosts"
if [ -z "$hosts" ]; then
- _error "Hosts not found"
+ _err "Hosts not found"
return 1
fi
@@ -313,7 +313,7 @@ _del_namecheap_TXT() {
_debug hosts "$hosts"
if [ -z "$hosts" ]; then
- _error "Hosts not found"
+ _err "Hosts not found"
return 1
fi
@@ -405,3 +405,7 @@ _namecheap_set_tld_sld() {
done
}
+
+_xml_decode() {
+ sed 's/"/"/g'
+}
diff --git a/dnsapi/dns_namesilo.sh b/dnsapi/dns_namesilo.sh
index 0b87b7f7..f961d0bd 100755
--- a/dnsapi/dns_namesilo.sh
+++ b/dnsapi/dns_namesilo.sh
@@ -110,7 +110,7 @@ _get_root() {
return 1
fi
- if _contains "$response" "$host"; then
+ if _contains "$response" ">$host"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="$host"
return 0
diff --git a/dnsapi/dns_nanelo.sh b/dnsapi/dns_nanelo.sh
new file mode 100644
index 00000000..8ccc8c29
--- /dev/null
+++ b/dnsapi/dns_nanelo.sh
@@ -0,0 +1,59 @@
+#!/usr/bin/env sh
+
+# Official DNS API for Nanelo.com
+
+# Provide the required API Key like this:
+# NANELO_TOKEN="FmD408PdqT1E269gUK57"
+
+NANELO_API="https://api.nanelo.com/v1/"
+
+######## Public functions #####################
+
+# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_nanelo_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ NANELO_TOKEN="${NANELO_TOKEN:-$(_readaccountconf_mutable NANELO_TOKEN)}"
+ if [ -z "$NANELO_TOKEN" ]; then
+ NANELO_TOKEN=""
+ _err "You didn't configure a Nanelo API Key yet."
+ _err "Please set NANELO_TOKEN and try again."
+ _err "Login to Nanelo.com and go to Settings > API Keys to get a Key"
+ return 1
+ fi
+ _saveaccountconf_mutable NANELO_TOKEN "$NANELO_TOKEN"
+
+ _info "Adding TXT record to ${fulldomain}"
+ response="$(_get "$NANELO_API$NANELO_TOKEN/dns/addrecord?type=TXT&ttl=60&name=${fulldomain}&value=${txtvalue}")"
+ if _contains "${response}" 'success'; then
+ return 0
+ fi
+ _err "Could not create resource record, please check the logs"
+ _err "${response}"
+ return 1
+}
+
+dns_nanelo_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ NANELO_TOKEN="${NANELO_TOKEN:-$(_readaccountconf_mutable NANELO_TOKEN)}"
+ if [ -z "$NANELO_TOKEN" ]; then
+ NANELO_TOKEN=""
+ _err "You didn't configure a Nanelo API Key yet."
+ _err "Please set NANELO_TOKEN and try again."
+ _err "Login to Nanelo.com and go to Settings > API Keys to get a Key"
+ return 1
+ fi
+ _saveaccountconf_mutable NANELO_TOKEN "$NANELO_TOKEN"
+
+ _info "Deleting resource record $fulldomain"
+ response="$(_get "$NANELO_API$NANELO_TOKEN/dns/deleterecord?type=TXT&ttl=60&name=${fulldomain}&value=${txtvalue}")"
+ if _contains "${response}" 'success'; then
+ return 0
+ fi
+ _err "Could not delete resource record, please check the logs"
+ _err "${response}"
+ return 1
+}
diff --git a/dnsapi/dns_nederhost.sh b/dnsapi/dns_nederhost.sh
index 0954ab65..abaae42b 100755
--- a/dnsapi/dns_nederhost.sh
+++ b/dnsapi/dns_nederhost.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env sh
-#NederHost_Key="sdfgikogfdfghjklkjhgfcdcfghjk"
+#NederHost_Key="sdfgikogfdfghjklkjhgfcdcfghj"
NederHost_Api="https://api.nederhost.nl/dns/v1"
@@ -112,12 +112,8 @@ _nederhost_rest() {
export _H1="Authorization: Bearer $NederHost_Key"
export _H2="Content-Type: application/json"
- if [ "$m" != "GET" ]; then
- _debug data "$data"
- response="$(_post "$data" "$NederHost_Api/$ep" "" "$m")"
- else
- response="$(_get "$NederHost_Api/$ep")"
- fi
+ _debug data "$data"
+ response="$(_post "$data" "$NederHost_Api/$ep" "" "$m")"
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
_debug "http response code $_code"
diff --git a/dnsapi/dns_netcup.sh b/dnsapi/dns_netcup.sh
index d519e4f7..776fa02d 100644
--- a/dnsapi/dns_netcup.sh
+++ b/dnsapi/dns_netcup.sh
@@ -119,16 +119,16 @@ login() {
tmp=$(_post "{\"action\": \"login\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apipassword\": \"$NC_Apipw\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST")
sid=$(echo "$tmp" | tr '{}' '\n' | grep apisessionid | cut -d '"' -f 4)
_debug "$tmp"
- if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
- _err "$msg"
+ if [ "$(_getfield "$tmp" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
+ _err "$tmp"
return 1
fi
}
logout() {
tmp=$(_post "{\"action\": \"logout\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST")
_debug "$tmp"
- if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
- _err "$msg"
+ if [ "$(_getfield "$tmp" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
+ _err "$tmp"
return 1
fi
}
diff --git a/dnsapi/dns_netlify.sh b/dnsapi/dns_netlify.sh
index 2ce13e2b..0e5dc327 100644
--- a/dnsapi/dns_netlify.sh
+++ b/dnsapi/dns_netlify.sh
@@ -18,15 +18,15 @@ dns_netlify_add() {
NETLIFY_ACCESS_TOKEN=""
_err "Please specify your Netlify Access Token and try again."
return 1
+ else
+ _saveaccountconf_mutable NETLIFY_ACCESS_TOKEN "$NETLIFY_ACCESS_TOKEN"
fi
_info "Using Netlify"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
- _saveaccountconf_mutable NETLIFY_ACCESS_TOKEN "$NETLIFY_ACCESS_TOKEN"
-
- if ! _get_root "$fulldomain" "$accesstoken"; then
+ if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
@@ -62,9 +62,9 @@ dns_netlify_rm() {
_debug txtdomain "$txtdomain"
_debug txt "$txt"
- _saveaccountconf_mutable NETLIFY_ACCESS_TOKEN "$NETLIFY_ACCESS_TOKEN"
+ NETLIFY_ACCESS_TOKEN="${NETLIFY_ACCESS_TOKEN:-$(_readaccountconf_mutable NETLIFY_ACCESS_TOKEN)}"
- if ! _get_root "$txtdomain" "$accesstoken"; then
+ if ! _get_root "$txtdomain"; then
_err "invalid domain"
return 1
fi
@@ -114,7 +114,7 @@ _get_root() {
fi
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
- _domain_id=$(echo "$response" | _egrep_o "\"[^\"]*\",\"name\":\"$h" | cut -d , -f 1 | tr -d \")
+ _domain_id=$(echo "$response" | _egrep_o "\"[^\"]*\",\"name\":\"$h\"" | cut -d , -f 1 | tr -d \")
if [ "$_domain_id" ]; then
if [ "$i" = 1 ]; then
#create the record at the domain apex (@) if only the domain name was provided as --domain-alias
diff --git a/dnsapi/dns_nsd.sh b/dnsapi/dns_nsd.sh
index 83cc4cac..0d29a485 100644
--- a/dnsapi/dns_nsd.sh
+++ b/dnsapi/dns_nsd.sh
@@ -51,7 +51,7 @@ dns_nsd_rm() {
Nsd_ZoneFile="${Nsd_ZoneFile:-$(_readdomainconf Nsd_ZoneFile)}"
Nsd_Command="${Nsd_Command:-$(_readdomainconf Nsd_Command)}"
- sed -i "/$fulldomain. $ttlvalue IN TXT \"$txtvalue\"/d" "$Nsd_ZoneFile"
+ _sed_i "/$fulldomain. $ttlvalue IN TXT \"$txtvalue\"/d" "$Nsd_ZoneFile"
_info "Removed TXT record for $fulldomain"
_debug "Running $Nsd_Command"
if eval "$Nsd_Command"; then
diff --git a/dnsapi/dns_oci.sh b/dnsapi/dns_oci.sh
new file mode 100644
index 00000000..3b81143f
--- /dev/null
+++ b/dnsapi/dns_oci.sh
@@ -0,0 +1,325 @@
+#!/usr/bin/env sh
+#
+# Acme.sh DNS API plugin for Oracle Cloud Infrastructure
+# Copyright (c) 2021, Oracle and/or its affiliates
+#
+# The plugin will automatically use the default profile from an OCI SDK and CLI
+# configuration file, if it exists.
+#
+# Alternatively, set the following environment variables:
+# - OCI_CLI_TENANCY : OCID of tenancy that contains the target DNS zone
+# - OCI_CLI_USER : OCID of user with permission to add/remove records from zones
+# - OCI_CLI_REGION : Should point to the tenancy home region
+#
+# One of the following two variables is required:
+# - OCI_CLI_KEY_FILE: Path to private API signing key file in PEM format; or
+# - OCI_CLI_KEY : The private API signing key in PEM format
+#
+# NOTE: using an encrypted private key that needs a passphrase is not supported.
+#
+
+dns_oci_add() {
+ _fqdn="$1"
+ _rdata="$2"
+
+ if _get_oci_zone; then
+
+ _add_record_body="{\"items\":[{\"domain\":\"${_sub_domain}.${_domain}\",\"rdata\":\"$_rdata\",\"rtype\":\"TXT\",\"ttl\": 30,\"operation\":\"ADD\"}]}"
+ response=$(_signed_request "PATCH" "/20180115/zones/${_domain}/records" "$_add_record_body")
+ if [ "$response" ]; then
+ _info "Success: added TXT record for ${_sub_domain}.${_domain}."
+ else
+ _err "Error: failed to add TXT record for ${_sub_domain}.${_domain}."
+ _err "Check that the user has permission to add records to this zone."
+ return 1
+ fi
+
+ else
+ return 1
+ fi
+
+}
+
+dns_oci_rm() {
+ _fqdn="$1"
+ _rdata="$2"
+
+ if _get_oci_zone; then
+
+ _remove_record_body="{\"items\":[{\"domain\":\"${_sub_domain}.${_domain}\",\"rdata\":\"$_rdata\",\"rtype\":\"TXT\",\"operation\":\"REMOVE\"}]}"
+ response=$(_signed_request "PATCH" "/20180115/zones/${_domain}/records" "$_remove_record_body")
+ if [ "$response" ]; then
+ _info "Success: removed TXT record for ${_sub_domain}.${_domain}."
+ else
+ _err "Error: failed to remove TXT record for ${_sub_domain}.${_domain}."
+ _err "Check that the user has permission to remove records from this zone."
+ return 1
+ fi
+
+ else
+ return 1
+ fi
+
+}
+
+#################### Private functions below ##################################
+_get_oci_zone() {
+
+ if ! _oci_config; then
+ return 1
+ fi
+
+ if ! _get_zone "$_fqdn"; then
+ _err "Error: DNS Zone not found for $_fqdn in $OCI_CLI_TENANCY"
+ return 1
+ fi
+
+ return 0
+
+}
+
+_oci_config() {
+
+ _DEFAULT_OCI_CLI_CONFIG_FILE="$HOME/.oci/config"
+ OCI_CLI_CONFIG_FILE="${OCI_CLI_CONFIG_FILE:-$(_readaccountconf_mutable OCI_CLI_CONFIG_FILE)}"
+
+ if [ -z "$OCI_CLI_CONFIG_FILE" ]; then
+ OCI_CLI_CONFIG_FILE="$_DEFAULT_OCI_CLI_CONFIG_FILE"
+ fi
+
+ if [ "$_DEFAULT_OCI_CLI_CONFIG_FILE" != "$OCI_CLI_CONFIG_FILE" ]; then
+ _saveaccountconf_mutable OCI_CLI_CONFIG_FILE "$OCI_CLI_CONFIG_FILE"
+ else
+ _clearaccountconf_mutable OCI_CLI_CONFIG_FILE
+ fi
+
+ _DEFAULT_OCI_CLI_PROFILE="DEFAULT"
+ OCI_CLI_PROFILE="${OCI_CLI_PROFILE:-$(_readaccountconf_mutable OCI_CLI_PROFILE)}"
+ if [ "$_DEFAULT_OCI_CLI_PROFILE" != "$OCI_CLI_PROFILE" ]; then
+ _saveaccountconf_mutable OCI_CLI_PROFILE "$OCI_CLI_PROFILE"
+ else
+ OCI_CLI_PROFILE="$_DEFAULT_OCI_CLI_PROFILE"
+ _clearaccountconf_mutable OCI_CLI_PROFILE
+ fi
+
+ OCI_CLI_TENANCY="${OCI_CLI_TENANCY:-$(_readaccountconf_mutable OCI_CLI_TENANCY)}"
+ if [ "$OCI_CLI_TENANCY" ]; then
+ _saveaccountconf_mutable OCI_CLI_TENANCY "$OCI_CLI_TENANCY"
+ elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
+ _debug "Reading OCI_CLI_TENANCY value from: $OCI_CLI_CONFIG_FILE"
+ OCI_CLI_TENANCY="${OCI_CLI_TENANCY:-$(_readini "$OCI_CLI_CONFIG_FILE" tenancy "$OCI_CLI_PROFILE")}"
+ fi
+
+ if [ -z "$OCI_CLI_TENANCY" ]; then
+ _err "Error: unable to read OCI_CLI_TENANCY from config file or environment variable."
+ return 1
+ fi
+
+ OCI_CLI_USER="${OCI_CLI_USER:-$(_readaccountconf_mutable OCI_CLI_USER)}"
+ if [ "$OCI_CLI_USER" ]; then
+ _saveaccountconf_mutable OCI_CLI_USER "$OCI_CLI_USER"
+ elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
+ _debug "Reading OCI_CLI_USER value from: $OCI_CLI_CONFIG_FILE"
+ OCI_CLI_USER="${OCI_CLI_USER:-$(_readini "$OCI_CLI_CONFIG_FILE" user "$OCI_CLI_PROFILE")}"
+ fi
+ if [ -z "$OCI_CLI_USER" ]; then
+ _err "Error: unable to read OCI_CLI_USER from config file or environment variable."
+ return 1
+ fi
+
+ OCI_CLI_REGION="${OCI_CLI_REGION:-$(_readaccountconf_mutable OCI_CLI_REGION)}"
+ if [ "$OCI_CLI_REGION" ]; then
+ _saveaccountconf_mutable OCI_CLI_REGION "$OCI_CLI_REGION"
+ elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
+ _debug "Reading OCI_CLI_REGION value from: $OCI_CLI_CONFIG_FILE"
+ OCI_CLI_REGION="${OCI_CLI_REGION:-$(_readini "$OCI_CLI_CONFIG_FILE" region "$OCI_CLI_PROFILE")}"
+ fi
+ if [ -z "$OCI_CLI_REGION" ]; then
+ _err "Error: unable to read OCI_CLI_REGION from config file or environment variable."
+ return 1
+ fi
+
+ OCI_CLI_KEY="${OCI_CLI_KEY:-$(_readaccountconf_mutable OCI_CLI_KEY)}"
+ if [ -z "$OCI_CLI_KEY" ]; then
+ _clearaccountconf_mutable OCI_CLI_KEY
+ OCI_CLI_KEY_FILE="${OCI_CLI_KEY_FILE:-$(_readini "$OCI_CLI_CONFIG_FILE" key_file "$OCI_CLI_PROFILE")}"
+ if [ "$OCI_CLI_KEY_FILE" ] && [ -f "$OCI_CLI_KEY_FILE" ]; then
+ _debug "Reading OCI_CLI_KEY value from: $OCI_CLI_KEY_FILE"
+ OCI_CLI_KEY=$(_base64 <"$OCI_CLI_KEY_FILE")
+ _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY"
+ fi
+ else
+ _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY"
+ fi
+
+ if [ -z "$OCI_CLI_KEY_FILE" ] && [ -z "$OCI_CLI_KEY" ]; then
+ _err "Error: unable to find key file path in OCI config file or OCI_CLI_KEY_FILE."
+ _err "Error: unable to load private API signing key from OCI_CLI_KEY."
+ return 1
+ fi
+
+ if [ "$(printf "%s\n" "$OCI_CLI_KEY" | wc -l)" -eq 1 ]; then
+ OCI_CLI_KEY=$(printf "%s" "$OCI_CLI_KEY" | _dbase64)
+ fi
+
+ return 0
+
+}
+
+# _get_zone(): retrieves the Zone name and OCID
+#
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_ociid=ocid1.dns-zone.oc1..
+_get_zone() {
+ domain=$1
+ i=1
+ p=1
+
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug h "$h"
+ if [ -z "$h" ]; then
+ # not valid
+ return 1
+ fi
+
+ _domain_id=$(_signed_request "GET" "/20180115/zones/$h" "" "id")
+ if [ "$_domain_id" ]; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain=$h
+
+ _debug _domain_id "$_domain_id"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+ return 0
+ fi
+
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+
+}
+
+#Usage: privatekey
+#Output MD5 fingerprint
+_fingerprint() {
+
+ pkey="$1"
+ if [ -z "$pkey" ]; then
+ _usage "Usage: _fingerprint privkey"
+ return 1
+ fi
+
+ printf "%s" "$pkey" | ${ACME_OPENSSL_BIN:-openssl} rsa -pubout -outform DER 2>/dev/null | ${ACME_OPENSSL_BIN:-openssl} md5 -c | cut -d = -f 2 | tr -d ' '
+
+}
+
+_signed_request() {
+
+ _sig_method="$1"
+ _sig_target="$2"
+ _sig_body="$3"
+ _return_field="$4"
+
+ _key_fingerprint=$(_fingerprint "$OCI_CLI_KEY")
+ _sig_host="dns.$OCI_CLI_REGION.oraclecloud.com"
+ _sig_keyId="$OCI_CLI_TENANCY/$OCI_CLI_USER/$_key_fingerprint"
+ _sig_alg="rsa-sha256"
+ _sig_version="1"
+ _sig_now="$(LC_ALL=C \date -u "+%a, %d %h %Y %H:%M:%S GMT")"
+
+ _request_method=$(printf %s "$_sig_method" | _lower_case)
+ _curl_method=$(printf %s "$_sig_method" | _upper_case)
+
+ _request_target="(request-target): $_request_method $_sig_target"
+ _date_header="date: $_sig_now"
+ _host_header="host: $_sig_host"
+
+ _string_to_sign="$_request_target\n$_date_header\n$_host_header"
+ _sig_headers="(request-target) date host"
+
+ if [ "$_sig_body" ]; then
+ _secure_debug3 _sig_body "$_sig_body"
+ _sig_body_sha256="x-content-sha256: $(printf %s "$_sig_body" | _digest sha256)"
+ _sig_body_type="content-type: application/json"
+ _sig_body_length="content-length: ${#_sig_body}"
+ _string_to_sign="$_string_to_sign\n$_sig_body_sha256\n$_sig_body_type\n$_sig_body_length"
+ _sig_headers="$_sig_headers x-content-sha256 content-type content-length"
+ fi
+
+ _tmp_file=$(_mktemp)
+ if [ -f "$_tmp_file" ]; then
+ printf '%s' "$OCI_CLI_KEY" >"$_tmp_file"
+ _signature=$(printf '%b' "$_string_to_sign" | _sign "$_tmp_file" sha256 | tr -d '\r\n')
+ rm -f "$_tmp_file"
+ fi
+
+ _signed_header="Authorization: Signature version=\"$_sig_version\",keyId=\"$_sig_keyId\",algorithm=\"$_sig_alg\",headers=\"$_sig_headers\",signature=\"$_signature\""
+ _secure_debug3 _signed_header "$_signed_header"
+
+ if [ "$_curl_method" = "GET" ]; then
+ export _H1="$_date_header"
+ export _H2="$_signed_header"
+ _response="$(_get "https://${_sig_host}${_sig_target}")"
+ elif [ "$_curl_method" = "PATCH" ]; then
+ export _H1="$_date_header"
+ # shellcheck disable=SC2090
+ export _H2="$_sig_body_sha256"
+ export _H3="$_sig_body_type"
+ export _H4="$_sig_body_length"
+ export _H5="$_signed_header"
+ _response="$(_post "$_sig_body" "https://${_sig_host}${_sig_target}" "" "PATCH")"
+ else
+ _err "Unable to process method: $_curl_method."
+ fi
+
+ _ret="$?"
+ if [ "$_return_field" ]; then
+ _response="$(echo "$_response" | sed 's/\\\"//g'))"
+ _return=$(echo "${_response}" | _egrep_o "\"$_return_field\"\\s*:\\s*\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d "\"")
+ else
+ _return="$_response"
+ fi
+
+ printf "%s" "$_return"
+ return $_ret
+
+}
+
+# file key [section]
+_readini() {
+ _file="$1"
+ _key="$2"
+ _section="${3:-DEFAULT}"
+
+ _start_n=$(grep -n '\['"$_section"']' "$_file" | cut -d : -f 1)
+ _debug3 _start_n "$_start_n"
+ if [ -z "$_start_n" ]; then
+ _err "Can not find section: $_section"
+ return 1
+ fi
+
+ _start_nn=$(_math "$_start_n" + 1)
+ _debug3 "_start_nn" "$_start_nn"
+
+ _left="$(sed -n "${_start_nn},99999p" "$_file")"
+ _debug3 _left "$_left"
+ _end="$(echo "$_left" | grep -n "^\[" | _head_n 1)"
+ _debug3 "_end" "$_end"
+ if [ "$_end" ]; then
+ _end_n=$(echo "$_end" | cut -d : -f 1)
+ _debug3 "_end_n" "$_end_n"
+ _seg_n=$(echo "$_left" | sed -n "1,${_end_n}p")
+ else
+ _seg_n="$_left"
+ fi
+
+ _debug3 "_seg_n" "$_seg_n"
+ _lineini="$(echo "$_seg_n" | grep "^ *$_key *= *")"
+ _inivalue="$(printf "%b" "$(eval "echo $_lineini | sed \"s/^ *${_key} *= *//g\"")")"
+ _debug2 _inivalue "$_inivalue"
+ echo "$_inivalue"
+
+}
diff --git a/dnsapi/dns_one.sh b/dnsapi/dns_one.sh
index 890cc804..1565b767 100644
--- a/dnsapi/dns_one.sh
+++ b/dnsapi/dns_one.sh
@@ -1,22 +1,9 @@
#!/usr/bin/env sh
-# -*- mode: sh; tab-width: 2; indent-tabs-mode: s; coding: utf-8 -*-
-
# one.com ui wrapper for acme.sh
-# Author: github: @diseq
-# Created: 2019-02-17
-# Fixed by: @der-berni
-# Modified: 2020-04-07
-#
-# Use ONECOM_KeepCnameProxy to keep the CNAME DNS record
-# export ONECOM_KeepCnameProxy="1"
+
#
# export ONECOM_User="username"
# export ONECOM_Password="password"
-#
-# Usage:
-# acme.sh --issue --dns dns_one -d example.com
-#
-# only single domain supported atm
dns_one_add() {
fulldomain=$1
@@ -36,27 +23,9 @@ dns_one_add() {
subdomain="${_sub_domain}"
maindomain=${_domain}
- useProxy=0
- if [ "${_sub_domain}" = "_acme-challenge" ]; then
- subdomain="proxy${_sub_domain}"
- useProxy=1
- fi
-
_debug subdomain "$subdomain"
_debug maindomain "$maindomain"
- if [ $useProxy -eq 1 ]; then
- #Check if the CNAME exists
- _dns_one_getrecord "CNAME" "$_sub_domain" "$subdomain.$maindomain"
- if [ -z "$id" ]; then
- _info "$(__red "Add CNAME Proxy record: '$(__green "\"$_sub_domain\" => \"$subdomain.$maindomain\"")'")"
- _dns_one_addrecord "CNAME" "$_sub_domain" "$subdomain.$maindomain"
-
- _info "Not valid yet, let's wait 1 hour to take effect."
- _sleep 3600
- fi
- fi
-
#Check if the TXT exists
_dns_one_getrecord "TXT" "$subdomain" "$txtvalue"
if [ -n "$id" ]; then
@@ -92,26 +61,8 @@ dns_one_rm() {
subdomain="${_sub_domain}"
maindomain=${_domain}
- useProxy=0
- if [ "${_sub_domain}" = "_acme-challenge" ]; then
- subdomain="proxy${_sub_domain}"
- useProxy=1
- fi
-
_debug subdomain "$subdomain"
_debug maindomain "$maindomain"
- if [ $useProxy -eq 1 ]; then
- if [ "$ONECOM_KeepCnameProxy" = "1" ]; then
- _info "$(__red "Keeping CNAME Proxy record: '$(__green "\"$_sub_domain\" => \"$subdomain.$maindomain\"")'")"
- else
- #Check if the CNAME exists
- _dns_one_getrecord "CNAME" "$_sub_domain" "$subdomain.$maindomain"
- if [ -n "$id" ]; then
- _info "$(__red "Removing CNAME Proxy record: '$(__green "\"$_sub_domain\" => \"$subdomain.$maindomain\"")'")"
- _dns_one_delrecord "$id"
- fi
- fi
- fi
#Check if the TXT exists
_dns_one_getrecord "TXT" "$subdomain" "$txtvalue"
@@ -136,7 +87,7 @@ dns_one_rm() {
# _domain=domain.com
_get_root() {
domain="$1"
- i=2
+ i=1
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
@@ -163,8 +114,6 @@ _get_root() {
_dns_one_login() {
# get credentials
- ONECOM_KeepCnameProxy="${ONECOM_KeepCnameProxy:-$(_readaccountconf_mutable ONECOM_KeepCnameProxy)}"
- ONECOM_KeepCnameProxy="${ONECOM_KeepCnameProxy:-0}"
ONECOM_User="${ONECOM_User:-$(_readaccountconf_mutable ONECOM_User)}"
ONECOM_Password="${ONECOM_Password:-$(_readaccountconf_mutable ONECOM_Password)}"
if [ -z "$ONECOM_User" ] || [ -z "$ONECOM_Password" ]; then
@@ -176,7 +125,6 @@ _dns_one_login() {
fi
#save the api key and email to the account conf file.
- _saveaccountconf_mutable ONECOM_KeepCnameProxy "$ONECOM_KeepCnameProxy"
_saveaccountconf_mutable ONECOM_User "$ONECOM_User"
_saveaccountconf_mutable ONECOM_Password "$ONECOM_Password"
diff --git a/dnsapi/dns_openstack.sh b/dnsapi/dns_openstack.sh
index 38619e6f..fcc1dc2e 100755
--- a/dnsapi/dns_openstack.sh
+++ b/dnsapi/dns_openstack.sh
@@ -57,16 +57,16 @@ _dns_openstack_create_recordset() {
if [ -z "$_recordset_id" ]; then
_info "Creating a new recordset"
- if ! _recordset_id=$(openstack recordset create -c id -f value --type TXT --record "$txtvalue" "$_zone_id" "$fulldomain."); then
+ if ! _recordset_id=$(openstack recordset create -c id -f value --type TXT --record="$txtvalue" "$_zone_id" "$fulldomain."); then
_err "No recordset ID found after create"
return 1
fi
else
_info "Updating existing recordset"
- # Build new list of --record args for update
- _record_args="--record $txtvalue"
+ # Build new list of --record= args for update
+ _record_args="--record=$txtvalue"
for _rec in $_records; do
- _record_args="$_record_args --record $_rec"
+ _record_args="$_record_args --record=$_rec"
done
# shellcheck disable=SC2086
if ! _recordset_id=$(openstack recordset set -c id -f value $_record_args "$_zone_id" "$fulldomain."); then
@@ -107,13 +107,13 @@ _dns_openstack_delete_recordset() {
fi
else
_info "Found existing records, updating recordset"
- # Build new list of --record args for update
+ # Build new list of --record= args for update
_record_args=""
for _rec in $_records; do
if [ "$_rec" = "$txtvalue" ]; then
continue
fi
- _record_args="$_record_args --record $_rec"
+ _record_args="$_record_args --record=$_rec"
done
# shellcheck disable=SC2086
if ! openstack recordset set -c id -f value $_record_args "$_zone_id" "$fulldomain." >/dev/null; then
diff --git a/dnsapi/dns_opnsense.sh b/dnsapi/dns_opnsense.sh
index 069f6c32..d40cbe28 100755
--- a/dnsapi/dns_opnsense.sh
+++ b/dnsapi/dns_opnsense.sh
@@ -137,7 +137,7 @@ _get_root() {
domain=$1
i=2
p=1
- if _opns_rest "GET" "/domain/get"; then
+ if _opns_rest "GET" "/domain/searchPrimaryDomain"; then
_domain_response="$response"
else
return 1
@@ -150,8 +150,7 @@ _get_root() {
return 1
fi
_debug h "$h"
- id=$(echo "$_domain_response" | _egrep_o "\"[^\"]*\":{\"enabled\":\"1\",\"type\":{\"master\":{\"value\":\"master\",\"selected\":1},\"slave\":{\"value\":\"slave\",\"selected\":0}},\"masterip\":\"[^\"]*\"(,\"allownotifyslave\":{\"\":{[^}]*}},|,)\"domainname\":\"${h}\"" | cut -d ':' -f 1 | cut -d '"' -f 2)
-
+ id=$(echo "$_domain_response" | _egrep_o "\"uuid\":\"[a-z0-9\-]*\",\"enabled\":\"1\",\"type\":\"primary\",\"domainname\":\"${h}\"" | cut -d ':' -f 2 | cut -d '"' -f 2)
if [ -n "$id" ]; then
_debug id "$id"
_host=$(printf "%s" "$domain" | cut -d . -f 1-$p)
diff --git a/dnsapi/dns_ovh.sh b/dnsapi/dns_ovh.sh
index f6f9689a..e1a958f6 100755
--- a/dnsapi/dns_ovh.sh
+++ b/dnsapi/dns_ovh.sh
@@ -14,6 +14,9 @@
#'ovh-eu'
OVH_EU='https://eu.api.ovh.com/1.0'
+#'ovh-us'
+OVH_US='https://api.us.ovhcloud.com/1.0'
+
#'ovh-ca':
OVH_CA='https://ca.api.ovh.com/1.0'
@@ -29,9 +32,6 @@ SYS_EU='https://eu.api.soyoustart.com/1.0'
#'soyoustart-ca'
SYS_CA='https://ca.api.soyoustart.com/1.0'
-#'runabove-ca'
-RAV_CA='https://api.runabove.com/1.0'
-
wiki="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-OVH-domain-api"
ovh_success="https://github.com/acmesh-official/acme.sh/wiki/OVH-Success"
@@ -45,6 +45,10 @@ _ovh_get_api() {
printf "%s" $OVH_EU
return
;;
+ ovh-us | ovhus)
+ printf "%s" $OVH_US
+ return
+ ;;
ovh-ca | ovhca)
printf "%s" $OVH_CA
return
@@ -65,14 +69,15 @@ _ovh_get_api() {
printf "%s" $SYS_CA
return
;;
- runabove-ca | runaboveca)
- printf "%s" $RAV_CA
+ # raw API url starts with https://
+ https*)
+ printf "%s" "$1"
return
;;
*)
- _err "Unknown parameter : $1"
+ _err "Unknown endpoint : $1"
return 1
;;
esac
@@ -92,7 +97,7 @@ _initAuth() {
if [ "$OVH_AK" != "$(_readaccountconf OVH_AK)" ]; then
_info "It seems that your ovh key is changed, let's clear consumer key first."
- _clearaccountconf OVH_CK
+ _clearaccountconf_mutable OVH_CK
fi
_saveaccountconf_mutable OVH_AK "$OVH_AK"
_saveaccountconf_mutable OVH_AS "$OVH_AS"
@@ -118,13 +123,14 @@ _initAuth() {
#return and wait for retry.
return 1
fi
+ _saveaccountconf_mutable OVH_CK "$OVH_CK"
_info "Checking authentication"
if ! _ovh_rest GET "domain" || _contains "$response" "INVALID_CREDENTIAL" || _contains "$response" "NOT_CREDENTIAL"; then
_err "The consumer key is invalid: $OVH_CK"
_err "Please retry to create a new one."
- _clearaccountconf OVH_CK
+ _clearaccountconf_mutable OVH_CK
return 1
fi
_info "Consumer key is ok."
@@ -198,6 +204,8 @@ dns_ovh_rm() {
if ! _ovh_rest DELETE "domain/zone/$_domain/record/$rid"; then
return 1
fi
+ _ovh_rest POST "domain/zone/$_domain/refresh"
+ _debug "Refresh:$response"
return 0
fi
done
@@ -233,8 +241,7 @@ _ovh_authentication() {
_secure_debug consumerKey "$consumerKey"
OVH_CK="$consumerKey"
- _saveaccountconf OVH_CK "$OVH_CK"
-
+ _saveaccountconf_mutable OVH_CK "$OVH_CK"
_info "Please open this link to do authentication: $(__green "$validationUrl")"
_info "Here is a guide for you: $(__green "$wiki")"
@@ -261,7 +268,9 @@ _get_root() {
return 1
fi
- if ! _contains "$response" "This service does not exist" >/dev/null && ! _contains "$response" "NOT_GRANTED_CALL" >/dev/null; then
+ if ! _contains "$response" "This service does not exist" >/dev/null &&
+ ! _contains "$response" "This call has not been granted" >/dev/null &&
+ ! _contains "$response" "NOT_GRANTED_CALL" >/dev/null; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="$h"
return 0
diff --git a/dnsapi/dns_pdns.sh b/dnsapi/dns_pdns.sh
index 8f07e8c4..6aa2e953 100755
--- a/dnsapi/dns_pdns.sh
+++ b/dnsapi/dns_pdns.sh
@@ -103,7 +103,7 @@ set_record() {
_build_record_string "$oldchallenge"
done
- if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}"; then
+ if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}" "application/json"; then
_err "Set txt record error."
return 1
fi
@@ -126,7 +126,7 @@ rm_record() {
if _contains "$_existing_challenges" "$txtvalue"; then
#Delete all challenges (PowerDNS API does not allow to delete content)
- if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}"; then
+ if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}" "application/json"; then
_err "Delete txt record error."
return 1
fi
@@ -140,7 +140,7 @@ rm_record() {
fi
done
#Recreate the existing challenges
- if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}"; then
+ if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}" "application/json"; then
_err "Set txt record error."
return 1
fi
@@ -175,13 +175,13 @@ _get_root() {
i=1
if _pdns_rest "GET" "/api/v1/servers/$PDNS_ServerId/zones"; then
- _zones_response="$response"
+ _zones_response=$(echo "$response" | _normalizeJson)
fi
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
- if _contains "$_zones_response" "\"name\": \"$h.\""; then
+ if _contains "$_zones_response" "\"name\":\"$h.\""; then
_domain="$h."
if [ -z "$h" ]; then
_domain="=2E"
@@ -203,12 +203,13 @@ _pdns_rest() {
method=$1
ep=$2
data=$3
+ ct=$4
export _H1="X-API-Key: $PDNS_Token"
if [ ! "$method" = "GET" ]; then
_debug data "$data"
- response="$(_post "$data" "$PDNS_Url$ep" "" "$method")"
+ response="$(_post "$data" "$PDNS_Url$ep" "" "$method" "$ct")"
else
response="$(_get "$PDNS_Url$ep")"
fi
diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh
index f5986827..799c374c 100644
--- a/dnsapi/dns_pleskxml.sh
+++ b/dnsapi/dns_pleskxml.sh
@@ -41,7 +41,7 @@ pleskxml_init_checks_done=0
NEWLINE='\
'
-pleskxml_tplt_get_domains=""
+pleskxml_tplt_get_domains=""
# Get a list of domains that PLESK can manage, so we can check root domain + host for acme.sh
# Also used to test credentials and URI.
# No params.
@@ -145,22 +145,25 @@ dns_pleskxml_rm() {
)"
if [ -z "$reclist" ]; then
- _err "No TXT records found for root domain ${root_domain_name} (Plesk domain ID ${root_domain_id}). Exiting."
+ _err "No TXT records found for root domain $fulldomain (Plesk domain ID ${root_domain_id}). Exiting."
return 1
fi
- _debug "Got list of DNS TXT records for root domain '$root_domain_name':"
+ _debug "Got list of DNS TXT records for root Plesk domain ID ${root_domain_id} of root domain $fulldomain:"
_debug "$reclist"
+ # Extracting the id of the TXT record for the full domain (NOT case-sensitive) and corresponding value
recid="$(
_value "$reclist" |
- grep "${fulldomain}." |
+ grep -i "${fulldomain}." |
grep "${txtvalue}" |
sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/'
)"
+ _debug "Got id from line: $recid"
+
if ! _value "$recid" | grep '^[0-9]\{1,\}$' >/dev/null; then
- _err "DNS records for root domain '${root_domain_name}' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'"
+ _err "DNS records for root domain '${fulldomain}.' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'"
_err "Cannot delete TXT record. Exiting."
return 1
fi
@@ -251,9 +254,12 @@ _call_api() {
# Detect any that isn't "ok". None of the used calls should fail if the API is working correctly.
# Also detect if there simply aren't any status lines (null result?) and report that, as well.
+ # Remove structure from result string, since it might contain values that are related to the status of the domain and not to the API request
- statuslines_count_total="$(echo "$pleskxml_prettyprint_result" | grep -c '^ *[^<]* *$')"
- statuslines_count_okay="$(echo "$pleskxml_prettyprint_result" | grep -c '^ *ok *$')"
+ statuslines_count_total="$(echo "$pleskxml_prettyprint_result" | sed '//,/<\/data>/d' | grep -c '^ *[^<]* *$')"
+ statuslines_count_okay="$(echo "$pleskxml_prettyprint_result" | sed '//,/<\/data>/d' | grep -c '^ *ok *$')"
+ _debug "statuslines_count_total=$statuslines_count_total."
+ _debug "statuslines_count_okay=$statuslines_count_okay."
if [ -z "$statuslines_count_total" ]; then
@@ -375,7 +381,7 @@ _pleskxml_get_root_domain() {
# Output will be one line per known domain, containing 2 tages and a single tag
# We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned.
- output="$(_api_response_split "$pleskxml_prettyprint_result" 'domain' 'domain' | sed 's///g;s/<\/ascii-name>/<\/name>/g' | grep '' | grep '')"
+ output="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' | sed 's///g;s/<\/ascii-name>/<\/name>/g' | grep '' | grep '')"
_debug 'Domains managed by Plesk server are (ignore the hacked output):'
_debug "$output"
diff --git a/dnsapi/dns_porkbun.sh b/dnsapi/dns_porkbun.sh
new file mode 100644
index 00000000..ad4455b6
--- /dev/null
+++ b/dnsapi/dns_porkbun.sh
@@ -0,0 +1,157 @@
+#!/usr/bin/env sh
+
+#
+#PORKBUN_API_KEY="pk1_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+#PORKBUN_SECRET_API_KEY="sk1_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+
+PORKBUN_Api="https://porkbun.com/api/json/v3"
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_porkbun_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ PORKBUN_API_KEY="${PORKBUN_API_KEY:-$(_readaccountconf_mutable PORKBUN_API_KEY)}"
+ PORKBUN_SECRET_API_KEY="${PORKBUN_SECRET_API_KEY:-$(_readaccountconf_mutable PORKBUN_SECRET_API_KEY)}"
+
+ if [ -z "$PORKBUN_API_KEY" ] || [ -z "$PORKBUN_SECRET_API_KEY" ]; then
+ PORKBUN_API_KEY=''
+ PORKBUN_SECRET_API_KEY=''
+ _err "You didn't specify a Porkbun api key and secret api key yet."
+ _err "You can get yours from here https://porkbun.com/account/api."
+ return 1
+ fi
+
+ #save the credentials to the account conf file.
+ _saveaccountconf_mutable PORKBUN_API_KEY "$PORKBUN_API_KEY"
+ _saveaccountconf_mutable PORKBUN_SECRET_API_KEY "$PORKBUN_SECRET_API_KEY"
+
+ _debug 'First detect the root zone'
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so
+ # we can not use updating anymore.
+ # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2)
+ # _debug count "$count"
+ # if [ "$count" = "0" ]; then
+ _info "Adding record"
+ if _porkbun_rest POST "dns/create/$_domain" "{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
+ if _contains "$response" '\"status\":"SUCCESS"'; then
+ _info "Added, OK"
+ return 0
+ elif _contains "$response" "The record already exists"; then
+ _info "Already exists, OK"
+ return 0
+ else
+ _err "Add txt record error. ($response)"
+ return 1
+ fi
+ fi
+ _err "Add txt record error."
+ return 1
+
+}
+
+#fulldomain txtvalue
+dns_porkbun_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ PORKBUN_API_KEY="${PORKBUN_API_KEY:-$(_readaccountconf_mutable PORKBUN_API_KEY)}"
+ PORKBUN_SECRET_API_KEY="${PORKBUN_SECRET_API_KEY:-$(_readaccountconf_mutable PORKBUN_SECRET_API_KEY)}"
+
+ _debug 'First detect the root zone'
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ count=$(echo "$response" | _egrep_o "\"count\": *[^,]*" | cut -d : -f 2 | tr -d " ")
+ _debug count "$count"
+ if [ "$count" = "0" ]; then
+ _info "Don't need to remove."
+ else
+ record_id=$(echo "$response" | tr '{' '\n' | grep -- "$txtvalue" | cut -d, -f1 | cut -d: -f2 | tr -d \")
+ _debug "record_id" "$record_id"
+ if [ -z "$record_id" ]; then
+ _err "Can not get record id to remove."
+ return 1
+ fi
+ if ! _porkbun_rest POST "dns/delete/$_domain/$record_id"; then
+ _err "Delete record error."
+ return 1
+ fi
+ echo "$response" | tr -d " " | grep '\"status\":"SUCCESS"' >/dev/null
+ fi
+
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ domain=$1
+ i=1
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug h "$h"
+ if [ -z "$h" ]; then
+ return 1
+ fi
+
+ if _porkbun_rest POST "dns/retrieve/$h"; then
+ if _contains "$response" "\"status\":\"SUCCESS\""; then
+ _domain=$h
+ _sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")"
+ return 0
+ else
+ _debug "Go to next level of $_domain"
+ fi
+ else
+ _debug "Go to next level of $_domain"
+ fi
+ i=$(_math "$i" + 1)
+ done
+
+ return 1
+}
+
+_porkbun_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+
+ api_key_trimmed=$(echo "$PORKBUN_API_KEY" | tr -d '"')
+ secret_api_key_trimmed=$(echo "$PORKBUN_SECRET_API_KEY" | tr -d '"')
+
+ test -z "$data" && data="{" || data="$(echo $data | cut -d'}' -f1),"
+ data="$data\"apikey\":\"$api_key_trimmed\",\"secretapikey\":\"$secret_api_key_trimmed\"}"
+
+ export _H1="Content-Type: application/json"
+
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$PORKBUN_Api/$ep" "" "$m")"
+ else
+ response="$(_get "$PORKBUN_Api/$ep")"
+ fi
+
+ _sleep 3 # prevent rate limit
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_rackcorp.sh b/dnsapi/dns_rackcorp.sh
new file mode 100644
index 00000000..6aabfddc
--- /dev/null
+++ b/dnsapi/dns_rackcorp.sh
@@ -0,0 +1,156 @@
+#!/usr/bin/env sh
+
+# Provider: RackCorp (www.rackcorp.com)
+# Author: Stephen Dendtler (sdendtler@rackcorp.com)
+# Report Bugs here: https://github.com/senjoo/acme.sh
+# Alternate email contact: support@rackcorp.com
+#
+# You'll need an API key (Portal: ADMINISTRATION -> API)
+# Set the environment variables as below:
+#
+# export RACKCORP_APIUUID="UUIDHERE"
+# export RACKCORP_APISECRET="SECRETHERE"
+#
+
+RACKCORP_API_ENDPOINT="https://api.rackcorp.net/api/rest/v2.4/json.php"
+
+######## Public functions #####################
+
+dns_rackcorp_add() {
+ fulldomain="$1"
+ txtvalue="$2"
+
+ _debug fulldomain="$fulldomain"
+ _debug txtvalue="$txtvalue"
+
+ if ! _rackcorp_validate; then
+ return 1
+ fi
+
+ _debug "Searching for root zone"
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+ _debug _lookup "$_lookup"
+ _debug _domain "$_domain"
+
+ _info "Creating TXT record."
+
+ if ! _rackcorp_api dns.record.create "\"name\":\"$_domain\",\"type\":\"TXT\",\"lookup\":\"$_lookup\",\"data\":\"$txtvalue\",\"ttl\":300"; then
+ return 1
+ fi
+
+ return 0
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_rackcorp_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _debug fulldomain="$fulldomain"
+ _debug txtvalue="$txtvalue"
+
+ if ! _rackcorp_validate; then
+ return 1
+ fi
+
+ _debug "Searching for root zone"
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+ _debug _lookup "$_lookup"
+ _debug _domain "$_domain"
+
+ _info "Creating TXT record."
+
+ if ! _rackcorp_api dns.record.delete "\"name\":\"$_domain\",\"type\":\"TXT\",\"lookup\":\"$_lookup\",\"data\":\"$txtvalue\""; then
+ return 1
+ fi
+
+ return 0
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.domain.com
+#returns
+# _lookup=_acme-challenge
+# _domain=domain.com
+_get_root() {
+ domain=$1
+ i=1
+ p=1
+ if ! _rackcorp_api dns.domain.getall "\"name\":\"$domain\""; then
+ return 1
+ fi
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug searchhost "$h"
+ if [ -z "$h" ]; then
+ _err "Could not find domain for record $domain in RackCorp using the provided credentials"
+ #not valid
+ return 1
+ fi
+
+ _rackcorp_api dns.domain.getall "\"exactName\":\"$h\""
+
+ if _contains "$response" "\"matches\":1"; then
+ if _contains "$response" "\"name\":\"$h\""; then
+ _lookup=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="$h"
+ return 0
+ fi
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+
+ return 1
+}
+
+_rackcorp_validate() {
+ RACKCORP_APIUUID="${RACKCORP_APIUUID:-$(_readaccountconf_mutable RACKCORP_APIUUID)}"
+ if [ -z "$RACKCORP_APIUUID" ]; then
+ RACKCORP_APIUUID=""
+ _err "You require a RackCorp API UUID (export RACKCORP_APIUUID=\"\")"
+ _err "Please login to the portal and create an API key and try again."
+ return 1
+ fi
+
+ _saveaccountconf_mutable RACKCORP_APIUUID "$RACKCORP_APIUUID"
+
+ RACKCORP_APISECRET="${RACKCORP_APISECRET:-$(_readaccountconf_mutable RACKCORP_APISECRET)}"
+ if [ -z "$RACKCORP_APISECRET" ]; then
+ RACKCORP_APISECRET=""
+ _err "You require a RackCorp API secret (export RACKCORP_APISECRET=\"\")"
+ _err "Please login to the portal and create an API key and try again."
+ return 1
+ fi
+
+ _saveaccountconf_mutable RACKCORP_APISECRET "$RACKCORP_APISECRET"
+
+ return 0
+}
+_rackcorp_api() {
+ _rackcorpcmd=$1
+ _rackcorpinputdata=$2
+ _debug cmd "$_rackcorpcmd $_rackcorpinputdata"
+
+ export _H1="Accept: application/json"
+ response="$(_post "{\"APIUUID\":\"$RACKCORP_APIUUID\",\"APISECRET\":\"$RACKCORP_APISECRET\",\"cmd\":\"$_rackcorpcmd\",$_rackcorpinputdata}" "$RACKCORP_API_ENDPOINT" "" "POST")"
+
+ if [ "$?" != "0" ]; then
+ _err "error $response"
+ return 1
+ fi
+ _debug2 response "$response"
+ if _contains "$response" "\"code\":\"OK\""; then
+ _debug code "OK"
+ else
+ _debug code "FAILED"
+ response=""
+ return 1
+ fi
+ return 0
+}
diff --git a/dnsapi/dns_rackspace.sh b/dnsapi/dns_rackspace.sh
index 03e1fa68..b50d9168 100644
--- a/dnsapi/dns_rackspace.sh
+++ b/dnsapi/dns_rackspace.sh
@@ -7,6 +7,7 @@
RACKSPACE_Endpoint="https://dns.api.rackspacecloud.com/v1.0"
+# 20210923 - RS changed the fields in the API response; fix sed
# 20190213 - The name & id fields swapped in the API response; fix sed
# 20190101 - Duplicating file for new pull request to dev branch
# Original - tcocca:rackspace_dnsapi https://github.com/acmesh-official/acme.sh/pull/1297
@@ -79,8 +80,8 @@ _get_root_zone() {
_debug2 response "$response"
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
# Response looks like:
- # {"ttl":300,"accountId":12345,"id":1111111,"name":"example.com","emailAddress": ...
- _domain_id=$(echo "$response" | sed -n "s/^.*\"id\":\([^,]*\),\"name\":\"$h\",.*/\1/p")
+ # {"id":"12345","accountId":"1111111","name": "example.com","ttl":3600,"emailAddress": ...
+ _domain_id=$(echo "$response" | sed -n "s/^.*\"id\":\"\([^,]*\)\",\"accountId\":\"[0-9]*\",\"name\":\"$h\",.*/\1/p")
_debug2 domain_id "$_domain_id"
if [ -n "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
diff --git a/dnsapi/dns_rage4.sh b/dnsapi/dns_rage4.sh
new file mode 100755
index 00000000..4af4541d
--- /dev/null
+++ b/dnsapi/dns_rage4.sh
@@ -0,0 +1,115 @@
+#!/usr/bin/env sh
+
+#
+#RAGE4_TOKEN="sdfsdfsdfljlbjkljlkjsdfoiwje"
+#
+#RAGE4_USERNAME="xxxx@sss.com"
+
+RAGE4_Api="https://rage4.com/rapi/"
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_rage4_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ unquotedtxtvalue=$(echo "$txtvalue" | tr -d \")
+
+ RAGE4_USERNAME="${RAGE4_USERNAME:-$(_readaccountconf_mutable RAGE4_USERNAME)}"
+ RAGE4_TOKEN="${RAGE4_TOKEN:-$(_readaccountconf_mutable RAGE4_TOKEN)}"
+
+ if [ -z "$RAGE4_USERNAME" ] || [ -z "$RAGE4_TOKEN" ]; then
+ RAGE4_USERNAME=""
+ RAGE4_TOKEN=""
+ _err "You didn't specify a Rage4 api token and username yet."
+ return 1
+ fi
+
+ #save the api key and email to the account conf file.
+ _saveaccountconf_mutable RAGE4_USERNAME "$RAGE4_USERNAME"
+ _saveaccountconf_mutable RAGE4_TOKEN "$RAGE4_TOKEN"
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _domain_id "$_domain_id"
+
+ _rage4_rest "createrecord/?id=$_domain_id&name=$fulldomain&content=$unquotedtxtvalue&type=TXT&active=true&ttl=1"
+ return 0
+}
+
+#fulldomain txtvalue
+dns_rage4_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ RAGE4_USERNAME="${RAGE4_USERNAME:-$(_readaccountconf_mutable RAGE4_USERNAME)}"
+ RAGE4_TOKEN="${RAGE4_TOKEN:-$(_readaccountconf_mutable RAGE4_TOKEN)}"
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _domain_id "$_domain_id"
+
+ _debug "Getting txt records"
+ _rage4_rest "getrecords/?id=${_domain_id}"
+
+ _record_id=$(echo "$response" | sed -rn 's/.*"id":([[:digit:]]+)[^\}]*'"$txtvalue"'.*/\1/p')
+ _rage4_rest "deleterecord/?id=${_record_id}"
+ return 0
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_get_root() {
+ domain=$1
+
+ if ! _rage4_rest "getdomains"; then
+ return 1
+ fi
+ _debug _get_root_domain "$domain"
+
+ for line in $(echo "$response" | tr '}' '\n'); do
+ __domain=$(echo "$line" | sed -rn 's/.*"name":"([^"]*)",.*/\1/p')
+ __domain_id=$(echo "$line" | sed -rn 's/.*"id":([^,]*),.*/\1/p')
+ if [ "$domain" != "${domain%"$__domain"*}" ]; then
+ _domain_id="$__domain_id"
+ break
+ fi
+ done
+
+ if [ -z "$_domain_id" ]; then
+ return 1
+ fi
+
+ return 0
+}
+
+_rage4_rest() {
+ ep="$1"
+ _debug "$ep"
+
+ username_trimmed=$(echo "$RAGE4_USERNAME" | tr -d '"')
+ token_trimmed=$(echo "$RAGE4_TOKEN" | tr -d '"')
+ auth=$(printf '%s:%s' "$username_trimmed" "$token_trimmed" | _base64)
+
+ export _H1="Content-Type: application/json"
+ export _H2="Authorization: Basic $auth"
+
+ response="$(_get "$RAGE4_Api$ep")"
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_regru.sh b/dnsapi/dns_regru.sh
index 29f758ea..8ff380f0 100644
--- a/dnsapi/dns_regru.sh
+++ b/dnsapi/dns_regru.sh
@@ -92,9 +92,10 @@ _get_root() {
domains_list=$(echo "${response}" | grep dname | sed -r "s/.*dname=\"([^\"]+)\".*/\\1/g")
for ITEM in ${domains_list}; do
+ IDN_ITEM=${ITEM}
case "${domain}" in
- *${ITEM}*)
- _domain=${ITEM}
+ *${IDN_ITEM}*)
+ _domain="$(_idn "${ITEM}")"
_debug _domain "${_domain}"
return 0
;;
diff --git a/dnsapi/dns_scaleway.sh b/dnsapi/dns_scaleway.sh
new file mode 100755
index 00000000..a0a0f318
--- /dev/null
+++ b/dnsapi/dns_scaleway.sh
@@ -0,0 +1,176 @@
+#!/usr/bin/env sh
+
+# Scaleway API
+# https://developers.scaleway.com/en/products/domain/dns/api/
+#
+# Requires Scaleway API token set in SCALEWAY_API_TOKEN
+
+######## Public functions #####################
+
+SCALEWAY_API="https://api.scaleway.com/domain/v2beta1"
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_scaleway_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ if ! _scaleway_check_config; 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"
+
+ _info "Adding record"
+ _scaleway_create_TXT_record "$_domain" "$_sub_domain" "$txtvalue"
+ if _contains "$response" "records"; then
+ return 0
+ else
+ _err error "$response"
+ return 1
+ fi
+ _info "Record added."
+
+ return 0
+}
+
+dns_scaleway_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ if ! _scaleway_check_config; 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"
+
+ _info "Deleting record"
+ _scaleway_delete_TXT_record "$_domain" "$_sub_domain" "$txtvalue"
+ if _contains "$response" "records"; then
+ return 0
+ else
+ _err error "$response"
+ return 1
+ fi
+ _info "Record deleted."
+
+ return 0
+}
+
+#################### Private functions below ##################################
+
+_scaleway_check_config() {
+ SCALEWAY_API_TOKEN="${SCALEWAY_API_TOKEN:-$(_readaccountconf_mutable SCALEWAY_API_TOKEN)}"
+ if [ -z "$SCALEWAY_API_TOKEN" ]; then
+ _err "No API key specified for Scaleway API."
+ _err "Create your key and export it as SCALEWAY_API_TOKEN"
+ return 1
+ fi
+ if ! _scaleway_rest GET "dns-zones"; then
+ _err "Invalid API key specified for Scaleway API."
+ return 1
+ fi
+
+ _saveaccountconf_mutable SCALEWAY_API_TOKEN "$SCALEWAY_API_TOKEN"
+
+ return 0
+}
+
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ domain=$1
+ i=1
+ p=1
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+
+ _scaleway_rest GET "dns-zones/$h/records"
+
+ if ! _contains "$response" "subdomain not found" >/dev/null; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="$h"
+ return 0
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ _err "Unable to retrive DNS zone matching this domain"
+ return 1
+}
+
+# this function add a TXT record
+_scaleway_create_TXT_record() {
+ txt_zone=$1
+ txt_name=$2
+ txt_value=$3
+
+ _scaleway_rest PATCH "dns-zones/$txt_zone/records" "{\"return_all_records\":false,\"changes\":[{\"add\":{\"records\":[{\"name\":\"$txt_name\",\"data\":\"$txt_value\",\"type\":\"TXT\",\"ttl\":60}]}}]}"
+
+ if _contains "$response" "records"; then
+ return 0
+ else
+ _err "error1 $response"
+ return 1
+ fi
+}
+
+# this function delete a TXT record based on name and content
+_scaleway_delete_TXT_record() {
+ txt_zone=$1
+ txt_name=$2
+ txt_value=$3
+
+ _scaleway_rest PATCH "dns-zones/$txt_zone/records" "{\"return_all_records\":false,\"changes\":[{\"delete\":{\"id_fields\":{\"name\":\"$txt_name\",\"data\":\"$txt_value\",\"type\":\"TXT\"}}}]}"
+
+ if _contains "$response" "records"; then
+ return 0
+ else
+ _err "error2 $response"
+ return 1
+ fi
+}
+
+_scaleway_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+ _scaleway_url="$SCALEWAY_API/$ep"
+ _debug2 _scaleway_url "$_scaleway_url"
+ export _H1="x-auth-token: $SCALEWAY_API_TOKEN"
+ export _H2="Accept: application/json"
+ export _H3="Content-Type: application/json"
+
+ if [ "$data" ] || [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$_scaleway_url" "" "$m")"
+ else
+ response="$(_get "$_scaleway_url")"
+ fi
+ if [ "$?" != "0" ] || _contains "$response" "denied_authentication" || _contains "$response" "Method not allowed" || _contains "$response" "json parse error: unexpected EOF"; then
+ _err "error $response"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_selectel.sh b/dnsapi/dns_selectel.sh
index 94252d81..1b09882d 100644
--- a/dnsapi/dns_selectel.sh
+++ b/dnsapi/dns_selectel.sh
@@ -76,7 +76,7 @@ dns_selectel_rm() {
return 1
fi
- _record_seg="$(echo "$response" | _egrep_o "\"content\" *: *\"$txtvalue\"[^}]*}")"
+ _record_seg="$(echo "$response" | _egrep_o "[^{]*\"content\" *: *\"$txtvalue\"[^}]*}")"
_debug2 "_record_seg" "$_record_seg"
if [ -z "$_record_seg" ]; then
_err "can not find _record_seg"
@@ -120,7 +120,7 @@ _get_root() {
return 1
fi
- if _contains "$response" "\"name\": \"$h\","; then
+ if _contains "$response" "\"name\" *: *\"$h\","; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain=$h
_debug "Getting domain id for $h"
diff --git a/dnsapi/dns_selfhost.sh b/dnsapi/dns_selfhost.sh
new file mode 100644
index 00000000..a6ef1f94
--- /dev/null
+++ b/dnsapi/dns_selfhost.sh
@@ -0,0 +1,94 @@
+#!/usr/bin/env sh
+#
+# Author: Marvin Edeler
+# Report Bugs here: https://github.com/Marvo2011/acme.sh/issues/1
+# Last Edit: 17.02.2022
+
+dns_selfhost_add() {
+ fulldomain=$1
+ txt=$2
+ _info "Calling acme-dns on selfhost"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txt"
+
+ SELFHOSTDNS_UPDATE_URL="https://selfhost.de/cgi-bin/api.pl"
+
+ # Get values, but don't save until we successfully validated
+ SELFHOSTDNS_USERNAME="${SELFHOSTDNS_USERNAME:-$(_readaccountconf_mutable SELFHOSTDNS_USERNAME)}"
+ SELFHOSTDNS_PASSWORD="${SELFHOSTDNS_PASSWORD:-$(_readaccountconf_mutable SELFHOSTDNS_PASSWORD)}"
+ # These values are domain dependent, so read them from there
+ SELFHOSTDNS_MAP="${SELFHOSTDNS_MAP:-$(_readdomainconf SELFHOSTDNS_MAP)}"
+ # Selfhost api can't dynamically add TXT record,
+ # so we have to store the last used RID of the domain to support a second RID for wildcard domains
+ # (format: 'fulldomainA:lastRid fulldomainB:lastRid ...')
+ SELFHOSTDNS_MAP_LAST_USED_INTERNAL=$(_readdomainconf SELFHOSTDNS_MAP_LAST_USED_INTERNAL)
+
+ if [ -z "${SELFHOSTDNS_USERNAME:-}" ] || [ -z "${SELFHOSTDNS_PASSWORD:-}" ]; then
+ _err "SELFHOSTDNS_USERNAME and SELFHOSTDNS_PASSWORD must be set"
+ return 1
+ fi
+
+ # get the domain entry from SELFHOSTDNS_MAP
+ # only match full domains (at the beginning of the string or with a leading whitespace),
+ # e.g. don't match mytest.example.com or sub.test.example.com for test.example.com
+ # if the domain is defined multiple times only the last occurance will be matched
+ mapEntry=$(echo "$SELFHOSTDNS_MAP" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain)(:[[:digit:]]+)([:]?[[:digit:]]*)(.*)/\2\3\4/p")
+ _debug2 mapEntry "$mapEntry"
+ if test -z "$mapEntry"; then
+ _err "SELFHOSTDNS_MAP must contain the fulldomain incl. prefix and at least one RID"
+ return 1
+ fi
+
+ # get the RIDs from the map entry
+ rid1=$(echo "$mapEntry" | cut -d: -f2)
+ rid2=$(echo "$mapEntry" | cut -d: -f3)
+
+ # read last used rid domain
+ lastUsedRidForDomainEntry=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain:[[:digit:]]+)(.*)/\2/p")
+ _debug2 lastUsedRidForDomainEntry "$lastUsedRidForDomainEntry"
+ lastUsedRidForDomain=$(echo "$lastUsedRidForDomainEntry" | cut -d: -f2)
+
+ rid="$rid1"
+ if [ "$lastUsedRidForDomain" = "$rid" ] && ! test -z "$rid2"; then
+ rid="$rid2"
+ fi
+
+ _info "Trying to add $txt on selfhost for rid: $rid"
+
+ data="?username=$SELFHOSTDNS_USERNAME&password=$SELFHOSTDNS_PASSWORD&rid=$rid&content=$txt"
+ response="$(_get "$SELFHOSTDNS_UPDATE_URL$data")"
+
+ if ! echo "$response" | grep "200 OK" >/dev/null; then
+ _err "Invalid response of acme-dns for selfhost"
+ return 1
+ fi
+
+ # write last used rid domain
+ newLastUsedRidForDomainEntry="$fulldomain:$rid"
+ if ! test -z "$lastUsedRidForDomainEntry"; then
+ # replace last used rid entry for domain
+ SELFHOSTDNS_MAP_LAST_USED_INTERNAL=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed -n -E "s/$lastUsedRidForDomainEntry/$newLastUsedRidForDomainEntry/p")
+ else
+ # add last used rid entry for domain
+ if test -z "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL"; then
+ SELFHOSTDNS_MAP_LAST_USED_INTERNAL="$newLastUsedRidForDomainEntry"
+ else
+ SELFHOSTDNS_MAP_LAST_USED_INTERNAL="$SELFHOSTDNS_MAP_LAST_USED_INTERNAL $newLastUsedRidForDomainEntry"
+ fi
+ fi
+
+ # Now that we know the values are good, save them
+ _saveaccountconf_mutable SELFHOSTDNS_USERNAME "$SELFHOSTDNS_USERNAME"
+ _saveaccountconf_mutable SELFHOSTDNS_PASSWORD "$SELFHOSTDNS_PASSWORD"
+ # These values are domain dependent, so store them there
+ _savedomainconf SELFHOSTDNS_MAP "$SELFHOSTDNS_MAP"
+ _savedomainconf SELFHOSTDNS_MAP_LAST_USED_INTERNAL "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL"
+}
+
+dns_selfhost_rm() {
+ fulldomain=$1
+ txt=$2
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txt"
+ _info "Creating and removing of records is not supported by selfhost API, will not delete anything."
+}
diff --git a/dnsapi/dns_servercow.sh b/dnsapi/dns_servercow.sh
index e73d85b0..52137905 100755
--- a/dnsapi/dns_servercow.sh
+++ b/dnsapi/dns_servercow.sh
@@ -49,16 +49,42 @@ dns_servercow_add() {
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
- if _servercow_api POST "$_domain" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":20}"; then
- if printf -- "%s" "$response" | grep "ok" >/dev/null; then
- _info "Added, OK"
- return 0
- else
- _err "add txt record error."
- return 1
+ # check whether a txt record already exists for the subdomain
+ if printf -- "%s" "$response" | grep "{\"name\":\"$_sub_domain\",\"ttl\":20,\"type\":\"TXT\"" >/dev/null; then
+ _info "A txt record with the same name already exists."
+ # trim the string on the left
+ txtvalue_old=${response#*{\"name\":\""$_sub_domain"\",\"ttl\":20,\"type\":\"TXT\",\"content\":\"}
+ # trim the string on the right
+ txtvalue_old=${txtvalue_old%%\"*}
+
+ _debug txtvalue_old "$txtvalue_old"
+
+ _info "Add the new txtvalue to the existing txt record."
+ if _servercow_api POST "$_domain" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":[\"$txtvalue\",\"$txtvalue_old\"],\"ttl\":20}"; then
+ if printf -- "%s" "$response" | grep "ok" >/dev/null; then
+ _info "Added additional txtvalue, OK"
+ return 0
+ else
+ _err "add txt record error."
+ return 1
+ fi
fi
+ _err "add txt record error."
+ return 1
+ else
+ _info "There is no txt record with the name yet."
+ if _servercow_api POST "$_domain" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":20}"; then
+ if printf -- "%s" "$response" | grep "ok" >/dev/null; then
+ _info "Added, OK"
+ return 0
+ else
+ _err "add txt record error."
+ return 1
+ fi
+ fi
+ _err "add txt record error."
+ return 1
fi
- _err "add txt record error."
return 1
}
diff --git a/dnsapi/dns_simply.sh b/dnsapi/dns_simply.sh
new file mode 100644
index 00000000..6a8d0e18
--- /dev/null
+++ b/dnsapi/dns_simply.sh
@@ -0,0 +1,269 @@
+#!/usr/bin/env sh
+
+# API-integration for Simply.com (https://www.simply.com)
+
+#SIMPLY_AccountName="accountname"
+#SIMPLY_ApiKey="apikey"
+#
+#SIMPLY_Api="https://api.simply.com/2/"
+SIMPLY_Api_Default="https://api.simply.com/2"
+
+#This is used for determining success of REST call
+SIMPLY_SUCCESS_CODE='"status":200'
+
+######## Public functions #####################
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_simply_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ if ! _simply_load_config; then
+ return 1
+ fi
+
+ _simply_save_config
+
+ _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"
+
+ _info "Adding record"
+
+ if ! _simply_add_record "$_domain" "$_sub_domain" "$txtvalue"; then
+ _err "Could not add DNS record"
+ return 1
+ fi
+ return 0
+}
+
+dns_simply_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ if ! _simply_load_config; then
+ return 1
+ fi
+
+ _simply_save_config
+
+ _debug "Find the DNS zone"
+
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+ _debug txtvalue "$txtvalue"
+
+ _info "Getting all existing records"
+
+ if ! _simply_get_all_records "$_domain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ records=$(echo "$response" | tr '{' "\n" | grep 'record_id\|type\|data\|\name' | sed 's/\"record_id/;\"record_id/' | tr "\n" ' ' | tr -d ' ' | tr ';' ' ')
+
+ nr_of_deleted_records=0
+ _info "Fetching txt record"
+
+ for record in $records; do
+ _debug record "$record"
+
+ record_data=$(echo "$record" | sed -n "s/.*\"data\":\"\([^\"]*\)\".*/\1/p")
+ record_type=$(echo "$record" | sed -n "s/.*\"type\":\"\([^\"]*\)\".*/\1/p")
+
+ _debug2 record_data "$record_data"
+ _debug2 record_type "$record_type"
+
+ if [ "$record_data" = "$txtvalue" ] && [ "$record_type" = "TXT" ]; then
+
+ record_id=$(echo "$record" | cut -d "," -f 1 | grep "record_id" | cut -d ":" -f 2)
+
+ _info "Deleting record $record"
+ _debug2 record_id "$record_id"
+
+ if [ "$record_id" -gt 0 ]; then
+
+ if ! _simply_delete_record "$_domain" "$_sub_domain" "$record_id"; then
+ _err "Record with id $record_id could not be deleted"
+ return 1
+ fi
+
+ nr_of_deleted_records=1
+ break
+ else
+ _err "Fetching record_id could not be done, this should not happen, exiting function. Failing record is $record"
+ break
+ fi
+ fi
+
+ done
+
+ if [ "$nr_of_deleted_records" -eq 0 ]; then
+ _err "No record deleted, the DNS record needs to be removed manually."
+ else
+ _info "Deleted $nr_of_deleted_records record"
+ fi
+
+ return 0
+}
+
+#################### Private functions below ##################################
+
+_simply_load_config() {
+ SIMPLY_Api="${SIMPLY_Api:-$(_readaccountconf_mutable SIMPLY_Api)}"
+ SIMPLY_AccountName="${SIMPLY_AccountName:-$(_readaccountconf_mutable SIMPLY_AccountName)}"
+ SIMPLY_ApiKey="${SIMPLY_ApiKey:-$(_readaccountconf_mutable SIMPLY_ApiKey)}"
+
+ if [ -z "$SIMPLY_Api" ]; then
+ SIMPLY_Api="$SIMPLY_Api_Default"
+ fi
+
+ if [ -z "$SIMPLY_AccountName" ] || [ -z "$SIMPLY_ApiKey" ]; then
+ SIMPLY_AccountName=""
+ SIMPLY_ApiKey=""
+
+ _err "A valid Simply API account and apikey not provided."
+ _err "Please provide a valid API user and try again."
+
+ return 1
+ fi
+
+ return 0
+}
+
+_simply_save_config() {
+ if [ "$SIMPLY_Api" != "$SIMPLY_Api_Default" ]; then
+ _saveaccountconf_mutable SIMPLY_Api "$SIMPLY_Api"
+ fi
+ _saveaccountconf_mutable SIMPLY_AccountName "$SIMPLY_AccountName"
+ _saveaccountconf_mutable SIMPLY_ApiKey "$SIMPLY_ApiKey"
+}
+
+_simply_get_all_records() {
+ domain=$1
+
+ if ! _simply_rest GET "my/products/$domain/dns/records/"; then
+ return 1
+ fi
+
+ return 0
+}
+
+_get_root() {
+ domain=$1
+ i=2
+ p=1
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+
+ if ! _simply_rest GET "my/products/$h/dns/"; then
+ return 1
+ fi
+
+ if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then
+ _debug "$h not found"
+ else
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="$h"
+ return 0
+ fi
+ p="$i"
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+_simply_add_record() {
+ domain=$1
+ sub_domain=$2
+ txtval=$3
+
+ data="{\"name\": \"$sub_domain\", \"type\":\"TXT\", \"data\": \"$txtval\", \"priority\":0, \"ttl\": 3600}"
+
+ if ! _simply_rest POST "my/products/$domain/dns/records/" "$data"; then
+ _err "Adding record not successfull!"
+ return 1
+ fi
+
+ if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then
+ _err "Call to API not sucessfull, see below message for more details"
+ _err "$response"
+ return 1
+ fi
+
+ return 0
+}
+
+_simply_delete_record() {
+ domain=$1
+ sub_domain=$2
+ record_id=$3
+
+ _debug record_id "Delete record with id $record_id"
+
+ if ! _simply_rest DELETE "my/products/$domain/dns/records/$record_id/"; then
+ _err "Deleting record not successfull!"
+ return 1
+ fi
+
+ if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then
+ _err "Call to API not sucessfull, see below message for more details"
+ _err "$response"
+ return 1
+ fi
+
+ return 0
+}
+
+_simply_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+
+ _debug2 data "$data"
+ _debug2 ep "$ep"
+ _debug2 m "$m"
+
+ basicauth=$(printf "%s:%s" "$SIMPLY_AccountName" "$SIMPLY_ApiKey" | _base64)
+
+ if [ "$basicauth" ]; then
+ export _H1="Authorization: Basic $basicauth"
+ fi
+
+ export _H2="Content-Type: application/json"
+
+ if [ "$m" != "GET" ]; then
+ response="$(_post "$data" "$SIMPLY_Api/$ep" "" "$m")"
+ else
+ response="$(_get "$SIMPLY_Api/$ep")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+
+ response="$(echo "$response" | _normalizeJson)"
+
+ _debug2 response "$response"
+
+ if _contains "$response" "Invalid account authorization"; then
+ _err "It seems that your api key or accountnumber is not correct."
+ return 1
+ fi
+
+ return 0
+}
diff --git a/dnsapi/dns_transip.sh b/dnsapi/dns_transip.sh
index 23debe0d..64a256ec 100644
--- a/dnsapi/dns_transip.sh
+++ b/dnsapi/dns_transip.sh
@@ -1,7 +1,6 @@
#!/usr/bin/env sh
TRANSIP_Api_Url="https://api.transip.nl/v6"
TRANSIP_Token_Read_Only="false"
-TRANSIP_Token_Global_Key="false"
TRANSIP_Token_Expiration="30 minutes"
# You can't reuse a label token, so we leave this empty normally
TRANSIP_Token_Label=""
@@ -96,7 +95,11 @@ _transip_get_token() {
nonce=$(echo "TRANSIP$(_time)" | _digest sha1 hex | cut -c 1-32)
_debug nonce "$nonce"
- data="{\"login\":\"${TRANSIP_Username}\",\"nonce\":\"${nonce}\",\"read_only\":\"${TRANSIP_Token_Read_Only}\",\"expiration_time\":\"${TRANSIP_Token_Expiration}\",\"label\":\"${TRANSIP_Token_Label}\",\"global_key\":\"${TRANSIP_Token_Global_Key}\"}"
+ # make IP whitelisting configurable
+ TRANSIP_Token_Global_Key="${TRANSIP_Token_Global_Key:-$(_readaccountconf_mutable TRANSIP_Token_Global_Key)}"
+ _saveaccountconf_mutable TRANSIP_Token_Global_Key "$TRANSIP_Token_Global_Key"
+
+ data="{\"login\":\"${TRANSIP_Username}\",\"nonce\":\"${nonce}\",\"read_only\":\"${TRANSIP_Token_Read_Only}\",\"expiration_time\":\"${TRANSIP_Token_Expiration}\",\"label\":\"${TRANSIP_Token_Label}\",\"global_key\":\"${TRANSIP_Token_Global_Key:-false}\"}"
_debug data "$data"
#_signature=$(printf "%s" "$data" | openssl dgst -sha512 -sign "$TRANSIP_Key_File" | _base64)
@@ -139,6 +142,18 @@ _transip_setup() {
_saveaccountconf_mutable TRANSIP_Username "$TRANSIP_Username"
_saveaccountconf_mutable TRANSIP_Key_File "$TRANSIP_Key_File"
+ # download key file if it's an URL
+ if _startswith "$TRANSIP_Key_File" "http"; then
+ _debug "download transip key file"
+ TRANSIP_Key_URL=$TRANSIP_Key_File
+ TRANSIP_Key_File="$(_mktemp)"
+ chmod 600 "$TRANSIP_Key_File"
+ if ! _get "$TRANSIP_Key_URL" >"$TRANSIP_Key_File"; then
+ _err "Error getting key file from : $TRANSIP_Key_URL"
+ return 1
+ fi
+ fi
+
if [ -f "$TRANSIP_Key_File" ]; then
if ! grep "BEGIN PRIVATE KEY" "$TRANSIP_Key_File" >/dev/null 2>&1; then
_err "Key file doesn't seem to be a valid key: ${TRANSIP_Key_File}"
@@ -156,6 +171,12 @@ _transip_setup() {
fi
fi
+ if [ -n "${TRANSIP_Key_URL}" ]; then
+ _debug "delete transip key file"
+ rm "${TRANSIP_Key_File}"
+ TRANSIP_Key_File=$TRANSIP_Key_URL
+ fi
+
_get_root "$fulldomain" || return 1
return 0
diff --git a/dnsapi/dns_udr.sh b/dnsapi/dns_udr.sh
new file mode 100644
index 00000000..caada826
--- /dev/null
+++ b/dnsapi/dns_udr.sh
@@ -0,0 +1,160 @@
+#!/usr/bin/env sh
+
+# united-domains Reselling (https://www.ud-reselling.com/) DNS API
+# Author: Andreas Scherer (https://github.com/andischerer)
+# Created: 2021-02-01
+#
+# Set the environment variables as below:
+#
+# export UDR_USER="your_username_goes_here"
+# export UDR_PASS="some_password_goes_here"
+#
+
+UDR_API="https://api.domainreselling.de/api/call.cgi"
+UDR_TTL="30"
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "some_long_string_of_characters_go_here_from_lets_encrypt"
+dns_udr_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ UDR_USER="${UDR_USER:-$(_readaccountconf_mutable UDR_USER)}"
+ UDR_PASS="${UDR_PASS:-$(_readaccountconf_mutable UDR_PASS)}"
+ if [ -z "$UDR_USER" ] || [ -z "$UDR_PASS" ]; then
+ UDR_USER=""
+ UDR_PASS=""
+ _err "You didn't specify an UD-Reselling username and password yet"
+ return 1
+ fi
+ # save the username and password to the account conf file.
+ _saveaccountconf_mutable UDR_USER "$UDR_USER"
+ _saveaccountconf_mutable UDR_PASS "$UDR_PASS"
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _dnszone "${_dnszone}"
+
+ _debug "Getting txt records"
+ if ! _udr_rest "QueryDNSZoneRRList" "dnszone=${_dnszone}"; then
+ return 1
+ fi
+
+ rr="${fulldomain}. ${UDR_TTL} IN TXT ${txtvalue}"
+ _debug resource_record "${rr}"
+ if _contains "$response" "$rr" >/dev/null; then
+ _err "Error, it would appear that this record already exists. Please review existing TXT records for this domain."
+ return 1
+ fi
+
+ _info "Adding record"
+ if ! _udr_rest "UpdateDNSZone" "dnszone=${_dnszone}&addrr0=${rr}"; then
+ _err "Adding the record did not succeed, please verify/check."
+ return 1
+ fi
+
+ _info "Added, OK"
+ return 0
+}
+
+dns_udr_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ UDR_USER="${UDR_USER:-$(_readaccountconf_mutable UDR_USER)}"
+ UDR_PASS="${UDR_PASS:-$(_readaccountconf_mutable UDR_PASS)}"
+ if [ -z "$UDR_USER" ] || [ -z "$UDR_PASS" ]; then
+ UDR_USER=""
+ UDR_PASS=""
+ _err "You didn't specify an UD-Reselling username and password yet"
+ return 1
+ fi
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _dnszone "${_dnszone}"
+
+ _debug "Getting txt records"
+ if ! _udr_rest "QueryDNSZoneRRList" "dnszone=${_dnszone}"; then
+ return 1
+ fi
+
+ rr="${fulldomain}. ${UDR_TTL} IN TXT ${txtvalue}"
+ _debug resource_record "${rr}"
+ if _contains "$response" "$rr" >/dev/null; then
+ if ! _udr_rest "UpdateDNSZone" "dnszone=${_dnszone}&delrr0=${rr}"; then
+ _err "Deleting the record did not succeed, please verify/check."
+ return 1
+ fi
+ _info "Removed, OK"
+ return 0
+ else
+ _info "Text record is not present, will not delete anything."
+ return 0
+ fi
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ domain=$1
+ i=1
+
+ if ! _udr_rest "QueryDNSZoneList" ""; then
+ return 1
+ fi
+
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug h "$h"
+
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+
+ if _contains "${response}" "${h}." >/dev/null; then
+ _dnszone=$(echo "$response" | _egrep_o "${h}")
+ if [ "$_dnszone" ]; then
+ return 0
+ fi
+ return 1
+ fi
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+_udr_rest() {
+ if [ -n "$2" ]; then
+ data="command=$1&$2"
+ else
+ data="command=$1"
+ fi
+
+ _debug data "${data}"
+ response="$(_post "${data}" "${UDR_API}?s_login=${UDR_USER}&s_pw=${UDR_PASS}" "" "POST")"
+
+ _code=$(echo "$response" | _egrep_o "code = ([0-9]+)" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
+ _description=$(echo "$response" | _egrep_o "description = .*" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
+
+ _debug response_code "$_code"
+ _debug response_description "$_description"
+
+ if [ ! "$_code" = "200" ]; then
+ _err "DNS-API-Error: $_description"
+ return 1
+ fi
+
+ return 0
+}
diff --git a/dnsapi/dns_ultra.sh b/dnsapi/dns_ultra.sh
index 0100b3b7..0f26bd97 100644
--- a/dnsapi/dns_ultra.sh
+++ b/dnsapi/dns_ultra.sh
@@ -5,7 +5,8 @@
#
# ULTRA_PWD="some_password_goes_here"
-ULTRA_API="https://restapi.ultradns.com/v2/"
+ULTRA_API="https://api.ultradns.com/v3/"
+ULTRA_AUTH_API="https://api.ultradns.com/v2/"
#Usage: add _acme-challenge.www.domain.com "some_long_string_of_characters_go_here_from_lets_encrypt"
dns_ultra_add() {
@@ -121,7 +122,7 @@ _get_root() {
return 1
fi
if _contains "${response}" "${h}." >/dev/null; then
- _domain_id=$(echo "$response" | _egrep_o "${h}")
+ _domain_id=$(echo "$response" | _egrep_o "${h}" | head -1)
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="${h}"
@@ -142,23 +143,25 @@ _ultra_rest() {
ep="$2"
data="$3"
_debug "$ep"
- _debug TOKEN "${AUTH_TOKEN}"
+ if [ -z "$AUTH_TOKEN" ]; then
+ _ultra_login
+ fi
+ _debug TOKEN "$AUTH_TOKEN"
- _ultra_login
export _H1="Content-Type: application/json"
- export _H2="Authorization: Bearer ${AUTH_TOKEN}"
+ export _H2="Authorization: Bearer $AUTH_TOKEN"
if [ "$m" != "GET" ]; then
- _debug data "${data}"
- response="$(_post "${data}" "${ULTRA_API}"/"${ep}" "" "${m}")"
+ _debug data "$data"
+ response="$(_post "$data" "$ULTRA_API$ep" "" "$m")"
else
- response="$(_get "$ULTRA_API/$ep")"
+ response="$(_get "$ULTRA_API$ep")"
fi
}
_ultra_login() {
export _H1=""
export _H2=""
- AUTH_TOKEN=$(_post "grant_type=password&username=${ULTRA_USR}&password=${ULTRA_PWD}" "${ULTRA_API}authorization/token" | cut -d, -f3 | cut -d\" -f4)
+ AUTH_TOKEN=$(_post "grant_type=password&username=${ULTRA_USR}&password=${ULTRA_PWD}" "${ULTRA_AUTH_API}authorization/token" | cut -d, -f3 | cut -d\" -f4)
export AUTH_TOKEN
}
diff --git a/dnsapi/dns_veesp.sh b/dnsapi/dns_veesp.sh
new file mode 100644
index 00000000..b8a41d00
--- /dev/null
+++ b/dnsapi/dns_veesp.sh
@@ -0,0 +1,158 @@
+#!/usr/bin/env sh
+
+# bug reports to stepan@plyask.in
+
+#
+# export VEESP_User="username"
+# export VEESP_Password="password"
+
+VEESP_Api="https://secure.veesp.com/api"
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_veesp_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ VEESP_Password="${VEESP_Password:-$(_readaccountconf_mutable VEESP_Password)}"
+ VEESP_User="${VEESP_User:-$(_readaccountconf_mutable VEESP_User)}"
+ VEESP_auth=$(printf "%s" "$VEESP_User:$VEESP_Password" | _base64)
+
+ if [ -z "$VEESP_Password" ] || [ -z "$VEESP_User" ]; then
+ VEESP_Password=""
+ VEESP_User=""
+ _err "You don't specify veesp api key and email yet."
+ _err "Please create you key and try again."
+ return 1
+ fi
+
+ #save the api key and email to the account conf file.
+ _saveaccountconf_mutable VEESP_Password "$VEESP_Password"
+ _saveaccountconf_mutable VEESP_User "$VEESP_User"
+
+ _debug "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"
+
+ _info "Adding record"
+ if VEESP_rest POST "service/$_service_id/dns/$_domain_id/records" "{\"name\":\"$fulldomain\",\"ttl\":1,\"priority\":0,\"type\":\"TXT\",\"content\":\"$txtvalue\"}"; then
+ if _contains "$response" "\"success\":true"; then
+ _info "Added"
+ #todo: check if the record takes effect
+ return 0
+ else
+ _err "Add txt record error."
+ return 1
+ fi
+ fi
+}
+
+# Usage: fulldomain txtvalue
+# Used to remove the txt record after validation
+dns_veesp_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ VEESP_Password="${VEESP_Password:-$(_readaccountconf_mutable VEESP_Password)}"
+ VEESP_User="${VEESP_User:-$(_readaccountconf_mutable VEESP_User)}"
+ VEESP_auth=$(printf "%s" "$VEESP_User:$VEESP_Password" | _base64)
+
+ _debug "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"
+
+ _debug "Getting txt records"
+ VEESP_rest GET "service/$_service_id/dns/$_domain_id"
+
+ count=$(printf "%s\n" "$response" | _egrep_o "\"type\":\"TXT\",\"content\":\".\"$txtvalue.\"\"" | wc -l | tr -d " ")
+ _debug count "$count"
+ if [ "$count" = "0" ]; then
+ _info "Don't need to remove."
+ else
+ record_id=$(printf "%s\n" "$response" | _egrep_o "{\"id\":[^}]*\"type\":\"TXT\",\"content\":\".\"$txtvalue.\"\"" | cut -d\" -f4)
+ _debug "record_id" "$record_id"
+ if [ -z "$record_id" ]; then
+ _err "Can not get record id to remove."
+ return 1
+ fi
+ if ! VEESP_rest DELETE "service/$_service_id/dns/$_domain_id/records/$record_id"; then
+ _err "Delete record error."
+ return 1
+ fi
+ _contains "$response" "\"success\":true"
+ fi
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_get_root() {
+ domain=$1
+ i=2
+ p=1
+ if ! VEESP_rest GET "dns"; then
+ return 1
+ fi
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug h "$h"
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+
+ if _contains "$response" "\"name\":\"$h\""; then
+ _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"domain_id\":[^,]*,\"name\":\"$h\"" | cut -d : -f 2 | cut -d , -f 1 | cut -d '"' -f 2)
+ _debug _domain_id "$_domain_id"
+ _service_id=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$h\",\"service_id\":[^}]*" | cut -d : -f 3 | cut -d '"' -f 2)
+ _debug _service_id "$_service_id"
+ if [ "$_domain_id" ]; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="$h"
+ return 0
+ fi
+ return 1
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+VEESP_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+
+ export _H1="Accept: application/json"
+ export _H2="Authorization: Basic $VEESP_auth"
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ export _H3="Content-Type: application/json"
+ response="$(_post "$data" "$VEESP_Api/$ep" "" "$m")"
+ else
+ response="$(_get "$VEESP_Api/$ep")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_vercel.sh b/dnsapi/dns_vercel.sh
new file mode 100644
index 00000000..7bf6b0e5
--- /dev/null
+++ b/dnsapi/dns_vercel.sh
@@ -0,0 +1,142 @@
+#!/usr/bin/env sh
+
+# Vercel DNS API
+#
+# This is your API token which can be acquired on the account page.
+# https://vercel.com/account/tokens
+#
+# VERCEL_TOKEN="sdfsdfsdfljlbjkljlkjsdfoiwje"
+
+VERCEL_API="https://api.vercel.com"
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_vercel_add() {
+ fulldomain=$1
+ txtvalue=$2
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ VERCEL_TOKEN="${VERCEL_TOKEN:-$(_readaccountconf_mutable VERCEL_TOKEN)}"
+
+ if [ -z "$VERCEL_TOKEN" ]; then
+ VERCEL_TOKEN=""
+ _err "You have not set the Vercel API token yet."
+ _err "Please visit https://vercel.com/account/tokens to generate it."
+ return 1
+ fi
+
+ _saveaccountconf_mutable VERCEL_TOKEN "$VERCEL_TOKEN"
+
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _info "Adding record"
+ if _vercel_rest POST "v2/domains/$_domain/records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\"}"; then
+ if printf -- "%s" "$response" | grep "\"uid\":\"" >/dev/null; then
+ _info "Added"
+ return 0
+ else
+ _err "Unexpected response while adding text record."
+ return 1
+ fi
+ fi
+ _err "Add txt record error."
+}
+
+dns_vercel_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _vercel_rest GET "v2/domains/$_domain/records"
+
+ count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$_sub_domain\",[^{]*\"type\":\"TXT\"" | wc -l | tr -d " ")
+
+ if [ "$count" = "0" ]; then
+ _info "Don't need to remove."
+ else
+ _record_id=$(printf "%s" "$response" | _egrep_o "\"id\":[^,]*,\"slug\":\"[^,]*\",\"name\":\"$_sub_domain\",[^{]*\"type\":\"TXT\",\"value\":\"$txtvalue\"" | cut -d: -f2 | cut -d, -f1 | tr -d '"')
+
+ if [ "$_record_id" ]; then
+ echo "$_record_id" | while read -r item; do
+ if _vercel_rest DELETE "v2/domains/$_domain/records/$item"; then
+ _info "removed record" "$item"
+ return 0
+ else
+ _err "failed to remove record" "$item"
+ return 1
+ fi
+ done
+ fi
+ fi
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ domain="$1"
+ ep="$2"
+ i=1
+ p=1
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+
+ if ! _vercel_rest GET "v4/domains/$h"; then
+ return 1
+ fi
+
+ if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain=$h
+ return 0
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+_vercel_rest() {
+ m="$1"
+ ep="$2"
+ data="$3"
+
+ path="$VERCEL_API/$ep"
+
+ export _H1="Content-Type: application/json"
+ export _H2="Authorization: Bearer $VERCEL_TOKEN"
+
+ if [ "$m" != "GET" ]; then
+ _secure_debug2 data "$data"
+ response="$(_post "$data" "$path" "" "$m")"
+ else
+ response="$(_get "$path")"
+ fi
+ _ret="$?"
+ _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+ _debug "http response code $_code"
+ _secure_debug2 response "$response"
+ if [ "$_ret" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+
+ response="$(printf "%s" "$response" | _normalizeJson)"
+ return 0
+}
diff --git a/dnsapi/dns_vultr.sh b/dnsapi/dns_vultr.sh
index c7b52e84..54e5b6ce 100644
--- a/dnsapi/dns_vultr.sh
+++ b/dnsapi/dns_vultr.sh
@@ -3,10 +3,10 @@
#
#VULTR_API_KEY=000011112222333344445555666677778888
-VULTR_Api="https://api.vultr.com/v1"
+VULTR_Api="https://api.vultr.com/v2"
######## Public functions #####################
-
+#
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_vultr_add() {
fulldomain=$1
@@ -31,14 +31,14 @@ dns_vultr_add() {
_debug _domain "$_domain"
_debug 'Getting txt records'
- _vultr_rest GET "dns/records?domain=$_domain"
+ _vultr_rest GET "domains/$_domain/records"
- if printf "%s\n" "$response" | grep "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
+ if printf "%s\n" "$response" | grep -- "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
_err 'Error'
return 1
fi
- if ! _vultr_rest POST 'dns/create_record' "domain=$_domain&name=$_sub_domain&data=\"$txtvalue\"&type=TXT"; then
+ if ! _vultr_rest POST "domains/$_domain/records" "{\"name\":\"$_sub_domain\",\"data\":\"$txtvalue\",\"type\":\"TXT\"}"; then
_err "$response"
return 1
fi
@@ -71,14 +71,14 @@ dns_vultr_rm() {
_debug _domain "$_domain"
_debug 'Getting txt records'
- _vultr_rest GET "dns/records?domain=$_domain"
+ _vultr_rest GET "domains/$_domain/records"
- if printf "%s\n" "$response" | grep "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
+ if printf "%s\n" "$response" | grep -- "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
_err 'Error'
return 1
fi
- _record_id="$(echo "$response" | tr '{}' '\n' | grep '"TXT"' | grep "$txtvalue" | tr ',' '\n' | grep -i 'RECORDID' | cut -d : -f 2)"
+ _record_id="$(echo "$response" | tr '{}' '\n' | grep '"TXT"' | grep -- "$txtvalue" | tr ',' '\n' | grep -i 'id' | cut -d : -f 2 | tr -d '"')"
_debug _record_id "$_record_id"
if [ "$_record_id" ]; then
_info "Successfully retrieved the record id for ACME challenge."
@@ -87,7 +87,7 @@ dns_vultr_rm() {
return 0
fi
- if ! _vultr_rest POST 'dns/delete_record' "domain=$_domain&RECORDID=$_record_id"; then
+ if ! _vultr_rest DELETE "domains/$_domain/records/$_record_id"; then
_err "$response"
return 1
fi
@@ -112,11 +112,11 @@ _get_root() {
return 1
fi
- if ! _vultr_rest GET "dns/list"; then
+ if ! _vultr_rest GET "domains"; then
return 1
fi
- if printf "%s\n" "$response" | grep '^\[.*\]' >/dev/null; then
+ if printf "%s\n" "$response" | grep -E '^\{.*\}' >/dev/null; then
if _contains "$response" "\"domain\":\"$_domain\""; then
_sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")"
return 0
@@ -139,10 +139,10 @@ _vultr_rest() {
data="$3"
_debug "$ep"
- api_key_trimmed=$(echo $VULTR_API_KEY | tr -d '"')
+ api_key_trimmed=$(echo "$VULTR_API_KEY" | tr -d '"')
- export _H1="Api-Key: $api_key_trimmed"
- export _H2='Content-Type: application/x-www-form-urlencoded'
+ export _H1="Authorization: Bearer $api_key_trimmed"
+ export _H2='Content-Type: application/json'
if [ "$m" != "GET" ]; then
_debug data "$data"
diff --git a/dnsapi/dns_websupport.sh b/dnsapi/dns_websupport.sh
new file mode 100644
index 00000000..e824c9c0
--- /dev/null
+++ b/dnsapi/dns_websupport.sh
@@ -0,0 +1,207 @@
+#!/usr/bin/env sh
+
+# Acme.sh DNS API wrapper for websupport.sk
+#
+# Original author: trgo.sk (https://github.com/trgosk)
+# Tweaks by: akulumbeg (https://github.com/akulumbeg)
+# Report Bugs here: https://github.com/akulumbeg/acme.sh
+
+# Requirements: API Key and Secret from https://admin.websupport.sk/en/auth/apiKey
+#
+# WS_ApiKey="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+# (called "Identifier" in the WS Admin)
+#
+# WS_ApiSecret="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+# (called "Secret key" in the WS Admin)
+
+WS_Api="https://rest.websupport.sk"
+
+######## Public functions #####################
+
+dns_websupport_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ WS_ApiKey="${WS_ApiKey:-$(_readaccountconf_mutable WS_ApiKey)}"
+ WS_ApiSecret="${WS_ApiSecret:-$(_readaccountconf_mutable WS_ApiSecret)}"
+
+ if [ "$WS_ApiKey" ] && [ "$WS_ApiSecret" ]; then
+ _saveaccountconf_mutable WS_ApiKey "$WS_ApiKey"
+ _saveaccountconf_mutable WS_ApiSecret "$WS_ApiSecret"
+ else
+ WS_ApiKey=""
+ WS_ApiSecret=""
+ _err "You did not specify the API Key and/or API Secret"
+ _err "You can get the API login credentials from https://admin.websupport.sk/en/auth/apiKey"
+ 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"
+
+ # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so
+ # we can not use updating anymore.
+ # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2)
+ # _debug count "$count"
+ # if [ "$count" = "0" ]; then
+ _info "Adding record"
+ if _ws_rest POST "/v1/user/self/zone/$_domain/record" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
+ if _contains "$response" "$txtvalue"; then
+ _info "Added, OK"
+ return 0
+ elif _contains "$response" "The record already exists"; then
+ _info "Already exists, OK"
+ return 0
+ else
+ _err "Add txt record error."
+ return 1
+ fi
+ fi
+ _err "Add txt record error."
+ return 1
+
+}
+
+dns_websupport_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _debug2 fulldomain "$fulldomain"
+ _debug2 txtvalue "$txtvalue"
+
+ _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"
+ _ws_rest GET "/v1/user/self/zone/$_domain/record"
+
+ if [ "$(printf "%s" "$response" | tr -d " " | grep -c \"items\")" -lt "1" ]; then
+ _err "Error: $response"
+ return 1
+ fi
+
+ record_line="$(_get_from_array "$response" "$txtvalue")"
+ _debug record_line "$record_line"
+ if [ -z "$record_line" ]; then
+ _info "Don't need to remove."
+ else
+ record_id=$(echo "$record_line" | _egrep_o "\"id\": *[^,]*" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ")
+ _debug "record_id" "$record_id"
+ if [ -z "$record_id" ]; then
+ _err "Can not get record id to remove."
+ return 1
+ fi
+ if ! _ws_rest DELETE "/v1/user/self/zone/$_domain/record/$record_id"; then
+ _err "Delete record error."
+ return 1
+ fi
+ if [ "$(printf "%s" "$response" | tr -d " " | grep -c \"success\")" -lt "1" ]; then
+ return 1
+ else
+ return 0
+ fi
+ fi
+
+}
+
+#################### Private Functions ##################################
+
+_get_root() {
+ domain=$1
+ i=1
+ p=1
+
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug h "$h"
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+
+ if ! _ws_rest GET "/v1/user/self/zone"; then
+ return 1
+ fi
+
+ if _contains "$response" "\"name\":\"$h\""; then
+ _domain_id=$(echo "$response" | _egrep_o "\[.\"id\": *[^,]*" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ")
+ if [ "$_domain_id" ]; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain=$h
+ return 0
+ fi
+ return 1
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+_ws_rest() {
+ me=$1
+ pa="$2"
+ da="$3"
+
+ _debug2 api_key "$WS_ApiKey"
+ _debug2 api_secret "$WS_ApiSecret"
+
+ timestamp=$(_time)
+ datez="$(_utc_date | sed "s/ /T/" | sed "s/$/+0000/")"
+ canonical_request="${me} ${pa} ${timestamp}"
+ signature_hash=$(printf "%s" "$canonical_request" | _hmac sha1 "$(printf "%s" "$WS_ApiSecret" | _hex_dump | tr -d " ")" hex)
+ basicauth="$(printf "%s:%s" "$WS_ApiKey" "$signature_hash" | _base64)"
+
+ _debug2 method "$me"
+ _debug2 path "$pa"
+ _debug2 data "$da"
+ _debug2 timestamp "$timestamp"
+ _debug2 datez "$datez"
+ _debug2 canonical_request "$canonical_request"
+ _debug2 signature_hash "$signature_hash"
+ _debug2 basicauth "$basicauth"
+
+ export _H1="Accept: application/json"
+ export _H2="Content-Type: application/json"
+ export _H3="Authorization: Basic ${basicauth}"
+ export _H4="Date: ${datez}"
+
+ _debug2 H1 "$_H1"
+ _debug2 H2 "$_H2"
+ _debug2 H3 "$_H3"
+ _debug2 H4 "$_H4"
+
+ if [ "$me" != "GET" ]; then
+ _debug2 "${me} $WS_Api${pa}"
+ _debug data "$da"
+ response="$(_post "$da" "${WS_Api}${pa}" "" "$me")"
+ else
+ _debug2 "GET $WS_Api${pa}"
+ response="$(_get "$WS_Api${pa}")"
+ fi
+
+ _debug2 response "$response"
+ return "$?"
+}
+
+_get_from_array() {
+ va="$1"
+ fi="$2"
+ for i in $(echo "$va" | sed "s/{/ /g"); do
+ if _contains "$i" "$fi"; then
+ echo "$i"
+ break
+ fi
+ done
+}
diff --git a/dnsapi/dns_world4you.sh b/dnsapi/dns_world4you.sh
new file mode 100644
index 00000000..dfda4efd
--- /dev/null
+++ b/dnsapi/dns_world4you.sh
@@ -0,0 +1,220 @@
+#!/usr/bin/env sh
+
+# World4You - www.world4you.com
+# Lorenz Stechauner, 2020 - https://www.github.com/NerLOR
+
+WORLD4YOU_API="https://my.world4you.com/en"
+PAKETNR=''
+TLD=''
+RECORD=''
+
+################ Public functions ################
+
+# Usage: dns_world4you_add
+dns_world4you_add() {
+ fqdn=$(echo "$1" | _lower_case)
+ value="$2"
+ _info "Using world4you to add record"
+ _debug fulldomain "$fqdn"
+ _debug txtvalue "$value"
+
+ _login
+ if [ "$?" != 0 ]; then
+ return 1
+ fi
+
+ export _H1="Cookie: W4YSESSID=$sessid"
+ form=$(_get "$WORLD4YOU_API/")
+ _get_paketnr "$fqdn" "$form"
+ paketnr="$PAKETNR"
+ if [ -z "$paketnr" ]; then
+ _err "Unable to parse paketnr"
+ return 3
+ fi
+ _debug paketnr "$paketnr"
+
+ export _H1="Cookie: W4YSESSID=$sessid"
+ form=$(_get "$WORLD4YOU_API/$paketnr/dns")
+ formiddp=$(echo "$form" | grep 'AddDnsRecordForm\[uniqueFormIdDP\]' | sed 's/^.*name="AddDnsRecordForm\[uniqueFormIdDP\]" value="\([^"]*\)".*$/\1/')
+ form_token=$(echo "$form" | grep 'AddDnsRecordForm\[_token\]' | sed 's/^.*name="AddDnsRecordForm\[_token\]" value="\([^"]*\)".*$/\1/')
+ if [ -z "$formiddp" ]; then
+ _err "Unable to parse form"
+ return 3
+ fi
+
+ _resethttp
+ export ACME_HTTP_NO_REDIRECTS=1
+ body="AddDnsRecordForm[name]=$RECORD&AddDnsRecordForm[dnsType][type]=TXT&AddDnsRecordForm[value]=$value&AddDnsRecordForm[uniqueFormIdDP]=$formiddp&AddDnsRecordForm[_token]=$form_token"
+ _info "Adding record..."
+ ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/dns" '' POST 'application/x-www-form-urlencoded')
+ _resethttp
+
+ if _contains "$(_head_n 1 <"$HTTP_HEADER")" '302'; then
+ res=$(_get "$WORLD4YOU_API/$paketnr/dns")
+ if _contains "$res" "successfully"; then
+ return 0
+ else
+ msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "
]*>[^<]" | sed 's/<[^>]*>//g' | sed 's/^\s*//g')
+ if [ "$msg" = '' ]; then
+ _err "Unable to add record: Unknown error"
+ echo "$ret" >'error-01.html'
+ echo "$res" >'error-02.html'
+ _err "View error-01.html and error-02.html for debugging"
+ else
+ _err "Unable to add record: my.world4you.com: $msg"
+ fi
+ return 1
+ fi
+ else
+ msg=$(echo "$ret" | grep '"form-error-message"' | sed 's/^.*