- # anchor. And finally extract the domain ID.
+ # anchor. And finally extract the domain ID.
if [ -n "$data_id" ]; then
printf "%s" "$data_id"
return 0
diff --git a/dnsapi/dns_gandi_livedns.sh b/dnsapi/dns_gandi_livedns.sh
index cdda4775..87119521 100644
--- a/dnsapi/dns_gandi_livedns.sh
+++ b/dnsapi/dns_gandi_livedns.sh
@@ -69,9 +69,9 @@ dns_gandi_livedns_rm() {
_gandi_livedns_rest PUT \
"domains/$_domain/records/$_sub_domain/TXT" \
- "{\"rrset_ttl\": 300, \"rrset_values\": $_new_rrset_values}" \
- && _contains "$response" '{"message": "DNS Record Created"}' \
- && _info "Removing record $(__green "success")"
+ "{\"rrset_ttl\": 300, \"rrset_values\": $_new_rrset_values}" &&
+ _contains "$response" '{"message": "DNS Record Created"}' &&
+ _info "Removing record $(__green "success")"
}
#################### Private functions below ##################################
@@ -125,9 +125,9 @@ _dns_gandi_append_record() {
fi
_debug new_rrset_values "$_rrset_values"
_gandi_livedns_rest PUT "domains/$_domain/records/$sub_domain/TXT" \
- "{\"rrset_ttl\": 300, \"rrset_values\": $_rrset_values}" \
- && _contains "$response" '{"message": "DNS Record Created"}' \
- && _info "Adding record $(__green "success")"
+ "{\"rrset_ttl\": 300, \"rrset_values\": $_rrset_values}" &&
+ _contains "$response" '{"message": "DNS Record Created"}' &&
+ _info "Adding record $(__green "success")"
}
_dns_gandi_existing_rrset_values() {
@@ -145,8 +145,8 @@ _dns_gandi_existing_rrset_values() {
return 1
fi
_debug "Already has TXT record."
- _rrset_values=$(echo "$response" | _egrep_o 'rrset_values.*\[.*\]' \
- | _egrep_o '\[".*\"]')
+ _rrset_values=$(echo "$response" | _egrep_o 'rrset_values.*\[.*\]' |
+ _egrep_o '\[".*\"]')
return 0
}
diff --git a/dnsapi/dns_gcloud.sh b/dnsapi/dns_gcloud.sh
index ebbeecf2..03060a8c 100755
--- a/dnsapi/dns_gcloud.sh
+++ b/dnsapi/dns_gcloud.sh
@@ -78,8 +78,8 @@ _dns_gcloud_execute_tr() {
for i in $(seq 1 120); do
if gcloud dns record-sets changes list \
--zone="$managedZone" \
- --filter='status != done' \
- | grep -q '^.*'; then
+ --filter='status != done' |
+ grep -q '^.*'; then
_info "_dns_gcloud_execute_tr: waiting for transaction to be comitted ($i/120)..."
sleep 5
else
@@ -131,17 +131,17 @@ _dns_gcloud_find_zone() {
filter="$filter$part. "
part="$(echo "$part" | sed 's/[^.]*\.*//')"
done
- filter="$filter)"
+ filter="$filter) AND visibility=public"
_debug filter "$filter"
# List domains and find the zone with the deepest sub-domain (in case of some levels of delegation)
if ! match=$(gcloud dns managed-zones list \
--format="value(name, dnsName)" \
- --filter="$filter" \
- | while read -r dnsName name; do
+ --filter="$filter" |
+ while read -r dnsName name; do
printf "%s\t%s\t%s\n" "$(echo "$name" | awk -F"." '{print NF-1}')" "$dnsName" "$name"
- done \
- | sort -n -r | _head_n 1 | cut -f2,3 | grep '^.*'); then
+ done |
+ sort -n -r | _head_n 1 | cut -f2,3 | grep '^.*'); then
_err "_dns_gcloud_find_zone: Can't find a matching managed zone! Perhaps wrong project or gcloud credentials?"
return 1
fi
diff --git a/dnsapi/dns_gd.sh b/dnsapi/dns_gd.sh
index 7cf47386..7f8efca9 100755
--- a/dnsapi/dns_gd.sh
+++ b/dnsapi/dns_gd.sh
@@ -91,7 +91,7 @@ dns_gd_rm() {
fi
if ! _contains "$response" "$txtvalue"; then
- _info "The record is not existing, skip"
+ _info "The record does not exist, skip"
return 0
fi
diff --git a/dnsapi/dns_gdnsdk.sh b/dnsapi/dns_gdnsdk.sh
index 8c4962c0..90842b25 100755
--- a/dnsapi/dns_gdnsdk.sh
+++ b/dnsapi/dns_gdnsdk.sh
@@ -157,9 +157,18 @@ _successful_update() {
}
_findentry() {
+ #args $1: fulldomain, $2: txtvalue
#returns id of dns entry, if it exists
_myget "action=dns_primary_changeDNSsetup&user_domain=$_domain"
- _id=$(echo "$_result" | _egrep_o "| $1 | \s*$2 | [^?]*[^&]*&id=[^&]*" | sed 's/^.*=//')
+ _debug3 "_result: $_result"
+
+ _tmp_result=$(echo "$_result" | tr -d '\n\r' | _egrep_o "$1 | \s*$2 | [^?]*[^&]*&id=[^&]*")
+ _debug _tmp_result "$_tmp_result"
+ if [ -z "${_tmp_result:-}" ]; then
+ _debug "The variable is _tmp_result is not supposed to be empty, there may be something wrong with the script"
+ fi
+
+ _id=$(echo "$_tmp_result" | sed 's/^.*=//')
if [ -n "$_id" ]; then
_debug "Entry found with _id=$_id"
return 0
diff --git a/dnsapi/dns_he.sh b/dnsapi/dns_he.sh
index caa4d2c4..ef09fa0a 100755
--- a/dnsapi/dns_he.sh
+++ b/dnsapi/dns_he.sh
@@ -24,7 +24,7 @@ dns_he_add() {
if [ -z "$HE_Username" ] || [ -z "$HE_Password" ]; then
HE_Username=
HE_Password=
- _err "No auth details provided. Please set user credentials using the \$HE_Username and \$HE_Password envoronment variables."
+ _err "No auth details provided. Please set user credentials using the \$HE_Username and \$HE_Password environment variables."
return 1
fi
_saveaccountconf_mutable HE_Username "$HE_Username"
@@ -101,8 +101,8 @@ dns_he_rm() {
body="$body&hosted_dns_editzone=1"
body="$body&hosted_dns_delrecord=1"
body="$body&hosted_dns_delconfirm=delete"
- _post "$body" "https://dns.he.net/" \
- | grep '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
new file mode 100644
index 00000000..911d4a35
--- /dev/null
+++ b/dnsapi/dns_hetzner.sh
@@ -0,0 +1,252 @@
+#!/usr/bin/env sh
+
+#
+#HETZNER_Token="sdfsdfsdfljlbjkljlkjsdfoiwje"
+#
+
+HETZNER_Api="https://dns.hetzner.com/api/v1"
+
+######## Public functions #####################
+
+# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Used to add txt record
+# Ref: https://dns.hetzner.com/api-docs/
+dns_hetzner_add() {
+ full_domain=$1
+ txt_value=$2
+
+ HETZNER_Token="${HETZNER_Token:-$(_readaccountconf_mutable HETZNER_Token)}"
+
+ if [ -z "$HETZNER_Token" ]; then
+ HETZNER_Token=""
+ _err "You didn't specify a Hetzner api token."
+ _err "You can get yours from here https://dns.hetzner.com/settings/api-token."
+ return 1
+ fi
+
+ #save the api key and email to the account conf file.
+ _saveaccountconf_mutable HETZNER_Token "$HETZNER_Token"
+
+ _debug "First detect the root zone"
+
+ if ! _get_root "$full_domain"; then
+ _err "Invalid domain"
+ return 1
+ fi
+ _debug _domain_id "$_domain_id"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _debug "Getting TXT records"
+ if ! _find_record "$_sub_domain" "$txt_value"; then
+ return 1
+ fi
+
+ if [ -z "$_record_id" ]; then
+ _info "Adding record"
+ if _hetzner_rest POST "records" "{\"zone_id\":\"${HETZNER_Zone_ID}\",\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txt_value\",\"ttl\":120}"; then
+ if _contains "$response" "$txt_value"; then
+ _info "Record added, OK"
+ _sleep 2
+ return 0
+ fi
+ fi
+ _err "Add txt record error${_response_error}"
+ return 1
+ else
+ _info "Found record id: $_record_id."
+ _info "Record found, do nothing."
+ return 0
+ # we could modify a record, if the names for txt records for *.example.com and example.com would be not the same
+ #if _hetzner_rest PUT "records/${_record_id}" "{\"zone_id\":\"${HETZNER_Zone_ID}\",\"type\":\"TXT\",\"name\":\"$full_domain\",\"value\":\"$txt_value\",\"ttl\":120}"; then
+ # if _contains "$response" "$txt_value"; then
+ # _info "Modified, OK"
+ # return 0
+ # fi
+ #fi
+ #_err "Add txt record error (modify)."
+ #return 1
+ fi
+}
+
+# Usage: full_domain txt_value
+# Used to remove the txt record after validation
+dns_hetzner_rm() {
+ full_domain=$1
+ txt_value=$2
+
+ HETZNER_Token="${HETZNER_Token:-$(_readaccountconf_mutable HETZNER_Token)}"
+
+ _debug "First detect the root zone"
+ if ! _get_root "$full_domain"; then
+ _err "Invalid domain"
+ return 1
+ fi
+ _debug _domain_id "$_domain_id"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _debug "Getting TXT records"
+ if ! _find_record "$_sub_domain" "$txt_value"; then
+ return 1
+ fi
+
+ if [ -z "$_record_id" ]; then
+ _info "Remove not needed. Record not found."
+ else
+ if ! _hetzner_rest DELETE "records/$_record_id"; then
+ _err "Delete record error${_response_error}"
+ return 1
+ fi
+ _sleep 2
+ _info "Record deleted"
+ fi
+}
+
+#################### Private functions below ##################################
+#returns
+# _record_id=a8d58f22d6931bf830eaa0ec6464bf81 if found; or 1 if error
+_find_record() {
+ unset _record_id
+ _record_name=$1
+ _record_value=$2
+
+ if [ -z "$_record_value" ]; then
+ _record_value='[^"]*'
+ fi
+
+ _debug "Getting all records"
+ _hetzner_rest GET "records?zone_id=${_domain_id}"
+
+ if _response_has_error; then
+ _err "Error${_response_error}"
+ return 1
+ else
+ _record_id=$(
+ 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 \"
+ break
+ fi
+ done
+ )
+ fi
+}
+
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_get_root() {
+ domain=$1
+ i=1
+ p=1
+
+ domain_without_acme=$(echo "$domain" | cut -d . -f 2-)
+ domain_param_name=$(echo "HETZNER_Zone_ID_for_${domain_without_acme}" | sed 's/[\.\-]/_/g')
+
+ _debug "Reading zone_id for '$domain_without_acme' from config..."
+ HETZNER_Zone_ID=$(_readdomainconf "$domain_param_name")
+ if [ "$HETZNER_Zone_ID" ]; then
+ _debug "Found, using: $HETZNER_Zone_ID"
+ if ! _hetzner_rest GET "zones/${HETZNER_Zone_ID}"; then
+ _debug "Zone with id '$HETZNER_Zone_ID' does not exist."
+ _cleardomainconf "$domain_param_name"
+ unset HETZNER_Zone_ID
+ else
+ if _contains "$response" "\"id\":\"$HETZNER_Zone_ID\""; then
+ _domain=$(printf "%s\n" "$response" | _egrep_o '"name":"[^"]*"' | cut -d : -f 2 | tr -d \" | head -n 1)
+ if [ "$_domain" ]; then
+ _cut_length=$((${#domain} - ${#_domain} - 1))
+ _sub_domain=$(printf "%s" "$domain" | cut -c "1-$_cut_length")
+ _domain_id="$HETZNER_Zone_ID"
+ return 0
+ else
+ return 1
+ fi
+ else
+ return 1
+ fi
+ fi
+ fi
+
+ _debug "Trying to get zone id by domain name for '$domain_without_acme'."
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+ _debug h "$h"
+
+ _hetzner_rest GET "zones?name=$h"
+
+ if _contains "$response" "\"name\":\"$h\"" || _contains "$response" '"total_entries":1'; then
+ _domain_id=$(echo "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
+ if [ "$_domain_id" ]; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain=$h
+ HETZNER_Zone_ID=$_domain_id
+ _savedomainconf "$domain_param_name" "$HETZNER_Zone_ID"
+ return 0
+ fi
+ return 1
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+#returns
+# _response_error
+_response_has_error() {
+ unset _response_error
+
+ err_part="$(echo "$response" | _egrep_o '"error":{[^}]*}')"
+
+ if [ -n "$err_part" ]; then
+ err_code=$(echo "$err_part" | _egrep_o '"code":[0-9]+' | cut -d : -f 2)
+ err_message=$(echo "$err_part" | _egrep_o '"message":"[^"]+"' | cut -d : -f 2 | tr -d \")
+
+ if [ -n "$err_code" ] && [ -n "$err_message" ]; then
+ _response_error=" - message: ${err_message}, code: ${err_code}"
+ return 0
+ fi
+ fi
+
+ return 1
+}
+
+#returns
+# response
+_hetzner_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+
+ key_trimmed=$(echo "$HETZNER_Token" | tr -d \")
+
+ export _H1="Content-TType: application/json"
+ export _H2="Auth-API-Token: $key_trimmed"
+
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$HETZNER_Api/$ep" "" "$m")"
+ else
+ response="$(_get "$HETZNER_Api/$ep")"
+ fi
+
+ if [ "$?" != "0" ] || _response_has_error; then
+ _debug "Error$_response_error"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_hexonet.sh b/dnsapi/dns_hexonet.sh
index f1503118..525efe73 100755
--- a/dnsapi/dns_hexonet.sh
+++ b/dnsapi/dns_hexonet.sh
@@ -42,7 +42,7 @@ dns_hexonet_add() {
_debug _domain "$_domain"
_debug "Getting txt records"
- _hexonet_rest "&command=QueryDNSZoneRRList&dnszone=${h}.&RRTYPE=TXT"
+ _hexonet_rest "command=QueryDNSZoneRRList&dnszone=${h}.&RRTYPE=TXT"
if ! _contains "$response" "CODE=200"; then
_err "Error"
@@ -88,7 +88,7 @@ dns_hexonet_rm() {
_debug _domain "$_domain"
_debug "Getting txt records"
- _hexonet_rest "&command=QueryDNSZoneRRList&dnszone=${h}.&RRTYPE=TXT&RR=${txtvalue}"
+ _hexonet_rest "command=QueryDNSZoneRRList&dnszone=${h}.&RRTYPE=TXT&RR=${_sub_domain}%20IN%20TXT%20\"${txtvalue}\""
if ! _contains "$response" "CODE=200"; then
_err "Error"
@@ -100,7 +100,7 @@ dns_hexonet_rm() {
if [ "$count" = "0" ]; then
_info "Don't need to remove."
else
- if ! _hexonet_rest "&command=UpdateDNSZone&dnszone=${_domain}.&delrr0='${_sub_domain}%20IN%20TXT%20\"${txtvalue}\""; then
+ if ! _hexonet_rest "command=UpdateDNSZone&dnszone=${_domain}.&delrr0=${_sub_domain}%20IN%20TXT%20\"${txtvalue}\""; then
_err "Delete record error."
return 1
fi
@@ -126,7 +126,7 @@ _get_root() {
return 1
fi
- if ! _hexonet_rest "&command=QueryDNSZoneRRList&dnszone=${h}."; then
+ if ! _hexonet_rest "command=QueryDNSZoneRRList&dnszone=${h}."; then
return 1
fi
diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh
index f4590cf8..ba789da9 100755
--- a/dnsapi/dns_inwx.sh
+++ b/dnsapi/dns_inwx.sh
@@ -34,6 +34,10 @@ dns_inwx_add() {
_saveaccountconf_mutable INWX_Password "$INWX_Password"
_saveaccountconf_mutable INWX_Shared_Secret "$INWX_Shared_Secret"
+ if ! _inwx_login; then
+ return 1
+ fi
+
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
@@ -55,6 +59,7 @@ dns_inwx_rm() {
INWX_User="${INWX_User:-$(_readaccountconf_mutable INWX_User)}"
INWX_Password="${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}"
+ INWX_Shared_Secret="${INWX_Shared_Secret:-$(_readaccountconf_mutable INWX_Shared_Secret)}"
if [ -z "$INWX_User" ] || [ -z "$INWX_Password" ]; then
INWX_User=""
INWX_Password=""
@@ -63,9 +68,9 @@ dns_inwx_rm() {
return 1
fi
- #save the api key and email to the account conf file.
- _saveaccountconf_mutable INWX_User "$INWX_User"
- _saveaccountconf_mutable INWX_Password "$INWX_Password"
+ if ! _inwx_login; then
+ return 1
+ fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
@@ -126,8 +131,42 @@ dns_inwx_rm() {
#################### Private functions below ##################################
+_inwx_check_cookie() {
+ INWX_Cookie="${INWX_Cookie:-$(_readaccountconf_mutable INWX_Cookie)}"
+ if [ -z "$INWX_Cookie" ]; then
+ _debug "No cached cookie found"
+ return 1
+ fi
+ _H1="$INWX_Cookie"
+ export _H1
+
+ xml_content=$(printf '
+
+ account.info
+ ')
+
+ response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
+
+ if _contains "$response" "code1000"; then
+ _debug "Cached cookie still valid"
+ return 0
+ fi
+
+ _debug "Cached cookie no longer valid"
+ _H1=""
+ export _H1
+ INWX_Cookie=""
+ _saveaccountconf_mutable INWX_Cookie "$INWX_Cookie"
+ return 1
+}
+
_inwx_login() {
+ if _inwx_check_cookie; then
+ _debug "Already logged in"
+ return 0
+ fi
+
xml_content=$(printf '
account.login
@@ -151,17 +190,25 @@ _inwx_login() {
- ' $INWX_User $INWX_Password)
+ ' "$INWX_User" "$INWX_Password")
response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
- _H1=$(printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')")
+
+ INWX_Cookie=$(printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')")
+ _H1=$INWX_Cookie
export _H1
+ export INWX_Cookie
+ _saveaccountconf_mutable INWX_Cookie "$INWX_Cookie"
+
+ if ! _contains "$response" "code1000"; then
+ _err "INWX API: Authentication error (username/password correct?)"
+ return 1
+ fi
#https://github.com/inwx/php-client/blob/master/INWX/Domrobot.php#L71
- if _contains "$response" "code1000" \
- && _contains "$response" "tfaGOOGLE-AUTH"; then
+ if _contains "$response" "tfaGOOGLE-AUTH"; then
if [ -z "$INWX_Shared_Secret" ]; then
- _err "Mobile TAN detected."
+ _err "INWX API: Mobile TAN detected."
_err "Please define a shared secret."
return 1
fi
@@ -194,6 +241,11 @@ _inwx_login() {
' "$tan")
response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
+
+ if ! _contains "$response" "code1000"; then
+ _err "INWX API: Mobile TAN not correct."
+ return 1
+ fi
fi
}
@@ -206,11 +258,23 @@ _get_root() {
i=2
p=1
- _inwx_login
-
xml_content='
nameserver.list
+
+
+
+
+
+ pagelimit
+
+ 9999
+
+
+
+
+
+
'
response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
diff --git a/dnsapi/dns_ispconfig.sh b/dnsapi/dns_ispconfig.sh
index 2d8d6b0a..bd1e0391 100755
--- a/dnsapi/dns_ispconfig.sh
+++ b/dnsapi/dns_ispconfig.sh
@@ -95,29 +95,29 @@ _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." ;;
+ '' | *[!0-9]*)
+ _err "Client ID is not numeric."
+ return 1
+ ;;
+ *) _info "Retrieved Client ID." ;;
esac
zoneFound=""
zoneEnd=""
@@ -135,11 +135,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 +153,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
new file mode 100644
index 00000000..78399a1d
--- /dev/null
+++ b/dnsapi/dns_joker.sh
@@ -0,0 +1,129 @@
+#!/usr/bin/env sh
+
+# Joker.com API for acme.sh
+#
+# This script adds the necessary TXT record to a domain in Joker.com.
+#
+# You must activate Dynamic DNS in Joker.com DNS configuration first.
+# Username and password below refer to Dynamic DNS authentication,
+# not your Joker.com login credentials.
+# See: https://joker.com/faq/content/11/427/en/what-is-dynamic-dns-dyndns.html
+#
+# NOTE: This script does not support wildcard certificates, because
+# Joker.com API does not support adding two TXT records with the same
+# subdomain. Adding the second record will overwrite the first one.
+# See: https://joker.com/faq/content/6/496/en/let_s-encrypt-support.html
+# "... this request will replace all TXT records for the specified
+# label by the provided content"
+#
+# Author: aattww (https://github.com/aattww/)
+#
+# Report bugs to https://github.com/acmesh-official/acme.sh/issues/2840
+#
+# JOKER_USERNAME="xxxx"
+# JOKER_PASSWORD="xxxx"
+
+JOKER_API="https://svc.joker.com/nic/replace"
+
+######## Public functions #####################
+
+#Usage: dns_joker_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_joker_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ JOKER_USERNAME="${JOKER_USERNAME:-$(_readaccountconf_mutable JOKER_USERNAME)}"
+ JOKER_PASSWORD="${JOKER_PASSWORD:-$(_readaccountconf_mutable JOKER_PASSWORD)}"
+
+ if [ -z "$JOKER_USERNAME" ] || [ -z "$JOKER_PASSWORD" ]; then
+ _err "No Joker.com username and password specified."
+ return 1
+ fi
+
+ _saveaccountconf_mutable JOKER_USERNAME "$JOKER_USERNAME"
+ _saveaccountconf_mutable JOKER_PASSWORD "$JOKER_PASSWORD"
+
+ if ! _get_root "$fulldomain"; then
+ _err "Invalid domain"
+ return 1
+ fi
+
+ _info "Adding TXT record"
+ if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT&value=$txtvalue"; then
+ if _startswith "$response" "OK"; then
+ _info "Added, OK"
+ return 0
+ fi
+ fi
+ _err "Error adding TXT record."
+ return 1
+}
+
+#fulldomain txtvalue
+dns_joker_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ JOKER_USERNAME="${JOKER_USERNAME:-$(_readaccountconf_mutable JOKER_USERNAME)}"
+ JOKER_PASSWORD="${JOKER_PASSWORD:-$(_readaccountconf_mutable JOKER_PASSWORD)}"
+
+ if ! _get_root "$fulldomain"; then
+ _err "Invalid domain"
+ return 1
+ fi
+
+ _info "Removing TXT record"
+ # TXT record is removed by setting its value to empty.
+ if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT&value="; then
+ if _startswith "$response" "OK"; then
+ _info "Removed, OK"
+ return 0
+ fi
+ fi
+ _err "Error removing TXT record."
+ return 1
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ fulldomain=$1
+ i=1
+ while true; do
+ h=$(printf "%s" "$fulldomain" | cut -d . -f $i-100)
+ _debug h "$h"
+ if [ -z "$h" ]; then
+ return 1
+ 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.
+ 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\$//")"
+ _domain=$h
+ return 0
+ fi
+ fi
+
+ i=$(_math "$i" + 1)
+ done
+
+ _debug "Root domain not found"
+ return 1
+}
+
+_joker_rest() {
+ data="$1"
+ _debug data "$data"
+
+ if ! response="$(_post "$data" "$JOKER_API" "" "POST")"; then
+ _err "Error POSTing"
+ return 1
+ fi
+ _debug response "$response"
+ return 0
+}
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_kas.sh b/dnsapi/dns_kas.sh
new file mode 100755
index 00000000..2cb0b439
--- /dev/null
+++ b/dnsapi/dns_kas.sh
@@ -0,0 +1,168 @@
+#!/usr/bin/env sh
+########################################################################
+# All-inkl Kasserver hook script for acme.sh
+#
+# Environment variables:
+#
+# - $KAS_Login (Kasserver API login name)
+# - $KAS_Authtype (Kasserver API auth type. Default: sha1)
+# - $KAS_Authdata (Kasserver API auth data.)
+#
+# Author: Martin Kammerlander, Phlegx Systems OG
+# Updated by: Marc-Oliver Lange
+# Credits: Inspired by dns_he.sh. Thanks a lot man!
+# Git repo: https://github.com/phlegx/acme.sh
+# TODO: Better Error handling
+########################################################################
+KAS_Api="https://kasapi.kasserver.com/dokumentation/formular.php"
+######## Public functions #####################
+dns_kas_add() {
+ _fulldomain=$1
+ _txtvalue=$2
+ _info "Using DNS-01 All-inkl/Kasserver hook"
+ _info "Adding $_fulldomain DNS TXT entry on All-inkl/Kasserver"
+ _info "Check and Save Props"
+ _check_and_save
+ _info "Checking Zone and Record_Name"
+ _get_zone_and_record_name "$_fulldomain"
+ _info "Getting Record ID"
+ _get_record_id
+
+ _info "Creating TXT DNS record"
+ params="?kas_login=$KAS_Login"
+ params="$params&kas_auth_type=$KAS_Authtype"
+ params="$params&kas_auth_data=$KAS_Authdata"
+ params="$params&var1=record_name"
+ params="$params&wert1=$_record_name"
+ params="$params&var2=record_type"
+ params="$params&wert2=TXT"
+ params="$params&var3=record_data"
+ params="$params&wert3=$_txtvalue"
+ params="$params&var4=record_aux"
+ params="$params&wert4=0"
+ params="$params&kas_action=add_dns_settings"
+ params="$params&var5=zone_host"
+ params="$params&wert5=$_zone"
+ _debug2 "Wait for 10 seconds by default before calling KAS API."
+ _sleep 10
+ response="$(_get "$KAS_Api$params")"
+ _debug2 "response" "$response"
+
+ if ! _contains "$response" "TRUE"; then
+ _err "An unkown error occurred, please check manually."
+ return 1
+ fi
+ return 0
+}
+
+dns_kas_rm() {
+ _fulldomain=$1
+ _txtvalue=$2
+ _info "Using DNS-01 All-inkl/Kasserver hook"
+ _info "Cleaning up after All-inkl/Kasserver hook"
+ _info "Removing $_fulldomain DNS TXT entry on All-inkl/Kasserver"
+
+ _info "Check and Save Props"
+ _check_and_save
+ _info "Checking Zone and Record_Name"
+ _get_zone_and_record_name "$_fulldomain"
+ _info "Getting Record ID"
+ _get_record_id
+
+ # If there is a record_id, delete the entry
+ if [ -n "$_record_id" ]; then
+ params="?kas_login=$KAS_Login"
+ params="$params&kas_auth_type=$KAS_Authtype"
+ params="$params&kas_auth_data=$KAS_Authdata"
+ params="$params&kas_action=delete_dns_settings"
+
+ for i in $_record_id; do
+ params2="$params&var1=record_id"
+ params2="$params2&wert1=$i"
+ _debug2 "Wait for 10 seconds by default before calling KAS API."
+ _sleep 10
+ response="$(_get "$KAS_Api$params2")"
+ _debug2 "response" "$response"
+ if ! _contains "$response" "TRUE"; then
+ _err "Either the txt record is not found or another error occurred, please check manually."
+ return 1
+ fi
+ done
+ else # Cannot delete or unkown error
+ _err "No record_id found that can be deleted. Please check manually."
+ return 1
+ fi
+ return 0
+}
+
+########################## PRIVATE FUNCTIONS ###########################
+
+# Checks for the ENV variables and saves them
+_check_and_save() {
+ KAS_Login="${KAS_Login:-$(_readaccountconf_mutable KAS_Login)}"
+ KAS_Authtype="${KAS_Authtype:-$(_readaccountconf_mutable KAS_Authtype)}"
+ KAS_Authdata="${KAS_Authdata:-$(_readaccountconf_mutable KAS_Authdata)}"
+
+ if [ -z "$KAS_Login" ] || [ -z "$KAS_Authtype" ] || [ -z "$KAS_Authdata" ]; then
+ KAS_Login=
+ KAS_Authtype=
+ KAS_Authdata=
+ _err "No auth details provided. Please set user credentials using the \$KAS_Login, \$KAS_Authtype, and \$KAS_Authdata environment variables."
+ return 1
+ fi
+ _saveaccountconf_mutable KAS_Login "$KAS_Login"
+ _saveaccountconf_mutable KAS_Authtype "$KAS_Authtype"
+ _saveaccountconf_mutable KAS_Authdata "$KAS_Authdata"
+ return 0
+}
+
+# Gets back the base domain/zone and record name.
+# See: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide
+_get_zone_and_record_name() {
+ params="?kas_login=$KAS_Login"
+ params="?kas_login=$KAS_Login"
+ params="$params&kas_auth_type=$KAS_Authtype"
+ params="$params&kas_auth_data=$KAS_Authdata"
+ params="$params&kas_action=get_domains"
+
+ _debug2 "Wait for 10 seconds by default before calling KAS API."
+ _sleep 10
+ response="$(_get "$KAS_Api$params")"
+ _debug2 "response" "$response"
+ _zonen="$(echo "$response" | tr -d "\n\r" | tr -d " " | tr '[]' '<>' | sed "s/=>Array/\n=> Array/g" | tr ' ' '\n' | grep "domain_name" | tr '<' '\n' | grep "domain_name" | sed "s/domain_name>=>//g")"
+ _domain="$1"
+ _temp_domain="$(echo "$1" | sed 's/\.$//')"
+ _rootzone="$_domain"
+ for i in $_zonen; do
+ l1=${#_rootzone}
+ l2=${#i}
+ if _endswith "$_domain" "$i" && [ "$l1" -ge "$l2" ]; then
+ _rootzone="$i"
+ fi
+ done
+ _zone="${_rootzone}."
+ _temp_record_name="$(echo "$_temp_domain" | sed "s/$_rootzone//g")"
+ _record_name="$(echo "$_temp_record_name" | sed 's/\.$//')"
+ _debug2 "Zone:" "$_zone"
+ _debug2 "Domain:" "$_domain"
+ _debug2 "Record_Name:" "$_record_name"
+ return 0
+}
+
+# Retrieve the DNS record ID
+_get_record_id() {
+ params="?kas_login=$KAS_Login"
+ params="$params&kas_auth_type=$KAS_Authtype"
+ params="$params&kas_auth_data=$KAS_Authdata"
+ params="$params&kas_action=get_dns_settings"
+ params="$params&var1=zone_host"
+ params="$params&wert1=$_zone"
+
+ _debug2 "Wait for 10 seconds by default before calling KAS API."
+ _sleep 10
+ response="$(_get "$KAS_Api$params")"
+ _debug2 "response" "$response"
+ _record_id="$(echo "$response" | tr -d "\n\r" | tr -d " " | tr '[]' '<>' | sed "s/=>Array/\n=> Array/g" | tr ' ' '\n' | grep "=>$_record_name<" | grep '>TXT<' | tr '<' '\n' | grep record_id | sed "s/record_id>=>//g")"
+ _debug2 _record_id "$_record_id"
+ return 0
+}
diff --git a/dnsapi/dns_kinghost.sh b/dnsapi/dns_kinghost.sh
index 898ab286..6253c71d 100644
--- a/dnsapi/dns_kinghost.sh
+++ b/dnsapi/dns_kinghost.sh
@@ -37,7 +37,7 @@ dns_kinghost_add() {
_debug "Getting txt records"
_kinghost_rest GET "dns" "name=$fulldomain&content=$txtvalue"
- #This API call returns "status":"ok" if dns record does not exists
+ #This API call returns "status":"ok" if dns record does not exist
#We are creating a new txt record here, so we expect the "ok" status
if ! echo "$response" | grep '"status":"ok"' >/dev/null; then
_err "Error"
diff --git a/dnsapi/dns_leaseweb.sh b/dnsapi/dns_leaseweb.sh
new file mode 100644
index 00000000..a1d9e749
--- /dev/null
+++ b/dnsapi/dns_leaseweb.sh
@@ -0,0 +1,149 @@
+#!/usr/bin/env sh
+
+#Author: Rolph Haspers
+#Utilize leaseweb.com API to finish dns-01 verifications.
+#Requires a Leaseweb API Key (export LSW_Key="Your Key")
+#See http://developer.leaseweb.com for more information.
+######## Public functions #####################
+
+LSW_API="https://api.leaseweb.com/hosting/v2/domains/"
+
+#Usage: dns_leaseweb_add _acme-challenge.www.domain.com
+dns_leaseweb_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ LSW_Key="${LSW_Key:-$(_readaccountconf_mutable LSW_Key)}"
+ if [ -z "$LSW_Key" ]; then
+ LSW_Key=""
+ _err "You don't specify Leaseweb api key yet."
+ _err "Please create your key and try again."
+ return 1
+ fi
+
+ #save the api key to the account conf file.
+ _saveaccountconf_mutable LSW_Key "$LSW_Key"
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _root_domain "$_domain"
+ _debug _domain "$fulldomain"
+
+ if _lsw_api "POST" "$_domain" "$fulldomain" "$txtvalue"; then
+ if [ "$_code" = "201" ]; then
+ _info "Added, OK"
+ return 0
+ else
+ _err "Add txt record error, invalid code. Code: $_code"
+ return 1
+ fi
+ fi
+ _err "Add txt record error."
+
+ return 1
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_leaseweb_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ LSW_Key="${LSW_Key:-$(_readaccountconf_mutable LSW_Key)}"
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _root_domain "$_domain"
+ _debug _domain "$fulldomain"
+
+ if _lsw_api "DELETE" "$_domain" "$fulldomain" "$txtvalue"; then
+ if [ "$_code" = "204" ]; then
+ _info "Deleted, OK"
+ return 0
+ else
+ _err "Delete txt record error."
+ return 1
+ fi
+ fi
+ _err "Delete txt record error."
+
+ return 1
+}
+
+#################### Private functions below ##################################
+# _acme-challenge.www.domain.com
+# returns
+# _domain=domain.com
+_get_root() {
+ rdomain=$1
+ i="$(echo "$rdomain" | tr '.' ' ' | wc -w)"
+ i=$(_math "$i" - 1)
+
+ while true; do
+ h=$(printf "%s" "$rdomain" | cut -d . -f "$i"-100)
+ _debug h "$h"
+ if [ -z "$h" ]; then
+ return 1 #not valid domain
+ fi
+
+ #Check API if domain exists
+ if _lsw_api "GET" "$h"; then
+ if [ "$_code" = "200" ]; then
+ _domain="$h"
+ return 0
+ fi
+ fi
+ i=$(_math "$i" - 1)
+ if [ "$i" -lt 2 ]; then
+ return 1 #not found, no need to check _acme-challenge.sub.domain in leaseweb api.
+ fi
+ done
+
+ return 1
+}
+
+_lsw_api() {
+ cmd=$1
+ d=$2
+ fd=$3
+ tvalue=$4
+
+ # Construct the HTTP Authorization header
+ export _H2="Content-Type: application/json"
+ export _H1="X-Lsw-Auth: ${LSW_Key}"
+
+ if [ "$cmd" = "GET" ]; then
+ response="$(_get "$LSW_API/$d")"
+ _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+ _debug "http response code $_code"
+ _debug response "$response"
+ return 0
+ fi
+
+ if [ "$cmd" = "POST" ]; then
+ data="{\"name\": \"$fd.\",\"type\": \"TXT\",\"content\": [\"$tvalue\"],\"ttl\": 60}"
+ response="$(_post "$data" "$LSW_API/$d/resourceRecordSets" "$data" "POST")"
+ _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+ _debug "http response code $_code"
+ _debug response "$response"
+ return 0
+ fi
+
+ if [ "$cmd" = "DELETE" ]; then
+ response="$(_post "" "$LSW_API/$d/resourceRecordSets/$fd/TXT" "" "DELETE")"
+ _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+ _debug "http response code $_code"
+ _debug response "$response"
+ return 0
+ fi
+
+ return 1
+}
diff --git a/dnsapi/dns_lexicon.sh b/dnsapi/dns_lexicon.sh
index f6f54464..19702343 100755
--- a/dnsapi/dns_lexicon.sh
+++ b/dnsapi/dns_lexicon.sh
@@ -5,7 +5,7 @@
# https://github.com/AnalogJ/lexicon
lexicon_cmd="lexicon"
-wiki="https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api"
+wiki="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-lexicon-dns-api"
_lexicon_init() {
if ! _exists "$lexicon_cmd"; then
@@ -63,6 +63,16 @@ _lexicon_init() {
_saveaccountconf_mutable "$Lx_domaintoken" "$Lx_domaintoken_v"
eval export "$Lx_domaintoken"
fi
+
+ # shellcheck disable=SC2018,SC2019
+ Lx_api_key=$(echo LEXICON_"${PROVIDER}"_API_KEY | tr 'a-z' 'A-Z')
+ eval "$Lx_api_key=\${$Lx_api_key:-$(_readaccountconf_mutable "$Lx_api_key")}"
+ Lx_api_key_v=$(eval echo \$"$Lx_api_key")
+ _secure_debug "$Lx_api_key" "$Lx_api_key_v"
+ if [ "$Lx_api_key_v" ]; then
+ _saveaccountconf_mutable "$Lx_api_key" "$Lx_api_key_v"
+ eval export "$Lx_api_key"
+ fi
}
######## Public functions #####################
@@ -82,7 +92,7 @@ dns_lexicon_add() {
_savedomainconf LEXICON_OPTS "$LEXICON_OPTS"
# shellcheck disable=SC2086
- $lexicon_cmd "$PROVIDER" $LEXICON_OPTS create "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}"
+ $lexicon_cmd "$PROVIDER" $LEXICON_OPTS create "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}" --output QUIET
}
@@ -98,6 +108,6 @@ dns_lexicon_rm() {
domain=$(printf "%s" "$fulldomain" | cut -d . -f 2-999)
# shellcheck disable=SC2086
- $lexicon_cmd "$PROVIDER" $LEXICON_OPTS delete "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}"
+ $lexicon_cmd "$PROVIDER" $LEXICON_OPTS delete "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}" --output QUIET
}
diff --git a/dnsapi/dns_linode_v4.sh b/dnsapi/dns_linode_v4.sh
index c9a83c77..c2bebc57 100755
--- a/dnsapi/dns_linode_v4.sh
+++ b/dnsapi/dns_linode_v4.sh
@@ -31,11 +31,12 @@ dns_linode_v4_add() {
_payload="{
\"type\": \"TXT\",
\"name\": \"$_sub_domain\",
- \"target\": \"$txtvalue\"
+ \"target\": \"$txtvalue\",
+ \"ttl_sec\": 300
}"
if _rest POST "/$_domain_id/records" "$_payload" && [ -n "$response" ]; then
- _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1)
+ _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"id\": *[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1)
_debug _resource_id "$_resource_id"
if [ -z "$_resource_id" ]; then
@@ -73,9 +74,9 @@ dns_linode_v4_rm() {
if _rest GET "/$_domain_id/records" && [ -n "$response" ]; then
response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")"
- resource="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$_sub_domain\".*}")"
+ resource="$(echo "$response" | _egrep_o "\{.*\"name\": *\"$_sub_domain\".*}")"
if [ "$resource" ]; then
- _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
+ _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"id\": *[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
if [ "$_resource_id" ]; then
_debug _resource_id "$_resource_id"
@@ -138,9 +139,9 @@ _get_root() {
return 1
fi
- hostedzone="$(echo "$response" | _egrep_o "{.*\"domain\":\s*\"$h\".*}")"
+ hostedzone="$(echo "$response" | _egrep_o "\{.*\"domain\": *\"$h\".*}")"
if [ "$hostedzone" ]; then
- _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
+ _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\": *[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain=$h
diff --git a/dnsapi/dns_loopia.sh b/dnsapi/dns_loopia.sh
index 1316a274..7760b53e 100644
--- a/dnsapi/dns_loopia.sh
+++ b/dnsapi/dns_loopia.sh
@@ -217,7 +217,7 @@ _loopia_add_record() {
ttl
- 60
+ 300
rdata
diff --git a/dnsapi/dns_me.sh b/dnsapi/dns_me.sh
index 382eeedd..49007402 100644
--- a/dnsapi/dns_me.sh
+++ b/dnsapi/dns_me.sh
@@ -2,7 +2,7 @@
# bug reports to dev@1e.ca
-# ME_Key=qmlkdjflmkqdjf
+# ME_Key=qmlkdjflmkqdjf
# ME_Secret=qmsdlkqmlksdvnnpae
ME_Api=https://api.dnsmadeeasy.com/V2.0/dns/managed
@@ -114,7 +114,7 @@ _get_root() {
fi
if _contains "$response" "\"name\":\"$h\""; then
- _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | head -n 1 | cut -d : -f 2 | tr -d '}')
+ _domain_id=$(printf "%s\n" "$response" | sed 's/^{//; s/}$//; s/{.*}//' | sed -r 's/^.*"id":([0-9]+).*$/\1/')
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="$h"
diff --git a/dnsapi/dns_miab.sh b/dnsapi/dns_miab.sh
new file mode 100644
index 00000000..7e697704
--- /dev/null
+++ b/dnsapi/dns_miab.sh
@@ -0,0 +1,210 @@
+#!/usr/bin/env sh
+
+# Name: dns_miab.sh
+#
+# Authors:
+# Darven Dissek 2018
+# William Gertz 2019
+#
+# Thanks to Neil Pang and other developers here for code reused from acme.sh from DNS-01
+# used to communicate with the MailinaBox Custom DNS API
+# Report Bugs here:
+# https://github.com/billgertz/MIAB_dns_api (for dns_miab.sh)
+# https://github.com/acmesh-official/acme.sh (for acme.sh)
+#
+######## Public functions #####################
+
+#Usage: dns_miab_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_miab_add() {
+ fulldomain=$1
+ txtvalue=$2
+ _info "Using miab challange add"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ #retrieve MIAB environemt vars
+ if ! _retrieve_miab_env; then
+ return 1
+ fi
+
+ #check domain and seperate into doamin and host
+ if ! _get_root "$fulldomain"; then
+ _err "Cannot find any part of ${fulldomain} is hosted on ${MIAB_Server}"
+ return 1
+ fi
+
+ _debug2 _sub_domain "$_sub_domain"
+ _debug2 _domain "$_domain"
+
+ #add the challenge record
+ _api_path="custom/${fulldomain}/txt"
+ _miab_rest "$txtvalue" "$_api_path" "POST"
+
+ #check if result was good
+ if _contains "$response" "updated DNS"; then
+ _info "Successfully created the txt record"
+ return 0
+ else
+ _err "Error encountered during record add"
+ _err "$response"
+ return 1
+ fi
+}
+
+#Usage: dns_miab_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_miab_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _info "Using miab challage delete"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ #retrieve MIAB environemt vars
+ if ! _retrieve_miab_env; then
+ return 1
+ fi
+
+ #check domain and seperate into doamin and host
+ if ! _get_root "$fulldomain"; then
+ _err "Cannot find any part of ${fulldomain} is hosted on ${MIAB_Server}"
+ return 1
+ fi
+
+ _debug2 _sub_domain "$_sub_domain"
+ _debug2 _domain "$_domain"
+
+ #Remove the challenge record
+ _api_path="custom/${fulldomain}/txt"
+ _miab_rest "$txtvalue" "$_api_path" "DELETE"
+
+ #check if result was good
+ if _contains "$response" "updated DNS"; then
+ _info "Successfully removed the txt record"
+ return 0
+ else
+ _err "Error encountered during record remove"
+ _err "$response"
+ return 1
+ fi
+}
+
+#################### Private functions below ##################################
+#
+#Usage: _get_root _acme-challenge.www.domain.com
+#Returns:
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ _passed_domain=$1
+ _debug _passed_domain "$_passed_domain"
+ _i=2
+ _p=1
+
+ #get the zones hosed on MIAB server, must be a json stream
+ _miab_rest "" "zones" "GET"
+
+ if ! _is_json "$response"; then
+ _err "ERROR fetching domain list"
+ _err "$response"
+ return 1
+ fi
+
+ #cycle through the passed domain seperating out a test domain discarding
+ # the subdomain by marching thorugh the dots
+ while true; do
+ _test_domain=$(printf "%s" "$_passed_domain" | cut -d . -f ${_i}-100)
+ _debug _test_domain "$_test_domain"
+
+ if [ -z "$_test_domain" ]; then
+ return 1
+ fi
+
+ #report found if the test domain is in the json response and
+ # report the subdomain
+ if _contains "$response" "\"$_test_domain\""; then
+ _sub_domain=$(printf "%s" "$_passed_domain" | cut -d . -f 1-${_p})
+ _domain=${_test_domain}
+ return 0
+ fi
+
+ #cycle to the next dot in the passed domain
+ _p=${_i}
+ _i=$(_math "$_i" + 1)
+ done
+
+ return 1
+}
+
+#Usage: _retrieve_miab_env
+#Returns (from store or environment variables):
+# MIAB_Username
+# MIAB_Password
+# MIAB_Server
+#retrieve MIAB environment variables, report errors and quit if problems
+_retrieve_miab_env() {
+ MIAB_Username="${MIAB_Username:-$(_readaccountconf_mutable MIAB_Username)}"
+ MIAB_Password="${MIAB_Password:-$(_readaccountconf_mutable MIAB_Password)}"
+ MIAB_Server="${MIAB_Server:-$(_readaccountconf_mutable MIAB_Server)}"
+
+ #debug log the environmental variables
+ _debug MIAB_Username "$MIAB_Username"
+ _debug MIAB_Password "$MIAB_Password"
+ _debug MIAB_Server "$MIAB_Server"
+
+ #check if MIAB environemt vars set and quit if not
+ if [ -z "$MIAB_Username" ] || [ -z "$MIAB_Password" ] || [ -z "$MIAB_Server" ]; then
+ _err "You didn't specify one or more of MIAB_Username, MIAB_Password or MIAB_Server."
+ _err "Please check these environment variables and try again."
+ return 1
+ fi
+
+ #save the credentials to the account conf file.
+ _saveaccountconf_mutable MIAB_Username "$MIAB_Username"
+ _saveaccountconf_mutable MIAB_Password "$MIAB_Password"
+ _saveaccountconf_mutable MIAB_Server "$MIAB_Server"
+}
+
+#Useage: _miab_rest "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" "custom/_acme-challenge.www.domain.com/txt "POST"
+#Returns: "updated DNS: domain.com"
+#rest interface MIAB dns
+_miab_rest() {
+ _data="$1"
+ _api_path="$2"
+ _httpmethod="$3"
+
+ #encode username and password for basic authentication
+ _credentials="$(printf "%s" "$MIAB_Username:$MIAB_Password" | _base64)"
+ export _H1="Authorization: Basic $_credentials"
+ _url="https://${MIAB_Server}/admin/dns/${_api_path}"
+
+ _debug2 _data "$_data"
+ _debug _api_path "$_api_path"
+ _debug2 _url "$_url"
+ _debug2 _credentails "$_credentials"
+ _debug _httpmethod "$_httpmethod"
+
+ if [ "$_httpmethod" = "GET" ]; then
+ response="$(_get "$_url")"
+ else
+ response="$(_post "$_data" "$_url" "" "$_httpmethod")"
+ fi
+
+ _retcode="$?"
+
+ if [ "$_retcode" != "0" ]; then
+ _err "MIAB REST authentication failed on $_httpmethod"
+ return 1
+ fi
+
+ _debug response "$response"
+ return 0
+}
+
+#Usage: _is_json "\[\n "mydomain.com"\n]"
+#Reurns "\[\n "mydomain.com"\n]"
+#returns the string if it begins and ends with square braces
+_is_json() {
+ _str="$(echo "$1" | _normalizeJson)"
+ echo "$_str" | grep '^\[.*\]$' >/dev/null 2>&1
+}
diff --git a/dnsapi/dns_misaka.sh b/dnsapi/dns_misaka.sh
new file mode 100755
index 00000000..36ba5cfd
--- /dev/null
+++ b/dnsapi/dns_misaka.sh
@@ -0,0 +1,159 @@
+#!/usr/bin/env sh
+
+# bug reports to support+acmesh@misaka.io
+# based on dns_nsone.sh by dev@1e.ca
+
+#
+#Misaka_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
+#
+
+Misaka_Api="https://dnsapi.misaka.io/dns"
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_misaka_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ if [ -z "$Misaka_Key" ]; then
+ Misaka_Key=""
+ _err "You didn't specify misaka.io dns api key yet."
+ _err "Please create you key and try again."
+ return 1
+ fi
+
+ #save the api key and email to the account conf file.
+ _saveaccountconf Misaka_Key "$Misaka_Key"
+
+ _debug "checking root zone [$fulldomain]"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _debug "Getting txt records"
+ _misaka_rest GET "zones/${_domain}/recordsets?search=${_sub_domain}"
+
+ if ! _contains "$response" "\"results\":"; then
+ _err "Error"
+ return 1
+ fi
+
+ count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$_sub_domain\",[^{]*\"type\":\"TXT\"" | wc -l | tr -d " ")
+ _debug count "$count"
+ if [ "$count" = "0" ]; then
+ _info "Adding record"
+
+ 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"
+ return 0
+ else
+ _err "Add txt record error."
+ return 1
+ fi
+ fi
+ _err "Add txt record error."
+ else
+ _info "Updating record"
+
+ _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
+ return 0
+ fi
+ _err "Update error"
+ return 1
+ fi
+
+}
+
+#fulldomain
+dns_misaka_rm() {
+ fulldomain=$1
+ txtvalue=$2
+ _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"
+ _misaka_rest GET "zones/${_domain}/recordsets?search=${_sub_domain}"
+
+ count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$_sub_domain\",[^{]*\"type\":\"TXT\"" | wc -l | tr -d " ")
+ _debug count "$count"
+ if [ "$count" = "0" ]; then
+ _info "Don't need to remove."
+ else
+ if ! _misaka_rest DELETE "zones/${_domain}/recordsets/${_sub_domain}/TXT"; then
+ _err "Delete record error."
+ return 1
+ fi
+ _contains "$response" ""
+ 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 ! _misaka_rest GET "zones?limit=1000"; 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
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="$h"
+ return 0
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+_misaka_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+
+ export _H1="Content-Type: application/json"
+ export _H2="User-Agent: acme.sh/$VER misaka-dns-acmesh/20191213"
+ export _H3="Authorization: Token $Misaka_Key"
+
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$Misaka_Api/$ep" "" "$m")"
+ else
+ response="$(_get "$Misaka_Api/$ep")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_myapi.sh b/dnsapi/dns_myapi.sh
index 2451d193..7f3c5a86 100755
--- a/dnsapi/dns_myapi.sh
+++ b/dnsapi/dns_myapi.sh
@@ -7,11 +7,11 @@
#returns 0 means success, otherwise error.
#
#Author: Neilpang
-#Report Bugs here: https://github.com/Neilpang/acme.sh
+#Report Bugs here: https://github.com/acmesh-official/acme.sh
#
######## Public functions #####################
-# Please Read this guide first: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide
+# Please Read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_myapi_add() {
diff --git a/dnsapi/dns_namecheap.sh b/dnsapi/dns_namecheap.sh
index a82e12d7..2e389265 100755
--- a/dnsapi/dns_namecheap.sh
+++ b/dnsapi/dns_namecheap.sh
@@ -3,10 +3,10 @@
# Namecheap API
# https://www.namecheap.com/support/api/intro.aspx
#
-# Requires Namecheap API key set in
-#NAMECHEAP_API_KEY,
+# Requires Namecheap API key set in
+#NAMECHEAP_API_KEY,
#NAMECHEAP_USERNAME,
-#NAMECHEAP_SOURCEIP
+#NAMECHEAP_SOURCEIP
# Due to Namecheap's API limitation all the records of your domain will be read and re applied, make sure to have a backup of your records you could apply if any issue would arise.
######## Public functions #####################
diff --git a/dnsapi/dns_namesilo.sh b/dnsapi/dns_namesilo.sh
index ed6d0e08..0b87b7f7 100755
--- a/dnsapi/dns_namesilo.sh
+++ b/dnsapi/dns_namesilo.sh
@@ -110,7 +110,7 @@ _get_root() {
return 1
fi
- if _contains "$response" "$host"; then
+ if _contains "$response" "$host"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="$host"
return 0
diff --git a/dnsapi/dns_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
new file mode 100644
index 00000000..56170f87
--- /dev/null
+++ b/dnsapi/dns_nic.sh
@@ -0,0 +1,205 @@
+#!/usr/bin/env sh
+
+#
+#NIC_ClientID='0dc0xxxxxxxxxxxxxxxxxxxxxxxxce88'
+#NIC_ClientSecret='3LTtxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxnuW8'
+#NIC_Username="000000/NIC-D"
+#NIC_Password="xxxxxxx"
+
+NIC_Api="https://api.nic.ru"
+
+dns_nic_add() {
+ fulldomain="${1}"
+ txtvalue="${2}"
+
+ if ! _nic_get_authtoken save; then
+ _err "get NIC auth token failed"
+ 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"
+ _debug _service "$_service"
+
+ _info "Adding record"
+ if ! _nic_rest PUT "services/$_service/zones/$_domain/records" "$_sub_domainTXT$txtvalue"; then
+ _err "Add TXT record error"
+ return 1
+ fi
+
+ if ! _nic_rest POST "services/$_service/zones/$_domain/commit" ""; then
+ return 1
+ fi
+ _info "Added, OK"
+}
+
+dns_nic_rm() {
+ fulldomain="${1}"
+ txtvalue="${2}"
+
+ if ! _nic_get_authtoken; then
+ _err "get NIC auth token failed"
+ return 1
+ fi
+
+ if ! _get_root "$fulldomain"; then
+ _err "Invalid domain"
+ return 1
+ fi
+
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+ _debug _service "$_service"
+
+ if ! _nic_rest GET "services/$_service/zones/$_domain/records"; then
+ _err "Get records error"
+ return 1
+ fi
+
+ _domain_id=$(printf "%s" "$response" | grep "$_sub_domain" | grep -- "$txtvalue" | sed -r "s/.*"; then
+ error=$(printf "%s" "$response" | grep "error code" | sed -r "s/.*(.*)<\/error>/\1/g")
+ _err "Error: $error"
+ return 1
+ fi
+
+ if ! _contains "$response" "success"; then
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_njalla.sh b/dnsapi/dns_njalla.sh
new file mode 100644
index 00000000..e9243288
--- /dev/null
+++ b/dnsapi/dns_njalla.sh
@@ -0,0 +1,168 @@
+#!/usr/bin/env sh
+
+#
+#NJALLA_Token="sdfsdfsdfljlbjkljlkjsdfoiwje"
+
+NJALLA_Api="https://njal.la/api/1/"
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_njalla_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ NJALLA_Token="${NJALLA_Token:-$(_readaccountconf_mutable NJALLA_Token)}"
+
+ if [ "$NJALLA_Token" ]; then
+ _saveaccountconf_mutable NJALLA_Token "$NJALLA_Token"
+ else
+ NJALLA_Token=""
+ _err "You didn't specify a Njalla api token yet."
+ 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 _njalla_rest "{\"method\":\"add-record\",\"params\":{\"domain\":\"$_domain\",\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}}"; then
+ if _contains "$response" "$txtvalue"; then
+ _info "Added, OK"
+ return 0
+ else
+ _err "Add txt record error."
+ return 1
+ fi
+ fi
+ _err "Add txt record error."
+ return 1
+
+}
+
+#fulldomain txtvalue
+dns_njalla_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ NJALLA_Token="${NJALLA_Token:-$(_readaccountconf_mutable NJALLA_Token)}"
+
+ if [ "$NJALLA_Token" ]; then
+ _saveaccountconf_mutable NJALLA_Token "$NJALLA_Token"
+ else
+ NJALLA_Token=""
+ _err "You didn't specify a Njalla api token yet."
+ 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"
+
+ _debug "Getting records for domain"
+ if ! _njalla_rest "{\"method\":\"list-records\",\"params\":{\"domain\":\"${_domain}\"}}"; then
+ return 1
+ fi
+
+ if ! echo "$response" | tr -d " " | grep "\"id\":" >/dev/null; then
+ _err "Error: $response"
+ return 1
+ fi
+
+ records=$(echo "$response" | _egrep_o "\"records\":\s?\[(.*)\]\}" | _egrep_o "\[.*\]" | _egrep_o "\{[^\{\}]*\"id\":[^\{\}]*\}")
+ count=$(echo "$records" | wc -l)
+ _debug count "$count"
+
+ if [ "$count" = "0" ]; then
+ _info "Don't need to remove."
+ else
+ echo "$records" | while read -r record; do
+ record_name=$(echo "$record" | _egrep_o "\"name\":\s?\"[^\"]*\"" | cut -d : -f 2 | tr -d " " | tr -d \")
+ record_content=$(echo "$record" | _egrep_o "\"content\":\s?\"[^\"]*\"" | cut -d : -f 2 | tr -d " " | tr -d \")
+ record_id=$(echo "$record" | _egrep_o "\"id\":\s?[0-9]+" | cut -d : -f 2 | tr -d " " | tr -d \")
+ if [ "$_sub_domain" = "$record_name" ]; then
+ if [ "$txtvalue" = "$record_content" ]; then
+ _debug "record_id" "$record_id"
+ if ! _njalla_rest "{\"method\":\"remove-record\",\"params\":{\"domain\":\"${_domain}\",\"id\":${record_id}}}"; then
+ _err "Delete record error."
+ return 1
+ fi
+ echo "$response" | tr -d " " | grep "\"result\"" >/dev/null
+ fi
+ fi
+ done
+ 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=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 ! _njalla_rest "{\"method\":\"get-domain\",\"params\":{\"domain\":\"${h}\"}}"; then
+ return 1
+ fi
+
+ if _contains "$response" "\"$h\""; then
+ _domain_returned=$(echo "$response" | _egrep_o "\{\"name\": *\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ")
+ if [ "$_domain_returned" ]; 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
+}
+
+_njalla_rest() {
+ data="$1"
+
+ token_trimmed=$(echo "$NJALLA_Token" | tr -d '"')
+
+ export _H1="Content-Type: application/json"
+ export _H2="Accept: application/json"
+ export _H3="Authorization: Njalla $token_trimmed"
+
+ _debug data "$data"
+ response="$(_post "$data" "$NJALLA_Api" "" "POST")"
+
+ if [ "$?" != "0" ]; then
+ _err "error $data"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_nm.sh b/dnsapi/dns_nm.sh
new file mode 100644
index 00000000..4dfcc777
--- /dev/null
+++ b/dnsapi/dns_nm.sh
@@ -0,0 +1,88 @@
+#!/usr/bin/env sh
+
+########################################################################
+# https://namemaster.de hook script for acme.sh
+#
+# Environment variables:
+#
+# - $NM_user (your namemaster.de API username)
+# - $NM_sha256 (your namemaster.de API password_as_sha256hash)
+#
+# Author: Thilo Gass
+# Git repo: https://github.com/ThiloGa/acme.sh
+
+#-- dns_nm_add() - Add TXT record --------------------------------------
+# Usage: dns_nm_add _acme-challenge.subdomain.domain.com "XyZ123..."
+
+namemaster_api="https://namemaster.de/api/api.php"
+
+dns_nm_add() {
+ fulldomain=$1
+ txt_value=$2
+ _info "Using DNS-01 namemaster hook"
+
+ NM_user="${NM_user:-$(_readaccountconf_mutable NM_user)}"
+ NM_sha256="${NM_sha256:-$(_readaccountconf_mutable NM_sha256)}"
+ if [ -z "$NM_user" ] || [ -z "$NM_sha256" ]; then
+ NM_user=""
+ NM_sha256=""
+ _err "No auth details provided. Please set user credentials using the \$NM_user and \$NM_sha256 environment variables."
+ return 1
+ fi
+ #save the api user and sha256 password to the account conf file.
+ _debug "Save user and hash"
+ _saveaccountconf_mutable NM_user "$NM_user"
+ _saveaccountconf_mutable NM_sha256 "$NM_sha256"
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain" "$fulldomain"
+ return 1
+ fi
+
+ _info "die Zone lautet:" "$zone"
+
+ get="$namemaster_api?User=$NM_user&Password=$NM_sha256&Antwort=csv&Typ=ACME&zone=$zone&hostname=$fulldomain&TXT=$txt_value&Action=Auto&Lifetime=3600"
+
+ if ! erg="$(_get "$get")"; then
+ _err "error Adding $fulldomain TXT: $txt_value"
+ return 1
+ fi
+
+ if _contains "$erg" "Success"; then
+ _info "Success, TXT Added, OK"
+ else
+ _err "error Adding $fulldomain TXT: $txt_value erg: $erg"
+ return 1
+ fi
+
+ _debug "ok Auto $fulldomain TXT: $txt_value erg: $erg"
+ return 0
+}
+
+dns_nm_rm() {
+
+ fulldomain=$1
+ txtvalue=$2
+ _info "TXT enrty in $fulldomain is deleted automatically"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+}
+
+_get_root() {
+
+ domain=$1
+
+ get="$namemaster_api?User=$NM_user&Password=$NM_sha256&Typ=acme&hostname=$domain&Action=getzone&antwort=csv"
+
+ if ! zone="$(_get "$get")"; then
+ _err "error getting Zone"
+ return 1
+ else
+ if _contains "$zone" "hostname not found"; then
+ return 1
+ fi
+ fi
+
+}
diff --git a/dnsapi/dns_nsupdate.sh b/dnsapi/dns_nsupdate.sh
index dfb3672a..cd4b7140 100755
--- a/dnsapi/dns_nsupdate.sh
+++ b/dnsapi/dns_nsupdate.sh
@@ -27,7 +27,7 @@ dns_nsupdate_add() {
[ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_2" ] && nsdebug="-D"
if [ -z "${NSUPDATE_ZONE}" ]; then
nsupdate -k "${NSUPDATE_KEY}" $nsdebug < \"$subdomain.$maindomain\"")'")"
+ _dns_one_addrecord "CNAME" "$_sub_domain" "$subdomain.$maindomain"
- id=$(echo "$response" | sed -n "s/{\"result\":{\"data\":{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"$mysubdomain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"priority\":0,\"ttl\":600}}},\"metadata\":null}/\1/p")
+ _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
+ _info "$(__green "Txt record with the same value found. Skip adding.")"
+ return 0
+ fi
+
+ _dns_one_addrecord "TXT" "$subdomain" "$txtvalue"
if [ -z "$id" ]; then
- _err "Add txt record error."
+ _err "Add TXT record error."
return 1
else
- _info "Added, OK ($id)"
+ _info "$(__green "Added, OK ($id)")"
return 0
fi
-
}
dns_one_rm() {
@@ -73,36 +89,45 @@ dns_one_rm() {
return 1
fi
- mysubdomain=$_sub_domain
- mydomain=$_domain
- _debug mysubdomain "$mysubdomain"
- _debug mydomain "$mydomain"
+ subdomain="${_sub_domain}"
+ maindomain=${_domain}
- # get entries
- response="$(_get "https://www.one.com/admin/api/domains/$mydomain/dns/custom_records")"
- response="$(echo "$response" | _normalizeJson)"
- _debug response "$response"
+ useProxy=0
+ if [ "${_sub_domain}" = "_acme-challenge" ]; then
+ subdomain="proxy${_sub_domain}"
+ useProxy=1
+ fi
- id=$(printf -- "%s" "$response" | sed -n "s/.*{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"$mysubdomain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"priority\":0,\"ttl\":600}.*/\1/p")
+ _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"
if [ -z "$id" ]; then
_err "Txt record not found."
return 1
fi
# delete entry
- response="$(_post "$postdata" "https://www.one.com/admin/api/domains/$mydomain/dns/custom_records/$id" "" "DELETE" "application/json")"
- response="$(echo "$response" | _normalizeJson)"
- _debug response "$response"
-
- if [ "$response" = '{"result":null,"metadata":null}' ]; then
- _info "Removed, OK"
+ if _dns_one_delrecord "$id"; then
+ _info "$(__green Removed, OK)"
return 0
else
_err "Removing txt record error."
return 1
fi
-
}
#_acme-challenge.www.domain.com
@@ -138,6 +163,8 @@ _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
@@ -149,6 +176,7 @@ _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"
@@ -177,3 +205,75 @@ _dns_one_login() {
return 0
}
+
+_dns_one_getrecord() {
+ type="$1"
+ name="$2"
+ value="$3"
+ if [ -z "$type" ]; then
+ type="TXT"
+ fi
+ if [ -z "$name" ]; then
+ _err "Record name is empty."
+ return 1
+ fi
+
+ response="$(_get "https://www.one.com/admin/api/domains/$maindomain/dns/custom_records")"
+ response="$(echo "$response" | _normalizeJson)"
+ _debug response "$response"
+
+ if [ -z "${value}" ]; then
+ id=$(printf -- "%s" "$response" | sed -n "s/.*{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"${name}\",\"type\":\"${type}\",\"content\":\"[^\"]*\",\"priority\":0,\"ttl\":600}.*/\1/p")
+ response=$(printf -- "%s" "$response" | sed -n "s/.*{\"type\":\"dns_custom_records\",\"id\":\"[^\"]*\",\"attributes\":{\"prefix\":\"${name}\",\"type\":\"${type}\",\"content\":\"\([^\"]*\)\",\"priority\":0,\"ttl\":600}.*/\1/p")
+ else
+ id=$(printf -- "%s" "$response" | sed -n "s/.*{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"${name}\",\"type\":\"${type}\",\"content\":\"${value}\",\"priority\":0,\"ttl\":600}.*/\1/p")
+ fi
+ if [ -z "$id" ]; then
+ return 1
+ fi
+ return 0
+}
+
+_dns_one_addrecord() {
+ type="$1"
+ name="$2"
+ value="$3"
+ if [ -z "$type" ]; then
+ type="TXT"
+ fi
+ if [ -z "$name" ]; then
+ _err "Record name is empty."
+ return 1
+ fi
+
+ postdata="{\"type\":\"dns_custom_records\",\"attributes\":{\"priority\":0,\"ttl\":600,\"type\":\"${type}\",\"prefix\":\"${name}\",\"content\":\"${value}\"}}"
+ _debug postdata "$postdata"
+ response="$(_post "$postdata" "https://www.one.com/admin/api/domains/$maindomain/dns/custom_records" "" "POST" "application/json")"
+ response="$(echo "$response" | _normalizeJson)"
+ _debug response "$response"
+
+ id=$(echo "$response" | sed -n "s/{\"result\":{\"data\":{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"$subdomain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"priority\":0,\"ttl\":600}}},\"metadata\":null}/\1/p")
+
+ if [ -z "$id" ]; then
+ return 1
+ else
+ return 0
+ fi
+}
+
+_dns_one_delrecord() {
+ id="$1"
+ if [ -z "$id" ]; then
+ return 1
+ fi
+
+ response="$(_post "" "https://www.one.com/admin/api/domains/$maindomain/dns/custom_records/$id" "" "DELETE" "application/json")"
+ response="$(echo "$response" | _normalizeJson)"
+ _debug response "$response"
+
+ if [ "$response" = '{"result":null,"metadata":null}' ]; then
+ return 0
+ else
+ return 1
+ fi
+}
diff --git a/dnsapi/dns_openprovider.sh b/dnsapi/dns_openprovider.sh
index 1b1b760e..0a9e5ade 100755
--- a/dnsapi/dns_openprovider.sh
+++ b/dnsapi/dns_openprovider.sh
@@ -3,7 +3,7 @@
# This is the OpenProvider API wrapper for acme.sh
#
# Author: Sylvia van Os
-# Report Bugs here: https://github.com/Neilpang/acme.sh/issues/2104
+# Report Bugs here: https://github.com/acmesh-official/acme.sh/issues/2104
#
# export OPENPROVIDER_USER="username"
# export OPENPROVIDER_PASSWORDHASH="hashed_password"
@@ -59,16 +59,17 @@ dns_openprovider_add() {
break
fi
- items="$(echo "$items" | sed "s|${item}||")"
+ tmpitem="$(echo "$item" | sed 's/\*/\\*/g')"
+ items="$(echo "$items" | sed "s|${tmpitem}||")"
results_retrieved="$(_math "$results_retrieved" + 1)"
new_item="$(echo "$item" | sed -n 's/.*- .*\(\(.*\)\.'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(.*<\/type>\).*\(.*<\/value>\).*\(.*<\/prio>\).*\(.*<\/ttl>\)\).*<\/item>.*/
- \2<\/name>\3\4\5\6<\/item>/p')"
if [ -z "$new_item" ]; then
- # Base record
+ # Domain apex
new_item="$(echo "$item" | sed -n 's/.*
- .*\(\(.*\)'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(.*<\/type>\).*\(.*<\/value>\).*\(.*<\/prio>\).*\(.*<\/ttl>\)\).*<\/item>.*/
- \2<\/name>\3\4\5\6<\/item>/p')"
fi
- if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA)<\/type>.*")" ]; then
+ if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA|NS)<\/type>.*")" ]; then
_debug "not an allowed record type, skipping" "$new_item"
continue
fi
@@ -86,7 +87,7 @@ dns_openprovider_add() {
_debug "Creating acme record"
acme_record="$(echo "$fulldomain" | sed -e "s/.$_domain_name.$_domain_extension$//")"
- _openprovider_request "$(printf '%s%smaster%s
- %sTXT%s86400
' "$_domain_name" "$_domain_extension" "$existing_items" "$acme_record" "$txtvalue")"
+ _openprovider_request "$(printf '%s%smaster%s- %sTXT%s600
' "$_domain_name" "$_domain_extension" "$existing_items" "$acme_record" "$txtvalue")"
return 0
}
@@ -136,7 +137,8 @@ dns_openprovider_rm() {
break
fi
- items="$(echo "$items" | sed "s|${item}||")"
+ tmpitem="$(echo "$item" | sed 's/\*/\\*/g')"
+ items="$(echo "$items" | sed "s|${tmpitem}||")"
results_retrieved="$(_math "$results_retrieved" + 1)"
if ! echo "$item" | grep -v "$fulldomain"; then
@@ -147,11 +149,11 @@ dns_openprovider_rm() {
new_item="$(echo "$item" | sed -n 's/.*- .*\(\(.*\)\.'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(.*<\/type>\).*\(.*<\/value>\).*\(.*<\/prio>\).*\(.*<\/ttl>\)\).*<\/item>.*/
- \2<\/name>\3\4\5\6<\/item>/p')"
if [ -z "$new_item" ]; then
- # Base record
+ # domain apex
new_item="$(echo "$item" | sed -n 's/.*
- .*\(\(.*\)'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(.*<\/type>\).*\(.*<\/value>\).*\(.*<\/prio>\).*\(.*<\/ttl>\)\).*<\/item>.*/
- \2<\/name>\3\4\5\6<\/item>/p')"
fi
- if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA)<\/type>.*")" ]; then
+ if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA|NS)<\/type>.*")" ]; then
_debug "not an allowed record type, skipping" "$new_item"
continue
fi
@@ -205,7 +207,8 @@ _get_root() {
break
fi
- items="$(echo "$items" | sed "s|${item}||")"
+ tmpitem="$(echo "$item" | sed 's/\*/\\*/g')"
+ items="$(echo "$items" | sed "s|${tmpitem}||")"
results_retrieved="$(_math "$results_retrieved" + 1)"
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
new file mode 100755
index 00000000..069f6c32
--- /dev/null
+++ b/dnsapi/dns_opnsense.sh
@@ -0,0 +1,273 @@
+#!/usr/bin/env sh
+
+#OPNsense Bind API
+#https://docs.opnsense.org/development/api.html
+#
+#OPNs_Host="opnsense.example.com"
+#OPNs_Port="443"
+# optional, defaults to 443 if unset
+#OPNs_Key="qocfU9RSbt8vTIBcnW8bPqCrpfAHMDvj5OzadE7Str+rbjyCyk7u6yMrSCHtBXabgDDXx/dY0POUp7ZA"
+#OPNs_Token="pZEQ+3ce8dDlfBBdg3N8EpqpF5I1MhFqdxX06le6Gl8YzyQvYCfCzNaFX9O9+IOSyAs7X71fwdRiZ+Lv"
+#OPNs_Api_Insecure=0
+# optional, defaults to 0 if unset
+# Set 1 for insecure and 0 for secure -> difference is whether ssl cert is checked for validity (0) or whether it is just accepted (1)
+
+######## Public functions #####################
+#Usage: add _acme-challenge.www.domain.com "123456789ABCDEF0000000000000000000000000000000000000"
+#fulldomain
+#txtvalue
+OPNs_DefaultPort=443
+OPNs_DefaultApi_Insecure=0
+
+dns_opnsense_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _opns_check_auth || return 1
+
+ if ! set_record "$fulldomain" "$txtvalue"; then
+ return 1
+ fi
+
+ return 0
+}
+
+#fulldomain
+dns_opnsense_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _opns_check_auth || return 1
+
+ if ! rm_record "$fulldomain" "$txtvalue"; then
+ return 1
+ fi
+
+ return 0
+}
+
+set_record() {
+ fulldomain=$1
+ new_challenge=$2
+ _info "Adding record $fulldomain with challenge: $new_challenge"
+
+ _debug "Detect root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _domain "$_domain"
+ _debug _host "$_host"
+ _debug _domainid "$_domainid"
+ _return_str=""
+ _record_string=""
+ _build_record_string "$_domainid" "$_host" "$new_challenge"
+ _uuid=""
+ if _existingchallenge "$_domain" "$_host" "$new_challenge"; then
+ # Update
+ if _opns_rest "POST" "/record/setRecord/${_uuid}" "$_record_string"; then
+ _return_str="$response"
+ else
+ return 1
+ fi
+
+ else
+ #create
+ if _opns_rest "POST" "/record/addRecord" "$_record_string"; then
+ _return_str="$response"
+ else
+ return 1
+ fi
+ fi
+
+ if echo "$_return_str" | _egrep_o "\"result\":\"saved\"" >/dev/null; then
+ _opns_rest "POST" "/service/reconfigure" "{}"
+ _debug "Record created"
+ else
+ _err "Error creating record $_record_string"
+ return 1
+ fi
+
+ return 0
+}
+
+rm_record() {
+ fulldomain=$1
+ new_challenge="$2"
+ _info "Remove record $fulldomain with challenge: $new_challenge"
+
+ _debug "Detect root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _domain "$_domain"
+ _debug _host "$_host"
+ _debug _domainid "$_domainid"
+ _uuid=""
+ if _existingchallenge "$_domain" "$_host" "$new_challenge"; then
+ # Delete
+ if _opns_rest "POST" "/record/delRecord/${_uuid}" "\{\}"; then
+ if echo "$_return_str" | _egrep_o "\"result\":\"deleted\"" >/dev/null; then
+ _opns_rest "POST" "/service/reconfigure" "{}"
+ _debug "Record deleted"
+ else
+ _err "Error deleting record $_host from domain $fulldomain"
+ return 1
+ fi
+ else
+ _err "Error deleting record $_host from domain $fulldomain"
+ return 1
+ fi
+ else
+ _info "Record not found, nothing to remove"
+ fi
+
+ return 0
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _domainid=domid
+#_domain=domain.com
+_get_root() {
+ domain=$1
+ i=2
+ p=1
+ if _opns_rest "GET" "/domain/get"; then
+ _domain_response="$response"
+ else
+ return 1
+ fi
+
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ if [ -z "$h" ]; then
+ #not valid
+ 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)
+
+ if [ -n "$id" ]; then
+ _debug id "$id"
+ _host=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="${h}"
+ _domainid="${id}"
+ return 0
+ fi
+ p=$i
+ i=$(_math $i + 1)
+ done
+ _debug "$domain not found"
+
+ return 1
+}
+
+_opns_rest() {
+ method=$1
+ ep=$2
+ data=$3
+ #Percent encode user and token
+ key=$(echo "$OPNs_Key" | tr -d "\n\r" | _url_encode)
+ token=$(echo "$OPNs_Token" | tr -d "\n\r" | _url_encode)
+
+ opnsense_url="https://${key}:${token}@${OPNs_Host}:${OPNs_Port:-$OPNs_DefaultPort}/api/bind${ep}"
+ export _H1="Content-Type: application/json"
+ _debug2 "Try to call api: https://${OPNs_Host}:${OPNs_Port:-$OPNs_DefaultPort}/api/bind${ep}"
+ if [ ! "$method" = "GET" ]; then
+ _debug data "$data"
+ export _H1="Content-Type: application/json"
+ response="$(_post "$data" "$opnsense_url" "" "$method")"
+ else
+ export _H1=""
+ response="$(_get "$opnsense_url")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+ _debug2 response "$response"
+
+ return 0
+}
+
+_build_record_string() {
+ _record_string="{\"record\":{\"enabled\":\"1\",\"domain\":\"$1\",\"name\":\"$2\",\"type\":\"TXT\",\"value\":\"$3\"}}"
+}
+
+_existingchallenge() {
+ if _opns_rest "GET" "/record/searchRecord"; then
+ _record_response="$response"
+ else
+ return 1
+ fi
+ _uuid=""
+ _uuid=$(echo "$_record_response" | _egrep_o "\"uuid\":\"[^\"]*\",\"enabled\":\"[01]\",\"domain\":\"$1\",\"name\":\"$2\",\"type\":\"TXT\",\"value\":\"$3\"" | cut -d ':' -f 2 | cut -d '"' -f 2)
+
+ if [ -n "$_uuid" ]; then
+ _debug uuid "$_uuid"
+ return 0
+ fi
+ _debug "${2}.$1{1} record not found"
+
+ return 1
+}
+
+_opns_check_auth() {
+ OPNs_Host="${OPNs_Host:-$(_readaccountconf_mutable OPNs_Host)}"
+ OPNs_Port="${OPNs_Port:-$(_readaccountconf_mutable OPNs_Port)}"
+ OPNs_Key="${OPNs_Key:-$(_readaccountconf_mutable OPNs_Key)}"
+ OPNs_Token="${OPNs_Token:-$(_readaccountconf_mutable OPNs_Token)}"
+ OPNs_Api_Insecure="${OPNs_Api_Insecure:-$(_readaccountconf_mutable OPNs_Api_Insecure)}"
+
+ if [ -z "$OPNs_Host" ]; then
+ _err "You don't specify OPNsense address."
+ return 1
+ else
+ _saveaccountconf_mutable OPNs_Host "$OPNs_Host"
+ fi
+
+ if ! printf '%s' "$OPNs_Port" | grep '^[0-9]*$' >/dev/null; then
+ _err 'OPNs_Port specified but not numeric value'
+ return 1
+ elif [ -z "$OPNs_Port" ]; then
+ _info "OPNSense port not specified. Defaulting to using port $OPNs_DefaultPort"
+ else
+ _saveaccountconf_mutable OPNs_Port "$OPNs_Port"
+ fi
+
+ if ! printf '%s' "$OPNs_Api_Insecure" | grep '^[01]$' >/dev/null; then
+ _err 'OPNs_Api_Insecure specified but not 0/1 value'
+ return 1
+ elif [ -n "$OPNs_Api_Insecure" ]; then
+ _saveaccountconf_mutable OPNs_Api_Insecure "$OPNs_Api_Insecure"
+ fi
+ export HTTPS_INSECURE="${OPNs_Api_Insecure:-$OPNs_DefaultApi_Insecure}"
+
+ if [ -z "$OPNs_Key" ]; then
+ _err "you have not specified your OPNsense api key id."
+ _err "Please set OPNs_Key and try again."
+ return 1
+ else
+ _saveaccountconf_mutable OPNs_Key "$OPNs_Key"
+ fi
+
+ if [ -z "$OPNs_Token" ]; then
+ _err "you have not specified your OPNsense token."
+ _err "Please create OPNs_Token and try again."
+ return 1
+ else
+ _saveaccountconf_mutable OPNs_Token "$OPNs_Token"
+ fi
+
+ if ! _opns_rest "GET" "/general/get"; then
+ _err "Call to OPNsense API interface failed. Unable to access OPNsense API."
+ return 1
+ fi
+ return 0
+}
diff --git a/dnsapi/dns_ovh.sh b/dnsapi/dns_ovh.sh
index 65567efd..dda47dda 100755
--- a/dnsapi/dns_ovh.sh
+++ b/dnsapi/dns_ovh.sh
@@ -32,49 +32,49 @@ SYS_CA='https://ca.api.soyoustart.com/1.0'
#'runabove-ca'
RAV_CA='https://api.runabove.com/1.0'
-wiki="https://github.com/Neilpang/acme.sh/wiki/How-to-use-OVH-domain-api"
+wiki="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-OVH-domain-api"
-ovh_success="https://github.com/Neilpang/acme.sh/wiki/OVH-Success"
+ovh_success="https://github.com/acmesh-official/acme.sh/wiki/OVH-Success"
_ovh_get_api() {
_ogaep="$1"
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
- ;;
-
- *)
-
- _err "Unknown parameter : $1"
- return 1
- ;;
+ 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
+ ;;
esac
}
diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh
new file mode 100644
index 00000000..f5986827
--- /dev/null
+++ b/dnsapi/dns_pleskxml.sh
@@ -0,0 +1,417 @@
+#!/usr/bin/env sh
+
+## Name: dns_pleskxml.sh
+## Created by Stilez.
+## Also uses some code from PR#1832 by @romanlum (https://github.com/acmesh-official/acme.sh/pull/1832/files)
+
+## This DNS-01 method uses the Plesk XML API described at:
+## https://docs.plesk.com/en-US/12.5/api-rpc/about-xml-api.28709
+## and more specifically: https://docs.plesk.com/en-US/12.5/api-rpc/reference.28784
+
+## Note: a DNS ID with host = empty string is OK for this API, see
+## https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798
+## For example, to add a TXT record to DNS alias domain "acme-alias.com" would be a valid Plesk action.
+## So this API module can handle such a request, if needed.
+
+## For ACME v2 purposes, new TXT records are appended when added, and removing one TXT record will not affect any other TXT records.
+
+## The user credentials (username+password) and URL/URI for the Plesk XML API must be set by the user
+## before this module is called (case sensitive):
+##
+## ```
+## export pleskxml_uri="https://address-of-my-plesk-server.net:8443/enterprise/control/agent.php"
+## (or probably something similar)
+## export pleskxml_user="my plesk username"
+## export pleskxml_pass="my plesk password"
+## ```
+
+## Ok, let's issue a cert now:
+## ```
+## acme.sh --issue --dns dns_pleskxml -d example.com -d www.example.com
+## ```
+##
+## The `pleskxml_uri`, `pleskxml_user` and `pleskxml_pass` will be saved in `~/.acme.sh/account.conf` and reused when needed.
+
+#################### INTERNAL VARIABLES + NEWLINE + API TEMPLATES ##################################
+
+pleskxml_init_checks_done=0
+
+# Variable containing bare newline - not a style issue
+# shellcheck disable=SC1004
+NEWLINE='\
+'
+
+pleskxml_tplt_get_domains=""
+# Get a list of domains that PLESK can manage, so we can check root domain + host for acme.sh
+# Also used to test credentials and URI.
+# No params.
+
+pleskxml_tplt_get_dns_records="%s"
+# Get all DNS records for a Plesk domain ID.
+# PARAM = Plesk domain id to query
+
+pleskxml_tplt_add_txt_record="%sTXT%s%s"
+# Add a TXT record to a domain.
+# PARAMS = (1) Plesk internal domain ID, (2) "hostname" for the new record, eg '_acme_challenge', (3) TXT record value
+
+pleskxml_tplt_rmv_dns_record="%s"
+# Delete a specific TXT record from a domain.
+# PARAM = the Plesk internal ID for the DNS record to be deleted
+
+#################### Public functions ##################################
+
+#Usage: dns_pleskxml_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_pleskxml_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _info "Entering dns_pleskxml_add() to add TXT record '$txtvalue' to domain '$fulldomain'..."
+
+ # Get credentials if not already checked, and confirm we can log in to Plesk XML API
+ if ! _credential_check; then
+ return 1
+ fi
+
+ # Get root and subdomain details, and Plesk domain ID
+ if ! _pleskxml_get_root_domain "$fulldomain"; then
+ return 1
+ fi
+
+ _debug 'Credentials OK, and domain identified. Calling Plesk XML API to add TXT record'
+
+ # printf using template in a variable - not a style issue
+ # shellcheck disable=SC2059
+ request="$(printf "$pleskxml_tplt_add_txt_record" "$root_domain_id" "$sub_domain_name" "$txtvalue")"
+ if ! _call_api "$request"; then
+ return 1
+ fi
+
+ # OK, we should have added a TXT record. Let's check and return success if so.
+ # All that should be left in the result, is one section, containing okNEW_DNS_RECORD_ID
+
+ results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')"
+
+ if ! _value "$results" | grep 'ok' | grep '[0-9]\{1,\}' >/dev/null; then
+ # Error - doesn't contain expected string. Something's wrong.
+ _err 'Error when calling Plesk XML API.'
+ _err 'The result did not contain the expected XXXXX section, or contained other values as well.'
+ _err 'This is unexpected: something has gone wrong.'
+ _err 'The full response was:'
+ _err "$pleskxml_prettyprint_result"
+ return 1
+ fi
+
+ recid="$(_value "$results" | grep '[0-9]\{1,\}' | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/')"
+
+ _info "Success. TXT record appears to be correctly added (Plesk record ID=$recid). Exiting dns_pleskxml_add()."
+
+ return 0
+}
+
+#Usage: dns_pleskxml_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_pleskxml_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _info "Entering dns_pleskxml_rm() to remove TXT record '$txtvalue' from domain '$fulldomain'..."
+
+ # Get credentials if not already checked, and confirm we can log in to Plesk XML API
+ if ! _credential_check; then
+ return 1
+ fi
+
+ # Get root and subdomain details, and Plesk domain ID
+ if ! _pleskxml_get_root_domain "$fulldomain"; then
+ return 1
+ fi
+
+ _debug 'Credentials OK, and domain identified. Calling Plesk XML API to get list of TXT records and their IDs'
+
+ # printf using template in a variable - not a style issue
+ # shellcheck disable=SC2059
+ request="$(printf "$pleskxml_tplt_get_dns_records" "$root_domain_id")"
+ if ! _call_api "$request"; then
+ return 1
+ fi
+
+ # 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'
+ )"
+
+ if [ -z "$reclist" ]; then
+ _err "No TXT records found for root domain ${root_domain_name} (Plesk domain ID ${root_domain_id}). Exiting."
+ return 1
+ fi
+
+ _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/'
+ )"
+
+ if ! _value "$recid" | grep '^[0-9]\{1,\}$' >/dev/null; then
+ _err "DNS records for root domain '${root_domain_name}' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'"
+ _err "Cannot delete TXT record. Exiting."
+ return 1
+ fi
+
+ _debug "Found Plesk record ID for target text string '${txtvalue}': ID=${recid}"
+ _debug 'Calling Plesk XML API to remove TXT record'
+
+ # printf using template in a variable - not a style issue
+ # shellcheck disable=SC2059
+ request="$(printf "$pleskxml_tplt_rmv_dns_record" "$recid")"
+ if ! _call_api "$request"; then
+ return 1
+ fi
+
+ # OK, we should have removed a TXT record. Let's check and return success if so.
+ # All that should be left in the result, is one section, containing okPLESK_DELETED_DNS_RECORD_ID
+
+ results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')"
+
+ if ! _value "$results" | grep 'ok' | grep '[0-9]\{1,\}' >/dev/null; then
+ # Error - doesn't contain expected string. Something's wrong.
+ _err 'Error when calling Plesk XML API.'
+ _err 'The result did not contain the expected XXXXX section, or contained other values as well.'
+ _err 'This is unexpected: something has gone wrong.'
+ _err 'The full response was:'
+ _err "$pleskxml_prettyprint_result"
+ return 1
+ fi
+
+ _info "Success. TXT record appears to be correctly removed. Exiting dns_pleskxml_rm()."
+ return 0
+}
+
+#################### Private functions below (utility functions) ##################################
+
+# Outputs value of a variable without additional newlines etc
+_value() {
+ printf '%s' "$1"
+}
+
+# Outputs value of a variable (FQDN) and cuts it at 2 specified '.' delimiters, returning the text in between
+# $1, $2 = where to cut
+# $3 = FQDN
+_valuecut() {
+ printf '%s' "$3" | cut -d . -f "${1}-${2}"
+}
+
+# Counts '.' present in a domain name or other string
+# $1 = domain name
+_countdots() {
+ _value "$1" | tr -dc '.' | wc -c | sed 's/ //g'
+}
+
+# Cleans up an API response, splits it "one line per item in the response" and greps for a string that in the context, identifies "useful" lines
+# $1 - result string from API
+# $2 - plain text tag to resplit on (usually "result" or "domain"). NOT REGEX
+# $3 - basic regex to recognise useful return lines
+# note: $3 matches via basic NOT extended regex (BRE), as extended regex capabilities not needed at the moment.
+# 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"
+}
+
+#################### Private functions below (DNS functions) ##################################
+
+# Calls Plesk XML API, and checks results for obvious issues
+_call_api() {
+ request="$1"
+ errtext=''
+
+ _debug 'Entered _call_api(). Calling Plesk XML API with request:'
+ _debug "'$request'"
+
+ export _H1="HTTP_AUTH_LOGIN: $pleskxml_user"
+ export _H2="HTTP_AUTH_PASSWD: $pleskxml_pass"
+ export _H3="content-Type: text/xml"
+ export _H4="HTTP_PRETTY_PRINT: true"
+ pleskxml_prettyprint_result="$(_post "${request}" "$pleskxml_uri" "" "POST")"
+ pleskxml_retcode="$?"
+ _debug 'The responses from the Plesk XML server were:'
+ _debug "retcode=$pleskxml_retcode. Literal response:"
+ _debug "'$pleskxml_prettyprint_result'"
+
+ # Detect any that isn't "ok". None of the used calls should fail if the API is working correctly.
+ # Also detect if there simply aren't any status lines (null result?) and report that, as well.
+
+ statuslines_count_total="$(echo "$pleskxml_prettyprint_result" | grep -c '^ *[^<]* *$')"
+ statuslines_count_okay="$(echo "$pleskxml_prettyprint_result" | grep -c '^ *ok *$')"
+
+ if [ -z "$statuslines_count_total" ]; then
+
+ # We have no status lines at all. Results are empty
+ errtext='The Plesk XML API unexpectedly returned an empty set of results for this call.'
+
+ 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:
+ # - filter output to keep only lines like this: "SPACEStextSPACES" (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]'
+ )"
+
+ fi
+
+ if [ "$pleskxml_retcode" -ne 0 ] || [ "$errtext" != "" ]; then
+ # Call failed, for reasons either in the retcode or the response text...
+
+ if [ "$pleskxml_retcode" -eq 0 ]; then
+ _err "The POST request was successfully sent to the Plesk server."
+ else
+ _err "The return code for the POST request was $pleskxml_retcode (non-zero = failure in submitting request to server)."
+ fi
+
+ if [ "$errtext" != "" ]; then
+ _err 'The error responses received from the Plesk server were:'
+ _err "$errtext"
+ else
+ _err "No additional error messages were received back from the Plesk server"
+ fi
+
+ _err "The Plesk XML API call failed."
+ return 1
+
+ fi
+
+ _debug "Leaving _call_api(). Successful call."
+
+ return 0
+}
+
+# Startup checks (credentials, URI)
+_credential_check() {
+ _debug "Checking Plesk XML API login credentials and URI..."
+
+ if [ "$pleskxml_init_checks_done" -eq 1 ]; then
+ _debug "Initial checks already done, no need to repeat. Skipped."
+ return 0
+ fi
+
+ pleskxml_user="${pleskxml_user:-$(_readaccountconf_mutable pleskxml_user)}"
+ pleskxml_pass="${pleskxml_pass:-$(_readaccountconf_mutable pleskxml_pass)}"
+ pleskxml_uri="${pleskxml_uri:-$(_readaccountconf_mutable pleskxml_uri)}"
+
+ if [ -z "$pleskxml_user" ] || [ -z "$pleskxml_pass" ] || [ -z "$pleskxml_uri" ]; then
+ pleskxml_user=""
+ pleskxml_pass=""
+ pleskxml_uri=""
+ _err "You didn't specify one or more of the Plesk XML API username, password, or URI."
+ _err "Please create these and try again."
+ _err "Instructions are in the 'dns_pleskxml' plugin source code or in the acme.sh documentation."
+ return 1
+ fi
+
+ # Test the API is usable, by trying to read the list of managed domains...
+ _call_api "$pleskxml_tplt_get_domains"
+ if [ "$pleskxml_retcode" -ne 0 ]; then
+ _err 'Failed to access Plesk XML API.'
+ _err "Please check your login credentials and Plesk URI, and that the URI is reachable, and try again."
+ return 1
+ fi
+
+ _saveaccountconf_mutable pleskxml_uri "$pleskxml_uri"
+ _saveaccountconf_mutable pleskxml_user "$pleskxml_user"
+ _saveaccountconf_mutable pleskxml_pass "$pleskxml_pass"
+
+ _debug "Test login to Plesk XML API successful. Login credentials and URI successfully saved to the acme.sh configuration file for future use."
+
+ pleskxml_init_checks_done=1
+
+ return 0
+}
+
+# For a FQDN, identify the root domain managed by Plesk, its domain ID in Plesk, and the host if any.
+
+# IMPORTANT NOTE: a result with host = empty string is OK for this API, see
+# https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798
+# See notes at top of this file
+
+_pleskxml_get_root_domain() {
+ original_full_domain_name="$1"
+
+ _debug "Identifying DNS root domain for '$original_full_domain_name' that is managed by the Plesk account."
+
+ # test if the domain as provided is valid for splitting.
+
+ if [ "$(_countdots "$original_full_domain_name")" -eq 0 ]; then
+ _err "Invalid domain. The ACME domain must contain at least two parts (aa.bb) to identify a domain and tld for the TXT record."
+ return 1
+ fi
+
+ _debug "Querying Plesk server for list of managed domains..."
+
+ _call_api "$pleskxml_tplt_get_domains"
+ if [ "$pleskxml_retcode" -ne 0 ]; then
+ return 1
+ fi
+
+ # Generate a crude list of domains known to this Plesk account.
+ # We convert tags to so it'll flag on a hit with either or fields,
+ # for non-Western character sets.
+ # Output will be one line per known domain, containing 2 tages and a single tag
+ # We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned.
+
+ output="$(_api_response_split "$pleskxml_prettyprint_result" 'domain' 'domain' | sed 's///g;s/<\/ascii-name>/<\/name>/g' | grep '' | grep '')"
+
+ _debug 'Domains managed by Plesk server are (ignore the hacked output):'
+ _debug "$output"
+
+ # loop and test if domain, or any parent domain, is managed by Plesk
+ # Loop until we don't have any '.' in the string we're testing as a candidate Plesk-managed domain
+
+ root_domain_name="$original_full_domain_name"
+
+ while true; do
+
+ _debug "Checking if '$root_domain_name' is managed by the Plesk server..."
+
+ root_domain_id="$(_value "$output" | grep "$root_domain_name" | _head_n 1 | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/')"
+
+ if [ -n "$root_domain_id" ]; then
+ # Found a match
+ # SEE IMPORTANT NOTE ABOVE - THIS FUNCTION CAN RETURN HOST='', AND THAT'S OK FOR PLESK XML API WHICH ALLOWS IT.
+ # SO WE HANDLE IT AND DON'T PREVENT IT
+ sub_domain_name="$(_value "$original_full_domain_name" | sed "s/\.\{0,1\}${root_domain_name}"'$//')"
+ _info "Success. Matched host '$original_full_domain_name' to: DOMAIN '${root_domain_name}' (Plesk ID '${root_domain_id}'), HOST '${sub_domain_name}'. Returning."
+ return 0
+ fi
+
+ # No match, try next parent up (if any)...
+
+ root_domain_name="$(_valuecut 2 1000 "$root_domain_name")"
+
+ if [ "$(_countdots "$root_domain_name")" -eq 0 ]; then
+ _debug "No match, and next parent would be a TLD..."
+ _err "Cannot find '$original_full_domain_name' or any parent domain of it, in Plesk."
+ _err "Are you sure that this domain is managed by this Plesk server?"
+ return 1
+ fi
+
+ _debug "No match, trying next parent up..."
+
+ done
+}
diff --git a/dnsapi/dns_rackspace.sh b/dnsapi/dns_rackspace.sh
index 3939fd81..03e1fa68 100644
--- a/dnsapi/dns_rackspace.sh
+++ b/dnsapi/dns_rackspace.sh
@@ -9,7 +9,7 @@ RACKSPACE_Endpoint="https://dns.api.rackspacecloud.com/v1.0"
# 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/Neilpang/acme.sh/pull/1297
+# Original - tcocca:rackspace_dnsapi https://github.com/acmesh-official/acme.sh/pull/1297
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
@@ -73,7 +73,7 @@ _get_root_zone() {
#not valid
return 1
fi
- if ! _rackspace_rest GET "$RACKSPACE_Tenant/domains"; then
+ if ! _rackspace_rest GET "$RACKSPACE_Tenant/domains/search?name=$h"; then
return 1
fi
_debug2 response "$response"
diff --git a/dnsapi/dns_rcode0.sh b/dnsapi/dns_rcode0.sh
new file mode 100755
index 00000000..d3f7f219
--- /dev/null
+++ b/dnsapi/dns_rcode0.sh
@@ -0,0 +1,224 @@
+#!/usr/bin/env sh
+
+#Rcode0 API Integration
+#https://my.rcodezero.at/api-doc
+#
+# log into https://my.rcodezero.at/enableapi and get your ACME API Token (the ACME API token has limited
+# access to the REST calls needed for acme.sh only)
+#
+#RCODE0_URL="https://my.rcodezero.at"
+#RCODE0_API_TOKEN="0123456789ABCDEF"
+#RCODE0_TTL=60
+
+DEFAULT_RCODE0_URL="https://my.rcodezero.at"
+DEFAULT_RCODE0_TTL=60
+
+######## Public functions #####################
+#Usage: add _acme-challenge.www.domain.com "123456789ABCDEF0000000000000000000000000000000000000"
+#fulldomain
+#txtvalue
+dns_rcode0_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ RCODE0_API_TOKEN="${RCODE0_API_TOKEN:-$(_readaccountconf_mutable RCODE0_API_TOKEN)}"
+ RCODE0_URL="${RCODE0_URL:-$(_readaccountconf_mutable RCODE0_URL)}"
+ RCODE0_TTL="${RCODE0_TTL:-$(_readaccountconf_mutable RCODE0_TTL)}"
+
+ if [ -z "$RCODE0_URL" ]; then
+ RCODE0_URL="$DEFAULT_RCODE0_URL"
+ fi
+
+ if [ -z "$RCODE0_API_TOKEN" ]; then
+ RCODE0_API_TOKEN=""
+ _err "Missing Rcode0 ACME API Token."
+ _err "Please login and create your token at httsp://my.rcodezero.at/enableapi and try again."
+ return 1
+ fi
+
+ if [ -z "$RCODE0_TTL" ]; then
+ RCODE0_TTL="$DEFAULT_RCODE0_TTL"
+ fi
+
+ #save the token to the account conf file.
+ _saveaccountconf_mutable RCODE0_API_TOKEN "$RCODE0_API_TOKEN"
+
+ if [ "$RCODE0_URL" != "$DEFAULT_RCODE0_URL" ]; then
+ _saveaccountconf_mutable RCODE0_URL "$RCODE0_URL"
+ fi
+
+ if [ "$RCODE0_TTL" != "$DEFAULT_RCODE0_TTL" ]; then
+ _saveaccountconf_mutable RCODE0_TTL "$RCODE0_TTL"
+ fi
+
+ _debug "Detect root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "No 'MASTER' zone for $fulldomain found at RcodeZero Anycast."
+ return 1
+ fi
+ _debug _domain "$_domain"
+
+ _debug "Adding record"
+
+ _record_string=""
+ _build_record_string "$txtvalue"
+ _list_existingchallenges
+ for oldchallenge in $_existing_challenges; do
+ _build_record_string "$oldchallenge"
+ done
+
+ _debug "Challenges: $_existing_challenges"
+
+ if [ -z "$_existing_challenges" ]; then
+ if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"add\", \"name\": \"$fulldomain.\", \"type\": \"TXT\", \"ttl\": $RCODE0_TTL, \"records\": [$_record_string]}]"; then
+ _err "Add txt record error."
+ return 1
+ fi
+ else
+ # try update in case a records exists (need for wildcard certs)
+ if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"update\", \"name\": \"$fulldomain.\", \"type\": \"TXT\", \"ttl\": $RCODE0_TTL, \"records\": [$_record_string]}]"; then
+ _err "Set txt record error."
+ return 1
+ fi
+ fi
+
+ return 0
+}
+
+#fulldomain txtvalue
+dns_rcode0_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ RCODE0_API_TOKEN="${RCODE0_API_TOKEN:-$(_readaccountconf_mutable RCODE0_API_TOKEN)}"
+ RCODE0_URL="${RCODE0_URL:-$(_readaccountconf_mutable RCODE0_URL)}"
+ RCODE0_TTL="${RCODE0_TTL:-$(_readaccountconf_mutable RCODE0_TTL)}"
+
+ if [ -z "$RCODE0_URL" ]; then
+ RCODE0_URL="$DEFAULT_RCODE0_URL"
+ fi
+
+ if [ -z "$RCODE0_API_TOKEN" ]; then
+ RCODE0_API_TOKEN=""
+ _err "Missing Rcode0 API Token."
+ _err "Please login and create your token at httsp://my.rcodezero.at/enableapi and try again."
+ return 1
+ fi
+
+ #save the api addr and key to the account conf file.
+ _saveaccountconf_mutable RCODE0_URL "$RCODE0_URL"
+ _saveaccountconf_mutable RCODE0_API_TOKEN "$RCODE0_API_TOKEN"
+
+ if [ "$RCODE0_TTL" != "$DEFAULT_RCODE0_TTL" ]; then
+ _saveaccountconf_mutable RCODE0_TTL "$RCODE0_TTL"
+ fi
+
+ if [ -z "$RCODE0_TTL" ]; then
+ RCODE0_TTL="$DEFAULT_RCODE0_TTL"
+ fi
+
+ _debug "Detect root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug "Remove record"
+
+ #Enumerate existing acme challenges
+ _list_existingchallenges
+
+ if _contains "$_existing_challenges" "$txtvalue"; then
+ #Delete all challenges (PowerDNS API does not allow to delete content)
+ if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"delete\", \"name\": \"$fulldomain.\", \"type\": \"TXT\"}]"; then
+ _err "Delete txt record error."
+ return 1
+ fi
+ _record_string=""
+ #If the only existing challenge was the challenge to delete: nothing to do
+ if ! [ "$_existing_challenges" = "$txtvalue" ]; then
+ for oldchallenge in $_existing_challenges; do
+ #Build up the challenges to re-add, ommitting the one what should be deleted
+ if ! [ "$oldchallenge" = "$txtvalue" ]; then
+ _build_record_string "$oldchallenge"
+ fi
+ done
+ #Recreate the existing challenges
+ if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"update\", \"name\": \"$fulldomain.\", \"type\": \"TXT\", \"ttl\": $RCODE0_TTL, \"records\": [$_record_string]}]"; then
+ _err "Set txt record error."
+ return 1
+ fi
+ fi
+ else
+ _info "Record not found, nothing to remove"
+ fi
+
+ return 0
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _domain=domain.com
+_get_root() {
+ domain=$1
+ i=1
+
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+
+ _debug "try to find: $h"
+ if _rcode0_rest "GET" "/api/v1/acme/zones/$h"; then
+ if [ "$response" = "[\"found\"]" ]; then
+ _domain="$h"
+ if [ -z "$h" ]; then
+ _domain="=2E"
+ fi
+ return 0
+ elif [ "$response" = "[\"not a master domain\"]" ]; then
+ return 1
+ fi
+ fi
+
+ if [ -z "$h" ]; then
+ return 1
+ fi
+ i=$(_math $i + 1)
+ done
+ _debug "no matching domain for $domain found"
+
+ return 1
+}
+
+_rcode0_rest() {
+ method=$1
+ ep=$2
+ data=$3
+
+ export _H1="Authorization: Bearer $RCODE0_API_TOKEN"
+
+ if [ ! "$method" = "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$RCODE0_URL$ep" "" "$method")"
+ else
+ response="$(_get "$RCODE0_URL$ep")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+ _debug2 response "$response"
+
+ return 0
+}
+
+_build_record_string() {
+ _record_string="${_record_string:+${_record_string}, }{\"content\": \"\\\"${1}\\\"\", \"disabled\": false}"
+}
+
+_list_existingchallenges() {
+ _rcode0_rest "GET" "/api/v1/acme/zones/$_domain/rrsets"
+ _existing_challenges=$(echo "$response" | _normalizeJson | _egrep_o "\"name\":\"${fulldomain}[^]]*}" | _egrep_o 'content\":\"\\"[^\\]*' | sed -n 's/^content":"\\"//p')
+ _debug2 "$_existing_challenges"
+}
diff --git a/dnsapi/dns_regru.sh b/dnsapi/dns_regru.sh
index 369f62ad..29f758ea 100644
--- a/dnsapi/dns_regru.sh
+++ b/dnsapi/dns_regru.sh
@@ -5,7 +5,6 @@
#
# REGRU_API_Password="test"
#
-_domain=$_domain
REGRU_API_URL="https://api.reg.ru/api/regru2"
@@ -27,10 +26,20 @@ dns_regru_add() {
_saveaccountconf_mutable REGRU_API_Username "$REGRU_API_Username"
_saveaccountconf_mutable REGRU_API_Password "$REGRU_API_Password"
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _domain "$_domain"
+
+ _subdomain=$(echo "$fulldomain" | sed -r "s/.$_domain//")
+ _debug _subdomain "$_subdomain"
+
_info "Adding TXT record to ${fulldomain}"
- response="$(_get "$REGRU_API_URL/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}" 'success'; then
+ if ! _contains "${response}" 'error'; then
return 0
fi
_err "Could not create resource record, check logs"
@@ -51,13 +60,67 @@ dns_regru_rm() {
return 1
fi
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _domain "$_domain"
+
+ _subdomain=$(echo "$fulldomain" | sed -r "s/.$_domain//")
+ _debug _subdomain "$_subdomain"
+
_info "Deleting resource record $fulldomain"
- response="$(_get "$REGRU_API_URL/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}" 'success'; then
+ if ! _contains "${response}" 'error'; then
return 0
fi
_err "Could not delete resource record, check logs"
_err "${response}"
return 1
}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _domain=domain.com
+_get_root() {
+ domain=$1
+
+ _regru_rest POST "service/get_list" "username=${REGRU_API_Username}&password=${REGRU_API_Password}&output_format=xml&servtype=domain"
+ domains_list=$(echo "${response}" | grep dname | sed -r "s/.*dname=\"([^\"]+)\".*/\\1/g")
+
+ for ITEM in ${domains_list}; do
+ case "${domain}" in
+ *${ITEM}*)
+ _domain=${ITEM}
+ _debug _domain "${_domain}"
+ return 0
+ ;;
+ esac
+ done
+
+ return 1
+}
+
+#returns
+# response
+_regru_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+
+ export _H1="Content-Type: application/x-www-form-urlencoded"
+
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$REGRU_API_URL/$ep" "" "$m")"
+ else
+ response="$(_get "$REGRU_API_URL/$ep?$data")"
+ fi
+
+ _debug response "${response}"
+ return 0
+}
diff --git a/dnsapi/dns_servercow.sh b/dnsapi/dns_servercow.sh
index be4e59da..e73d85b0 100755
--- a/dnsapi/dns_servercow.sh
+++ b/dnsapi/dns_servercow.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env sh
##########
-# Custom servercow.de DNS API v1 for use with [acme.sh](https://github.com/Neilpang/acme.sh)
+# Custom servercow.de DNS API v1 for use with [acme.sh](https://github.com/acmesh-official/acme.sh)
#
# Usage:
# export SERVERCOW_API_Username=username
diff --git a/dnsapi/dns_transip.sh b/dnsapi/dns_transip.sh
new file mode 100644
index 00000000..23debe0d
--- /dev/null
+++ b/dnsapi/dns_transip.sh
@@ -0,0 +1,162 @@
+#!/usr/bin/env sh
+TRANSIP_Api_Url="https://api.transip.nl/v6"
+TRANSIP_Token_Read_Only="false"
+TRANSIP_Token_Global_Key="false"
+TRANSIP_Token_Expiration="30 minutes"
+# You can't reuse a label token, so we leave this empty normally
+TRANSIP_Token_Label=""
+
+######## Public functions #####################
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_transip_add() {
+ fulldomain="$1"
+ _debug fulldomain="$fulldomain"
+ txtvalue="$2"
+ _debug txtvalue="$txtvalue"
+ _transip_setup "$fulldomain" || return 1
+ _info "Creating TXT record."
+ if ! _transip_rest POST "domains/$_domain/dns" "{\"dnsEntry\":{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"expire\":300}}"; then
+ _err "Could not add TXT record."
+ return 1
+ fi
+ return 0
+}
+
+dns_transip_rm() {
+ fulldomain=$1
+ _debug fulldomain="$fulldomain"
+ txtvalue=$2
+ _debug txtvalue="$txtvalue"
+ _transip_setup "$fulldomain" || return 1
+ _info "Removing TXT record."
+ if ! _transip_rest DELETE "domains/$_domain/dns" "{\"dnsEntry\":{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"expire\":300}}"; then
+ _err "Could not remove TXT record $_sub_domain for $domain"
+ return 1
+ fi
+ return 0
+}
+
+#################### Private functions below ##################################
+#_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
+
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="$h"
+
+ if _transip_rest GET "domains/$h/dns" && _contains "$response" "dnsEntries"; then
+ return 0
+ fi
+
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ _err "Unable to parse this domain"
+ return 1
+}
+
+_transip_rest() {
+ m="$1"
+ ep="$2"
+ data="$3"
+ _debug ep "$ep"
+ export _H1="Accept: application/json"
+ export _H2="Authorization: Bearer $_token"
+ export _H4="Content-Type: application/json"
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$TRANSIP_Api_Url/$ep" "" "$m")"
+ retcode=$?
+ else
+ response="$(_get "$TRANSIP_Api_Url/$ep")"
+ retcode=$?
+ fi
+
+ if [ "$retcode" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
+
+_transip_get_token() {
+ nonce=$(echo "TRANSIP$(_time)" | _digest sha1 hex | cut -c 1-32)
+ _debug nonce "$nonce"
+
+ data="{\"login\":\"${TRANSIP_Username}\",\"nonce\":\"${nonce}\",\"read_only\":\"${TRANSIP_Token_Read_Only}\",\"expiration_time\":\"${TRANSIP_Token_Expiration}\",\"label\":\"${TRANSIP_Token_Label}\",\"global_key\":\"${TRANSIP_Token_Global_Key}\"}"
+ _debug data "$data"
+
+ #_signature=$(printf "%s" "$data" | openssl dgst -sha512 -sign "$TRANSIP_Key_File" | _base64)
+ _signature=$(printf "%s" "$data" | _sign "$TRANSIP_Key_File" "sha512")
+ _debug2 _signature "$_signature"
+
+ export _H1="Signature: $_signature"
+ export _H2="Content-Type: application/json"
+
+ response="$(_post "$data" "$TRANSIP_Api_Url/auth" "" "POST")"
+ retcode=$?
+ _debug2 response "$response"
+ if [ "$retcode" != "0" ]; then
+ _err "Authentication failed."
+ return 1
+ fi
+ if _contains "$response" "token"; then
+ _token="$(echo "$response" | _normalizeJson | sed -n 's/^{"token":"\(.*\)"}/\1/p')"
+ _debug _token "$_token"
+ return 0
+ fi
+ return 1
+}
+
+_transip_setup() {
+ fulldomain=$1
+
+ # retrieve the transip creds
+ TRANSIP_Username="${TRANSIP_Username:-$(_readaccountconf_mutable TRANSIP_Username)}"
+ TRANSIP_Key_File="${TRANSIP_Key_File:-$(_readaccountconf_mutable TRANSIP_Key_File)}"
+ # check their vals for null
+ if [ -z "$TRANSIP_Username" ] || [ -z "$TRANSIP_Key_File" ]; then
+ TRANSIP_Username=""
+ TRANSIP_Key_File=""
+ _err "You didn't specify a TransIP username and api key file location"
+ _err "Please set those values and try again."
+ return 1
+ fi
+ # save the username and api key to the account conf file.
+ _saveaccountconf_mutable TRANSIP_Username "$TRANSIP_Username"
+ _saveaccountconf_mutable TRANSIP_Key_File "$TRANSIP_Key_File"
+
+ if [ -f "$TRANSIP_Key_File" ]; then
+ if ! grep "BEGIN PRIVATE KEY" "$TRANSIP_Key_File" >/dev/null 2>&1; then
+ _err "Key file doesn't seem to be a valid key: ${TRANSIP_Key_File}"
+ return 1
+ fi
+ else
+ _err "Can't read private key file: ${TRANSIP_Key_File}"
+ return 1
+ fi
+
+ if [ -z "$_token" ]; then
+ if ! _transip_get_token; then
+ _err "Can not get token."
+ return 1
+ fi
+ fi
+
+ _get_root "$fulldomain" || return 1
+
+ return 0
+}
diff --git a/dnsapi/dns_unoeuro.sh b/dnsapi/dns_unoeuro.sh
index 9132f136..13ba8a00 100644
--- a/dnsapi/dns_unoeuro.sh
+++ b/dnsapi/dns_unoeuro.sh
@@ -5,7 +5,7 @@
#
#UNO_User="UExxxxxx"
-Uno_Api="https://api.unoeuro.com/1"
+Uno_Api="https://api.simply.com/1"
######## Public functions #####################
@@ -24,12 +24,6 @@ dns_unoeuro_add() {
return 1
fi
- if ! _contains "$UNO_User" "UE"; then
- _err "It seems that the UNO_User=$UNO_User is not a valid username."
- _err "Please check and retry."
- return 1
- fi
-
#save the api key and email to the account conf file.
_saveaccountconf_mutable UNO_Key "$UNO_Key"
_saveaccountconf_mutable UNO_User "$UNO_User"
@@ -52,7 +46,7 @@ dns_unoeuro_add() {
fi
_info "Adding record"
- if _uno_rest POST "my/products/$h/dns/records" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}"; then
+ if _uno_rest POST "my/products/$h/dns/records" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120,\"priority\":0}"; then
if _contains "$response" "\"status\": 200" >/dev/null; then
_info "Added, OK"
return 0
diff --git a/dnsapi/dns_variomedia.sh b/dnsapi/dns_variomedia.sh
new file mode 100644
index 00000000..a35b8f0f
--- /dev/null
+++ b/dnsapi/dns_variomedia.sh
@@ -0,0 +1,147 @@
+#!/usr/bin/env sh
+
+#
+#VARIOMEDIA_API_TOKEN=000011112222333344445555666677778888
+
+VARIOMEDIA_API="https://api.variomedia.de"
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_variomedia_add() {
+ fulldomain=$1
+ txtvalue=$2
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ VARIOMEDIA_API_TOKEN="${VARIOMEDIA_API_TOKEN:-$(_readaccountconf_mutable VARIOMEDIA_API_TOKEN)}"
+ if test -z "$VARIOMEDIA_API_TOKEN"; then
+ VARIOMEDIA_API_TOKEN=""
+ _err 'VARIOMEDIA_API_TOKEN was not exported'
+ return 1
+ fi
+
+ _saveaccountconf_mutable VARIOMEDIA_API_TOKEN "$VARIOMEDIA_API_TOKEN"
+
+ _debug 'First detect the root zone'
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ if ! _variomedia_rest POST "dns-records" "{\"data\": {\"type\": \"dns-record\", \"attributes\": {\"record_type\": \"TXT\", \"name\": \"$_sub_domain\", \"domain\": \"$_domain\", \"data\": \"$txtvalue\", \"ttl\":300}}}"; then
+ _err "$response"
+ return 1
+ fi
+
+ _debug2 _response "$response"
+ return 0
+}
+
+#fulldomain txtvalue
+dns_variomedia_rm() {
+ fulldomain=$1
+ txtvalue=$2
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ VARIOMEDIA_API_TOKEN="${VARIOMEDIA_API_TOKEN:-$(_readaccountconf_mutable VARIOMEDIA_API_TOKEN)}"
+ if test -z "$VARIOMEDIA_API_TOKEN"; then
+ VARIOMEDIA_API_TOKEN=""
+ _err 'VARIOMEDIA_API_TOKEN was not exported'
+ return 1
+ fi
+
+ _saveaccountconf_mutable VARIOMEDIA_API_TOKEN "$VARIOMEDIA_API_TOKEN"
+
+ _debug 'First detect the root zone'
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _debug 'Getting txt records'
+
+ if ! _variomedia_rest GET "dns-records?filter[domain]=$_domain"; then
+ _err 'Error'
+ return 1
+ fi
+
+ _record_id="$(echo "$response" | cut -d '[' -f2 | cut -d']' -f1 | sed 's/},[ \t]*{/\},§\{/g' | tr § '\n' | grep "$_sub_domain" | grep "$txtvalue" | sed 's/^{//;s/}[,]?$//' | tr , '\n' | tr -d '\"' | grep ^id | cut -d : -f2 | tr -d ' ')"
+ _debug _record_id "$_record_id"
+ if [ "$_record_id" ]; then
+ _info "Successfully retrieved the record id for ACME challenge."
+ else
+ _info "Empty record id, it seems no such record."
+ return 0
+ fi
+
+ if ! _variomedia_rest DELETE "/dns-records/$_record_id"; then
+ _err "$response"
+ return 1
+ fi
+
+ _debug2 _response "$response"
+ return 0
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ fulldomain=$1
+ i=1
+ while true; do
+ h=$(printf "%s" "$fulldomain" | cut -d . -f $i-100)
+ _debug h "$h"
+ if [ -z "$h" ]; then
+ return 1
+ fi
+
+ if ! _variomedia_rest GET "domains/$h"; then
+ return 1
+ fi
+
+ if _startswith "$response" "\{\"data\":"; then
+ if _contains "$response" "\"id\":\"$h\""; then
+ _sub_domain="$(echo "$fulldomain" | sed "s/\\.$h\$//")"
+ _domain=$h
+ return 0
+ fi
+ fi
+ i=$(_math "$i" + 1)
+ done
+
+ _debug "root domain not found"
+ return 1
+}
+
+_variomedia_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+
+ export _H1="Authorization: token $VARIOMEDIA_API_TOKEN"
+ export _H2="Content-Type: application/vnd.api+json"
+ export _H3="Accept: application/vnd.variomedia.v1+json"
+
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$VARIOMEDIA_API/$ep" "" "$m")"
+ else
+ response="$(_get "$VARIOMEDIA_API/$ep")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "Error $ep"
+ return 1
+ fi
+
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_vscale.sh b/dnsapi/dns_vscale.sh
index e50b7d8b..d717d6e2 100755
--- a/dnsapi/dns_vscale.sh
+++ b/dnsapi/dns_vscale.sh
@@ -102,7 +102,7 @@ _get_root() {
return 1
fi
- hostedzone="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$h\".*}")"
+ hostedzone="$(echo "$response" | tr "{" "\n" | _egrep_o "\"name\":\s*\"$h\".*}")"
if [ "$hostedzone" ]; then
_domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
if [ "$_domain_id" ]; then
diff --git a/dnsapi/dns_vultr.sh b/dnsapi/dns_vultr.sh
index f15e7c49..c7b52e84 100644
--- a/dnsapi/dns_vultr.sh
+++ b/dnsapi/dns_vultr.sh
@@ -106,9 +106,9 @@ _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
+ _domain=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug h "$_domain"
+ if [ -z "$_domain" ]; then
return 1
fi
@@ -119,11 +119,9 @@ _get_root() {
if printf "%s\n" "$response" | grep '^\[.*\]' >/dev/null; then
if _contains "$response" "\"domain\":\"$_domain\""; then
_sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")"
- _domain=$_domain
return 0
else
- _err 'Invalid domain'
- return 1
+ _debug "Go to next level of $_domain"
fi
else
_err "$response"
diff --git a/dnsapi/dns_yandex.sh b/dnsapi/dns_yandex.sh
index a4f39784..0a2c3330 100755
--- a/dnsapi/dns_yandex.sh
+++ b/dnsapi/dns_yandex.sh
@@ -6,6 +6,9 @@
# Values to export:
# export PDD_Token="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+# Sometimes cloudflare / google doesn't pick new dns records fast enough.
+# You can add --dnssleep XX to params as workaround.
+
######## Public functions #####################
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
@@ -13,97 +16,106 @@ dns_yandex_add() {
fulldomain="${1}"
txtvalue="${2}"
_debug "Calling: dns_yandex_add() '${fulldomain}' '${txtvalue}'"
+
_PDD_credentials || return 1
- export _H1="PddToken: $PDD_Token"
- _PDD_get_domain "$fulldomain" || return 1
- _debug "Found suitable domain in pdd: $curDomain"
- curData="domain=${curDomain}&type=TXT&subdomain=${curSubdomain}&ttl=360&content=${txtvalue}"
- curUri="https://pddimp.yandex.ru/api2/admin/dns/add"
- curResult="$(_post "${curData}" "${curUri}")"
- _debug "Result: $curResult"
+ _PDD_get_domain || return 1
+ _debug "Found suitable domain: $domain"
+
+ _PDD_get_record_ids || return 1
+ _debug "Record_ids: $record_ids"
+
+ if [ -n "$record_ids" ]; then
+ _info "All existing $subdomain records from $domain will be removed at the very end."
+ fi
+
+ data="domain=${domain}&type=TXT&subdomain=${subdomain}&ttl=300&content=${txtvalue}"
+ uri="https://pddimp.yandex.ru/api2/admin/dns/add"
+ result="$(_post "${data}" "${uri}" | _normalizeJson)"
+ _debug "Result: $result"
+
+ if ! _contains "$result" '"success":"ok"'; then
+ if _contains "$result" '"success":"error"' && _contains "$result" '"error":"record_exists"'; then
+ _info "Record already exists."
+ else
+ _err "Can't add $subdomain to $domain."
+ return 1
+ fi
+ fi
}
#Usage: dns_myapi_rm _acme-challenge.www.domain.com
dns_yandex_rm() {
fulldomain="${1}"
_debug "Calling: dns_yandex_rm() '${fulldomain}'"
+
_PDD_credentials || return 1
- export _H1="PddToken: $PDD_Token"
_PDD_get_domain "$fulldomain" || return 1
- _debug "Found suitable domain in pdd: $curDomain"
+ _debug "Found suitable domain: $domain"
- record_id=$(pdd_get_record_id "${fulldomain}")
- _debug "Result: $record_id"
+ _PDD_get_record_ids "${domain}" "${subdomain}" || return 1
+ _debug "Record_ids: $record_ids"
- for rec_i in $record_id; do
- curUri="https://pddimp.yandex.ru/api2/admin/dns/del"
- curData="domain=${curDomain}&record_id=${rec_i}"
- curResult="$(_post "${curData}" "${curUri}")"
- _debug "Result: $curResult"
+ for record_id in $record_ids; do
+ data="domain=${domain}&record_id=${record_id}"
+ uri="https://pddimp.yandex.ru/api2/admin/dns/del"
+ result="$(_post "${data}" "${uri}" | _normalizeJson)"
+ _debug "Result: $result"
+
+ if ! _contains "$result" '"success":"ok"'; then
+ _info "Can't remove $subdomain from $domain."
+ fi
done
}
#################### Private functions below ##################################
_PDD_get_domain() {
- fulldomain="${1}"
- __page=1
- __last=0
- while [ $__last -eq 0 ]; do
- uri1="https://pddimp.yandex.ru/api2/admin/domain/domains?page=${__page}&on_page=20"
- res1="$(_get "$uri1" | _normalizeJson)"
- _debug2 "res1" "$res1"
- __found="$(echo "$res1" | sed -n -e 's#.* "found": \([^,]*\),.*#\1#p')"
- _debug "found: $__found results on page"
- if [ "0$__found" -lt 20 ]; then
- _debug "last page: $__page"
- __last=1
+ subdomain_start=1
+ while true; do
+ domain_start=$(_math $subdomain_start + 1)
+ domain=$(echo "$fulldomain" | cut -d . -f "$domain_start"-)
+ subdomain=$(echo "$fulldomain" | cut -d . -f -"$subdomain_start")
+
+ _debug "Checking domain $domain"
+ if [ -z "$domain" ]; then
+ return 1
fi
- __all_domains="$__all_domains $(echo "$res1" | tr "," "\n" | grep '"name"' | cut -d: -f2 | sed -e 's@"@@g')"
+ uri="https://pddimp.yandex.ru/api2/admin/dns/list?domain=$domain"
+ result="$(_get "${uri}" | _normalizeJson)"
+ _debug "Result: $result"
- __page=$(_math $__page + 1)
- done
-
- k=2
- while [ $k -lt 10 ]; do
- __t=$(echo "$fulldomain" | cut -d . -f $k-100)
- _debug "finding zone for domain $__t"
- for d in $__all_domains; do
- if [ "$d" = "$__t" ]; then
- p=$(_math $k - 1)
- curSubdomain="$(echo "$fulldomain" | cut -d . -f "1-$p")"
- curDomain="$__t"
- return 0
- fi
- done
- k=$(_math $k + 1)
+ if _contains "$result" '"success":"ok"'; then
+ return 0
+ fi
+ subdomain_start=$(_math $subdomain_start + 1)
done
- _err "No suitable domain found in your account"
- return 1
}
_PDD_credentials() {
if [ -z "${PDD_Token}" ]; then
PDD_Token=""
- _err "You need to export PDD_Token=xxxxxxxxxxxxxxxxx"
- _err "You can get it at https://pddimp.yandex.ru/api2/admin/get_token"
+ _err "You need to export PDD_Token=xxxxxxxxxxxxxxxxx."
+ _err "You can get it at https://pddimp.yandex.ru/api2/admin/get_token."
return 1
else
_saveaccountconf PDD_Token "${PDD_Token}"
fi
+ export _H1="PddToken: $PDD_Token"
}
-pdd_get_record_id() {
- fulldomain="${1}"
+_PDD_get_record_ids() {
+ _debug "Check existing records for $subdomain"
+
+ uri="https://pddimp.yandex.ru/api2/admin/dns/list?domain=${domain}"
+ result="$(_get "${uri}" | _normalizeJson)"
+ _debug "Result: $result"
- _PDD_get_domain "$fulldomain"
- _debug "Found suitable domain in pdd: $curDomain"
+ if ! _contains "$result" '"success":"ok"'; then
+ return 1
+ fi
- curUri="https://pddimp.yandex.ru/api2/admin/dns/list?domain=${curDomain}"
- curResult="$(_get "${curUri}" | _normalizeJson)"
- _debug "Result: $curResult"
- echo "$curResult" | _egrep_o "{[^{]*\"content\":[^{]*\"subdomain\":\"${curSubdomain}\"" | sed -n -e 's#.* "record_id": \(.*\),[^,]*#\1#p'
+ record_ids=$(echo "$result" | _egrep_o "{[^{]*\"subdomain\":\"${subdomain}\"[^}]*}" | sed -n -e 's#.*"record_id": \([0-9]*\).*#\1#p')
}
diff --git a/dnsapi/dns_zone.sh b/dnsapi/dns_zone.sh
index 847e32cd..176fc494 100755
--- a/dnsapi/dns_zone.sh
+++ b/dnsapi/dns_zone.sh
@@ -136,10 +136,10 @@ _get_root() {
if [ -z "$h" ]; then
return 1
fi
- if ! _zone_rest GET "dns/$h/a"; then
+ if ! _zone_rest GET "dns/$h"; then
return 1
fi
- if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
+ if _contains "$response" "\"identificator\":\"$h\"" >/dev/null; then
_domain=$h
return 0
fi
diff --git a/notify/cqhttp.sh b/notify/cqhttp.sh
new file mode 100644
index 00000000..ac76f5b8
--- /dev/null
+++ b/notify/cqhttp.sh
@@ -0,0 +1,64 @@
+#!/usr/bin/env sh
+
+#Support for CQHTTP api. Push notification on CoolQ
+#CQHTTP_TOKEN="" Recommended to be not empty, QQ application token
+#CQHTTP_USER="" Required, QQ receiver ID
+#CQHTTP_APIROOT="" Required, CQHTTP Server URL (without slash suffix)
+#CQHTTP_CUSTOM_MSGHEAD="" Optional, custom message header
+
+CQHTTP_APIPATH="/send_private_msg"
+
+cqhttp_send() {
+ _subject="$1"
+ _content="$2"
+ _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+ _debug "_statusCode" "$_statusCode"
+
+ CQHTTP_TOKEN="${CQHTTP_TOKEN:-$(_readaccountconf_mutable CQHTTP_TOKEN)}"
+ if [ -z "$CQHTTP_TOKEN" ]; then
+ CQHTTP_TOKEN=""
+ _info "You didn't specify a CQHTTP application token yet, which is unsafe. Assuming it to be empty."
+ else
+ _saveaccountconf_mutable CQHTTP_TOKEN "$CQHTTP_TOKEN"
+ fi
+
+ CQHTTP_USER="${CQHTTP_USER:-$(_readaccountconf_mutable CQHTTP_USER)}"
+ if [ -z "$CQHTTP_USER" ]; then
+ CQHTTP_USER=""
+ _err "You didn't specify a QQ user yet."
+ return 1
+ fi
+ _saveaccountconf_mutable CQHTTP_USER "$CQHTTP_USER"
+
+ CQHTTP_APIROOT="${CQHTTP_APIROOT:-$(_readaccountconf_mutable CQHTTP_APIROOT)}"
+ if [ -z "$CQHTTP_APIROOT" ]; then
+ CQHTTP_APIROOT=""
+ _err "You didn't specify the API root yet."
+ return 1
+ fi
+ _saveaccountconf_mutable CQHTTP_APIROOT "$CQHTTP_APIROOT"
+
+ CQHTTP_CUSTOM_MSGHEAD="${CQHTTP_CUSTOM_MSGHEAD:-$(_readaccountconf_mutable CQHTTP_CUSTOM_MSGHEAD)}"
+ if [ -z "$CQHTTP_CUSTOM_MSGHEAD" ]; then
+ CQHTTP_CUSTOM_MSGHEAD="A message from acme.sh:"
+ else
+ _saveaccountconf_mutable CQHTTP_CUSTOM_MSGHEAD "$CQHTTP_CUSTOM_MSGHEAD"
+ fi
+
+ _access_token="$(printf "%s" "$CQHTTP_TOKEN" | _url_encode)"
+ _user_id="$(printf "%s" "$CQHTTP_USER" | _url_encode)"
+ _message="$(printf "$CQHTTP_CUSTOM_MSGHEAD %s\\n%s" "$_subject" "$_content" | _url_encode)"
+
+ _finalUrl="$CQHTTP_APIROOT$CQHTTP_APIPATH?access_token=$_access_token&user_id=$_user_id&message=$_message"
+ response="$(_get "$_finalUrl")"
+
+ if [ "$?" = "0" ] && _contains "$response" "\"retcode\":0,\"status\":\"ok\""; then
+ _info "QQ send success."
+ return 0
+ fi
+
+ _err "QQ send error."
+ _debug "URL" "$_finalUrl"
+ _debug "Response" "$response"
+ return 1
+}
diff --git a/notify/dingtalk.sh b/notify/dingtalk.sh
new file mode 100644
index 00000000..c547da6e
--- /dev/null
+++ b/notify/dingtalk.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env sh
+
+#Support dingtalk webhooks api
+
+#DINGTALK_WEBHOOK="xxxx"
+
+#optional
+#DINGTALK_KEYWORD="yyyy"
+
+#DINGTALK_SIGNING_KEY="SEC08ffdbd403cbc3fc8a65xxxxxxxxxxxxxxxxxxxx"
+
+# subject content statusCode
+dingtalk_send() {
+ _subject="$1"
+ _content="$2"
+ _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+ _debug "_subject" "$_subject"
+ _debug "_content" "$_content"
+ _debug "_statusCode" "$_statusCode"
+
+ DINGTALK_WEBHOOK="${DINGTALK_WEBHOOK:-$(_readaccountconf_mutable DINGTALK_WEBHOOK)}"
+ if [ -z "$DINGTALK_WEBHOOK" ]; then
+ DINGTALK_WEBHOOK=""
+ _err "You didn't specify a dingtalk webhooks DINGTALK_WEBHOOK yet."
+ _err "You can get yours from https://dingtalk.com"
+ return 1
+ fi
+ _saveaccountconf_mutable DINGTALK_WEBHOOK "$DINGTALK_WEBHOOK"
+
+ DINGTALK_KEYWORD="${DINGTALK_KEYWORD:-$(_readaccountconf_mutable DINGTALK_KEYWORD)}"
+ if [ "$DINGTALK_KEYWORD" ]; then
+ _saveaccountconf_mutable DINGTALK_KEYWORD "$DINGTALK_KEYWORD"
+ fi
+
+ # DINGTALK_SIGNING_KEY="${DINGTALK_SIGNING_KEY:-$(_readaccountconf_mutable DINGTALK_SIGNING_KEY)}"
+ # if [ -z "$DINGTALK_SIGNING_KEY" ]; then
+ # DINGTALK_SIGNING_KEY="value1"
+ # _info "The DINGTALK_SIGNING_KEY is not set, so use the default value1 as key."
+ # elif ! _hasfield "$_IFTTT_AVAIL_MSG_KEYS" "$DINGTALK_SIGNING_KEY"; then
+ # _err "The DINGTALK_SIGNING_KEY \"$DINGTALK_SIGNING_KEY\" is not available, should be one of $_IFTTT_AVAIL_MSG_KEYS"
+ # DINGTALK_SIGNING_KEY=""
+ # return 1
+ # else
+ # _saveaccountconf_mutable DINGTALK_SIGNING_KEY "$DINGTALK_SIGNING_KEY"
+ # fi
+
+ # if [ "$DINGTALK_SIGNING_KEY" = "$IFTTT_CONTENT_KEY" ]; then
+ # DINGTALK_SIGNING_KEY=""
+ # IFTTT_CONTENT_KEY=""
+ # _err "The DINGTALK_SIGNING_KEY must not be same as IFTTT_CONTENT_KEY."
+ # return 1
+ # fi
+
+ _content=$(echo "$_content" | _json_encode)
+ _subject=$(echo "$_subject" | _json_encode)
+ _data="{\"msgtype\": \"text\", \"text\": {\"content\": \"[$DINGTALK_KEYWORD]\n$_subject\n$_content\"}}"
+
+ response="$(_post "$_data" "$DINGTALK_WEBHOOK" "" "POST" "application/json")"
+
+ if [ "$?" = "0" ] && _contains "$response" "errmsg\":\"ok"; then
+ _info "dingtalk webhooks event fired success."
+ return 0
+ fi
+
+ _err "dingtalk webhooks event fired error."
+ _err "$response"
+ return 1
+}
diff --git a/notify/mail.sh b/notify/mail.sh
index ec9aa0de..d33fd0d2 100644
--- a/notify/mail.sh
+++ b/notify/mail.sh
@@ -6,6 +6,7 @@
#MAIL_FROM="yyyy@gmail.com"
#MAIL_TO="yyyy@gmail.com"
#MAIL_NOVALIDATE=""
+#MAIL_MSMTP_ACCOUNT=""
mail_send() {
_subject="$1"
@@ -76,18 +77,17 @@ mail_send() {
}
_mail_bin() {
- if [ -n "$MAIL_BIN" ]; then
- _MAIL_BIN="$MAIL_BIN"
- elif _exists "sendmail"; then
- _MAIL_BIN="sendmail"
- elif _exists "ssmtp"; then
- _MAIL_BIN="ssmtp"
- elif _exists "mutt"; then
- _MAIL_BIN="mutt"
- elif _exists "mail"; then
- _MAIL_BIN="mail"
- else
- _err "Please install sendmail, ssmtp, mutt or mail first."
+ _MAIL_BIN=""
+
+ for b in "$MAIL_BIN" sendmail ssmtp mutt mail msmtp; do
+ if _exists "$b"; then
+ _MAIL_BIN="$b"
+ break
+ fi
+ done
+
+ if [ -z "$_MAIL_BIN" ]; then
+ _err "Please install sendmail, ssmtp, mutt, mail or msmtp first."
return 1
fi
@@ -95,39 +95,44 @@ _mail_bin() {
}
_mail_cmnd() {
+ _MAIL_ARGS=""
+
case $(basename "$_MAIL_BIN") in
- sendmail)
- if [ -n "$MAIL_FROM" ]; then
- echo "'$_MAIL_BIN' -f '$MAIL_FROM' '$MAIL_TO'"
- else
- echo "'$_MAIL_BIN' '$MAIL_TO'"
- fi
- ;;
- ssmtp)
- echo "'$_MAIL_BIN' '$MAIL_TO'"
- ;;
- mutt | mail)
- echo "'$_MAIL_BIN' -s '$_subject' '$MAIL_TO'"
- ;;
- *)
- _err "Command $MAIL_BIN is not supported, use sendmail, ssmtp, mutt or mail."
- return 1
- ;;
+ sendmail)
+ if [ -n "$MAIL_FROM" ]; then
+ _MAIL_ARGS="-f '$MAIL_FROM'"
+ fi
+ ;;
+ mutt | mail)
+ _MAIL_ARGS="-s '$_subject'"
+ ;;
+ msmtp)
+ if [ -n "$MAIL_FROM" ]; then
+ _MAIL_ARGS="-f '$MAIL_FROM'"
+ fi
+
+ if [ -n "$MAIL_MSMTP_ACCOUNT" ]; then
+ _MAIL_ARGS="$_MAIL_ARGS -a '$MAIL_MSMTP_ACCOUNT'"
+ fi
+ ;;
+ *) ;;
esac
+
+ echo "'$_MAIL_BIN' $_MAIL_ARGS '$MAIL_TO'"
}
_mail_body() {
case $(basename "$_MAIL_BIN") in
- sendmail | ssmtp)
- if [ -n "$MAIL_FROM" ]; then
- echo "From: $MAIL_FROM"
- fi
-
- echo "To: $MAIL_TO"
- echo "Subject: $subject"
- echo "Content-Type: $contenttype"
- echo
- ;;
+ sendmail | ssmtp | msmtp)
+ if [ -n "$MAIL_FROM" ]; then
+ echo "From: $MAIL_FROM"
+ fi
+
+ echo "To: $MAIL_TO"
+ echo "Subject: $subject"
+ echo "Content-Type: $contenttype"
+ echo
+ ;;
esac
echo "$_content"
diff --git a/notify/mailgun.sh b/notify/mailgun.sh
index 4b6ee3ba..93cdf793 100644
--- a/notify/mailgun.sh
+++ b/notify/mailgun.sh
@@ -7,7 +7,7 @@
#MAILGUN_REGION="us|eu" #optional, use "us" as default
#MAILGUN_API_DOMAIN="xxxxxx.com" #optional, use the default sandbox domain
-#MAILGUN_FROM="xxx@xxxxx.com" #optional, use the default sendbox account
+#MAILGUN_FROM="xxx@xxxxx.com" #optional, use the default sandbox account
_MAILGUN_BASE_US="https://api.mailgun.net/v3"
_MAILGUN_BASE_EU="https://api.eu.mailgun.net/v3"
diff --git a/notify/teams.sh b/notify/teams.sh
new file mode 100644
index 00000000..1bc5ed08
--- /dev/null
+++ b/notify/teams.sh
@@ -0,0 +1,86 @@
+#!/usr/bin/env sh
+
+#Support Microsoft Teams webhooks
+
+#TEAMS_WEBHOOK_URL=""
+#TEAMS_THEME_COLOR=""
+#TEAMS_SUCCESS_COLOR=""
+#TEAMS_ERROR_COLOR=""
+#TEAMS_SKIP_COLOR=""
+
+teams_send() {
+ _subject="$1"
+ _content="$2"
+ _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+ _debug "_statusCode" "$_statusCode"
+
+ _color_success="2cbe4e" # green
+ _color_danger="cb2431" # red
+ _color_muted="586069" # gray
+
+ TEAMS_WEBHOOK_URL="${TEAMS_WEBHOOK_URL:-$(_readaccountconf_mutable TEAMS_WEBHOOK_URL)}"
+ if [ -z "$TEAMS_WEBHOOK_URL" ]; then
+ TEAMS_WEBHOOK_URL=""
+ _err "You didn't specify a Microsoft Teams webhook url TEAMS_WEBHOOK_URL yet."
+ return 1
+ fi
+ _saveaccountconf_mutable TEAMS_WEBHOOK_URL "$TEAMS_WEBHOOK_URL"
+
+ TEAMS_THEME_COLOR="${TEAMS_THEME_COLOR:-$(_readaccountconf_mutable TEAMS_THEME_COLOR)}"
+ if [ -n "$TEAMS_THEME_COLOR" ]; then
+ _saveaccountconf_mutable TEAMS_THEME_COLOR "$TEAMS_THEME_COLOR"
+ fi
+
+ TEAMS_SUCCESS_COLOR="${TEAMS_SUCCESS_COLOR:-$(_readaccountconf_mutable TEAMS_SUCCESS_COLOR)}"
+ if [ -n "$TEAMS_SUCCESS_COLOR" ]; then
+ _saveaccountconf_mutable TEAMS_SUCCESS_COLOR "$TEAMS_SUCCESS_COLOR"
+ fi
+
+ TEAMS_ERROR_COLOR="${TEAMS_ERROR_COLOR:-$(_readaccountconf_mutable TEAMS_ERROR_COLOR)}"
+ if [ -n "$TEAMS_ERROR_COLOR" ]; then
+ _saveaccountconf_mutable TEAMS_ERROR_COLOR "$TEAMS_ERROR_COLOR"
+ fi
+
+ TEAMS_SKIP_COLOR="${TEAMS_SKIP_COLOR:-$(_readaccountconf_mutable TEAMS_SKIP_COLOR)}"
+ if [ -n "$TEAMS_SKIP_COLOR" ]; then
+ _saveaccountconf_mutable TEAMS_SKIP_COLOR "$TEAMS_SKIP_COLOR"
+ fi
+
+ export _H1="Content-Type: application/json"
+
+ _subject=$(echo "$_subject" | _json_encode)
+ _content=$(echo "$_content" | _json_encode)
+
+ case "$_statusCode" in
+ 0)
+ _color="${TEAMS_SUCCESS_COLOR:-$_color_success}"
+ ;;
+ 1)
+ _color="${TEAMS_ERROR_COLOR:-$_color_danger}"
+ ;;
+ 2)
+ _color="${TEAMS_SKIP_COLOR:-$_color_muted}"
+ ;;
+ esac
+
+ _color=$(echo "$_color" | tr -cd 'a-fA-F0-9')
+ if [ -z "$_color" ]; then
+ _color=$(echo "${TEAMS_THEME_COLOR:-$_color_muted}" | tr -cd 'a-fA-F0-9')
+ fi
+
+ _data="{\"title\": \"$_subject\","
+ if [ -n "$_color" ]; then
+ _data="$_data\"themeColor\": \"$_color\", "
+ fi
+ _data="$_data\"text\": \"$_content\"}"
+
+ if response=$(_post "$_data" "$TEAMS_WEBHOOK_URL"); then
+ if ! _contains "$response" error; then
+ _info "teams send success."
+ return 0
+ fi
+ fi
+ _err "teams send error."
+ _err "$response"
+ return 1
+}
diff --git a/notify/xmpp.sh b/notify/xmpp.sh
new file mode 100644
index 00000000..0ce1119e
--- /dev/null
+++ b/notify/xmpp.sh
@@ -0,0 +1,90 @@
+#!/usr/bin/env sh
+
+#Support xmpp via sendxmpp
+
+#XMPP_BIN="/usr/bin/sendxmpp"
+#XMPP_BIN_ARGS="-n -t --tls-ca-path=/etc/ssl/certs"
+#XMPP_TO="zzzz@example.com"
+
+xmpp_send() {
+ _subject="$1"
+ _content="$2"
+ _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+ _debug "_subject" "$_subject"
+ _debug "_content" "$_content"
+ _debug "_statusCode" "$_statusCode"
+
+ XMPP_BIN="${XMPP_BIN:-$(_readaccountconf_mutable XMPP_BIN)}"
+ if [ -n "$XMPP_BIN" ] && ! _exists "$XMPP_BIN"; then
+ _err "It seems that the command $XMPP_BIN is not in path."
+ return 1
+ fi
+ _XMPP_BIN=$(_xmpp_bin)
+ if [ -n "$XMPP_BIN" ]; then
+ _saveaccountconf_mutable XMPP_BIN "$XMPP_BIN"
+ else
+ _clearaccountconf "XMPP_BIN"
+ fi
+
+ XMPP_BIN_ARGS="${XMPP_BIN_ARGS:-$(_readaccountconf_mutable XMPP_BIN_ARGS)}"
+ if [ -n "$XMPP_BIN_ARGS" ]; then
+ _saveaccountconf_mutable XMPP_BIN_ARGS "$XMPP_BIN_ARGS"
+ else
+ _clearaccountconf "XMPP_BIN_ARGS"
+ fi
+
+ XMPP_TO="${XMPP_TO:-$(_readaccountconf_mutable XMPP_TO)}"
+ if [ -n "$XMPP_TO" ]; then
+ if ! _xmpp_valid "$XMPP_TO"; then
+ _err "It seems that the XMPP_TO=$XMPP_TO is not a valid xmpp address."
+ return 1
+ fi
+
+ _saveaccountconf_mutable XMPP_TO "$XMPP_TO"
+ fi
+
+ result=$({ _xmpp_message | eval "$(_xmpp_cmnd)"; } 2>&1)
+
+ # shellcheck disable=SC2181
+ if [ $? -ne 0 ]; then
+ _debug "xmpp send error."
+ _err "$result"
+ return 1
+ fi
+
+ _debug "xmpp send success."
+ return 0
+}
+
+_xmpp_bin() {
+ if [ -n "$XMPP_BIN" ]; then
+ _XMPP_BIN="$XMPP_BIN"
+ elif _exists "sendxmpp"; then
+ _XMPP_BIN="sendxmpp"
+ else
+ _err "Please install sendxmpp first."
+ return 1
+ fi
+
+ echo "$_XMPP_BIN"
+}
+
+_xmpp_cmnd() {
+ case $(basename "$_XMPP_BIN") in
+ sendxmpp)
+ echo "'$_XMPP_BIN' '$XMPP_TO' $XMPP_BIN_ARGS"
+ ;;
+ *)
+ _err "Command $XMPP_BIN is not supported, use sendxmpp."
+ return 1
+ ;;
+ esac
+}
+
+_xmpp_message() {
+ echo "$_subject"
+}
+
+_xmpp_valid() {
+ _contains "$1" "@"
+}