Successfully removed record.' \
+ _post "$body" "https://dns.he.net/" |
+ grep 'Successfully removed record.
' \
>/dev/null
exit_code="$?"
if [ "$exit_code" -eq 0 ]; then
diff --git a/dnsapi/dns_hetzner.sh b/dnsapi/dns_hetzner.sh
index 5db0418c..911d4a35 100644
--- a/dnsapi/dns_hetzner.sh
+++ b/dnsapi/dns_hetzner.sh
@@ -123,10 +123,10 @@ _find_record() {
return 1
else
_record_id=$(
- echo "$response" \
- | grep -o "{[^\{\}]*\"name\":\"$_record_name\"[^\}]*}" \
- | grep "\"value\":\"$_record_value\"" \
- | while read -r record; do
+ echo "$response" |
+ grep -o "{[^\{\}]*\"name\":\"$_record_name\"[^\}]*}" |
+ grep "\"value\":\"$_record_value\"" |
+ while read -r record; do
# test for type and
if [ -n "$(echo "$record" | _egrep_o '"type":"TXT"')" ]; then
echo "$record" | _egrep_o '"id":"[^"]*"' | cut -d : -f 2 | tr -d \"
diff --git a/dnsapi/dns_huaweicloud.sh b/dnsapi/dns_huaweicloud.sh
new file mode 100644
index 00000000..ac3ede65
--- /dev/null
+++ b/dnsapi/dns_huaweicloud.sh
@@ -0,0 +1,289 @@
+#!/usr/bin/env sh
+
+# HUAWEICLOUD_Username
+# HUAWEICLOUD_Password
+# HUAWEICLOUD_ProjectID
+
+iam_api="https://iam.myhuaweicloud.com"
+dns_api="https://dns.ap-southeast-1.myhuaweicloud.com" # Should work
+
+######## Public functions #####################
+
+# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Used to add txt record
+#
+# Ref: https://support.huaweicloud.com/intl/zh-cn/api-dns/zh-cn_topic_0132421999.html
+#
+
+dns_huaweicloud_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}"
+ HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}"
+ HUAWEICLOUD_ProjectID="${HUAWEICLOUD_ProjectID:-$(_readaccountconf_mutable HUAWEICLOUD_ProjectID)}"
+
+ # Check information
+ if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_ProjectID}" ]; then
+ _err "Not enough information provided to dns_huaweicloud!"
+ return 1
+ fi
+
+ unset token # Clear token
+ token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_ProjectID}")"
+ if [ -z "${token}" ]; then # Check token
+ _err "dns_api(dns_huaweicloud): Error getting token."
+ return 1
+ fi
+ _secure_debug "Access token is:" "${token}"
+
+ unset zoneid
+ zoneid="$(_get_zoneid "${token}" "${fulldomain}")"
+ if [ -z "${zoneid}" ]; then
+ _err "dns_api(dns_huaweicloud): Error getting zone id."
+ return 1
+ fi
+ _debug "Zone ID is:" "${zoneid}"
+
+ _debug "Adding Record"
+ _add_record "${token}" "${fulldomain}" "${txtvalue}"
+ ret="$?"
+ if [ "${ret}" != "0" ]; then
+ _err "dns_api(dns_huaweicloud): Error adding record."
+ return 1
+ fi
+
+ # Do saving work if all succeeded
+ _saveaccountconf_mutable HUAWEICLOUD_Username "${HUAWEICLOUD_Username}"
+ _saveaccountconf_mutable HUAWEICLOUD_Password "${HUAWEICLOUD_Password}"
+ _saveaccountconf_mutable HUAWEICLOUD_ProjectID "${HUAWEICLOUD_ProjectID}"
+ return 0
+}
+
+# Usage: fulldomain txtvalue
+# Used to remove the txt record after validation
+#
+# Ref: https://support.huaweicloud.com/intl/zh-cn/api-dns/dns_api_64005.html
+#
+
+dns_huaweicloud_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}"
+ HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}"
+ HUAWEICLOUD_ProjectID="${HUAWEICLOUD_ProjectID:-$(_readaccountconf_mutable HUAWEICLOUD_ProjectID)}"
+
+ # Check information
+ if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_ProjectID}" ]; then
+ _err "Not enough information provided to dns_huaweicloud!"
+ return 1
+ fi
+
+ unset token # Clear token
+ token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_ProjectID}")"
+ if [ -z "${token}" ]; then # Check token
+ _err "dns_api(dns_huaweicloud): Error getting token."
+ return 1
+ fi
+ _secure_debug "Access token is:" "${token}"
+
+ unset zoneid
+ zoneid="$(_get_zoneid "${token}" "${fulldomain}")"
+ if [ -z "${zoneid}" ]; then
+ _err "dns_api(dns_huaweicloud): Error getting zone id."
+ return 1
+ fi
+ _debug "Zone ID is:" "${zoneid}"
+
+ # Remove all records
+ # Therotically HuaweiCloud does not allow more than one record set
+ # But remove them recurringly to increase robusty
+ while [ "${record_id}" != "0" ]; do
+ _debug "Removing Record"
+ _rm_record "${token}" "${zoneid}" "${record_id}"
+ record_id="$(_get_recordset_id "${token}" "${fulldomain}" "${zoneid}")"
+ done
+ return 0
+}
+
+################### Private functions below ##################################
+
+# _get_zoneid
+#
+# _token=$1
+# _domain_string=$2
+#
+# printf "%s" "${_zoneid}"
+_get_zoneid() {
+ _token=$1
+ _domain_string=$2
+ export _H1="X-Auth-Token: ${_token}"
+
+ i=1
+ while true; do
+ h=$(printf "%s" "${_domain_string}" | cut -d . -f $i-100)
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+ _debug "$h"
+ response=$(_get "${dns_api}/v2/zones?name=${h}")
+ _debug2 "$response"
+ if _contains "${response}" '"id"'; then
+ zoneidlist=$(echo "${response}" | _egrep_o "\"id\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | tr -d " ")
+ zonenamelist=$(echo "${response}" | _egrep_o "\"name\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | tr -d " ")
+ _debug2 "Return Zone ID(s):" "${zoneidlist}"
+ _debug2 "Return Zone Name(s):" "${zonenamelist}"
+ zoneidnum=0
+ zoneidcount=$(echo "${zoneidlist}" | grep -c '^')
+ _debug "Retund Zone ID(s) Count:" "${zoneidcount}"
+ while [ "${zoneidnum}" -lt "${zoneidcount}" ]; do
+ zoneidnum=$(_math "$zoneidnum" + 1)
+ _zoneid=$(echo "${zoneidlist}" | sed -n "${zoneidnum}p")
+ zonename=$(echo "${zonenamelist}" | sed -n "${zoneidnum}p")
+ _debug "Check Zone Name" "${zonename}"
+ if [ "${zonename}" = "${h}." ]; then
+ _debug "Get Zone ID Success."
+ _debug "ZoneID:" "${_zoneid}"
+ printf "%s" "${_zoneid}"
+ return 0
+ fi
+ done
+ fi
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+_get_recordset_id() {
+ _token=$1
+ _domain=$2
+ _zoneid=$3
+ export _H1="X-Auth-Token: ${_token}"
+
+ response=$(_get "${dns_api}/v2/zones/${_zoneid}/recordsets?name=${_domain}")
+ if _contains "${response}" '"id"'; then
+ _id="$(echo "${response}" | _egrep_o "\"id\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | tr -d " ")"
+ printf "%s" "${_id}"
+ return 0
+ fi
+ printf "%s" "0"
+ return 1
+}
+
+_add_record() {
+ _token=$1
+ _domain=$2
+ _txtvalue=$3
+
+ # Get Existing Records
+ export _H1="X-Auth-Token: ${_token}"
+ response=$(_get "${dns_api}/v2/zones/${zoneid}/recordsets?name=${_domain}")
+
+ _debug2 "${response}"
+ _exist_record=$(echo "${response}" | _egrep_o '"records":[^]]*' | sed 's/\"records\"\:\[//g')
+ _debug "${_exist_record}"
+
+ # Check if record exist
+ # Generate body data
+ if [ -z "${_exist_record}" ]; then
+ _post_body="{
+ \"name\": \"${_domain}.\",
+ \"description\": \"ACME Challenge\",
+ \"type\": \"TXT\",
+ \"ttl\": 1,
+ \"records\": [
+ \"\\\"${_txtvalue}\\\"\"
+ ]
+ }"
+ else
+ _post_body="{
+ \"name\": \"${_domain}.\",
+ \"description\": \"ACME Challenge\",
+ \"type\": \"TXT\",
+ \"ttl\": 1,
+ \"records\": [
+ ${_exist_record},
+ \"\\\"${_txtvalue}\\\"\"
+ ]
+ }"
+ fi
+
+ _record_id="$(_get_recordset_id "${_token}" "${_domain}" "${zoneid}")"
+ _debug "Record Set ID is:" "${_record_id}"
+
+ # Remove all records
+ while [ "${_record_id}" != "0" ]; do
+ _debug "Removing Record"
+ _rm_record "${_token}" "${zoneid}" "${_record_id}"
+ _record_id="$(_get_recordset_id "${_token}" "${_domain}" "${zoneid}")"
+ done
+
+ # Add brand new records with all old and new records
+ export _H2="Content-Type: application/json"
+ export _H1="X-Auth-Token: ${_token}"
+
+ _debug2 "${_post_body}"
+ _post "${_post_body}" "${dns_api}/v2/zones/${zoneid}/recordsets" >/dev/null
+ _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
+ _project=$3
+
+ _debug "Getting Token"
+ body="{
+ \"auth\": {
+ \"identity\": {
+ \"methods\": [
+ \"password\"
+ ],
+ \"password\": {
+ \"user\": {
+ \"name\": \"${_username}\",
+ \"password\": \"${_password}\",
+ \"domain\": {
+ \"name\": \"${_username}\"
+ }
+ }
+ }
+ },
+ \"scope\": {
+ \"project\": {
+ \"id\": \"${_project}\"
+ }
+ }
+ }
+ }"
+ 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..765cf39d
--- /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..c2c431bb
--- /dev/null
+++ b/dnsapi/dns_ionos.sh
@@ -0,0 +1,163 @@
+#!/usr/bin/env sh
+
+# Supports IONOS DNS API Beta v1.0.0
+#
+# 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" && [ -z "$response" ]; 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" && [ -z "$response" ]; 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"
+
+ 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
+
+ if [ "$?" != "0" ]; then
+ _err "Error $route: $response"
+ return 1
+ fi
+ _debug2 "response" "$response"
+
+ return 0
+}
diff --git a/dnsapi/dns_ispconfig.sh b/dnsapi/dns_ispconfig.sh
index 2d8d6b0a..e68ddd49 100755
--- a/dnsapi/dns_ispconfig.sh
+++ b/dnsapi/dns_ispconfig.sh
@@ -75,7 +75,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
@@ -95,33 +95,47 @@ _ISPC_getZoneInfo() {
server_id=$(echo "${curResult}" | _egrep_o "server_id.*" | cut -d ':' -f 2 | cut -d '"' -f 2)
_debug "Server ID: '${server_id}'"
case "${server_id}" in
- '' | *[!0-9]*)
- _err "Server ID is not numeric."
- return 1
- ;;
- *) _info "Retrieved Server ID" ;;
+ '' | *[!0-9]*)
+ _err "Server ID is not numeric."
+ return 1
+ ;;
+ *) _info "Retrieved Server ID" ;;
esac
zone=$(echo "${curResult}" | _egrep_o "\"id.*" | cut -d ':' -f 2 | cut -d '"' -f 2)
_debug "Zone: '${zone}'"
case "${zone}" in
- '' | *[!0-9]*)
- _err "Zone ID is not numeric."
- return 1
- ;;
- *) _info "Retrieved Zone ID" ;;
+ '' | *[!0-9]*)
+ _err "Zone ID is not numeric."
+ return 1
+ ;;
+ *) _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
- '' | *[!0-9]*)
- _err "Client ID is not numeric."
- return 1
- ;;
- *) _info "Retrieved Client ID." ;;
+ 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 "SYS User ID is not numeric."
+ return 1
+ ;;
+ *) _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() {
@@ -135,11 +149,11 @@ _ISPC_addTxt() {
record_id=$(echo "${curResult}" | _egrep_o "\"response.*" | cut -d ':' -f 2 | cut -d '"' -f 2)
_debug "Record ID: '${record_id}'"
case "${record_id}" in
- '' | *[!0-9]*)
- _err "Couldn't add ACME Challenge TXT record to zone."
- return 1
- ;;
- *) _info "Added ACME Challenge TXT record to zone." ;;
+ '' | *[!0-9]*)
+ _err "Couldn't add ACME Challenge TXT record to zone."
+ return 1
+ ;;
+ *) _info "Added ACME Challenge TXT record to zone." ;;
esac
}
@@ -153,24 +167,24 @@ _ISPC_rmTxt() {
record_id=$(echo "${curResult}" | _egrep_o "\"id.*" | cut -d ':' -f 2 | cut -d '"' -f 2)
_debug "Record ID: '${record_id}'"
case "${record_id}" in
- '' | *[!0-9]*)
- _err "Record ID is not numeric."
+ '' | *[!0-9]*)
+ _err "Record ID is not numeric."
+ return 1
+ ;;
+ *)
+ unset IFS
+ _info "Retrieved Record ID."
+ curData="{\"session_id\":\"${sessionID}\",\"primary_id\":\"${record_id}\",\"update_serial\":true}"
+ curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_delete")"
+ _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_delete'"
+ _debug "Result of _ISPC_rmTxt: '$curResult'"
+ if _contains "${curResult}" '"code":"ok"'; then
+ _info "Removed ACME Challenge TXT record from zone."
+ else
+ _err "Couldn't remove ACME Challenge TXT record from zone."
return 1
- ;;
- *)
- unset IFS
- _info "Retrieved Record ID."
- curData="{\"session_id\":\"${sessionID}\",\"primary_id\":\"${record_id}\",\"update_serial\":true}"
- curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_delete")"
- _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_delete'"
- _debug "Result of _ISPC_rmTxt: '$curResult'"
- if _contains "${curResult}" '"code":"ok"'; then
- _info "Removed ACME Challenge TXT record from zone."
- else
- _err "Couldn't remove ACME Challenge TXT record from zone."
- return 1
- fi
- ;;
+ fi
+ ;;
esac
fi
}
diff --git a/dnsapi/dns_joker.sh b/dnsapi/dns_joker.sh
index 5d50953e..78399a1d 100644
--- a/dnsapi/dns_joker.sh
+++ b/dnsapi/dns_joker.sh
@@ -100,7 +100,7 @@ _get_root() {
fi
# Try to remove a test record. With correct root domain, username and password this will return "OK: ..." regardless
- # of record in question existing or not.
+ # of record in question existing or not.
if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$h&label=jokerTXTUpdateTest&type=TXT&value="; then
if _startswith "$response" "OK"; then
_sub_domain="$(echo "$fulldomain" | sed "s/\\.$h\$//")"
diff --git a/dnsapi/dns_kappernet.sh b/dnsapi/dns_kappernet.sh
new file mode 100644
index 00000000..83a7e5f8
--- /dev/null
+++ b/dnsapi/dns_kappernet.sh
@@ -0,0 +1,150 @@
+#!/usr/bin/env sh
+
+# kapper.net domain api
+# for further questions please contact: support@kapper.net
+# please report issues here: https://github.com/acmesh-official/acme.sh/issues/2977
+
+#KAPPERNETDNS_Key="yourKAPPERNETapikey"
+#KAPPERNETDNS_Secret="yourKAPPERNETapisecret"
+
+KAPPERNETDNS_Api="https://dnspanel.kapper.net/API/1.2?APIKey=$KAPPERNETDNS_Key&APISecret=$KAPPERNETDNS_Secret"
+
+###############################################################################
+# called with
+# fullhostname: something.example.com
+# txtvalue: someacmegenerated string
+dns_kappernet_add() {
+ fullhostname=$1
+ txtvalue=$2
+
+ KAPPERNETDNS_Key="${KAPPERNETDNS_Key:-$(_readaccountconf_mutable KAPPERNETDNS_Key)}"
+ KAPPERNETDNS_Secret="${KAPPERNETDNS_Secret:-$(_readaccountconf_mutable KAPPERNETDNS_Secret)}"
+
+ if [ -z "$KAPPERNETDNS_Key" ] || [ -z "$KAPPERNETDNS_Secret" ]; then
+ KAPPERNETDNS_Key=""
+ KAPPERNETDNS_Secret=""
+ _err "Please specify your kapper.net api key and secret."
+ _err "If you have not received yours - send your mail to"
+ _err "support@kapper.net to get your key and secret."
+ return 1
+ fi
+
+ #store the api key and email to the account conf file.
+ _saveaccountconf_mutable KAPPERNETDNS_Key "$KAPPERNETDNS_Key"
+ _saveaccountconf_mutable KAPPERNETDNS_Secret "$KAPPERNETDNS_Secret"
+ _debug "Checking Domain ..."
+ if ! _get_root "$fullhostname"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _sub_domain "SUBDOMAIN: $_sub_domain"
+ _debug _domain "DOMAIN: $_domain"
+
+ _info "Trying to add TXT DNS Record"
+ data="%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%223600%22%2C%22prio%22%3A%22%22%7D"
+ if _kappernet_api GET "action=new&subject=$_domain&data=$data"; then
+
+ if _contains "$response" "{\"OK\":true"; then
+ _info "Waiting 120 seconds for DNS to spread the new record"
+ _sleep 120
+ return 0
+ else
+ _err "Error creating a TXT DNS Record: $fullhostname TXT $txtvalue"
+ _err "Error Message: $response"
+ return 1
+ fi
+ fi
+ _err "Failed creating TXT Record"
+}
+
+###############################################################################
+# called with
+# fullhostname: something.example.com
+dns_kappernet_rm() {
+ fullhostname=$1
+ txtvalue=$2
+
+ KAPPERNETDNS_Key="${KAPPERNETDNS_Key:-$(_readaccountconf_mutable KAPPERNETDNS_Key)}"
+ KAPPERNETDNS_Secret="${KAPPERNETDNS_Secret:-$(_readaccountconf_mutable KAPPERNETDNS_Secret)}"
+
+ if [ -z "$KAPPERNETDNS_Key" ] || [ -z "$KAPPERNETDNS_Secret" ]; then
+ KAPPERNETDNS_Key=""
+ KAPPERNETDNS_Secret=""
+ _err "Please specify your kapper.net api key and secret."
+ _err "If you have not received yours - send your mail to"
+ _err "support@kapper.net to get your key and secret."
+ return 1
+ fi
+
+ #store the api key and email to the account conf file.
+ _saveaccountconf_mutable KAPPERNETDNS_Key "$KAPPERNETDNS_Key"
+ _saveaccountconf_mutable KAPPERNETDNS_Secret "$KAPPERNETDNS_Secret"
+
+ _info "Trying to remove the TXT Record: $fullhostname containing $txtvalue"
+ data="%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%223600%22%2C%22prio%22%3A%22%22%7D"
+ if _kappernet_api GET "action=del&subject=$fullhostname&data=$data"; then
+ if _contains "$response" "{\"OK\":true"; then
+ return 0
+ else
+ _err "Error deleting DNS Record: $fullhostname containing $txtvalue"
+ _err "Problem: $response"
+ return 1
+ fi
+ fi
+ _err "Problem deleting TXT DNS record"
+}
+
+#################### Private functions below ##################################
+# called with hostname
+# e.g._acme-challenge.www.domain.com returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_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 ! _kappernet_api GET "action=list&subject=$h"; then
+ return 1
+ fi
+ if _contains "$response" '"OK":false'; 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
+}
+
+################################################################################
+# calls the kapper.net DNS Panel API
+# with
+# method
+# param
+_kappernet_api() {
+ method=$1
+ param="$2"
+
+ _debug param "PARAMETER=$param"
+ url="$KAPPERNETDNS_Api&$param"
+ _debug url "URL=$url"
+
+ if [ "$method" = "GET" ]; then
+ response="$(_get "$url")"
+ else
+ _err "Unsupported method"
+ return 1
+ fi
+
+ _debug2 response "$response"
+ return 0
+}
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}" <%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" | grep -oPm1 "(?<=)[^<]+")
+ _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 quoute or double quoute 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" | grep -oPm1 "(?<=)[^<]+")
+ _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" | grep -oPm1 "(?<=)[^<]+")
+ _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" | grep -oPm1 "(?<=)[^<]+")
+ _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_misaka.sh b/dnsapi/dns_misaka.sh
index eed4170e..36ba5cfd 100755
--- a/dnsapi/dns_misaka.sh
+++ b/dnsapi/dns_misaka.sh
@@ -47,7 +47,7 @@ dns_misaka_add() {
if [ "$count" = "0" ]; then
_info "Adding record"
- if _misaka_rest PUT "zones/${_domain}/recordsets/${_sub_domain}/TXT" "{\"records\":[{\"value\":\"\\\"$txtvalue\\\"\"}],\"filters\":[],\"ttl\":1}"; then
+ if _misaka_rest POST "zones/${_domain}/recordsets/${_sub_domain}/TXT" "{\"records\":[{\"value\":\"\\\"$txtvalue\\\"\"}],\"filters\":[],\"ttl\":1}"; then
_debug response "$response"
if _contains "$response" "$_sub_domain"; then
_info "Added"
@@ -61,7 +61,7 @@ dns_misaka_add() {
else
_info "Updating record"
- _misaka_rest POST "zones/${_domain}/recordsets/${_sub_domain}/TXT?append=true" "{\"records\": [{\"value\": \"\\\"$txtvalue\\\"\"}],\"ttl\":1}"
+ _misaka_rest PUT "zones/${_domain}/recordsets/${_sub_domain}/TXT?append=true" "{\"records\": [{\"value\": \"\\\"$txtvalue\\\"\"}],\"ttl\":1}"
if [ "$?" = "0" ] && _contains "$response" "$_sub_domain"; then
_info "Updated!"
#todo: check if the record takes effect
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..d15d6b0e 100755
--- a/dnsapi/dns_namecheap.sh
+++ b/dnsapi/dns_namecheap.sh
@@ -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)
@@ -405,3 +405,7 @@ _namecheap_set_tld_sld() {
done
}
+
+_xml_decode() {
+ sed 's/"/"/g'
+}
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
new file mode 100644
index 00000000..2ce13e2b
--- /dev/null
+++ b/dnsapi/dns_netlify.sh
@@ -0,0 +1,162 @@
+#!/usr/bin/env sh
+
+#NETLIFY_ACCESS_TOKEN="xxxx"
+
+NETLIFY_HOST="api.netlify.com/api/v1/"
+NETLIFY_URL="https://$NETLIFY_HOST"
+
+######## Public functions #####################
+
+#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_netlify_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ NETLIFY_ACCESS_TOKEN="${NETLIFY_ACCESS_TOKEN:-$(_readaccountconf_mutable NETLIFY_ACCESS_TOKEN)}"
+
+ if [ -z "$NETLIFY_ACCESS_TOKEN" ]; then
+ NETLIFY_ACCESS_TOKEN=""
+ _err "Please specify your Netlify Access Token and try again."
+ return 1
+ fi
+
+ _info "Using Netlify"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ _saveaccountconf_mutable NETLIFY_ACCESS_TOKEN "$NETLIFY_ACCESS_TOKEN"
+
+ if ! _get_root "$fulldomain" "$accesstoken"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _domain_id "$_domain_id"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ dnsRecordURI="dns_zones/$_domain_id/dns_records"
+
+ body="{\"type\":\"TXT\", \"hostname\":\"$_sub_domain\", \"value\":\"$txtvalue\", \"ttl\":\"10\"}"
+
+ _netlify_rest POST "$dnsRecordURI" "$body" "$NETLIFY_ACCESS_TOKEN"
+ _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+ if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then
+ _info "validation value added"
+ return 0
+ else
+ _err "error adding validation value ($_code)"
+ return 1
+ fi
+
+ _err "Not fully implemented!"
+ return 1
+}
+
+#Usage: dns_myapi_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+#Remove the txt record after validation.
+dns_netlify_rm() {
+ _info "Using Netlify"
+ txtdomain="$1"
+ txt="$2"
+ _debug txtdomain "$txtdomain"
+ _debug txt "$txt"
+
+ _saveaccountconf_mutable NETLIFY_ACCESS_TOKEN "$NETLIFY_ACCESS_TOKEN"
+
+ if ! _get_root "$txtdomain" "$accesstoken"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _domain_id "$_domain_id"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ dnsRecordURI="dns_zones/$_domain_id/dns_records"
+
+ _netlify_rest GET "$dnsRecordURI" "" "$NETLIFY_ACCESS_TOKEN"
+
+ _record_id=$(echo "$response" | _egrep_o "\"type\":\"TXT\",[^\}]*\"value\":\"$txt\"" | head -n 1 | _egrep_o "\"id\":\"[^\"\}]*\"" | cut -d : -f 2 | tr -d \")
+ _debug _record_id "$_record_id"
+ if [ "$_record_id" ]; then
+ _netlify_rest DELETE "$dnsRecordURI/$_record_id" "" "$NETLIFY_ACCESS_TOKEN"
+ _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+ if [ "$_code" = "200" ] || [ "$_code" = '204' ]; then
+ _info "validation value removed"
+ return 0
+ else
+ _err "error removing validation value ($_code)"
+ return 1
+ fi
+ return 0
+ fi
+ return 1
+}
+
+#################### Private functions below ##################################
+
+_get_root() {
+ domain=$1
+ accesstoken=$2
+ i=1
+ p=1
+
+ _netlify_rest GET "dns_zones" "" "$accesstoken"
+
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug2 "Checking domain: $h"
+ if [ -z "$h" ]; then
+ #not valid
+ _err "Invalid domain"
+ return 1
+ fi
+
+ if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
+ _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
+ _sub_domain="@"
+ else
+ _sub_domain=$(echo "$domain" | cut -d . -f 1-$p)
+ fi
+ _domain=$h
+ return 0
+ fi
+ return 1
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+_netlify_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+
+ token_trimmed=$(echo "$NETLIFY_ACCESS_TOKEN" | tr -d '"')
+
+ export _H1="Content-Type: application/json"
+ export _H2="Authorization: Bearer $token_trimmed"
+
+ : >"$HTTP_HEADER"
+
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$NETLIFY_URL$ep" "" "$m")"
+ else
+ response="$(_get "$NETLIFY_URL$ep")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_nic.sh b/dnsapi/dns_nic.sh
index 5052ee10..56170f87 100644
--- a/dnsapi/dns_nic.sh
+++ b/dnsapi/dns_nic.sh
@@ -166,7 +166,7 @@ _get_root() {
if _contains "$_all_domains" "^$h$"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain=$h
- _service=$(printf "%s" "$response" | grep "idn-name=\"$_domain\"" | sed -r "s/.*service=\"(.*)\".*$/\1/")
+ _service=$(printf "%s" "$response" | grep -m 1 "idn-name=\"$_domain\"" | sed -r "s/.*service=\"(.*)\".*$/\1/")
return 0
fi
p="$i"
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..eb006120
--- /dev/null
+++ b/dnsapi/dns_oci.sh
@@ -0,0 +1,324 @@
+#!/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 multiline)
+ 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"
+ 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 96ef5969..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
new file mode 100755
index 00000000..38619e6f
--- /dev/null
+++ b/dnsapi/dns_openstack.sh
@@ -0,0 +1,348 @@
+#!/usr/bin/env sh
+
+# OpenStack Designate API plugin
+#
+# This requires you to have OpenStackClient and python-desginateclient
+# installed.
+#
+# You will require Keystone V3 credentials loaded into your environment, which
+# could be either password or v3applicationcredential type.
+#
+# Author: Andy Botting
+
+######## Public functions #####################
+
+# Usage: dns_openstack_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_openstack_add() {
+ fulldomain=$1
+ txtvalue=$2
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ _dns_openstack_credentials || return $?
+ _dns_openstack_check_setup || return $?
+ _dns_openstack_find_zone || return $?
+ _dns_openstack_get_recordset || return $?
+ _debug _recordset_id "$_recordset_id"
+ if [ -n "$_recordset_id" ]; then
+ _dns_openstack_get_records || return $?
+ _debug _records "$_records"
+ fi
+ _dns_openstack_create_recordset || return $?
+}
+
+# Usage: dns_openstack_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Remove the txt record after validation.
+dns_openstack_rm() {
+ fulldomain=$1
+ txtvalue=$2
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ _dns_openstack_credentials || return $?
+ _dns_openstack_check_setup || return $?
+ _dns_openstack_find_zone || return $?
+ _dns_openstack_get_recordset || return $?
+ _debug _recordset_id "$_recordset_id"
+ if [ -n "$_recordset_id" ]; then
+ _dns_openstack_get_records || return $?
+ _debug _records "$_records"
+ fi
+ _dns_openstack_delete_recordset || return $?
+}
+
+#################### Private functions below ##################################
+
+_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
+ _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"
+ for _rec in $_records; do
+ _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
+ _err "Recordset update failed"
+ return 1
+ fi
+ fi
+
+ _max_retries=60
+ _sleep_sec=5
+ _retry_times=0
+ while [ "$_retry_times" -lt "$_max_retries" ]; do
+ _retry_times=$(_math "$_retry_times" + 1)
+ _debug3 _retry_times "$_retry_times"
+
+ _record_status=$(openstack recordset show -c status -f value "$_zone_id" "$_recordset_id")
+ _info "Recordset status is $_record_status"
+ if [ "$_record_status" = "ACTIVE" ]; then
+ return 0
+ elif [ "$_record_status" = "ERROR" ]; then
+ return 1
+ else
+ _sleep $_sleep_sec
+ fi
+ done
+
+ _err "Recordset failed to become ACTIVE"
+ return 1
+}
+
+_dns_openstack_delete_recordset() {
+
+ if [ "$_records" = "$txtvalue" ]; then
+ _info "Only one record found, deleting recordset"
+ if ! openstack recordset delete "$_zone_id" "$fulldomain." >/dev/null; then
+ _err "Failed to delete recordset"
+ return 1
+ fi
+ else
+ _info "Found existing records, updating recordset"
+ # 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"
+ done
+ # shellcheck disable=SC2086
+ if ! openstack recordset set -c id -f value $_record_args "$_zone_id" "$fulldomain." >/dev/null; then
+ _err "Recordset update failed"
+ return 1
+ fi
+ fi
+}
+
+_dns_openstack_get_root() {
+ # Take the full fqdn and strip away pieces until we get an exact zone name
+ # match. For example, _acme-challenge.something.domain.com might need to go
+ # into something.domain.com or domain.com
+ _zone_name=$1
+ _zone_list=$2
+ while [ "$_zone_name" != "" ]; do
+ _zone_name="$(echo "$_zone_name" | sed 's/[^.]*\.*//')"
+ echo "$_zone_list" | while read -r id name; do
+ if _startswith "$_zone_name." "$name"; then
+ echo "$id"
+ fi
+ done
+ done | _head_n 1
+}
+
+_dns_openstack_find_zone() {
+ if ! _zone_list="$(openstack zone list -c id -c name -f value)"; then
+ _err "Can't list zones. Check your OpenStack credentials"
+ return 1
+ fi
+ _debug _zone_list "$_zone_list"
+
+ if ! _zone_id="$(_dns_openstack_get_root "$fulldomain" "$_zone_list")"; then
+ _err "Can't find a matching zone. Check your OpenStack credentials"
+ return 1
+ fi
+ _debug _zone_id "$_zone_id"
+}
+
+_dns_openstack_get_records() {
+ if ! _records=$(openstack recordset show -c records -f value "$_zone_id" "$fulldomain."); then
+ _err "Failed to get records"
+ return 1
+ fi
+ return 0
+}
+
+_dns_openstack_get_recordset() {
+ if ! _recordset_id=$(openstack recordset list -c id -f value --name "$fulldomain." "$_zone_id"); then
+ _err "Failed to get recordset"
+ return 1
+ fi
+ return 0
+}
+
+_dns_openstack_check_setup() {
+ if ! _exists openstack; then
+ _err "OpenStack client not found"
+ return 1
+ fi
+}
+
+_dns_openstack_credentials() {
+ _debug "Check OpenStack credentials"
+
+ # If we have OS_AUTH_URL already set in the environment, then assume we want
+ # to use those, otherwise use stored credentials
+ if [ -n "$OS_AUTH_URL" ]; then
+ _debug "OS_AUTH_URL env var found, using environment"
+ else
+ _debug "OS_AUTH_URL not found, loading stored credentials"
+ OS_AUTH_URL="${OS_AUTH_URL:-$(_readaccountconf_mutable OS_AUTH_URL)}"
+ OS_IDENTITY_API_VERSION="${OS_IDENTITY_API_VERSION:-$(_readaccountconf_mutable OS_IDENTITY_API_VERSION)}"
+ OS_AUTH_TYPE="${OS_AUTH_TYPE:-$(_readaccountconf_mutable OS_AUTH_TYPE)}"
+ OS_APPLICATION_CREDENTIAL_ID="${OS_APPLICATION_CREDENTIAL_ID:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID)}"
+ OS_APPLICATION_CREDENTIAL_SECRET="${OS_APPLICATION_CREDENTIAL_SECRET:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET)}"
+ OS_USERNAME="${OS_USERNAME:-$(_readaccountconf_mutable OS_USERNAME)}"
+ OS_PASSWORD="${OS_PASSWORD:-$(_readaccountconf_mutable OS_PASSWORD)}"
+ OS_PROJECT_NAME="${OS_PROJECT_NAME:-$(_readaccountconf_mutable OS_PROJECT_NAME)}"
+ OS_PROJECT_ID="${OS_PROJECT_ID:-$(_readaccountconf_mutable OS_PROJECT_ID)}"
+ OS_USER_DOMAIN_NAME="${OS_USER_DOMAIN_NAME:-$(_readaccountconf_mutable OS_USER_DOMAIN_NAME)}"
+ OS_USER_DOMAIN_ID="${OS_USER_DOMAIN_ID:-$(_readaccountconf_mutable OS_USER_DOMAIN_ID)}"
+ OS_PROJECT_DOMAIN_NAME="${OS_PROJECT_DOMAIN_NAME:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_NAME)}"
+ OS_PROJECT_DOMAIN_ID="${OS_PROJECT_DOMAIN_ID:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_ID)}"
+ fi
+
+ # Check each var and either save or clear it depending on whether its set.
+ # The helps us clear out old vars in the case where a user may want
+ # to switch between password and app creds
+ _debug "OS_AUTH_URL" "$OS_AUTH_URL"
+ if [ -n "$OS_AUTH_URL" ]; then
+ export OS_AUTH_URL
+ _saveaccountconf_mutable OS_AUTH_URL "$OS_AUTH_URL"
+ else
+ unset OS_AUTH_URL
+ _clearaccountconf SAVED_OS_AUTH_URL
+ fi
+
+ _debug "OS_IDENTITY_API_VERSION" "$OS_IDENTITY_API_VERSION"
+ if [ -n "$OS_IDENTITY_API_VERSION" ]; then
+ export OS_IDENTITY_API_VERSION
+ _saveaccountconf_mutable OS_IDENTITY_API_VERSION "$OS_IDENTITY_API_VERSION"
+ else
+ unset OS_IDENTITY_API_VERSION
+ _clearaccountconf SAVED_OS_IDENTITY_API_VERSION
+ fi
+
+ _debug "OS_AUTH_TYPE" "$OS_AUTH_TYPE"
+ if [ -n "$OS_AUTH_TYPE" ]; then
+ export OS_AUTH_TYPE
+ _saveaccountconf_mutable OS_AUTH_TYPE "$OS_AUTH_TYPE"
+ else
+ unset OS_AUTH_TYPE
+ _clearaccountconf SAVED_OS_AUTH_TYPE
+ fi
+
+ _debug "OS_APPLICATION_CREDENTIAL_ID" "$OS_APPLICATION_CREDENTIAL_ID"
+ if [ -n "$OS_APPLICATION_CREDENTIAL_ID" ]; then
+ export OS_APPLICATION_CREDENTIAL_ID
+ _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID "$OS_APPLICATION_CREDENTIAL_ID"
+ else
+ unset OS_APPLICATION_CREDENTIAL_ID
+ _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_ID
+ fi
+
+ _secure_debug "OS_APPLICATION_CREDENTIAL_SECRET" "$OS_APPLICATION_CREDENTIAL_SECRET"
+ if [ -n "$OS_APPLICATION_CREDENTIAL_SECRET" ]; then
+ export OS_APPLICATION_CREDENTIAL_SECRET
+ _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET "$OS_APPLICATION_CREDENTIAL_SECRET"
+ else
+ unset OS_APPLICATION_CREDENTIAL_SECRET
+ _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_SECRET
+ fi
+
+ _debug "OS_USERNAME" "$OS_USERNAME"
+ if [ -n "$OS_USERNAME" ]; then
+ export OS_USERNAME
+ _saveaccountconf_mutable OS_USERNAME "$OS_USERNAME"
+ else
+ unset OS_USERNAME
+ _clearaccountconf SAVED_OS_USERNAME
+ fi
+
+ _secure_debug "OS_PASSWORD" "$OS_PASSWORD"
+ if [ -n "$OS_PASSWORD" ]; then
+ export OS_PASSWORD
+ _saveaccountconf_mutable OS_PASSWORD "$OS_PASSWORD"
+ else
+ unset OS_PASSWORD
+ _clearaccountconf SAVED_OS_PASSWORD
+ fi
+
+ _debug "OS_PROJECT_NAME" "$OS_PROJECT_NAME"
+ if [ -n "$OS_PROJECT_NAME" ]; then
+ export OS_PROJECT_NAME
+ _saveaccountconf_mutable OS_PROJECT_NAME "$OS_PROJECT_NAME"
+ else
+ unset OS_PROJECT_NAME
+ _clearaccountconf SAVED_OS_PROJECT_NAME
+ fi
+
+ _debug "OS_PROJECT_ID" "$OS_PROJECT_ID"
+ if [ -n "$OS_PROJECT_ID" ]; then
+ export OS_PROJECT_ID
+ _saveaccountconf_mutable OS_PROJECT_ID "$OS_PROJECT_ID"
+ else
+ unset OS_PROJECT_ID
+ _clearaccountconf SAVED_OS_PROJECT_ID
+ fi
+
+ _debug "OS_USER_DOMAIN_NAME" "$OS_USER_DOMAIN_NAME"
+ if [ -n "$OS_USER_DOMAIN_NAME" ]; then
+ export OS_USER_DOMAIN_NAME
+ _saveaccountconf_mutable OS_USER_DOMAIN_NAME "$OS_USER_DOMAIN_NAME"
+ else
+ unset OS_USER_DOMAIN_NAME
+ _clearaccountconf SAVED_OS_USER_DOMAIN_NAME
+ fi
+
+ _debug "OS_USER_DOMAIN_ID" "$OS_USER_DOMAIN_ID"
+ if [ -n "$OS_USER_DOMAIN_ID" ]; then
+ export OS_USER_DOMAIN_ID
+ _saveaccountconf_mutable OS_USER_DOMAIN_ID "$OS_USER_DOMAIN_ID"
+ else
+ unset OS_USER_DOMAIN_ID
+ _clearaccountconf SAVED_OS_USER_DOMAIN_ID
+ fi
+
+ _debug "OS_PROJECT_DOMAIN_NAME" "$OS_PROJECT_DOMAIN_NAME"
+ if [ -n "$OS_PROJECT_DOMAIN_NAME" ]; then
+ export OS_PROJECT_DOMAIN_NAME
+ _saveaccountconf_mutable OS_PROJECT_DOMAIN_NAME "$OS_PROJECT_DOMAIN_NAME"
+ else
+ unset OS_PROJECT_DOMAIN_NAME
+ _clearaccountconf SAVED_OS_PROJECT_DOMAIN_NAME
+ fi
+
+ _debug "OS_PROJECT_DOMAIN_ID" "$OS_PROJECT_DOMAIN_ID"
+ if [ -n "$OS_PROJECT_DOMAIN_ID" ]; then
+ export OS_PROJECT_DOMAIN_ID
+ _saveaccountconf_mutable OS_PROJECT_DOMAIN_ID "$OS_PROJECT_DOMAIN_ID"
+ else
+ unset OS_PROJECT_DOMAIN_ID
+ _clearaccountconf SAVED_OS_PROJECT_DOMAIN_ID
+ fi
+
+ if [ "$OS_AUTH_TYPE" = "v3applicationcredential" ]; then
+ # Application Credential auth
+ if [ -z "$OS_APPLICATION_CREDENTIAL_ID" ] || [ -z "$OS_APPLICATION_CREDENTIAL_SECRET" ]; then
+ _err "When using OpenStack application credentials, OS_APPLICATION_CREDENTIAL_ID"
+ _err "and OS_APPLICATION_CREDENTIAL_SECRET must be set."
+ _err "Please check your credentials and try again."
+ return 1
+ fi
+ else
+ # Password auth
+ if [ -z "$OS_USERNAME" ] || [ -z "$OS_PASSWORD" ]; then
+ _err "OpenStack username or password not found."
+ _err "Please check your credentials and try again."
+ return 1
+ fi
+
+ if [ -z "$OS_PROJECT_NAME" ] && [ -z "$OS_PROJECT_ID" ]; then
+ _err "When using password authentication, OS_PROJECT_NAME or"
+ _err "OS_PROJECT_ID must be set."
+ _err "Please check your credentials and try again."
+ return 1
+ fi
+ fi
+
+ return 0
+}
diff --git a/dnsapi/dns_opnsense.sh b/dnsapi/dns_opnsense.sh
index 069f6c32..eb95902f 100755
--- a/dnsapi/dns_opnsense.sh
+++ b/dnsapi/dns_opnsense.sh
@@ -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 "\"[^\"]*\":{\"enabled\":\"1\",\"type\":{\"master\":{\"value\":\"master\",\"selected\":1},\"slave\":{\"value\":\"slave\",\"selected\":0}},\"masterip\":{\"[^\"]*\":{[^}]*}},\"transferkeyalgo\":{[^{]*{[^{]*{[^{]*{[^{]*{[^{]*{[^{]*{[^{]*{[^}]*}},\"transferkey\":\"[^\"]*\"(,\"allownotifyslave\":{\"\":{[^}]*}},|,)\"domainname\":\"${h}\"" | cut -d ':' -f 1 | 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 7c18d009..e65babbd 100755
--- a/dnsapi/dns_ovh.sh
+++ b/dnsapi/dns_ovh.sh
@@ -41,40 +41,40 @@ _ovh_get_api() {
case "${_ogaep}" in
- ovh-eu | ovheu)
- printf "%s" $OVH_EU
- return
- ;;
- ovh-ca | ovhca)
- printf "%s" $OVH_CA
- return
- ;;
- kimsufi-eu | kimsufieu)
- printf "%s" $KSF_EU
- return
- ;;
- kimsufi-ca | kimsufica)
- printf "%s" $KSF_CA
- return
- ;;
- soyoustart-eu | soyoustarteu)
- printf "%s" $SYS_EU
- return
- ;;
- soyoustart-ca | soyoustartca)
- printf "%s" $SYS_CA
- return
- ;;
- runabove-ca | runaboveca)
- printf "%s" $RAV_CA
- return
- ;;
+ ovh-eu | ovheu)
+ printf "%s" $OVH_EU
+ return
+ ;;
+ ovh-ca | ovhca)
+ printf "%s" $OVH_CA
+ return
+ ;;
+ kimsufi-eu | kimsufieu)
+ printf "%s" $KSF_EU
+ return
+ ;;
+ kimsufi-ca | kimsufica)
+ printf "%s" $KSF_CA
+ return
+ ;;
+ soyoustart-eu | soyoustarteu)
+ printf "%s" $SYS_EU
+ return
+ ;;
+ soyoustart-ca | soyoustartca)
+ printf "%s" $SYS_CA
+ return
+ ;;
+ runabove-ca | runaboveca)
+ printf "%s" $RAV_CA
+ return
+ ;;
- *)
+ *)
- _err "Unknown parameter : $1"
- return 1
- ;;
+ _err "Unknown parameter : $1"
+ return 1
+ ;;
esac
}
@@ -248,7 +248,7 @@ _ovh_authentication() {
# _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)
@@ -261,7 +261,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 fe18bef4..f5986827 100644
--- a/dnsapi/dns_pleskxml.sh
+++ b/dnsapi/dns_pleskxml.sh
@@ -136,11 +136,12 @@ dns_pleskxml_rm() {
# Reduce output to one line per DNS record, filtered for TXT records with a record ID only (which they should all have)
# Also strip out spaces between tags, redundant and group tags and any tags
- reclist="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok ' \
- | sed 's# \{1,\}<\([a-zA-Z]\)#<\1#g;s#\{0,1\}data>##g;s#<[a-z][^/<>]*/>##g' \
- | grep "${root_domain_id} " \
- | grep '[0-9]\{1,\} ' \
- | grep 'TXT '
+ reclist="$(
+ _api_response_split "$pleskxml_prettyprint_result" 'result' 'ok ' |
+ sed 's# \{1,\}<\([a-zA-Z]\)#<\1#g;s#\{0,1\}data>##g;s#<[a-z][^/<>]*/>##g' |
+ grep "${root_domain_id} " |
+ grep '[0-9]\{1,\} ' |
+ grep 'TXT '
)"
if [ -z "$reclist" ]; then
@@ -151,10 +152,11 @@ dns_pleskxml_rm() {
_debug "Got list of DNS TXT records for root domain '$root_domain_name':"
_debug "$reclist"
- recid="$(_value "$reclist" \
- | grep "${fulldomain}. " \
- | grep "${txtvalue} " \
- | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/'
+ recid="$(
+ _value "$reclist" |
+ grep "${fulldomain}. " |
+ grep "${txtvalue} " |
+ sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/'
)"
if ! _value "$recid" | grep '^[0-9]\{1,\}$' >/dev/null; then
@@ -220,11 +222,11 @@ _countdots() {
# Last line could change to instead, with suitable escaping of ['"/$],
# if future Plesk XML API changes ever require extended regex
_api_response_split() {
- printf '%s' "$1" \
- | sed 's/^ +//;s/ +$//' \
- | tr -d '\n\r' \
- | sed "s/<\/\{0,1\}$2>/${NEWLINE}/g" \
- | grep "$3"
+ printf '%s' "$1" |
+ sed 's/^ +//;s/ +$//' |
+ tr -d '\n\r' |
+ sed "s/<\/\{0,1\}$2>/${NEWLINE}/g" |
+ grep "$3"
}
#################### Private functions below (DNS functions) ##################################
@@ -261,14 +263,15 @@ _call_api() {
elif [ "$statuslines_count_okay" -ne "$statuslines_count_total" ]; then
# We have some status lines that aren't "ok". Any available details are in API response fields "status" "errcode" and "errtext"
- # Workaround for basic regex:
+ # Workaround for basic regex:
# - filter output to keep only lines like this: "SPACEStext SPACES" (shouldn't be necessary with prettyprint but guarantees subsequent code is ok)
# - then edit the 3 "useful" error tokens individually and remove closing tags on all lines
# - then filter again to remove all lines not edited (which will be the lines not starting A-Z)
- errtext="$(_value "$pleskxml_prettyprint_result" \
- | grep '^ *<[a-z]\{1,\}>[^<]*<\/[a-z]\{1,\}> *$' \
- | sed 's/^ */Status: /;s/^ */Error code: /;s/^ */Error text: /;s/<\/.*$//' \
- | grep '^[A-Z]'
+ errtext="$(
+ _value "$pleskxml_prettyprint_result" |
+ grep '^ *<[a-z]\{1,\}>[^<]*<\/[a-z]\{1,\}> *$' |
+ sed 's/^ */Status: /;s/^ */Error code: /;s/^ */Error text: /;s/<\/.*$//' |
+ grep '^[A-Z]'
)"
fi
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_regru.sh b/dnsapi/dns_regru.sh
index b5729fda..2a1ebaa5 100644
--- a/dnsapi/dns_regru.sh
+++ b/dnsapi/dns_regru.sh
@@ -33,8 +33,11 @@ dns_regru_add() {
fi
_debug _domain "$_domain"
+ _subdomain=$(echo "$fulldomain" | sed -r "s/.$_domain//")
+ _debug _subdomain "$_subdomain"
+
_info "Adding TXT record to ${fulldomain}"
- _regru_rest POST "zone/add_txt" "input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22_acme-challenge%22,%22text%22:%22${txtvalue}%22,%22output_content_type%22:%22plain%22}&input_format=json"
+ _regru_rest POST "zone/add_txt" "input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22${_subdomain}%22,%22text%22:%22${txtvalue}%22,%22output_content_type%22:%22plain%22}&input_format=json"
if ! _contains "${response}" 'error'; then
return 0
@@ -64,8 +67,11 @@ dns_regru_rm() {
fi
_debug _domain "$_domain"
+ _subdomain=$(echo "$fulldomain" | sed -r "s/.$_domain//")
+ _debug _subdomain "$_subdomain"
+
_info "Deleting resource record $fulldomain"
- _regru_rest POST "zone/remove_record" "input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22_acme-challenge%22,%22content%22:%22${txtvalue}%22,%22record_type%22:%22TXT%22,%22output_content_type%22:%22plain%22}&input_format=json"
+ _regru_rest POST "zone/remove_record" "input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22${_subdomain}%22,%22content%22:%22${txtvalue}%22,%22record_type%22:%22TXT%22,%22output_content_type%22:%22plain%22}&input_format=json"
if ! _contains "${response}" 'error'; then
return 0
@@ -86,12 +92,13 @@ _get_root() {
domains_list=$(echo "${response}" | grep dname | sed -r "s/.*dname=\"([^\"]+)\".*/\\1/g")
for ITEM in ${domains_list}; do
+ IDN_ITEM="$(_idn "${ITEM}")"
case "${domain}" in
- *${ITEM}*)
- _domain=${ITEM}
- _debug _domain "${_domain}"
- return 0
- ;;
+ *${IDN_ITEM}*)
+ _domain=${IDN_ITEM}
+ _debug _domain "${_domain}"
+ return 0
+ ;;
esac
done
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_servercow.sh b/dnsapi/dns_servercow.sh
index e73d85b0..f70a2294 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..437e5e5c
--- /dev/null
+++ b/dnsapi/dns_simply.sh
@@ -0,0 +1,263 @@
+#!/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/1/[ACCOUNTNAME]/[APIKEY]"
+SIMPLY_Api_Default="https://api.simply.com/1"
+
+#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"
+
+ export _H1="Content-Type: application/json"
+
+ if [ "$m" != "GET" ]; then
+ response="$(_post "$data" "$SIMPLY_Api/$SIMPLY_AccountName/$SIMPLY_ApiKey/$ep" "" "$m")"
+ else
+ response="$(_get "$SIMPLY_Api/$SIMPLY_AccountName/$SIMPLY_ApiKey/$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_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_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_vultr.sh b/dnsapi/dns_vultr.sh
index c7b52e84..84857966 100644
--- a/dnsapi/dns_vultr.sh
+++ b/dnsapi/dns_vultr.sh
@@ -33,7 +33,7 @@ dns_vultr_add() {
_debug 'Getting txt records'
_vultr_rest GET "dns/records?domain=$_domain"
- 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
@@ -73,12 +73,12 @@ dns_vultr_rm() {
_debug 'Getting txt records'
_vultr_rest GET "dns/records?domain=$_domain"
- 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 'RECORDID' | cut -d : -f 2)"
_debug _record_id "$_record_id"
if [ "$_record_id" ]; then
_info "Successfully retrieved the record id for ACME challenge."
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..fd124754
--- /dev/null
+++ b/dnsapi/dns_world4you.sh
@@ -0,0 +1,204 @@
+#!/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="$1"
+ 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 3 <"$HTTP_HEADER")" '302'; then
+ res=$(_get "$WORLD4YOU_API/$paketnr/dns")
+ if _contains "$res" "successfully"; then
+ return 0
+ else
+ msg=$(echo "$res" | tr '\n' '\t' | sed 's/.*[^\t]*\t *\([^\t]*\)\t.*/\1/')
+ if _contains "$msg" '^<\!DOCTYPE html>'; then
+ msg='Unknown error'
+ fi
+ _err "Unable to add record: $msg"
+ if _contains "$msg" '^<\!DOCTYPE html>'; then
+ echo "$ret" >'error-01.html'
+ echo "$res" >'error-02.html'
+ _err "View error-01.html and error-02.html for debugging"
+ fi
+ return 1
+ fi
+ else
+ _err "$(_head_n 3 <"$HTTP_HEADER")"
+ _err "View $HTTP_HEADER for debugging"
+ return 1
+ fi
+}
+
+# Usage: dns_world4you_rm
+dns_world4you_rm() {
+ fqdn="$1"
+ value="$2"
+ _info "Using world4you to remove 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"
+
+ form=$(_get "$WORLD4YOU_API/$paketnr/dns")
+ formiddp=$(echo "$form" | grep 'DeleteDnsRecordForm\[uniqueFormIdDP\]' | sed 's/^.*name="DeleteDnsRecordForm\[uniqueFormIdDP\]" value="\([^"]*\)".*$/\1/')
+ form_token=$(echo "$form" | grep 'DeleteDnsRecordForm\[_token\]' | sed 's/^.*name="DeleteDnsRecordForm\[_token\]" value="\([^"]*\)".*$/\1/')
+ if [ -z "$formiddp" ]; then
+ _err "Unable to parse form"
+ return 3
+ fi
+
+ recordid=$(printf "TXT:%s.:\"%s\"" "$fqdn" "$value" | _base64)
+ _debug recordid "$recordid"
+
+ _resethttp
+ export ACME_HTTP_NO_REDIRECTS=1
+ body="DeleteDnsRecordForm[recordId]=$recordid&DeleteDnsRecordForm[uniqueFormIdDP]=$formiddp&DeleteDnsRecordForm[_token]=$form_token"
+ _info "Removing record..."
+ ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/dns/record/delete" '' POST 'application/x-www-form-urlencoded')
+ _resethttp
+
+ if _contains "$(_head_n 3 <"$HTTP_HEADER")" '302'; then
+ res=$(_get "$WORLD4YOU_API/$paketnr/dns")
+ if _contains "$res" "successfully"; then
+ return 0
+ else
+ msg=$(echo "$res" | tr '\n' '\t' | sed 's/.*[^\t]*\t *\([^\t]*\)\t.*/\1/')
+ if _contains "$msg" '^<\!DOCTYPE html>'; then
+ msg='Unknown error'
+ fi
+ _err "Unable to remove record: $msg"
+ if _contains "$msg" '^<\!DOCTYPE html>'; then
+ echo "$ret" >'error-01.html'
+ echo "$res" >'error-02.html'
+ _err "View error-01.html and error-02.html for debugging"
+ fi
+ return 1
+ fi
+ else
+ _err "$(_head_n 3 <"$HTTP_HEADER")"
+ _err "View $HTTP_HEADER for debugging"
+ return 1
+ fi
+}
+
+################ Private functions ################
+
+# Usage: _login
+_login() {
+ WORLD4YOU_USERNAME="${WORLD4YOU_USERNAME:-$(_readaccountconf_mutable WORLD4YOU_USERNAME)}"
+ WORLD4YOU_PASSWORD="${WORLD4YOU_PASSWORD:-$(_readaccountconf_mutable WORLD4YOU_PASSWORD)}"
+
+ if [ -z "$WORLD4YOU_USERNAME" ] || [ -z "$WORLD4YOU_PASSWORD" ]; then
+ WORLD4YOU_USERNAME=""
+ WORLD4YOU_PASSWORD=""
+ _err "You didn't specify world4you username and password yet."
+ _err "Usage: export WORLD4YOU_USERNAME="
+ _err "Usage: export WORLD4YOU_PASSWORD="
+ return 1
+ fi
+
+ _saveaccountconf_mutable WORLD4YOU_USERNAME "$WORLD4YOU_USERNAME"
+ _saveaccountconf_mutable WORLD4YOU_PASSWORD "$WORLD4YOU_PASSWORD"
+
+ _info "Logging in..."
+
+ username="$WORLD4YOU_USERNAME"
+ password="$WORLD4YOU_PASSWORD"
+ csrf_token=$(_get "$WORLD4YOU_API/login" | grep '_csrf_token' | sed 's/^.* ]*value=\"\([^"]*\)\".*$/\1/')
+ sessid=$(grep 'W4YSESSID' <"$HTTP_HEADER" | sed 's/^.*W4YSESSID=\([^;]*\);.*$/\1/')
+
+ export _H1="Cookie: W4YSESSID=$sessid"
+ export _H2="X-Requested-With: XMLHttpRequest"
+ body="_username=$username&_password=$password&_csrf_token=$csrf_token"
+ ret=$(_post "$body" "$WORLD4YOU_API/login" '' POST 'application/x-www-form-urlencoded')
+ unset _H2
+ _debug ret "$ret"
+ if _contains "$ret" "\"success\":true"; then
+ _info "Successfully logged in"
+ sessid=$(grep 'W4YSESSID' <"$HTTP_HEADER" | sed 's/^.*W4YSESSID=\([^;]*\);.*$/\1/')
+ else
+ _err "Unable to log in: $(echo "$ret" | sed 's/^.*"message":"\([^\"]*\)".*$/\1/')"
+ return 1
+ fi
+}
+
+# Usage _get_paketnr