|')"
+ _debug2 table "${table}"
+ names=$(echo "${table}" | _egrep_o 'id="[0-9]+\.name">[^<]*' | sed 's|||; s|.*>||')
+ ids=$(echo "${table}" | _egrep_o 'id="[0-9]+\.name">[^<]*' | sed 's|\.name">.*||; s|id="||')
+ types=$(echo "${table}" | _egrep_o 'id="[0-9]+\.type">[^<]*' | sed 's|||; s|.*>||')
+ values=$(echo "${table}" | _egrep_o 'id="[0-9]+\.content">[^<]*' | sed 's|||; s|.*>||')
+
+ _debug2 names "${names}"
+ _debug2 ids "${ids}"
+ _debug2 types "${types}"
+ _debug2 values "${values}"
+
+ # look for line whose name is ${full_domain}, whose type is TXT, and whose value is ${txt_value}
+ line_num="$(echo "${values}" | grep -F -n -- "${txt_value}" | _head_n 1 | cut -d ':' -f 1)"
+ _debug2 line_num "${line_num}"
+ found_id=
+ if [ -n "$line_num" ]; then
+ type=$(echo "${types}" | sed -n "${line_num}p")
+ name=$(echo "${names}" | sed -n "${line_num}p")
+ id=$(echo "${ids}" | sed -n "${line_num}p")
+
+ _debug2 type "$type"
+ _debug2 name "$name"
+ _debug2 id "$id"
+ _debug2 full_domain "$full_domain"
+
+ if [ "${type}" = "TXT" ] && [ "${name}" = "${full_domain}" ]; then
+ found_id=${id}
+ fi
+ fi
+
+ if [ "${found_id}" = "" ]; then
+ _err "Can not find record id."
+ return 0
+ fi
+
+ # Remove the record
+ body="id=${zone_id}&record_id=${found_id}"
+ response=$(do_post "$body" "https://www.geoscaling.com/dns2/ajax/delete_record.php")
+ exit_code="$?"
+ if [ "$exit_code" -eq 0 ]; then
+ _info "Record removed successfully."
+ else
+ _err "Could not clean (remove) up the record. Please go to Geoscaling administration interface and clean it by hand."
+ fi
+ do_logout
+ return "${exit_code}"
+}
+
+########################## PRIVATE FUNCTIONS ###########################
+
+do_get() {
+ _url=$1
+ export _H1="Cookie: $geoscaling_phpsessid_cookie"
+ _get "${_url}"
+}
+
+do_post() {
+ _body=$1
+ _url=$2
+ export _H1="Cookie: $geoscaling_phpsessid_cookie"
+ _post "${_body}" "${_url}"
+}
+
+do_login() {
+
+ _info "Logging in..."
+
+ username_encoded="$(printf "%s" "${GEOSCALING_Username}" | _url_encode)"
+ password_encoded="$(printf "%s" "${GEOSCALING_Password}" | _url_encode)"
+ body="username=${username_encoded}&password=${password_encoded}"
+
+ response=$(_post "$body" "https://www.geoscaling.com/dns2/index.php?module=auth")
+ _debug2 response "${response}"
+
+ #retcode=$(grep '^HTTP[^ ]*' "${HTTP_HEADER}" | _head_n 1 | _egrep_o '[0-9]+$')
+ retcode=$(grep '^HTTP[^ ]*' "${HTTP_HEADER}" | _head_n 1 | cut -d ' ' -f 2)
+
+ if [ "$retcode" != "302" ]; then
+ _err "Geoscaling login failed for user ${GEOSCALING_Username}. Check ${HTTP_HEADER} file"
+ return 1
+ fi
+
+ geoscaling_phpsessid_cookie="$(grep -i '^set-cookie:' "${HTTP_HEADER}" | _egrep_o 'PHPSESSID=[^;]*;' | tr -d ';')"
+ return 0
+
+}
+
+do_logout() {
+ _info "Logging out."
+ response="$(do_get "https://www.geoscaling.com/dns2/index.php?module=auth")"
+ _debug2 response "$response"
+ return 0
+}
+
+find_zone() {
+ domain="$1"
+
+ # do login
+ do_login || return 1
+
+ # get zones
+ response="$(do_get "https://www.geoscaling.com/dns2/index.php?module=domains")"
+
+ table="$(echo "${response}" | tr -d '\n' | sed 's|.*
Your domains
|')"
+ _debug2 table "${table}"
+ zone_names="$(echo "${table}" | _egrep_o '
[^<]*' | sed 's|
||;s|||')"
+ _debug2 _matches "${zone_names}"
+ # Zone names and zone IDs are in same order
+ zone_ids=$(echo "${table}" | _egrep_o '
' | sed 's|.*id=||;s|. .*||')
+
+ _debug2 "These are the zones on this Geoscaling account:"
+ _debug2 "zone_names" "${zone_names}"
+ _debug2 "And these are their respective IDs:"
+ _debug2 "zone_ids" "${zone_ids}"
+ if [ -z "${zone_names}" ] || [ -z "${zone_ids}" ]; then
+ _err "Can not get zone names or IDs."
+ return 1
+ fi
+ # Walk through all possible zone names
+ strip_counter=1
+ while true; do
+ attempted_zone=$(echo "${domain}" | cut -d . -f ${strip_counter}-)
+
+ # All possible zone names have been tried
+ if [ -z "${attempted_zone}" ]; then
+ _err "No zone for domain '${domain}' found."
+ return 1
+ fi
+
+ _debug "Looking for zone '${attempted_zone}'"
+
+ line_num="$(echo "${zone_names}" | grep -n "^${attempted_zone}\$" | _head_n 1 | cut -d : -f 1)"
+ _debug2 line_num "${line_num}"
+ if [ "$line_num" ]; then
+ zone_id=$(echo "${zone_ids}" | sed -n "${line_num}p")
+ zone_name=$(echo "${zone_names}" | sed -n "${line_num}p")
+ if [ -z "${zone_id}" ]; then
+ _err "Can not find zone id."
+ return 1
+ fi
+ _debug "Found relevant zone '${attempted_zone}' with id '${zone_id}' - will be used for domain '${domain}'."
+ return 0
+ fi
+
+ _debug "Zone '${attempted_zone}' doesn't exist, let's try a less specific zone."
+ strip_counter=$(_math "${strip_counter}" + 1)
+ done
+}
+# vim: et:ts=2:sw=2:
diff --git a/dnsapi/dns_he.sh b/dnsapi/dns_he.sh
index ef09fa0a..bf4a5030 100755
--- a/dnsapi/dns_he.sh
+++ b/dnsapi/dns_he.sh
@@ -85,7 +85,7 @@ dns_he_rm() {
_debug "The txt record is not found, just skip"
return 0
fi
- _record_id="$(echo "$response" | tr -d "#" | sed "s//dev/null
_code=$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")
_token=$(grep "^X-Subject-Token" "$HTTP_HEADER" | cut -d " " -f 2-)
- _debug2 "${_code}"
+ _secure_debug "${_code}"
printf "%s" "${_token}"
return 0
}
diff --git a/dnsapi/dns_infoblox.sh b/dnsapi/dns_infoblox.sh
index 4cbb2146..6bfd36ee 100644
--- a/dnsapi/dns_infoblox.sh
+++ b/dnsapi/dns_infoblox.sh
@@ -9,7 +9,6 @@ dns_infoblox_add() {
## Nothing to see here, just some housekeeping
fulldomain=$1
txtvalue=$2
- baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=$Infoblox_View"
_info "Using Infoblox API"
_debug fulldomain "$fulldomain"
@@ -19,12 +18,13 @@ dns_infoblox_add() {
if [ -z "$Infoblox_Creds" ] || [ -z "$Infoblox_Server" ]; then
Infoblox_Creds=""
Infoblox_Server=""
- _err "You didn't specify the credentials, server or infoblox view yet (Infoblox_Creds, Infoblox_Server and Infoblox_View)."
- _err "Please set them via EXPORT ([username:password], [ip or hostname]) and try again."
+ _err "You didn't specify the Infoblox credentials or server (Infoblox_Creds; Infoblox_Server)."
+ _err "Please set them via EXPORT Infoblox_Creds=username:password or EXPORT Infoblox_server=ip/hostname and try again."
return 1
fi
if [ -z "$Infoblox_View" ]; then
+ _info "No Infoblox_View set, using fallback value 'default'"
Infoblox_View="default"
fi
@@ -33,6 +33,9 @@ dns_infoblox_add() {
_saveaccountconf Infoblox_Server "$Infoblox_Server"
_saveaccountconf Infoblox_View "$Infoblox_View"
+ ## URLencode Infoblox View to deal with e.g. spaces
+ Infoblox_ViewEncoded=$(printf "%b" "$Infoblox_View" | _url_encode)
+
## Base64 encode the credentials
Infoblox_CredsEncoded=$(printf "%b" "$Infoblox_Creds" | _base64)
@@ -40,11 +43,14 @@ dns_infoblox_add() {
export _H1="Accept-Language:en-US"
export _H2="Authorization: Basic $Infoblox_CredsEncoded"
+ ## Construct the request URL
+ baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=${Infoblox_ViewEncoded}"
+
## Add the challenge record to the Infoblox grid member
result="$(_post "" "$baseurlnObject" "" "POST")"
## Let's see if we get something intelligible back from the unit
- if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/$Infoblox_View")" ]; then
+ if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/${Infoblox_ViewEncoded}")" ]; then
_info "Successfully created the txt record"
return 0
else
@@ -65,6 +71,9 @@ dns_infoblox_rm() {
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
+ ## URLencode Infoblox View to deal with e.g. spaces
+ Infoblox_ViewEncoded=$(printf "%b" "$Infoblox_View" | _url_encode)
+
## Base64 encode the credentials
Infoblox_CredsEncoded="$(printf "%b" "$Infoblox_Creds" | _base64)"
@@ -73,18 +82,18 @@ dns_infoblox_rm() {
export _H2="Authorization: Basic $Infoblox_CredsEncoded"
## Does the record exist? Let's check.
- baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=$Infoblox_View&_return_type=xml-pretty"
+ baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=${Infoblox_ViewEncoded}&_return_type=xml-pretty"
result="$(_get "$baseurlnObject")"
## Let's see if we get something intelligible back from the grid
- if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/$Infoblox_View")" ]; then
+ if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/${Infoblox_ViewEncoded}")" ]; then
## Extract the object reference
- objRef="$(printf "%b" "$result" | _egrep_o "record:txt/.*:.*/$Infoblox_View")"
+ objRef="$(printf "%b" "$result" | _egrep_o "record:txt/.*:.*/${Infoblox_ViewEncoded}")"
objRmUrl="https://$Infoblox_Server/wapi/v2.2.2/$objRef"
## Delete them! All the stale records!
rmResult="$(_post "" "$objRmUrl" "" "DELETE")"
## Let's see if that worked
- if [ "$(echo "$rmResult" | _egrep_o "record:txt/.*:.*/$Infoblox_View")" ]; then
+ if [ "$(echo "$rmResult" | _egrep_o "record:txt/.*:.*/${Infoblox_ViewEncoded}")" ]; then
_info "Successfully deleted $objRef"
return 0
else
diff --git a/dnsapi/dns_ionos.sh b/dnsapi/dns_ionos.sh
new file mode 100755
index 00000000..c2c431bb
--- /dev/null
+++ b/dnsapi/dns_ionos.sh
@@ -0,0 +1,163 @@
+#!/usr/bin/env sh
+
+# Supports IONOS DNS API Beta v1.0.0
+#
+# Usage:
+# Export IONOS_PREFIX and IONOS_SECRET before calling acme.sh:
+#
+# $ export IONOS_PREFIX="..."
+# $ export IONOS_SECRET="..."
+#
+# $ acme.sh --issue --dns dns_ionos ...
+
+IONOS_API="https://api.hosting.ionos.com/dns"
+IONOS_ROUTE_ZONES="/v1/zones"
+
+IONOS_TXT_TTL=60 # minimum accepted by API
+IONOS_TXT_PRIO=10
+
+dns_ionos_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ if ! _ionos_init; then
+ return 1
+ fi
+
+ _body="[{\"name\":\"$_sub_domain.$_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":$IONOS_TXT_TTL,\"prio\":$IONOS_TXT_PRIO,\"disabled\":false}]"
+
+ if _ionos_rest POST "$IONOS_ROUTE_ZONES/$_zone_id/records" "$_body" && [ -z "$response" ]; then
+ _info "TXT record has been created successfully."
+ return 0
+ fi
+
+ return 1
+}
+
+dns_ionos_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ if ! _ionos_init; then
+ return 1
+ fi
+
+ if ! _ionos_get_record "$fulldomain" "$_zone_id" "$txtvalue"; then
+ _err "Could not find _acme-challenge TXT record."
+ return 1
+ fi
+
+ if _ionos_rest DELETE "$IONOS_ROUTE_ZONES/$_zone_id/records/$_record_id" && [ -z "$response" ]; then
+ _info "TXT record has been deleted successfully."
+ return 0
+ fi
+
+ return 1
+}
+
+_ionos_init() {
+ IONOS_PREFIX="${IONOS_PREFIX:-$(_readaccountconf_mutable IONOS_PREFIX)}"
+ IONOS_SECRET="${IONOS_SECRET:-$(_readaccountconf_mutable IONOS_SECRET)}"
+
+ if [ -z "$IONOS_PREFIX" ] || [ -z "$IONOS_SECRET" ]; then
+ _err "You didn't specify an IONOS api prefix and secret yet."
+ _err "Read https://beta.developer.hosting.ionos.de/docs/getstarted to learn how to get a prefix and secret."
+ _err ""
+ _err "Then set them before calling acme.sh:"
+ _err "\$ export IONOS_PREFIX=\"...\""
+ _err "\$ export IONOS_SECRET=\"...\""
+ _err "\$ acme.sh --issue -d ... --dns dns_ionos"
+ return 1
+ fi
+
+ _saveaccountconf_mutable IONOS_PREFIX "$IONOS_PREFIX"
+ _saveaccountconf_mutable IONOS_SECRET "$IONOS_SECRET"
+
+ if ! _get_root "$fulldomain"; then
+ _err "Cannot find this domain in your IONOS account."
+ return 1
+ fi
+}
+
+_get_root() {
+ domain=$1
+ i=1
+ p=1
+
+ if _ionos_rest GET "$IONOS_ROUTE_ZONES"; then
+ response="$(echo "$response" | tr -d "\n")"
+
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ if [ -z "$h" ]; then
+ return 1
+ fi
+
+ _zone="$(echo "$response" | _egrep_o "\"name\":\"$h\".*\}")"
+ if [ "$_zone" ]; then
+ _zone_id=$(printf "%s\n" "$_zone" | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"')
+ if [ "$_zone_id" ]; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain=$h
+
+ return 0
+ fi
+
+ return 1
+ fi
+
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ fi
+
+ return 1
+}
+
+_ionos_get_record() {
+ fulldomain=$1
+ zone_id=$2
+ txtrecord=$3
+
+ if _ionos_rest GET "$IONOS_ROUTE_ZONES/$zone_id?recordName=$fulldomain&recordType=TXT"; then
+ response="$(echo "$response" | tr -d "\n")"
+
+ _record="$(echo "$response" | _egrep_o "\"name\":\"$fulldomain\"[^\}]*\"type\":\"TXT\"[^\}]*\"content\":\"\\\\\"$txtrecord\\\\\"\".*\}")"
+ if [ "$_record" ]; then
+ _record_id=$(printf "%s\n" "$_record" | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"')
+
+ return 0
+ fi
+ fi
+
+ return 1
+}
+
+_ionos_rest() {
+ method="$1"
+ route="$2"
+ data="$3"
+
+ IONOS_API_KEY="$(printf "%s.%s" "$IONOS_PREFIX" "$IONOS_SECRET")"
+
+ export _H1="X-API-Key: $IONOS_API_KEY"
+
+ if [ "$method" != "GET" ]; then
+ export _H2="Accept: application/json"
+ export _H3="Content-Type: application/json"
+
+ response="$(_post "$data" "$IONOS_API$route" "" "$method" "application/json")"
+ else
+ export _H2="Accept: */*"
+ export _H3=
+ response="$(_get "$IONOS_API$route")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "Error $route: $response"
+ return 1
+ fi
+ _debug2 "response" "$response"
+
+ return 0
+}
diff --git a/dnsapi/dns_ispconfig.sh b/dnsapi/dns_ispconfig.sh
index bd1e0391..e68ddd49 100755
--- a/dnsapi/dns_ispconfig.sh
+++ b/dnsapi/dns_ispconfig.sh
@@ -75,7 +75,7 @@ _ISPC_getZoneInfo() {
# suffix . needed for zone -> domain.tld.
curData="{\"session_id\":\"${sessionID}\",\"primary_id\":{\"origin\":\"${curZone}.\"}}"
curResult="$(_post "${curData}" "${ISPC_Api}?dns_zone_get")"
- _debug "Calling _ISPC_getZoneInfo: '${curData}' '${ISPC_Api}?login'"
+ _debug "Calling _ISPC_getZoneInfo: '${curData}' '${ISPC_Api}?dns_zone_get'"
_debug "Result of _ISPC_getZoneInfo: '$curResult'"
if _contains "${curResult}" '"id":"'; then
zoneFound=true
@@ -110,18 +110,32 @@ _ISPC_getZoneInfo() {
;;
*) _info "Retrieved Zone ID" ;;
esac
- client_id=$(echo "${curResult}" | _egrep_o "sys_userid.*" | cut -d ':' -f 2 | cut -d '"' -f 2)
- _debug "Client ID: '${client_id}'"
- case "${client_id}" in
+ sys_userid=$(echo "${curResult}" | _egrep_o "sys_userid.*" | cut -d ':' -f 2 | cut -d '"' -f 2)
+ _debug "SYS User ID: '${sys_userid}'"
+ case "${sys_userid}" in
'' | *[!0-9]*)
- _err "Client ID is not numeric."
+ _err "SYS User ID is not numeric."
return 1
;;
- *) _info "Retrieved Client ID." ;;
+ *) _info "Retrieved SYS User ID." ;;
esac
zoneFound=""
zoneEnd=""
fi
+ # Need to get client_id as it is different from sys_userid
+ curData="{\"session_id\":\"${sessionID}\",\"sys_userid\":\"${sys_userid}\"}"
+ curResult="$(_post "${curData}" "${ISPC_Api}?client_get_id")"
+ _debug "Calling _ISPC_ClientGetID: '${curData}' '${ISPC_Api}?client_get_id'"
+ _debug "Result of _ISPC_ClientGetID: '$curResult'"
+ client_id=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2 | tr -d '{}')
+ _debug "Client ID: '${client_id}'"
+ case "${client_id}" in
+ '' | *[!0-9]*)
+ _err "Client ID is not numeric."
+ return 1
+ ;;
+ *) _info "Retrieved Client ID." ;;
+ esac
}
_ISPC_addTxt() {
diff --git a/dnsapi/dns_knot.sh b/dnsapi/dns_knot.sh
index 094a6981..729a89cb 100644
--- a/dnsapi/dns_knot.sh
+++ b/dnsapi/dns_knot.sh
@@ -19,8 +19,9 @@ dns_knot_add() {
_info "Adding ${fulldomain}. 60 TXT \"${txtvalue}\""
- knsupdate -y "${KNOT_KEY}" <%s
- ' "$LOOPIA_User" "$LOOPIA_Password" "$_domain" "$_sub_domain")
+ ' "$LOOPIA_User" "$Encoded_Password" "$_domain" "$_sub_domain")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
if ! _contains "$response" "OK"; then
- _err "Error could not get txt records"
+ err_response=$(echo "$response" | grep -oPm1 "(?<=)[^<]+")
+ _err "Error could not get txt records: $err_response"
return 1
fi
}
@@ -101,6 +106,12 @@ _loopia_load_config() {
return 1
fi
+ if _contains "$LOOPIA_Password" "'" || _contains "$LOOPIA_Password" '"'; then
+ _err "Password contains quoute or double quoute and this is not supported by dns_loopia.sh"
+ return 1
+ fi
+
+ Encoded_Password=$(_xml_encode "$LOOPIA_Password")
return 0
}
@@ -133,11 +144,12 @@ _loopia_get_records() {
%s
- ' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain")
+ ' "$LOOPIA_User" "$Encoded_Password" "$domain" "$sub_domain")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
if ! _contains "$response" ""; then
- _err "Error"
+ err_response=$(echo "$response" | grep -oPm1 "(?<=)[^<]+")
+ _err "Error: $err_response"
return 1
fi
return 0
@@ -162,7 +174,7 @@ _get_root() {
%s
- ' $LOOPIA_User $LOOPIA_Password)
+ ' "$LOOPIA_User" "$Encoded_Password")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
while true; do
@@ -206,32 +218,35 @@ _loopia_add_record() {
%s
-
-
- type
- TXT
-
-
- priority
- 0
-
-
- ttl
- 300
-
-
- rdata
- %s
-
-
+
+
+
+ type
+ TXT
+
+
+ priority
+ 0
+
+
+ ttl
+ 300
+
+
+ rdata
+ %s
+
+
+
- ' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain" "$txtval")
+ ' "$LOOPIA_User" "$Encoded_Password" "$domain" "$sub_domain" "$txtval")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
if ! _contains "$response" "OK"; then
- _err "Error"
+ err_response=$(echo "$response" | grep -oPm1 "(?<=)[^<]+")
+ _err "Error: $err_response"
return 1
fi
return 0
@@ -255,7 +270,7 @@ _sub_domain_exists() {
%s
- ' $LOOPIA_User $LOOPIA_Password "$domain")
+ ' "$LOOPIA_User" "$Encoded_Password" "$domain")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
@@ -290,13 +305,22 @@ _loopia_add_sub_domain() {
%s
- ' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain")
+ ' "$LOOPIA_User" "$Encoded_Password" "$domain" "$sub_domain")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
if ! _contains "$response" "OK"; then
- _err "Error"
+ err_response=$(echo "$response" | grep -oPm1 "(?<=)[^<]+")
+ _err "Error: $err_response"
return 1
fi
return 0
}
+
+_xml_encode() {
+ encoded_string=$1
+ encoded_string=$(echo "$encoded_string" | sed 's/&/\&/')
+ encoded_string=$(echo "$encoded_string" | sed 's/\</')
+ encoded_string=$(echo "$encoded_string" | sed 's/>/\>/')
+ printf "%s" "$encoded_string"
+}
diff --git a/dnsapi/dns_mythic_beasts.sh b/dnsapi/dns_mythic_beasts.sh
new file mode 100755
index 00000000..294ae84c
--- /dev/null
+++ b/dnsapi/dns_mythic_beasts.sh
@@ -0,0 +1,261 @@
+#!/usr/bin/env sh
+# Mythic Beasts is a long-standing UK service provider using standards-based OAuth2 authentication
+# To test: ./acme.sh --dns dns_mythic_beasts --test --debug 1 --output-insecure --issue --domain domain.com
+# Cannot retest once cert is issued
+# OAuth2 tokens only valid for 300 seconds so we do not store
+# NOTE: This will remove all TXT records matching the fulldomain, not just the added ones (_acme-challenge.www.domain.com)
+
+# Test OAuth2 credentials
+#MB_AK="aaaaaaaaaaaaaaaa"
+#MB_AS="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
+
+# URLs
+MB_API='https://api.mythic-beasts.com/dns/v2/zones'
+MB_AUTH='https://auth.mythic-beasts.com/login'
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_mythic_beasts_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _info "MYTHIC BEASTS Adding record $fulldomain = $txtvalue"
+ if ! _initAuth; then
+ return 1
+ fi
+
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+
+ # method path body_data
+ if _mb_rest POST "$_domain/records/$_sub_domain/TXT" "$txtvalue"; then
+
+ if _contains "$response" "1 records added"; then
+ _info "Added, verifying..."
+ # Max 120 seconds to publish
+ for i in $(seq 1 6); do
+ # Retry on error
+ if ! _mb_rest GET "$_domain/records/$_sub_domain/TXT?verify"; then
+ _sleep 20
+ else
+ _info "Record published!"
+ return 0
+ fi
+ done
+
+ else
+ _err "\n$response"
+ fi
+
+ fi
+ _err "Add txt record error."
+ return 1
+}
+
+#Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_mythic_beasts_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _info "MYTHIC BEASTS Removing record $fulldomain = $txtvalue"
+ if ! _initAuth; then
+ return 1
+ fi
+
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+
+ # method path body_data
+ if _mb_rest DELETE "$_domain/records/$_sub_domain/TXT" "$txtvalue"; then
+ _info "Record removed"
+ return 0
+ fi
+ _err "Remove txt record error."
+ return 1
+}
+
+#################### Private functions below ##################################
+
+#Possible formats:
+# _acme-challenge.www.example.com
+# _acme-challenge.example.com
+# _acme-challenge.example.co.uk
+# _acme-challenge.www.example.co.uk
+# _acme-challenge.sub1.sub2.www.example.co.uk
+# sub1.sub2.example.co.uk
+# example.com
+# example.co.uk
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ domain=$1
+ i=1
+ p=1
+
+ _debug "Detect the root zone"
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ if [ -z "$h" ]; then
+ _err "Domain exhausted"
+ return 1
+ fi
+
+ # Use the status errors to find the domain, continue on 403 Access denied
+ # method path body_data
+ _mb_rest GET "$h/records"
+ ret="$?"
+ if [ "$ret" -eq 0 ]; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="$h"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+ return 0
+ elif [ "$ret" -eq 1 ]; then
+ return 1
+ fi
+
+ p=$i
+ i=$(_math "$i" + 1)
+
+ if [ "$i" -gt 50 ]; then
+ break
+ fi
+ done
+ _err "Domain too long"
+ return 1
+}
+
+_initAuth() {
+ MB_AK="${MB_AK:-$(_readaccountconf_mutable MB_AK)}"
+ MB_AS="${MB_AS:-$(_readaccountconf_mutable MB_AS)}"
+
+ if [ -z "$MB_AK" ] || [ -z "$MB_AS" ]; then
+ MB_AK=""
+ MB_AS=""
+ _err "Please specify an OAuth2 Key & Secret"
+ return 1
+ fi
+
+ _saveaccountconf_mutable MB_AK "$MB_AK"
+ _saveaccountconf_mutable MB_AS "$MB_AS"
+
+ if ! _oauth2; then
+ return 1
+ fi
+
+ _info "Checking authentication"
+ _secure_debug access_token "$MB_TK"
+ _sleep 1
+
+ # GET a list of zones
+ # method path body_data
+ if ! _mb_rest GET ""; then
+ _err "The token is invalid"
+ return 1
+ fi
+ _info "Token OK"
+ return 0
+}
+
+# Github appears to use an outbound proxy for requests which means subsequent requests may not have the same
+# source IP. The standard Mythic Beasts OAuth2 tokens are tied to an IP, meaning github test requests fail
+# authentication. This is a work around using an undocumented MB API to obtain a token not tied to an
+# IP just for the github tests.
+_oauth2() {
+ if [ "$GITHUB_ACTIONS" = "true" ]; then
+ _oauth2_github
+ else
+ _oauth2_std
+ fi
+ return $?
+}
+
+_oauth2_std() {
+ # HTTP Basic Authentication
+ _H1="Authorization: Basic $(echo "$MB_AK:$MB_AS" | _base64)"
+ _H2="Accepts: application/json"
+ export _H1 _H2
+ body="grant_type=client_credentials"
+
+ _info "Getting OAuth2 token..."
+ # body url [needbase64] [POST|PUT|DELETE] [ContentType]
+ response="$(_post "$body" "$MB_AUTH" "" "POST" "application/x-www-form-urlencoded")"
+ if _contains "$response" "\"token_type\":\"bearer\""; then
+ MB_TK="$(echo "$response" | _egrep_o "access_token\":\"[^\"]*\"" | cut -d : -f 2 | tr -d '"')"
+ if [ -z "$MB_TK" ]; then
+ _err "Unable to get access_token"
+ _err "\n$response"
+ return 1
+ fi
+ else
+ _err "OAuth2 token_type not Bearer"
+ _err "\n$response"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
+
+_oauth2_github() {
+ _H1="Accepts: application/json"
+ export _H1
+ body="{\"login\":{\"handle\":\"$MB_AK\",\"pass\":\"$MB_AS\",\"floating\":1}}"
+
+ _info "Getting Floating token..."
+ # body url [needbase64] [POST|PUT|DELETE] [ContentType]
+ response="$(_post "$body" "$MB_AUTH" "" "POST" "application/json")"
+ MB_TK="$(echo "$response" | _egrep_o "\"token\":\"[^\"]*\"" | cut -d : -f 2 | tr -d '"')"
+ if [ -z "$MB_TK" ]; then
+ _err "Unable to get token"
+ _err "\n$response"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
+
+# method path body_data
+_mb_rest() {
+ # URL encoded body for single API operations
+ m="$1"
+ ep="$2"
+ data="$3"
+
+ if [ -z "$ep" ]; then
+ _mb_url="$MB_API"
+ else
+ _mb_url="$MB_API/$ep"
+ fi
+
+ _H1="Authorization: Bearer $MB_TK"
+ _H2="Accepts: application/json"
+ export _H1 _H2
+ if [ "$data" ] || [ "$m" = "POST" ] || [ "$m" = "PUT" ] || [ "$m" = "DELETE" ]; then
+ # body url [needbase64] [POST|PUT|DELETE] [ContentType]
+ response="$(_post "data=$data" "$_mb_url" "" "$m" "application/x-www-form-urlencoded")"
+ else
+ response="$(_get "$_mb_url")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "Request error"
+ return 1
+ fi
+
+ header="$(cat "$HTTP_HEADER")"
+ status="$(echo "$header" | _egrep_o "^HTTP[^ ]* .*$" | cut -d " " -f 2-100 | tr -d "\f\n")"
+ code="$(echo "$status" | _egrep_o "^[0-9]*")"
+ if [ "$code" -ge 400 ] || _contains "$response" "\"error\"" || _contains "$response" "invalid_client"; then
+ _err "error $status"
+ _err "\n$response"
+ _debug "\n$header"
+ return 2
+ fi
+
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_namecheap.sh b/dnsapi/dns_namecheap.sh
index 2e389265..d15d6b0e 100755
--- a/dnsapi/dns_namecheap.sh
+++ b/dnsapi/dns_namecheap.sh
@@ -157,7 +157,7 @@ _namecheap_set_publicip() {
if [ -z "$NAMECHEAP_SOURCEIP" ]; then
_err "No Source IP specified for Namecheap API."
- _err "Use your public ip address or an url to retrieve it (e.g. https://ipconfig.co/ip) and export it as NAMECHEAP_SOURCEIP"
+ _err "Use your public ip address or an url to retrieve it (e.g. https://ifconfig.co/ip) and export it as NAMECHEAP_SOURCEIP"
return 1
else
_saveaccountconf NAMECHEAP_SOURCEIP "$NAMECHEAP_SOURCEIP"
@@ -175,7 +175,7 @@ _namecheap_set_publicip() {
_publicip=$(_get "$addr")
else
_err "No Source IP specified for Namecheap API."
- _err "Use your public ip address or an url to retrieve it (e.g. https://ipconfig.co/ip) and export it as NAMECHEAP_SOURCEIP"
+ _err "Use your public ip address or an url to retrieve it (e.g. https://ifconfig.co/ip) and export it as NAMECHEAP_SOURCEIP"
return 1
fi
fi
@@ -208,7 +208,7 @@ _namecheap_parse_host() {
_hostid=$(echo "$_host" | _egrep_o ' HostId="[^"]*' | cut -d '"' -f 2)
_hostname=$(echo "$_host" | _egrep_o ' Name="[^"]*' | cut -d '"' -f 2)
_hosttype=$(echo "$_host" | _egrep_o ' Type="[^"]*' | cut -d '"' -f 2)
- _hostaddress=$(echo "$_host" | _egrep_o ' Address="[^"]*' | cut -d '"' -f 2)
+ _hostaddress=$(echo "$_host" | _egrep_o ' Address="[^"]*' | cut -d '"' -f 2 | _xml_decode)
_hostmxpref=$(echo "$_host" | _egrep_o ' MXPref="[^"]*' | cut -d '"' -f 2)
_hostttl=$(echo "$_host" | _egrep_o ' TTL="[^"]*' | cut -d '"' -f 2)
@@ -405,3 +405,7 @@ _namecheap_set_tld_sld() {
done
}
+
+_xml_decode() {
+ sed 's/"/"/g'
+}
diff --git a/dnsapi/dns_netcup.sh b/dnsapi/dns_netcup.sh
index d519e4f7..776fa02d 100644
--- a/dnsapi/dns_netcup.sh
+++ b/dnsapi/dns_netcup.sh
@@ -119,16 +119,16 @@ login() {
tmp=$(_post "{\"action\": \"login\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apipassword\": \"$NC_Apipw\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST")
sid=$(echo "$tmp" | tr '{}' '\n' | grep apisessionid | cut -d '"' -f 4)
_debug "$tmp"
- if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
- _err "$msg"
+ if [ "$(_getfield "$tmp" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
+ _err "$tmp"
return 1
fi
}
logout() {
tmp=$(_post "{\"action\": \"logout\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST")
_debug "$tmp"
- if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
- _err "$msg"
+ if [ "$(_getfield "$tmp" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
+ _err "$tmp"
return 1
fi
}
diff --git a/dnsapi/dns_nsd.sh b/dnsapi/dns_nsd.sh
index 83cc4cac..0d29a485 100644
--- a/dnsapi/dns_nsd.sh
+++ b/dnsapi/dns_nsd.sh
@@ -51,7 +51,7 @@ dns_nsd_rm() {
Nsd_ZoneFile="${Nsd_ZoneFile:-$(_readdomainconf Nsd_ZoneFile)}"
Nsd_Command="${Nsd_Command:-$(_readdomainconf Nsd_Command)}"
- sed -i "/$fulldomain. $ttlvalue IN TXT \"$txtvalue\"/d" "$Nsd_ZoneFile"
+ _sed_i "/$fulldomain. $ttlvalue IN TXT \"$txtvalue\"/d" "$Nsd_ZoneFile"
_info "Removed TXT record for $fulldomain"
_debug "Running $Nsd_Command"
if eval "$Nsd_Command"; then
diff --git a/dnsapi/dns_oci.sh b/dnsapi/dns_oci.sh
new file mode 100644
index 00000000..eb006120
--- /dev/null
+++ b/dnsapi/dns_oci.sh
@@ -0,0 +1,324 @@
+#!/usr/bin/env sh
+#
+# Acme.sh DNS API plugin for Oracle Cloud Infrastructure
+# Copyright (c) 2021, Oracle and/or its affiliates
+#
+# The plugin will automatically use the default profile from an OCI SDK and CLI
+# configuration file, if it exists.
+#
+# Alternatively, set the following environment variables:
+# - OCI_CLI_TENANCY : OCID of tenancy that contains the target DNS zone
+# - OCI_CLI_USER : OCID of user with permission to add/remove records from zones
+# - OCI_CLI_REGION : Should point to the tenancy home region
+#
+# One of the following two variables is required:
+# - OCI_CLI_KEY_FILE: Path to private API signing key file in PEM format; or
+# - OCI_CLI_KEY : The private API signing key in PEM format
+#
+# NOTE: using an encrypted private key that needs a passphrase is not supported.
+#
+
+dns_oci_add() {
+ _fqdn="$1"
+ _rdata="$2"
+
+ if _get_oci_zone; then
+
+ _add_record_body="{\"items\":[{\"domain\":\"${_sub_domain}.${_domain}\",\"rdata\":\"$_rdata\",\"rtype\":\"TXT\",\"ttl\": 30,\"operation\":\"ADD\"}]}"
+ response=$(_signed_request "PATCH" "/20180115/zones/${_domain}/records" "$_add_record_body")
+ if [ "$response" ]; then
+ _info "Success: added TXT record for ${_sub_domain}.${_domain}."
+ else
+ _err "Error: failed to add TXT record for ${_sub_domain}.${_domain}."
+ _err "Check that the user has permission to add records to this zone."
+ return 1
+ fi
+
+ else
+ return 1
+ fi
+
+}
+
+dns_oci_rm() {
+ _fqdn="$1"
+ _rdata="$2"
+
+ if _get_oci_zone; then
+
+ _remove_record_body="{\"items\":[{\"domain\":\"${_sub_domain}.${_domain}\",\"rdata\":\"$_rdata\",\"rtype\":\"TXT\",\"operation\":\"REMOVE\"}]}"
+ response=$(_signed_request "PATCH" "/20180115/zones/${_domain}/records" "$_remove_record_body")
+ if [ "$response" ]; then
+ _info "Success: removed TXT record for ${_sub_domain}.${_domain}."
+ else
+ _err "Error: failed to remove TXT record for ${_sub_domain}.${_domain}."
+ _err "Check that the user has permission to remove records from this zone."
+ return 1
+ fi
+
+ else
+ return 1
+ fi
+
+}
+
+#################### Private functions below ##################################
+_get_oci_zone() {
+
+ if ! _oci_config; then
+ return 1
+ fi
+
+ if ! _get_zone "$_fqdn"; then
+ _err "Error: DNS Zone not found for $_fqdn in $OCI_CLI_TENANCY"
+ return 1
+ fi
+
+ return 0
+
+}
+
+_oci_config() {
+
+ _DEFAULT_OCI_CLI_CONFIG_FILE="$HOME/.oci/config"
+ OCI_CLI_CONFIG_FILE="${OCI_CLI_CONFIG_FILE:-$(_readaccountconf_mutable OCI_CLI_CONFIG_FILE)}"
+
+ if [ -z "$OCI_CLI_CONFIG_FILE" ]; then
+ OCI_CLI_CONFIG_FILE="$_DEFAULT_OCI_CLI_CONFIG_FILE"
+ fi
+
+ if [ "$_DEFAULT_OCI_CLI_CONFIG_FILE" != "$OCI_CLI_CONFIG_FILE" ]; then
+ _saveaccountconf_mutable OCI_CLI_CONFIG_FILE "$OCI_CLI_CONFIG_FILE"
+ else
+ _clearaccountconf_mutable OCI_CLI_CONFIG_FILE
+ fi
+
+ _DEFAULT_OCI_CLI_PROFILE="DEFAULT"
+ OCI_CLI_PROFILE="${OCI_CLI_PROFILE:-$(_readaccountconf_mutable OCI_CLI_PROFILE)}"
+ if [ "$_DEFAULT_OCI_CLI_PROFILE" != "$OCI_CLI_PROFILE" ]; then
+ _saveaccountconf_mutable OCI_CLI_PROFILE "$OCI_CLI_PROFILE"
+ else
+ OCI_CLI_PROFILE="$_DEFAULT_OCI_CLI_PROFILE"
+ _clearaccountconf_mutable OCI_CLI_PROFILE
+ fi
+
+ OCI_CLI_TENANCY="${OCI_CLI_TENANCY:-$(_readaccountconf_mutable OCI_CLI_TENANCY)}"
+ if [ "$OCI_CLI_TENANCY" ]; then
+ _saveaccountconf_mutable OCI_CLI_TENANCY "$OCI_CLI_TENANCY"
+ elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
+ _debug "Reading OCI_CLI_TENANCY value from: $OCI_CLI_CONFIG_FILE"
+ OCI_CLI_TENANCY="${OCI_CLI_TENANCY:-$(_readini "$OCI_CLI_CONFIG_FILE" tenancy "$OCI_CLI_PROFILE")}"
+ fi
+
+ if [ -z "$OCI_CLI_TENANCY" ]; then
+ _err "Error: unable to read OCI_CLI_TENANCY from config file or environment variable."
+ return 1
+ fi
+
+ OCI_CLI_USER="${OCI_CLI_USER:-$(_readaccountconf_mutable OCI_CLI_USER)}"
+ if [ "$OCI_CLI_USER" ]; then
+ _saveaccountconf_mutable OCI_CLI_USER "$OCI_CLI_USER"
+ elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
+ _debug "Reading OCI_CLI_USER value from: $OCI_CLI_CONFIG_FILE"
+ OCI_CLI_USER="${OCI_CLI_USER:-$(_readini "$OCI_CLI_CONFIG_FILE" user "$OCI_CLI_PROFILE")}"
+ fi
+ if [ -z "$OCI_CLI_USER" ]; then
+ _err "Error: unable to read OCI_CLI_USER from config file or environment variable."
+ return 1
+ fi
+
+ OCI_CLI_REGION="${OCI_CLI_REGION:-$(_readaccountconf_mutable OCI_CLI_REGION)}"
+ if [ "$OCI_CLI_REGION" ]; then
+ _saveaccountconf_mutable OCI_CLI_REGION "$OCI_CLI_REGION"
+ elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
+ _debug "Reading OCI_CLI_REGION value from: $OCI_CLI_CONFIG_FILE"
+ OCI_CLI_REGION="${OCI_CLI_REGION:-$(_readini "$OCI_CLI_CONFIG_FILE" region "$OCI_CLI_PROFILE")}"
+ fi
+ if [ -z "$OCI_CLI_REGION" ]; then
+ _err "Error: unable to read OCI_CLI_REGION from config file or environment variable."
+ return 1
+ fi
+
+ OCI_CLI_KEY="${OCI_CLI_KEY:-$(_readaccountconf_mutable OCI_CLI_KEY)}"
+ if [ -z "$OCI_CLI_KEY" ]; then
+ _clearaccountconf_mutable OCI_CLI_KEY
+ OCI_CLI_KEY_FILE="${OCI_CLI_KEY_FILE:-$(_readini "$OCI_CLI_CONFIG_FILE" key_file "$OCI_CLI_PROFILE")}"
+ if [ "$OCI_CLI_KEY_FILE" ] && [ -f "$OCI_CLI_KEY_FILE" ]; then
+ _debug "Reading OCI_CLI_KEY value from: $OCI_CLI_KEY_FILE"
+ OCI_CLI_KEY=$(_base64 <"$OCI_CLI_KEY_FILE")
+ _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY"
+ fi
+ else
+ _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY"
+ fi
+
+ if [ -z "$OCI_CLI_KEY_FILE" ] && [ -z "$OCI_CLI_KEY" ]; then
+ _err "Error: unable to find key file path in OCI config file or OCI_CLI_KEY_FILE."
+ _err "Error: unable to load private API signing key from OCI_CLI_KEY."
+ return 1
+ fi
+
+ if [ "$(printf "%s\n" "$OCI_CLI_KEY" | wc -l)" -eq 1 ]; then
+ OCI_CLI_KEY=$(printf "%s" "$OCI_CLI_KEY" | _dbase64 multiline)
+ fi
+
+ return 0
+
+}
+
+# _get_zone(): retrieves the Zone name and OCID
+#
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_ociid=ocid1.dns-zone.oc1..
+_get_zone() {
+ domain=$1
+ i=1
+ p=1
+
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug h "$h"
+ if [ -z "$h" ]; then
+ # not valid
+ return 1
+ fi
+
+ _domain_id=$(_signed_request "GET" "/20180115/zones/$h" "" "id")
+ if [ "$_domain_id" ]; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain=$h
+
+ _debug _domain_id "$_domain_id"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+ return 0
+ fi
+
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+
+}
+
+#Usage: privatekey
+#Output MD5 fingerprint
+_fingerprint() {
+
+ pkey="$1"
+ if [ -z "$pkey" ]; then
+ _usage "Usage: _fingerprint privkey"
+ return 1
+ fi
+
+ printf "%s" "$pkey" | ${ACME_OPENSSL_BIN:-openssl} rsa -pubout -outform DER 2>/dev/null | ${ACME_OPENSSL_BIN:-openssl} md5 -c | cut -d = -f 2 | tr -d ' '
+
+}
+
+_signed_request() {
+
+ _sig_method="$1"
+ _sig_target="$2"
+ _sig_body="$3"
+ _return_field="$4"
+
+ _key_fingerprint=$(_fingerprint "$OCI_CLI_KEY")
+ _sig_host="dns.$OCI_CLI_REGION.oraclecloud.com"
+ _sig_keyId="$OCI_CLI_TENANCY/$OCI_CLI_USER/$_key_fingerprint"
+ _sig_alg="rsa-sha256"
+ _sig_version="1"
+ _sig_now="$(LC_ALL=C \date -u "+%a, %d %h %Y %H:%M:%S GMT")"
+
+ _request_method=$(printf %s "$_sig_method" | _lower_case)
+ _curl_method=$(printf %s "$_sig_method" | _upper_case)
+
+ _request_target="(request-target): $_request_method $_sig_target"
+ _date_header="date: $_sig_now"
+ _host_header="host: $_sig_host"
+
+ _string_to_sign="$_request_target\n$_date_header\n$_host_header"
+ _sig_headers="(request-target) date host"
+
+ if [ "$_sig_body" ]; then
+ _secure_debug3 _sig_body "$_sig_body"
+ _sig_body_sha256="x-content-sha256: $(printf %s "$_sig_body" | _digest sha256)"
+ _sig_body_type="content-type: application/json"
+ _sig_body_length="content-length: ${#_sig_body}"
+ _string_to_sign="$_string_to_sign\n$_sig_body_sha256\n$_sig_body_type\n$_sig_body_length"
+ _sig_headers="$_sig_headers x-content-sha256 content-type content-length"
+ fi
+
+ _tmp_file=$(_mktemp)
+ if [ -f "$_tmp_file" ]; then
+ printf '%s' "$OCI_CLI_KEY" >"$_tmp_file"
+ _signature=$(printf '%b' "$_string_to_sign" | _sign "$_tmp_file" sha256 | tr -d '\r\n')
+ rm -f "$_tmp_file"
+ fi
+
+ _signed_header="Authorization: Signature version=\"$_sig_version\",keyId=\"$_sig_keyId\",algorithm=\"$_sig_alg\",headers=\"$_sig_headers\",signature=\"$_signature\""
+ _secure_debug3 _signed_header "$_signed_header"
+
+ if [ "$_curl_method" = "GET" ]; then
+ export _H1="$_date_header"
+ export _H2="$_signed_header"
+ _response="$(_get "https://${_sig_host}${_sig_target}")"
+ elif [ "$_curl_method" = "PATCH" ]; then
+ export _H1="$_date_header"
+ export _H2="$_sig_body_sha256"
+ export _H3="$_sig_body_type"
+ export _H4="$_sig_body_length"
+ export _H5="$_signed_header"
+ _response="$(_post "$_sig_body" "https://${_sig_host}${_sig_target}" "" "PATCH")"
+ else
+ _err "Unable to process method: $_curl_method."
+ fi
+
+ _ret="$?"
+ if [ "$_return_field" ]; then
+ _response="$(echo "$_response" | sed 's/\\\"//g'))"
+ _return=$(echo "${_response}" | _egrep_o "\"$_return_field\"\\s*:\\s*\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d "\"")
+ else
+ _return="$_response"
+ fi
+
+ printf "%s" "$_return"
+ return $_ret
+
+}
+
+# file key [section]
+_readini() {
+ _file="$1"
+ _key="$2"
+ _section="${3:-DEFAULT}"
+
+ _start_n=$(grep -n '\['"$_section"']' "$_file" | cut -d : -f 1)
+ _debug3 _start_n "$_start_n"
+ if [ -z "$_start_n" ]; then
+ _err "Can not find section: $_section"
+ return 1
+ fi
+
+ _start_nn=$(_math "$_start_n" + 1)
+ _debug3 "_start_nn" "$_start_nn"
+
+ _left="$(sed -n "${_start_nn},99999p" "$_file")"
+ _debug3 _left "$_left"
+ _end="$(echo "$_left" | grep -n "^\[" | _head_n 1)"
+ _debug3 "_end" "$_end"
+ if [ "$_end" ]; then
+ _end_n=$(echo "$_end" | cut -d : -f 1)
+ _debug3 "_end_n" "$_end_n"
+ _seg_n=$(echo "$_left" | sed -n "1,${_end_n}p")
+ else
+ _seg_n="$_left"
+ fi
+
+ _debug3 "_seg_n" "$_seg_n"
+ _lineini="$(echo "$_seg_n" | grep "^ *$_key *= *")"
+ _inivalue="$(printf "%b" "$(eval "echo $_lineini | sed \"s/^ *${_key} *= *//g\"")")"
+ _debug2 _inivalue "$_inivalue"
+ echo "$_inivalue"
+
+}
diff --git a/dnsapi/dns_one.sh b/dnsapi/dns_one.sh
index 890cc804..1565b767 100644
--- a/dnsapi/dns_one.sh
+++ b/dnsapi/dns_one.sh
@@ -1,22 +1,9 @@
#!/usr/bin/env sh
-# -*- mode: sh; tab-width: 2; indent-tabs-mode: s; coding: utf-8 -*-
-
# one.com ui wrapper for acme.sh
-# Author: github: @diseq
-# Created: 2019-02-17
-# Fixed by: @der-berni
-# Modified: 2020-04-07
-#
-# Use ONECOM_KeepCnameProxy to keep the CNAME DNS record
-# export ONECOM_KeepCnameProxy="1"
+
#
# export ONECOM_User="username"
# export ONECOM_Password="password"
-#
-# Usage:
-# acme.sh --issue --dns dns_one -d example.com
-#
-# only single domain supported atm
dns_one_add() {
fulldomain=$1
@@ -36,27 +23,9 @@ dns_one_add() {
subdomain="${_sub_domain}"
maindomain=${_domain}
- useProxy=0
- if [ "${_sub_domain}" = "_acme-challenge" ]; then
- subdomain="proxy${_sub_domain}"
- useProxy=1
- fi
-
_debug subdomain "$subdomain"
_debug maindomain "$maindomain"
- if [ $useProxy -eq 1 ]; then
- #Check if the CNAME exists
- _dns_one_getrecord "CNAME" "$_sub_domain" "$subdomain.$maindomain"
- if [ -z "$id" ]; then
- _info "$(__red "Add CNAME Proxy record: '$(__green "\"$_sub_domain\" => \"$subdomain.$maindomain\"")'")"
- _dns_one_addrecord "CNAME" "$_sub_domain" "$subdomain.$maindomain"
-
- _info "Not valid yet, let's wait 1 hour to take effect."
- _sleep 3600
- fi
- fi
-
#Check if the TXT exists
_dns_one_getrecord "TXT" "$subdomain" "$txtvalue"
if [ -n "$id" ]; then
@@ -92,26 +61,8 @@ dns_one_rm() {
subdomain="${_sub_domain}"
maindomain=${_domain}
- useProxy=0
- if [ "${_sub_domain}" = "_acme-challenge" ]; then
- subdomain="proxy${_sub_domain}"
- useProxy=1
- fi
-
_debug subdomain "$subdomain"
_debug maindomain "$maindomain"
- if [ $useProxy -eq 1 ]; then
- if [ "$ONECOM_KeepCnameProxy" = "1" ]; then
- _info "$(__red "Keeping CNAME Proxy record: '$(__green "\"$_sub_domain\" => \"$subdomain.$maindomain\"")'")"
- else
- #Check if the CNAME exists
- _dns_one_getrecord "CNAME" "$_sub_domain" "$subdomain.$maindomain"
- if [ -n "$id" ]; then
- _info "$(__red "Removing CNAME Proxy record: '$(__green "\"$_sub_domain\" => \"$subdomain.$maindomain\"")'")"
- _dns_one_delrecord "$id"
- fi
- fi
- fi
#Check if the TXT exists
_dns_one_getrecord "TXT" "$subdomain" "$txtvalue"
@@ -136,7 +87,7 @@ dns_one_rm() {
# _domain=domain.com
_get_root() {
domain="$1"
- i=2
+ i=1
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
@@ -163,8 +114,6 @@ _get_root() {
_dns_one_login() {
# get credentials
- ONECOM_KeepCnameProxy="${ONECOM_KeepCnameProxy:-$(_readaccountconf_mutable ONECOM_KeepCnameProxy)}"
- ONECOM_KeepCnameProxy="${ONECOM_KeepCnameProxy:-0}"
ONECOM_User="${ONECOM_User:-$(_readaccountconf_mutable ONECOM_User)}"
ONECOM_Password="${ONECOM_Password:-$(_readaccountconf_mutable ONECOM_Password)}"
if [ -z "$ONECOM_User" ] || [ -z "$ONECOM_Password" ]; then
@@ -176,7 +125,6 @@ _dns_one_login() {
fi
#save the api key and email to the account conf file.
- _saveaccountconf_mutable ONECOM_KeepCnameProxy "$ONECOM_KeepCnameProxy"
_saveaccountconf_mutable ONECOM_User "$ONECOM_User"
_saveaccountconf_mutable ONECOM_Password "$ONECOM_Password"
diff --git a/dnsapi/dns_opnsense.sh b/dnsapi/dns_opnsense.sh
index 069f6c32..eb95902f 100755
--- a/dnsapi/dns_opnsense.sh
+++ b/dnsapi/dns_opnsense.sh
@@ -150,8 +150,7 @@ _get_root() {
return 1
fi
_debug h "$h"
- id=$(echo "$_domain_response" | _egrep_o "\"[^\"]*\":{\"enabled\":\"1\",\"type\":{\"master\":{\"value\":\"master\",\"selected\":1},\"slave\":{\"value\":\"slave\",\"selected\":0}},\"masterip\":\"[^\"]*\"(,\"allownotifyslave\":{\"\":{[^}]*}},|,)\"domainname\":\"${h}\"" | cut -d ':' -f 1 | cut -d '"' -f 2)
-
+ id=$(echo "$_domain_response" | _egrep_o "\"[^\"]*\":{\"enabled\":\"1\",\"type\":{\"master\":{\"value\":\"master\",\"selected\":1},\"slave\":{\"value\":\"slave\",\"selected\":0}},\"masterip\":{\"[^\"]*\":{[^}]*}},\"transferkeyalgo\":{[^{]*{[^{]*{[^{]*{[^{]*{[^{]*{[^{]*{[^{]*{[^}]*}},\"transferkey\":\"[^\"]*\"(,\"allownotifyslave\":{\"\":{[^}]*}},|,)\"domainname\":\"${h}\"" | cut -d ':' -f 1 | cut -d '"' -f 2)
if [ -n "$id" ]; then
_debug id "$id"
_host=$(printf "%s" "$domain" | cut -d . -f 1-$p)
diff --git a/dnsapi/dns_ovh.sh b/dnsapi/dns_ovh.sh
index f6f9689a..e65babbd 100755
--- a/dnsapi/dns_ovh.sh
+++ b/dnsapi/dns_ovh.sh
@@ -261,7 +261,9 @@ _get_root() {
return 1
fi
- if ! _contains "$response" "This service does not exist" >/dev/null && ! _contains "$response" "NOT_GRANTED_CALL" >/dev/null; then
+ if ! _contains "$response" "This service does not exist" >/dev/null &&
+ ! _contains "$response" "This call has not been granted" >/dev/null &&
+ ! _contains "$response" "NOT_GRANTED_CALL" >/dev/null; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="$h"
return 0
diff --git a/dnsapi/dns_pdns.sh b/dnsapi/dns_pdns.sh
index 8f07e8c4..6aa2e953 100755
--- a/dnsapi/dns_pdns.sh
+++ b/dnsapi/dns_pdns.sh
@@ -103,7 +103,7 @@ set_record() {
_build_record_string "$oldchallenge"
done
- if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}"; then
+ if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}" "application/json"; then
_err "Set txt record error."
return 1
fi
@@ -126,7 +126,7 @@ rm_record() {
if _contains "$_existing_challenges" "$txtvalue"; then
#Delete all challenges (PowerDNS API does not allow to delete content)
- if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}"; then
+ if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}" "application/json"; then
_err "Delete txt record error."
return 1
fi
@@ -140,7 +140,7 @@ rm_record() {
fi
done
#Recreate the existing challenges
- if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}"; then
+ if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}" "application/json"; then
_err "Set txt record error."
return 1
fi
@@ -175,13 +175,13 @@ _get_root() {
i=1
if _pdns_rest "GET" "/api/v1/servers/$PDNS_ServerId/zones"; then
- _zones_response="$response"
+ _zones_response=$(echo "$response" | _normalizeJson)
fi
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
- if _contains "$_zones_response" "\"name\": \"$h.\""; then
+ if _contains "$_zones_response" "\"name\":\"$h.\""; then
_domain="$h."
if [ -z "$h" ]; then
_domain="=2E"
@@ -203,12 +203,13 @@ _pdns_rest() {
method=$1
ep=$2
data=$3
+ ct=$4
export _H1="X-API-Key: $PDNS_Token"
if [ ! "$method" = "GET" ]; then
_debug data "$data"
- response="$(_post "$data" "$PDNS_Url$ep" "" "$method")"
+ response="$(_post "$data" "$PDNS_Url$ep" "" "$method" "$ct")"
else
response="$(_get "$PDNS_Url$ep")"
fi
diff --git a/dnsapi/dns_porkbun.sh b/dnsapi/dns_porkbun.sh
new file mode 100644
index 00000000..ad4455b6
--- /dev/null
+++ b/dnsapi/dns_porkbun.sh
@@ -0,0 +1,157 @@
+#!/usr/bin/env sh
+
+#
+#PORKBUN_API_KEY="pk1_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+#PORKBUN_SECRET_API_KEY="sk1_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+
+PORKBUN_Api="https://porkbun.com/api/json/v3"
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_porkbun_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ PORKBUN_API_KEY="${PORKBUN_API_KEY:-$(_readaccountconf_mutable PORKBUN_API_KEY)}"
+ PORKBUN_SECRET_API_KEY="${PORKBUN_SECRET_API_KEY:-$(_readaccountconf_mutable PORKBUN_SECRET_API_KEY)}"
+
+ if [ -z "$PORKBUN_API_KEY" ] || [ -z "$PORKBUN_SECRET_API_KEY" ]; then
+ PORKBUN_API_KEY=''
+ PORKBUN_SECRET_API_KEY=''
+ _err "You didn't specify a Porkbun api key and secret api key yet."
+ _err "You can get yours from here https://porkbun.com/account/api."
+ return 1
+ fi
+
+ #save the credentials to the account conf file.
+ _saveaccountconf_mutable PORKBUN_API_KEY "$PORKBUN_API_KEY"
+ _saveaccountconf_mutable PORKBUN_SECRET_API_KEY "$PORKBUN_SECRET_API_KEY"
+
+ _debug 'First detect the root zone'
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so
+ # we can not use updating anymore.
+ # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2)
+ # _debug count "$count"
+ # if [ "$count" = "0" ]; then
+ _info "Adding record"
+ if _porkbun_rest POST "dns/create/$_domain" "{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
+ if _contains "$response" '\"status\":"SUCCESS"'; then
+ _info "Added, OK"
+ return 0
+ elif _contains "$response" "The record already exists"; then
+ _info "Already exists, OK"
+ return 0
+ else
+ _err "Add txt record error. ($response)"
+ return 1
+ fi
+ fi
+ _err "Add txt record error."
+ return 1
+
+}
+
+#fulldomain txtvalue
+dns_porkbun_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ PORKBUN_API_KEY="${PORKBUN_API_KEY:-$(_readaccountconf_mutable PORKBUN_API_KEY)}"
+ PORKBUN_SECRET_API_KEY="${PORKBUN_SECRET_API_KEY:-$(_readaccountconf_mutable PORKBUN_SECRET_API_KEY)}"
+
+ _debug 'First detect the root zone'
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ count=$(echo "$response" | _egrep_o "\"count\": *[^,]*" | cut -d : -f 2 | tr -d " ")
+ _debug count "$count"
+ if [ "$count" = "0" ]; then
+ _info "Don't need to remove."
+ else
+ record_id=$(echo "$response" | tr '{' '\n' | grep -- "$txtvalue" | cut -d, -f1 | cut -d: -f2 | tr -d \")
+ _debug "record_id" "$record_id"
+ if [ -z "$record_id" ]; then
+ _err "Can not get record id to remove."
+ return 1
+ fi
+ if ! _porkbun_rest POST "dns/delete/$_domain/$record_id"; then
+ _err "Delete record error."
+ return 1
+ fi
+ echo "$response" | tr -d " " | grep '\"status\":"SUCCESS"' >/dev/null
+ fi
+
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ domain=$1
+ i=1
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug h "$h"
+ if [ -z "$h" ]; then
+ return 1
+ fi
+
+ if _porkbun_rest POST "dns/retrieve/$h"; then
+ if _contains "$response" "\"status\":\"SUCCESS\""; then
+ _domain=$h
+ _sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")"
+ return 0
+ else
+ _debug "Go to next level of $_domain"
+ fi
+ else
+ _debug "Go to next level of $_domain"
+ fi
+ i=$(_math "$i" + 1)
+ done
+
+ return 1
+}
+
+_porkbun_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+
+ api_key_trimmed=$(echo "$PORKBUN_API_KEY" | tr -d '"')
+ secret_api_key_trimmed=$(echo "$PORKBUN_SECRET_API_KEY" | tr -d '"')
+
+ test -z "$data" && data="{" || data="$(echo $data | cut -d'}' -f1),"
+ data="$data\"apikey\":\"$api_key_trimmed\",\"secretapikey\":\"$secret_api_key_trimmed\"}"
+
+ export _H1="Content-Type: application/json"
+
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$PORKBUN_Api/$ep" "" "$m")"
+ else
+ response="$(_get "$PORKBUN_Api/$ep")"
+ fi
+
+ _sleep 3 # prevent rate limit
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_rackcorp.sh b/dnsapi/dns_rackcorp.sh
new file mode 100644
index 00000000..6aabfddc
--- /dev/null
+++ b/dnsapi/dns_rackcorp.sh
@@ -0,0 +1,156 @@
+#!/usr/bin/env sh
+
+# Provider: RackCorp (www.rackcorp.com)
+# Author: Stephen Dendtler (sdendtler@rackcorp.com)
+# Report Bugs here: https://github.com/senjoo/acme.sh
+# Alternate email contact: support@rackcorp.com
+#
+# You'll need an API key (Portal: ADMINISTRATION -> API)
+# Set the environment variables as below:
+#
+# export RACKCORP_APIUUID="UUIDHERE"
+# export RACKCORP_APISECRET="SECRETHERE"
+#
+
+RACKCORP_API_ENDPOINT="https://api.rackcorp.net/api/rest/v2.4/json.php"
+
+######## Public functions #####################
+
+dns_rackcorp_add() {
+ fulldomain="$1"
+ txtvalue="$2"
+
+ _debug fulldomain="$fulldomain"
+ _debug txtvalue="$txtvalue"
+
+ if ! _rackcorp_validate; then
+ return 1
+ fi
+
+ _debug "Searching for root zone"
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+ _debug _lookup "$_lookup"
+ _debug _domain "$_domain"
+
+ _info "Creating TXT record."
+
+ if ! _rackcorp_api dns.record.create "\"name\":\"$_domain\",\"type\":\"TXT\",\"lookup\":\"$_lookup\",\"data\":\"$txtvalue\",\"ttl\":300"; then
+ return 1
+ fi
+
+ return 0
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_rackcorp_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _debug fulldomain="$fulldomain"
+ _debug txtvalue="$txtvalue"
+
+ if ! _rackcorp_validate; then
+ return 1
+ fi
+
+ _debug "Searching for root zone"
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+ _debug _lookup "$_lookup"
+ _debug _domain "$_domain"
+
+ _info "Creating TXT record."
+
+ if ! _rackcorp_api dns.record.delete "\"name\":\"$_domain\",\"type\":\"TXT\",\"lookup\":\"$_lookup\",\"data\":\"$txtvalue\""; then
+ return 1
+ fi
+
+ return 0
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.domain.com
+#returns
+# _lookup=_acme-challenge
+# _domain=domain.com
+_get_root() {
+ domain=$1
+ i=1
+ p=1
+ if ! _rackcorp_api dns.domain.getall "\"name\":\"$domain\""; then
+ return 1
+ fi
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug searchhost "$h"
+ if [ -z "$h" ]; then
+ _err "Could not find domain for record $domain in RackCorp using the provided credentials"
+ #not valid
+ return 1
+ fi
+
+ _rackcorp_api dns.domain.getall "\"exactName\":\"$h\""
+
+ if _contains "$response" "\"matches\":1"; then
+ if _contains "$response" "\"name\":\"$h\""; then
+ _lookup=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="$h"
+ return 0
+ fi
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+
+ return 1
+}
+
+_rackcorp_validate() {
+ RACKCORP_APIUUID="${RACKCORP_APIUUID:-$(_readaccountconf_mutable RACKCORP_APIUUID)}"
+ if [ -z "$RACKCORP_APIUUID" ]; then
+ RACKCORP_APIUUID=""
+ _err "You require a RackCorp API UUID (export RACKCORP_APIUUID=\"\")"
+ _err "Please login to the portal and create an API key and try again."
+ return 1
+ fi
+
+ _saveaccountconf_mutable RACKCORP_APIUUID "$RACKCORP_APIUUID"
+
+ RACKCORP_APISECRET="${RACKCORP_APISECRET:-$(_readaccountconf_mutable RACKCORP_APISECRET)}"
+ if [ -z "$RACKCORP_APISECRET" ]; then
+ RACKCORP_APISECRET=""
+ _err "You require a RackCorp API secret (export RACKCORP_APISECRET=\"\")"
+ _err "Please login to the portal and create an API key and try again."
+ return 1
+ fi
+
+ _saveaccountconf_mutable RACKCORP_APISECRET "$RACKCORP_APISECRET"
+
+ return 0
+}
+_rackcorp_api() {
+ _rackcorpcmd=$1
+ _rackcorpinputdata=$2
+ _debug cmd "$_rackcorpcmd $_rackcorpinputdata"
+
+ export _H1="Accept: application/json"
+ response="$(_post "{\"APIUUID\":\"$RACKCORP_APIUUID\",\"APISECRET\":\"$RACKCORP_APISECRET\",\"cmd\":\"$_rackcorpcmd\",$_rackcorpinputdata}" "$RACKCORP_API_ENDPOINT" "" "POST")"
+
+ if [ "$?" != "0" ]; then
+ _err "error $response"
+ return 1
+ fi
+ _debug2 response "$response"
+ if _contains "$response" "\"code\":\"OK\""; then
+ _debug code "OK"
+ else
+ _debug code "FAILED"
+ response=""
+ return 1
+ fi
+ return 0
+}
diff --git a/dnsapi/dns_rackspace.sh b/dnsapi/dns_rackspace.sh
index 03e1fa68..b50d9168 100644
--- a/dnsapi/dns_rackspace.sh
+++ b/dnsapi/dns_rackspace.sh
@@ -7,6 +7,7 @@
RACKSPACE_Endpoint="https://dns.api.rackspacecloud.com/v1.0"
+# 20210923 - RS changed the fields in the API response; fix sed
# 20190213 - The name & id fields swapped in the API response; fix sed
# 20190101 - Duplicating file for new pull request to dev branch
# Original - tcocca:rackspace_dnsapi https://github.com/acmesh-official/acme.sh/pull/1297
@@ -79,8 +80,8 @@ _get_root_zone() {
_debug2 response "$response"
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
# Response looks like:
- # {"ttl":300,"accountId":12345,"id":1111111,"name":"example.com","emailAddress": ...
- _domain_id=$(echo "$response" | sed -n "s/^.*\"id\":\([^,]*\),\"name\":\"$h\",.*/\1/p")
+ # {"id":"12345","accountId":"1111111","name": "example.com","ttl":3600,"emailAddress": ...
+ _domain_id=$(echo "$response" | sed -n "s/^.*\"id\":\"\([^,]*\)\",\"accountId\":\"[0-9]*\",\"name\":\"$h\",.*/\1/p")
_debug2 domain_id "$_domain_id"
if [ -n "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
diff --git a/dnsapi/dns_regru.sh b/dnsapi/dns_regru.sh
index 29f758ea..2a1ebaa5 100644
--- a/dnsapi/dns_regru.sh
+++ b/dnsapi/dns_regru.sh
@@ -92,9 +92,10 @@ _get_root() {
domains_list=$(echo "${response}" | grep dname | sed -r "s/.*dname=\"([^\"]+)\".*/\\1/g")
for ITEM in ${domains_list}; do
+ IDN_ITEM="$(_idn "${ITEM}")"
case "${domain}" in
- *${ITEM}*)
- _domain=${ITEM}
+ *${IDN_ITEM}*)
+ _domain=${IDN_ITEM}
_debug _domain "${_domain}"
return 0
;;
diff --git a/dnsapi/dns_scaleway.sh b/dnsapi/dns_scaleway.sh
new file mode 100755
index 00000000..a0a0f318
--- /dev/null
+++ b/dnsapi/dns_scaleway.sh
@@ -0,0 +1,176 @@
+#!/usr/bin/env sh
+
+# Scaleway API
+# https://developers.scaleway.com/en/products/domain/dns/api/
+#
+# Requires Scaleway API token set in SCALEWAY_API_TOKEN
+
+######## Public functions #####################
+
+SCALEWAY_API="https://api.scaleway.com/domain/v2beta1"
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_scaleway_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ if ! _scaleway_check_config; then
+ return 1
+ fi
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _info "Adding record"
+ _scaleway_create_TXT_record "$_domain" "$_sub_domain" "$txtvalue"
+ if _contains "$response" "records"; then
+ return 0
+ else
+ _err error "$response"
+ return 1
+ fi
+ _info "Record added."
+
+ return 0
+}
+
+dns_scaleway_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ if ! _scaleway_check_config; then
+ return 1
+ fi
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _info "Deleting record"
+ _scaleway_delete_TXT_record "$_domain" "$_sub_domain" "$txtvalue"
+ if _contains "$response" "records"; then
+ return 0
+ else
+ _err error "$response"
+ return 1
+ fi
+ _info "Record deleted."
+
+ return 0
+}
+
+#################### Private functions below ##################################
+
+_scaleway_check_config() {
+ SCALEWAY_API_TOKEN="${SCALEWAY_API_TOKEN:-$(_readaccountconf_mutable SCALEWAY_API_TOKEN)}"
+ if [ -z "$SCALEWAY_API_TOKEN" ]; then
+ _err "No API key specified for Scaleway API."
+ _err "Create your key and export it as SCALEWAY_API_TOKEN"
+ return 1
+ fi
+ if ! _scaleway_rest GET "dns-zones"; then
+ _err "Invalid API key specified for Scaleway API."
+ return 1
+ fi
+
+ _saveaccountconf_mutable SCALEWAY_API_TOKEN "$SCALEWAY_API_TOKEN"
+
+ return 0
+}
+
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ domain=$1
+ i=1
+ p=1
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+
+ _scaleway_rest GET "dns-zones/$h/records"
+
+ if ! _contains "$response" "subdomain not found" >/dev/null; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="$h"
+ return 0
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ _err "Unable to retrive DNS zone matching this domain"
+ return 1
+}
+
+# this function add a TXT record
+_scaleway_create_TXT_record() {
+ txt_zone=$1
+ txt_name=$2
+ txt_value=$3
+
+ _scaleway_rest PATCH "dns-zones/$txt_zone/records" "{\"return_all_records\":false,\"changes\":[{\"add\":{\"records\":[{\"name\":\"$txt_name\",\"data\":\"$txt_value\",\"type\":\"TXT\",\"ttl\":60}]}}]}"
+
+ if _contains "$response" "records"; then
+ return 0
+ else
+ _err "error1 $response"
+ return 1
+ fi
+}
+
+# this function delete a TXT record based on name and content
+_scaleway_delete_TXT_record() {
+ txt_zone=$1
+ txt_name=$2
+ txt_value=$3
+
+ _scaleway_rest PATCH "dns-zones/$txt_zone/records" "{\"return_all_records\":false,\"changes\":[{\"delete\":{\"id_fields\":{\"name\":\"$txt_name\",\"data\":\"$txt_value\",\"type\":\"TXT\"}}}]}"
+
+ if _contains "$response" "records"; then
+ return 0
+ else
+ _err "error2 $response"
+ return 1
+ fi
+}
+
+_scaleway_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+ _scaleway_url="$SCALEWAY_API/$ep"
+ _debug2 _scaleway_url "$_scaleway_url"
+ export _H1="x-auth-token: $SCALEWAY_API_TOKEN"
+ export _H2="Accept: application/json"
+ export _H3="Content-Type: application/json"
+
+ if [ "$data" ] || [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$_scaleway_url" "" "$m")"
+ else
+ response="$(_get "$_scaleway_url")"
+ fi
+ if [ "$?" != "0" ] || _contains "$response" "denied_authentication" || _contains "$response" "Method not allowed" || _contains "$response" "json parse error: unexpected EOF"; then
+ _err "error $response"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_servercow.sh b/dnsapi/dns_servercow.sh
index e73d85b0..f70a2294 100755
--- a/dnsapi/dns_servercow.sh
+++ b/dnsapi/dns_servercow.sh
@@ -49,16 +49,42 @@ dns_servercow_add() {
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
- if _servercow_api POST "$_domain" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":20}"; then
- if printf -- "%s" "$response" | grep "ok" >/dev/null; then
- _info "Added, OK"
- return 0
- else
- _err "add txt record error."
- return 1
+ # check whether a txt record already exists for the subdomain
+ if printf -- "%s" "$response" | grep "{\"name\":\"$_sub_domain\",\"ttl\":20,\"type\":\"TXT\"" >/dev/null; then
+ _info "A txt record with the same name already exists."
+ # trim the string on the left
+ txtvalue_old=${response#*{\"name\":\"$_sub_domain\",\"ttl\":20,\"type\":\"TXT\",\"content\":\"}
+ # trim the string on the right
+ txtvalue_old=${txtvalue_old%%\"*}
+
+ _debug txtvalue_old "$txtvalue_old"
+
+ _info "Add the new txtvalue to the existing txt record."
+ if _servercow_api POST "$_domain" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":[\"$txtvalue\",\"$txtvalue_old\"],\"ttl\":20}"; then
+ if printf -- "%s" "$response" | grep "ok" >/dev/null; then
+ _info "Added additional txtvalue, OK"
+ return 0
+ else
+ _err "add txt record error."
+ return 1
+ fi
fi
+ _err "add txt record error."
+ return 1
+ else
+ _info "There is no txt record with the name yet."
+ if _servercow_api POST "$_domain" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":20}"; then
+ if printf -- "%s" "$response" | grep "ok" >/dev/null; then
+ _info "Added, OK"
+ return 0
+ else
+ _err "add txt record error."
+ return 1
+ fi
+ fi
+ _err "add txt record error."
+ return 1
fi
- _err "add txt record error."
return 1
}
diff --git a/dnsapi/dns_simply.sh b/dnsapi/dns_simply.sh
new file mode 100644
index 00000000..6a8d0e18
--- /dev/null
+++ b/dnsapi/dns_simply.sh
@@ -0,0 +1,269 @@
+#!/usr/bin/env sh
+
+# API-integration for Simply.com (https://www.simply.com)
+
+#SIMPLY_AccountName="accountname"
+#SIMPLY_ApiKey="apikey"
+#
+#SIMPLY_Api="https://api.simply.com/2/"
+SIMPLY_Api_Default="https://api.simply.com/2"
+
+#This is used for determining success of REST call
+SIMPLY_SUCCESS_CODE='"status":200'
+
+######## Public functions #####################
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_simply_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ if ! _simply_load_config; then
+ return 1
+ fi
+
+ _simply_save_config
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _info "Adding record"
+
+ if ! _simply_add_record "$_domain" "$_sub_domain" "$txtvalue"; then
+ _err "Could not add DNS record"
+ return 1
+ fi
+ return 0
+}
+
+dns_simply_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ if ! _simply_load_config; then
+ return 1
+ fi
+
+ _simply_save_config
+
+ _debug "Find the DNS zone"
+
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+ _debug txtvalue "$txtvalue"
+
+ _info "Getting all existing records"
+
+ if ! _simply_get_all_records "$_domain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ records=$(echo "$response" | tr '{' "\n" | grep 'record_id\|type\|data\|\name' | sed 's/\"record_id/;\"record_id/' | tr "\n" ' ' | tr -d ' ' | tr ';' ' ')
+
+ nr_of_deleted_records=0
+ _info "Fetching txt record"
+
+ for record in $records; do
+ _debug record "$record"
+
+ record_data=$(echo "$record" | sed -n "s/.*\"data\":\"\([^\"]*\)\".*/\1/p")
+ record_type=$(echo "$record" | sed -n "s/.*\"type\":\"\([^\"]*\)\".*/\1/p")
+
+ _debug2 record_data "$record_data"
+ _debug2 record_type "$record_type"
+
+ if [ "$record_data" = "$txtvalue" ] && [ "$record_type" = "TXT" ]; then
+
+ record_id=$(echo "$record" | cut -d "," -f 1 | grep "record_id" | cut -d ":" -f 2)
+
+ _info "Deleting record $record"
+ _debug2 record_id "$record_id"
+
+ if [ "$record_id" -gt 0 ]; then
+
+ if ! _simply_delete_record "$_domain" "$_sub_domain" "$record_id"; then
+ _err "Record with id $record_id could not be deleted"
+ return 1
+ fi
+
+ nr_of_deleted_records=1
+ break
+ else
+ _err "Fetching record_id could not be done, this should not happen, exiting function. Failing record is $record"
+ break
+ fi
+ fi
+
+ done
+
+ if [ "$nr_of_deleted_records" -eq 0 ]; then
+ _err "No record deleted, the DNS record needs to be removed manually."
+ else
+ _info "Deleted $nr_of_deleted_records record"
+ fi
+
+ return 0
+}
+
+#################### Private functions below ##################################
+
+_simply_load_config() {
+ SIMPLY_Api="${SIMPLY_Api:-$(_readaccountconf_mutable SIMPLY_Api)}"
+ SIMPLY_AccountName="${SIMPLY_AccountName:-$(_readaccountconf_mutable SIMPLY_AccountName)}"
+ SIMPLY_ApiKey="${SIMPLY_ApiKey:-$(_readaccountconf_mutable SIMPLY_ApiKey)}"
+
+ if [ -z "$SIMPLY_Api" ]; then
+ SIMPLY_Api="$SIMPLY_Api_Default"
+ fi
+
+ if [ -z "$SIMPLY_AccountName" ] || [ -z "$SIMPLY_ApiKey" ]; then
+ SIMPLY_AccountName=""
+ SIMPLY_ApiKey=""
+
+ _err "A valid Simply API account and apikey not provided."
+ _err "Please provide a valid API user and try again."
+
+ return 1
+ fi
+
+ return 0
+}
+
+_simply_save_config() {
+ if [ "$SIMPLY_Api" != "$SIMPLY_Api_Default" ]; then
+ _saveaccountconf_mutable SIMPLY_Api "$SIMPLY_Api"
+ fi
+ _saveaccountconf_mutable SIMPLY_AccountName "$SIMPLY_AccountName"
+ _saveaccountconf_mutable SIMPLY_ApiKey "$SIMPLY_ApiKey"
+}
+
+_simply_get_all_records() {
+ domain=$1
+
+ if ! _simply_rest GET "my/products/$domain/dns/records/"; then
+ return 1
+ fi
+
+ return 0
+}
+
+_get_root() {
+ domain=$1
+ i=2
+ p=1
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+
+ if ! _simply_rest GET "my/products/$h/dns/"; then
+ return 1
+ fi
+
+ if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then
+ _debug "$h not found"
+ else
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="$h"
+ return 0
+ fi
+ p="$i"
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+_simply_add_record() {
+ domain=$1
+ sub_domain=$2
+ txtval=$3
+
+ data="{\"name\": \"$sub_domain\", \"type\":\"TXT\", \"data\": \"$txtval\", \"priority\":0, \"ttl\": 3600}"
+
+ if ! _simply_rest POST "my/products/$domain/dns/records/" "$data"; then
+ _err "Adding record not successfull!"
+ return 1
+ fi
+
+ if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then
+ _err "Call to API not sucessfull, see below message for more details"
+ _err "$response"
+ return 1
+ fi
+
+ return 0
+}
+
+_simply_delete_record() {
+ domain=$1
+ sub_domain=$2
+ record_id=$3
+
+ _debug record_id "Delete record with id $record_id"
+
+ if ! _simply_rest DELETE "my/products/$domain/dns/records/$record_id/"; then
+ _err "Deleting record not successfull!"
+ return 1
+ fi
+
+ if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then
+ _err "Call to API not sucessfull, see below message for more details"
+ _err "$response"
+ return 1
+ fi
+
+ return 0
+}
+
+_simply_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+
+ _debug2 data "$data"
+ _debug2 ep "$ep"
+ _debug2 m "$m"
+
+ basicauth=$(printf "%s:%s" "$SIMPLY_AccountName" "$SIMPLY_ApiKey" | _base64)
+
+ if [ "$basicauth" ]; then
+ export _H1="Authorization: Basic $basicauth"
+ fi
+
+ export _H2="Content-Type: application/json"
+
+ if [ "$m" != "GET" ]; then
+ response="$(_post "$data" "$SIMPLY_Api/$ep" "" "$m")"
+ else
+ response="$(_get "$SIMPLY_Api/$ep")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+
+ response="$(echo "$response" | _normalizeJson)"
+
+ _debug2 response "$response"
+
+ if _contains "$response" "Invalid account authorization"; then
+ _err "It seems that your api key or accountnumber is not correct."
+ return 1
+ fi
+
+ return 0
+}
diff --git a/dnsapi/dns_udr.sh b/dnsapi/dns_udr.sh
new file mode 100644
index 00000000..caada826
--- /dev/null
+++ b/dnsapi/dns_udr.sh
@@ -0,0 +1,160 @@
+#!/usr/bin/env sh
+
+# united-domains Reselling (https://www.ud-reselling.com/) DNS API
+# Author: Andreas Scherer (https://github.com/andischerer)
+# Created: 2021-02-01
+#
+# Set the environment variables as below:
+#
+# export UDR_USER="your_username_goes_here"
+# export UDR_PASS="some_password_goes_here"
+#
+
+UDR_API="https://api.domainreselling.de/api/call.cgi"
+UDR_TTL="30"
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "some_long_string_of_characters_go_here_from_lets_encrypt"
+dns_udr_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ UDR_USER="${UDR_USER:-$(_readaccountconf_mutable UDR_USER)}"
+ UDR_PASS="${UDR_PASS:-$(_readaccountconf_mutable UDR_PASS)}"
+ if [ -z "$UDR_USER" ] || [ -z "$UDR_PASS" ]; then
+ UDR_USER=""
+ UDR_PASS=""
+ _err "You didn't specify an UD-Reselling username and password yet"
+ return 1
+ fi
+ # save the username and password to the account conf file.
+ _saveaccountconf_mutable UDR_USER "$UDR_USER"
+ _saveaccountconf_mutable UDR_PASS "$UDR_PASS"
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _dnszone "${_dnszone}"
+
+ _debug "Getting txt records"
+ if ! _udr_rest "QueryDNSZoneRRList" "dnszone=${_dnszone}"; then
+ return 1
+ fi
+
+ rr="${fulldomain}. ${UDR_TTL} IN TXT ${txtvalue}"
+ _debug resource_record "${rr}"
+ if _contains "$response" "$rr" >/dev/null; then
+ _err "Error, it would appear that this record already exists. Please review existing TXT records for this domain."
+ return 1
+ fi
+
+ _info "Adding record"
+ if ! _udr_rest "UpdateDNSZone" "dnszone=${_dnszone}&addrr0=${rr}"; then
+ _err "Adding the record did not succeed, please verify/check."
+ return 1
+ fi
+
+ _info "Added, OK"
+ return 0
+}
+
+dns_udr_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ UDR_USER="${UDR_USER:-$(_readaccountconf_mutable UDR_USER)}"
+ UDR_PASS="${UDR_PASS:-$(_readaccountconf_mutable UDR_PASS)}"
+ if [ -z "$UDR_USER" ] || [ -z "$UDR_PASS" ]; then
+ UDR_USER=""
+ UDR_PASS=""
+ _err "You didn't specify an UD-Reselling username and password yet"
+ return 1
+ fi
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _dnszone "${_dnszone}"
+
+ _debug "Getting txt records"
+ if ! _udr_rest "QueryDNSZoneRRList" "dnszone=${_dnszone}"; then
+ return 1
+ fi
+
+ rr="${fulldomain}. ${UDR_TTL} IN TXT ${txtvalue}"
+ _debug resource_record "${rr}"
+ if _contains "$response" "$rr" >/dev/null; then
+ if ! _udr_rest "UpdateDNSZone" "dnszone=${_dnszone}&delrr0=${rr}"; then
+ _err "Deleting the record did not succeed, please verify/check."
+ return 1
+ fi
+ _info "Removed, OK"
+ return 0
+ else
+ _info "Text record is not present, will not delete anything."
+ return 0
+ fi
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ domain=$1
+ i=1
+
+ if ! _udr_rest "QueryDNSZoneList" ""; then
+ return 1
+ fi
+
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug h "$h"
+
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+
+ if _contains "${response}" "${h}." >/dev/null; then
+ _dnszone=$(echo "$response" | _egrep_o "${h}")
+ if [ "$_dnszone" ]; then
+ return 0
+ fi
+ return 1
+ fi
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+_udr_rest() {
+ if [ -n "$2" ]; then
+ data="command=$1&$2"
+ else
+ data="command=$1"
+ fi
+
+ _debug data "${data}"
+ response="$(_post "${data}" "${UDR_API}?s_login=${UDR_USER}&s_pw=${UDR_PASS}" "" "POST")"
+
+ _code=$(echo "$response" | _egrep_o "code = ([0-9]+)" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
+ _description=$(echo "$response" | _egrep_o "description = .*" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
+
+ _debug response_code "$_code"
+ _debug response_description "$_description"
+
+ if [ ! "$_code" = "200" ]; then
+ _err "DNS-API-Error: $_description"
+ return 1
+ fi
+
+ return 0
+}
diff --git a/dnsapi/dns_veesp.sh b/dnsapi/dns_veesp.sh
new file mode 100644
index 00000000..b8a41d00
--- /dev/null
+++ b/dnsapi/dns_veesp.sh
@@ -0,0 +1,158 @@
+#!/usr/bin/env sh
+
+# bug reports to stepan@plyask.in
+
+#
+# export VEESP_User="username"
+# export VEESP_Password="password"
+
+VEESP_Api="https://secure.veesp.com/api"
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_veesp_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ VEESP_Password="${VEESP_Password:-$(_readaccountconf_mutable VEESP_Password)}"
+ VEESP_User="${VEESP_User:-$(_readaccountconf_mutable VEESP_User)}"
+ VEESP_auth=$(printf "%s" "$VEESP_User:$VEESP_Password" | _base64)
+
+ if [ -z "$VEESP_Password" ] || [ -z "$VEESP_User" ]; then
+ VEESP_Password=""
+ VEESP_User=""
+ _err "You don't specify veesp api key and email yet."
+ _err "Please create you key and try again."
+ return 1
+ fi
+
+ #save the api key and email to the account conf file.
+ _saveaccountconf_mutable VEESP_Password "$VEESP_Password"
+ _saveaccountconf_mutable VEESP_User "$VEESP_User"
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _domain_id "$_domain_id"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _info "Adding record"
+ if VEESP_rest POST "service/$_service_id/dns/$_domain_id/records" "{\"name\":\"$fulldomain\",\"ttl\":1,\"priority\":0,\"type\":\"TXT\",\"content\":\"$txtvalue\"}"; then
+ if _contains "$response" "\"success\":true"; then
+ _info "Added"
+ #todo: check if the record takes effect
+ return 0
+ else
+ _err "Add txt record error."
+ return 1
+ fi
+ fi
+}
+
+# Usage: fulldomain txtvalue
+# Used to remove the txt record after validation
+dns_veesp_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ VEESP_Password="${VEESP_Password:-$(_readaccountconf_mutable VEESP_Password)}"
+ VEESP_User="${VEESP_User:-$(_readaccountconf_mutable VEESP_User)}"
+ VEESP_auth=$(printf "%s" "$VEESP_User:$VEESP_Password" | _base64)
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _domain_id "$_domain_id"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _debug "Getting txt records"
+ VEESP_rest GET "service/$_service_id/dns/$_domain_id"
+
+ count=$(printf "%s\n" "$response" | _egrep_o "\"type\":\"TXT\",\"content\":\".\"$txtvalue.\"\"" | wc -l | tr -d " ")
+ _debug count "$count"
+ if [ "$count" = "0" ]; then
+ _info "Don't need to remove."
+ else
+ record_id=$(printf "%s\n" "$response" | _egrep_o "{\"id\":[^}]*\"type\":\"TXT\",\"content\":\".\"$txtvalue.\"\"" | cut -d\" -f4)
+ _debug "record_id" "$record_id"
+ if [ -z "$record_id" ]; then
+ _err "Can not get record id to remove."
+ return 1
+ fi
+ if ! VEESP_rest DELETE "service/$_service_id/dns/$_domain_id/records/$record_id"; then
+ _err "Delete record error."
+ return 1
+ fi
+ _contains "$response" "\"success\":true"
+ fi
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_get_root() {
+ domain=$1
+ i=2
+ p=1
+ if ! VEESP_rest GET "dns"; then
+ return 1
+ fi
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug h "$h"
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+
+ if _contains "$response" "\"name\":\"$h\""; then
+ _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"domain_id\":[^,]*,\"name\":\"$h\"" | cut -d : -f 2 | cut -d , -f 1 | cut -d '"' -f 2)
+ _debug _domain_id "$_domain_id"
+ _service_id=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$h\",\"service_id\":[^}]*" | cut -d : -f 3 | cut -d '"' -f 2)
+ _debug _service_id "$_service_id"
+ if [ "$_domain_id" ]; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="$h"
+ return 0
+ fi
+ return 1
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+VEESP_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+
+ export _H1="Accept: application/json"
+ export _H2="Authorization: Basic $VEESP_auth"
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ export _H3="Content-Type: application/json"
+ response="$(_post "$data" "$VEESP_Api/$ep" "" "$m")"
+ else
+ response="$(_get "$VEESP_Api/$ep")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_vultr.sh b/dnsapi/dns_vultr.sh
index c7b52e84..84857966 100644
--- a/dnsapi/dns_vultr.sh
+++ b/dnsapi/dns_vultr.sh
@@ -33,7 +33,7 @@ dns_vultr_add() {
_debug 'Getting txt records'
_vultr_rest GET "dns/records?domain=$_domain"
- if printf "%s\n" "$response" | grep "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
+ if printf "%s\n" "$response" | grep -- "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
_err 'Error'
return 1
fi
@@ -73,12 +73,12 @@ dns_vultr_rm() {
_debug 'Getting txt records'
_vultr_rest GET "dns/records?domain=$_domain"
- if printf "%s\n" "$response" | grep "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
+ if printf "%s\n" "$response" | grep -- "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
_err 'Error'
return 1
fi
- _record_id="$(echo "$response" | tr '{}' '\n' | grep '"TXT"' | grep "$txtvalue" | tr ',' '\n' | grep -i 'RECORDID' | cut -d : -f 2)"
+ _record_id="$(echo "$response" | tr '{}' '\n' | grep '"TXT"' | grep -- "$txtvalue" | tr ',' '\n' | grep -i 'RECORDID' | cut -d : -f 2)"
_debug _record_id "$_record_id"
if [ "$_record_id" ]; then
_info "Successfully retrieved the record id for ACME challenge."
diff --git a/dnsapi/dns_websupport.sh b/dnsapi/dns_websupport.sh
new file mode 100644
index 00000000..e824c9c0
--- /dev/null
+++ b/dnsapi/dns_websupport.sh
@@ -0,0 +1,207 @@
+#!/usr/bin/env sh
+
+# Acme.sh DNS API wrapper for websupport.sk
+#
+# Original author: trgo.sk (https://github.com/trgosk)
+# Tweaks by: akulumbeg (https://github.com/akulumbeg)
+# Report Bugs here: https://github.com/akulumbeg/acme.sh
+
+# Requirements: API Key and Secret from https://admin.websupport.sk/en/auth/apiKey
+#
+# WS_ApiKey="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+# (called "Identifier" in the WS Admin)
+#
+# WS_ApiSecret="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+# (called "Secret key" in the WS Admin)
+
+WS_Api="https://rest.websupport.sk"
+
+######## Public functions #####################
+
+dns_websupport_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ WS_ApiKey="${WS_ApiKey:-$(_readaccountconf_mutable WS_ApiKey)}"
+ WS_ApiSecret="${WS_ApiSecret:-$(_readaccountconf_mutable WS_ApiSecret)}"
+
+ if [ "$WS_ApiKey" ] && [ "$WS_ApiSecret" ]; then
+ _saveaccountconf_mutable WS_ApiKey "$WS_ApiKey"
+ _saveaccountconf_mutable WS_ApiSecret "$WS_ApiSecret"
+ else
+ WS_ApiKey=""
+ WS_ApiSecret=""
+ _err "You did not specify the API Key and/or API Secret"
+ _err "You can get the API login credentials from https://admin.websupport.sk/en/auth/apiKey"
+ return 1
+ fi
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so
+ # we can not use updating anymore.
+ # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2)
+ # _debug count "$count"
+ # if [ "$count" = "0" ]; then
+ _info "Adding record"
+ if _ws_rest POST "/v1/user/self/zone/$_domain/record" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
+ if _contains "$response" "$txtvalue"; then
+ _info "Added, OK"
+ return 0
+ elif _contains "$response" "The record already exists"; then
+ _info "Already exists, OK"
+ return 0
+ else
+ _err "Add txt record error."
+ return 1
+ fi
+ fi
+ _err "Add txt record error."
+ return 1
+
+}
+
+dns_websupport_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _debug2 fulldomain "$fulldomain"
+ _debug2 txtvalue "$txtvalue"
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _debug "Getting txt records"
+ _ws_rest GET "/v1/user/self/zone/$_domain/record"
+
+ if [ "$(printf "%s" "$response" | tr -d " " | grep -c \"items\")" -lt "1" ]; then
+ _err "Error: $response"
+ return 1
+ fi
+
+ record_line="$(_get_from_array "$response" "$txtvalue")"
+ _debug record_line "$record_line"
+ if [ -z "$record_line" ]; then
+ _info "Don't need to remove."
+ else
+ record_id=$(echo "$record_line" | _egrep_o "\"id\": *[^,]*" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ")
+ _debug "record_id" "$record_id"
+ if [ -z "$record_id" ]; then
+ _err "Can not get record id to remove."
+ return 1
+ fi
+ if ! _ws_rest DELETE "/v1/user/self/zone/$_domain/record/$record_id"; then
+ _err "Delete record error."
+ return 1
+ fi
+ if [ "$(printf "%s" "$response" | tr -d " " | grep -c \"success\")" -lt "1" ]; then
+ return 1
+ else
+ return 0
+ fi
+ fi
+
+}
+
+#################### Private Functions ##################################
+
+_get_root() {
+ domain=$1
+ i=1
+ p=1
+
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug h "$h"
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+
+ if ! _ws_rest GET "/v1/user/self/zone"; then
+ return 1
+ fi
+
+ if _contains "$response" "\"name\":\"$h\""; then
+ _domain_id=$(echo "$response" | _egrep_o "\[.\"id\": *[^,]*" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ")
+ if [ "$_domain_id" ]; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain=$h
+ return 0
+ fi
+ return 1
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+_ws_rest() {
+ me=$1
+ pa="$2"
+ da="$3"
+
+ _debug2 api_key "$WS_ApiKey"
+ _debug2 api_secret "$WS_ApiSecret"
+
+ timestamp=$(_time)
+ datez="$(_utc_date | sed "s/ /T/" | sed "s/$/+0000/")"
+ canonical_request="${me} ${pa} ${timestamp}"
+ signature_hash=$(printf "%s" "$canonical_request" | _hmac sha1 "$(printf "%s" "$WS_ApiSecret" | _hex_dump | tr -d " ")" hex)
+ basicauth="$(printf "%s:%s" "$WS_ApiKey" "$signature_hash" | _base64)"
+
+ _debug2 method "$me"
+ _debug2 path "$pa"
+ _debug2 data "$da"
+ _debug2 timestamp "$timestamp"
+ _debug2 datez "$datez"
+ _debug2 canonical_request "$canonical_request"
+ _debug2 signature_hash "$signature_hash"
+ _debug2 basicauth "$basicauth"
+
+ export _H1="Accept: application/json"
+ export _H2="Content-Type: application/json"
+ export _H3="Authorization: Basic ${basicauth}"
+ export _H4="Date: ${datez}"
+
+ _debug2 H1 "$_H1"
+ _debug2 H2 "$_H2"
+ _debug2 H3 "$_H3"
+ _debug2 H4 "$_H4"
+
+ if [ "$me" != "GET" ]; then
+ _debug2 "${me} $WS_Api${pa}"
+ _debug data "$da"
+ response="$(_post "$da" "${WS_Api}${pa}" "" "$me")"
+ else
+ _debug2 "GET $WS_Api${pa}"
+ response="$(_get "$WS_Api${pa}")"
+ fi
+
+ _debug2 response "$response"
+ return "$?"
+}
+
+_get_from_array() {
+ va="$1"
+ fi="$2"
+ for i in $(echo "$va" | sed "s/{/ /g"); do
+ if _contains "$i" "$fi"; then
+ echo "$i"
+ break
+ fi
+ done
+}
diff --git a/dnsapi/dns_world4you.sh b/dnsapi/dns_world4you.sh
index 24b8dd68..fd124754 100644
--- a/dnsapi/dns_world4you.sh
+++ b/dnsapi/dns_world4you.sh
@@ -24,7 +24,7 @@ dns_world4you_add() {
fi
export _H1="Cookie: W4YSESSID=$sessid"
- form=$(_get "$WORLD4YOU_API/dashboard/paketuebersicht")
+ form=$(_get "$WORLD4YOU_API/")
_get_paketnr "$fqdn" "$form"
paketnr="$PAKETNR"
if [ -z "$paketnr" ]; then
@@ -36,7 +36,6 @@ dns_world4you_add() {
export _H1="Cookie: W4YSESSID=$sessid"
form=$(_get "$WORLD4YOU_API/$paketnr/dns")
formiddp=$(echo "$form" | grep 'AddDnsRecordForm\[uniqueFormIdDP\]' | sed 's/^.*name="AddDnsRecordForm\[uniqueFormIdDP\]" value="\([^"]*\)".*$/\1/')
- formidttl=$(echo "$form" | grep 'AddDnsRecordForm\[uniqueFormIdTTL\]' | sed 's/^.*name="AddDnsRecordForm\[uniqueFormIdTTL\]" value="\([^"]*\)".*$/\1/')
form_token=$(echo "$form" | grep 'AddDnsRecordForm\[_token\]' | sed 's/^.*name="AddDnsRecordForm\[_token\]" value="\([^"]*\)".*$/\1/')
if [ -z "$formiddp" ]; then
_err "Unable to parse form"
@@ -45,24 +44,31 @@ dns_world4you_add() {
_resethttp
export ACME_HTTP_NO_REDIRECTS=1
- body="AddDnsRecordForm[name]=$RECORD&AddDnsRecordForm[dnsType][type]=TXT&\
-AddDnsRecordForm[value]=$value&AddDnsRecordForm[aktivPaket]=$paketnr&AddDnsRecordForm[uniqueFormIdDP]=$formiddp&\
-AddDnsRecordForm[uniqueFormIdTTL]=$formidttl&AddDnsRecordForm[_token]=$form_token"
+ body="AddDnsRecordForm[name]=$RECORD&AddDnsRecordForm[dnsType][type]=TXT&AddDnsRecordForm[value]=$value&AddDnsRecordForm[uniqueFormIdDP]=$formiddp&AddDnsRecordForm[_token]=$form_token"
_info "Adding record..."
ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/dns" '' POST 'application/x-www-form-urlencoded')
_resethttp
- if grep '302' >/dev/null <"$HTTP_HEADER"; then
+ if _contains "$(_head_n 3 <"$HTTP_HEADER")" '302'; then
res=$(_get "$WORLD4YOU_API/$paketnr/dns")
if _contains "$res" "successfully"; then
return 0
else
msg=$(echo "$res" | tr '\n' '\t' | sed 's/.*[^\t]*\t *\([^\t]*\)\t.*/\1/')
+ if _contains "$msg" '^<\!DOCTYPE html>'; then
+ msg='Unknown error'
+ fi
_err "Unable to add record: $msg"
+ if _contains "$msg" '^<\!DOCTYPE html>'; then
+ echo "$ret" >'error-01.html'
+ echo "$res" >'error-02.html'
+ _err "View error-01.html and error-02.html for debugging"
+ fi
return 1
fi
else
- _err "$(_head_n 1 <"$HTTP_HEADER")"
+ _err "$(_head_n 3 <"$HTTP_HEADER")"
+ _err "View $HTTP_HEADER for debugging"
return 1
fi
}
@@ -81,7 +87,7 @@ dns_world4you_rm() {
fi
export _H1="Cookie: W4YSESSID=$sessid"
- form=$(_get "$WORLD4YOU_API/dashboard/paketuebersicht")
+ form=$(_get "$WORLD4YOU_API/")
_get_paketnr "$fqdn" "$form"
paketnr="$PAKETNR"
if [ -z "$paketnr" ]; then
@@ -92,7 +98,6 @@ dns_world4you_rm() {
form=$(_get "$WORLD4YOU_API/$paketnr/dns")
formiddp=$(echo "$form" | grep 'DeleteDnsRecordForm\[uniqueFormIdDP\]' | sed 's/^.*name="DeleteDnsRecordForm\[uniqueFormIdDP\]" value="\([^"]*\)".*$/\1/')
- formidttl=$(echo "$form" | grep 'DeleteDnsRecordForm\[uniqueFormIdTTL\]' | sed 's/^.*name="DeleteDnsRecordForm\[uniqueFormIdTTL\]" value="\([^"]*\)".*$/\1/')
form_token=$(echo "$form" | grep 'DeleteDnsRecordForm\[_token\]' | sed 's/^.*name="DeleteDnsRecordForm\[_token\]" value="\([^"]*\)".*$/\1/')
if [ -z "$formiddp" ]; then
_err "Unable to parse form"
@@ -104,24 +109,31 @@ dns_world4you_rm() {
_resethttp
export ACME_HTTP_NO_REDIRECTS=1
- body="DeleteDnsRecordForm[recordId]=$recordid&DeleteDnsRecordForm[aktivPaket]=$paketnr&\
-DeleteDnsRecordForm[uniqueFormIdDP]=$formiddp&DeleteDnsRecordForm[uniqueFormIdTTL]=$formidttl&\
-DeleteDnsRecordForm[_token]=$form_token"
+ body="DeleteDnsRecordForm[recordId]=$recordid&DeleteDnsRecordForm[uniqueFormIdDP]=$formiddp&DeleteDnsRecordForm[_token]=$form_token"
_info "Removing record..."
- ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/deleteRecord" '' POST 'application/x-www-form-urlencoded')
+ ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/dns/record/delete" '' POST 'application/x-www-form-urlencoded')
_resethttp
- if grep '302' >/dev/null <"$HTTP_HEADER"; then
+ if _contains "$(_head_n 3 <"$HTTP_HEADER")" '302'; then
res=$(_get "$WORLD4YOU_API/$paketnr/dns")
if _contains "$res" "successfully"; then
return 0
else
msg=$(echo "$res" | tr '\n' '\t' | sed 's/.*[^\t]*\t *\([^\t]*\)\t.*/\1/')
+ if _contains "$msg" '^<\!DOCTYPE html>'; then
+ msg='Unknown error'
+ fi
_err "Unable to remove record: $msg"
+ if _contains "$msg" '^<\!DOCTYPE html>'; then
+ echo "$ret" >'error-01.html'
+ echo "$res" >'error-02.html'
+ _err "View error-01.html and error-02.html for debugging"
+ fi
return 1
fi
else
- _err "$(_head_n 1 <"$HTTP_HEADER")"
+ _err "$(_head_n 3 <"$HTTP_HEADER")"
+ _err "View $HTTP_HEADER for debugging"
return 1
fi
}
@@ -172,10 +184,10 @@ _get_paketnr() {
fqdn="$1"
form="$2"
- domains=$(echo "$form" | grep '^ *[A-Za-z0-9_\.-]*\.[A-Za-z0-9_-]*$' | sed 's/^\s*\(\S*\)$/\1/')
+ domains=$(echo "$form" | grep 'header-paket-domain' | sed 's/<[^>]*>//g' | sed 's/^.*>\([^>]*\)$/\1/')
domain=''
for domain in $domains; do
- if echo "$fqdn" | grep "$domain\$" >/dev/null; then
+ if _contains "$fqdn" "$domain\$"; then
break
fi
domain=''
@@ -185,7 +197,8 @@ _get_paketnr() {
fi
TLD="$domain"
+ _debug domain "$domain"
RECORD=$(echo "$fqdn" | cut -c"1-$((${#fqdn} - ${#TLD} - 1))")
- PAKETNR=$(echo "$form" | grep "data-textfilter=\" $domain " | _head_n 1 | sed 's/^.* \([0-9]*\) .*$/\1/')
+ PAKETNR=$(echo "$form" | grep "data-textfilter=\".* $domain " | _head_n 1 | sed 's/^.* \([0-9]*\) .*$/\1/')
return 0
}
diff --git a/notify/bark.sh b/notify/bark.sh
new file mode 100644
index 00000000..bbd5bf34
--- /dev/null
+++ b/notify/bark.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env sh
+
+#Support iOS Bark Notification
+
+#BARK_API_URL="https://api.day.app/xxxx"
+#BARK_SOUND="yyyy"
+#BARK_GROUP="zzzz"
+
+# subject content statusCode
+bark_send() {
+ _subject="$1"
+ _content="$2"
+ _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+ _debug "_subject" "$_subject"
+ _debug "_content" "$_content"
+ _debug "_statusCode" "$_statusCode"
+
+ BARK_API_URL="${BARK_API_URL:-$(_readaccountconf_mutable BARK_API_URL)}"
+ if [ -z "$BARK_API_URL" ]; then
+ BARK_API_URL=""
+ _err "You didn't specify a Bark API URL BARK_API_URL yet."
+ _err "You can download Bark from App Store and get yours."
+ return 1
+ fi
+ _saveaccountconf_mutable BARK_API_URL "$BARK_API_URL"
+
+ BARK_SOUND="${BARK_SOUND:-$(_readaccountconf_mutable BARK_SOUND)}"
+ _saveaccountconf_mutable BARK_SOUND "$BARK_SOUND"
+
+ BARK_GROUP="${BARK_GROUP:-$(_readaccountconf_mutable BARK_GROUP)}"
+ if [ -z "$BARK_GROUP" ]; then
+ BARK_GROUP="ACME"
+ _info "The BARK_GROUP is not set, so use the default ACME as group name."
+ else
+ _saveaccountconf_mutable BARK_GROUP "$BARK_GROUP"
+ fi
+
+ _content=$(echo "$_content" | _url_encode)
+ _subject=$(echo "$_subject" | _url_encode)
+
+ response="$(_get "$BARK_API_URL/$_subject/$_content?sound=$BARK_SOUND&group=$BARK_GROUP")"
+
+ if [ "$?" = "0" ] && _contains "$response" "success"; then
+ _info "Bark API fired success."
+ return 0
+ fi
+
+ _err "Bark API fired error."
+ _err "$response"
+ return 1
+}
diff --git a/notify/discord.sh b/notify/discord.sh
new file mode 100644
index 00000000..58362a4e
--- /dev/null
+++ b/notify/discord.sh
@@ -0,0 +1,57 @@
+#!/usr/bin/env sh
+
+#Support Discord webhooks
+
+# Required:
+#DISCORD_WEBHOOK_URL=""
+# Optional:
+#DISCORD_USERNAME=""
+#DISCORD_AVATAR_URL=""
+
+discord_send() {
+ _subject="$1"
+ _content="$2"
+ _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+ _debug "_statusCode" "$_statusCode"
+
+ DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-$(_readaccountconf_mutable DISCORD_WEBHOOK_URL)}"
+ if [ -z "$DISCORD_WEBHOOK_URL" ]; then
+ DISCORD_WEBHOOK_URL=""
+ _err "You didn't specify a Discord webhook url DISCORD_WEBHOOK_URL yet."
+ return 1
+ fi
+ _saveaccountconf_mutable DISCORD_WEBHOOK_URL "$DISCORD_WEBHOOK_URL"
+
+ DISCORD_USERNAME="${DISCORD_USERNAME:-$(_readaccountconf_mutable DISCORD_USERNAME)}"
+ if [ "$DISCORD_USERNAME" ]; then
+ _saveaccountconf_mutable DISCORD_USERNAME "$DISCORD_USERNAME"
+ fi
+
+ DISCORD_AVATAR_URL="${DISCORD_AVATAR_URL:-$(_readaccountconf_mutable DISCORD_AVATAR_URL)}"
+ if [ "$DISCORD_AVATAR_URL" ]; then
+ _saveaccountconf_mutable DISCORD_AVATAR_URL "$DISCORD_AVATAR_URL"
+ fi
+
+ export _H1="Content-Type: application/json"
+
+ _content="$(printf "**%s**\n%s" "$_subject" "$_content" | _json_encode)"
+ _data="{\"content\": \"$_content\" "
+ if [ "$DISCORD_USERNAME" ]; then
+ _data="$_data, \"username\": \"$DISCORD_USERNAME\" "
+ fi
+ if [ "$DISCORD_AVATAR_URL" ]; then
+ _data="$_data, \"avatar_url\": \"$DISCORD_AVATAR_URL\" "
+ fi
+ _data="$_data}"
+
+ if _post "$_data" "$DISCORD_WEBHOOK_URL?wait=true"; then
+ # shellcheck disable=SC2154
+ if [ "$response" ]; then
+ _info "discord send success."
+ return 0
+ fi
+ fi
+ _err "discord send error."
+ _err "$response"
+ return 1
+}
diff --git a/notify/feishu.sh b/notify/feishu.sh
new file mode 100644
index 00000000..18693c2d
--- /dev/null
+++ b/notify/feishu.sh
@@ -0,0 +1,48 @@
+#!/usr/bin/env sh
+
+#Support feishu webhooks api
+
+#required
+#FEISHU_WEBHOOK="xxxx"
+
+#optional
+#FEISHU_KEYWORD="yyyy"
+
+# subject content statusCode
+feishu_send() {
+ _subject="$1"
+ _content="$2"
+ _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+ _debug "_subject" "$_subject"
+ _debug "_content" "$_content"
+ _debug "_statusCode" "$_statusCode"
+
+ FEISHU_WEBHOOK="${FEISHU_WEBHOOK:-$(_readaccountconf_mutable FEISHU_WEBHOOK)}"
+ if [ -z "$FEISHU_WEBHOOK" ]; then
+ FEISHU_WEBHOOK=""
+ _err "You didn't specify a feishu webhooks FEISHU_WEBHOOK yet."
+ _err "You can get yours from https://www.feishu.cn"
+ return 1
+ fi
+ _saveaccountconf_mutable FEISHU_WEBHOOK "$FEISHU_WEBHOOK"
+
+ FEISHU_KEYWORD="${FEISHU_KEYWORD:-$(_readaccountconf_mutable FEISHU_KEYWORD)}"
+ if [ "$FEISHU_KEYWORD" ]; then
+ _saveaccountconf_mutable FEISHU_KEYWORD "$FEISHU_KEYWORD"
+ fi
+
+ _content=$(echo "$_content" | _json_encode)
+ _subject=$(echo "$_subject" | _json_encode)
+ _data="{\"msg_type\": \"text\", \"content\": {\"text\": \"[$FEISHU_KEYWORD]\n$_subject\n$_content\"}}"
+
+ response="$(_post "$_data" "$FEISHU_WEBHOOK" "" "POST" "application/json")"
+
+ if [ "$?" = "0" ] && _contains "$response" "StatusCode\":0"; then
+ _info "feishu webhooks event fired success."
+ return 0
+ fi
+
+ _err "feishu webhooks event fired error."
+ _err "$response"
+ return 1
+}
diff --git a/notify/gotify.sh b/notify/gotify.sh
new file mode 100644
index 00000000..e370bc21
--- /dev/null
+++ b/notify/gotify.sh
@@ -0,0 +1,62 @@
+#!/usr/bin/env sh
+
+#Support Gotify
+
+#GOTIFY_URL="https://gotify.example.com"
+#GOTIFY_TOKEN="123456789ABCDEF"
+
+#optional
+#GOTIFY_PRIORITY=0
+
+# subject content statusCode
+gotify_send() {
+ _subject="$1"
+ _content="$2"
+ _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+ _debug "_subject" "$_subject"
+ _debug "_content" "$_content"
+ _debug "_statusCode" "$_statusCode"
+
+ GOTIFY_URL="${GOTIFY_URL:-$(_readaccountconf_mutable GOTIFY_URL)}"
+ if [ -z "$GOTIFY_URL" ]; then
+ GOTIFY_URL=""
+ _err "You didn't specify the gotify server url GOTIFY_URL."
+ return 1
+ fi
+ _saveaccountconf_mutable GOTIFY_URL "$GOTIFY_URL"
+
+ GOTIFY_TOKEN="${GOTIFY_TOKEN:-$(_readaccountconf_mutable GOTIFY_TOKEN)}"
+ if [ -z "$GOTIFY_TOKEN" ]; then
+ GOTIFY_TOKEN=""
+ _err "You didn't specify the gotify token GOTIFY_TOKEN."
+ return 1
+ fi
+ _saveaccountconf_mutable GOTIFY_TOKEN "$GOTIFY_TOKEN"
+
+ GOTIFY_PRIORITY="${GOTIFY_PRIORITY:-$(_readaccountconf_mutable GOTIFY_PRIORITY)}"
+ if [ -z "$GOTIFY_PRIORITY" ]; then
+ GOTIFY_PRIORITY=0
+ else
+ _saveaccountconf_mutable GOTIFY_PRIORITY "$GOTIFY_PRIORITY"
+ fi
+
+ export _H1="X-Gotify-Key: ${GOTIFY_TOKEN}"
+ export _H2="Content-Type: application/json"
+
+ _content=$(echo "$_content" | _json_encode)
+ _subject=$(echo "$_subject" | _json_encode)
+
+ _data="{\"title\": \"${_subject}\", \"message\": \"${_content}\", \"priority\": ${GOTIFY_PRIORITY}}"
+
+ response="$(_post "${_data}" "${GOTIFY_URL}/message" "" "POST" "application/json")"
+
+ if [ "$?" != "0" ]; then
+ _err "Failed to send message"
+ _err "$response"
+ return 1
+ fi
+
+ _debug2 response "$response"
+
+ return 0
+}
diff --git a/notify/mail.sh b/notify/mail.sh
index d33fd0d2..656dd371 100644
--- a/notify/mail.sh
+++ b/notify/mail.sh
@@ -62,7 +62,7 @@ mail_send() {
fi
contenttype="text/plain; charset=utf-8"
- subject="=?UTF-8?B?$(echo "$_subject" | _base64)?="
+ subject="=?UTF-8?B?$(printf -- "%b" "$_subject" | _base64)?="
result=$({ _mail_body | eval "$(_mail_cmnd)"; } 2>&1)
# shellcheck disable=SC2181
@@ -79,7 +79,7 @@ mail_send() {
_mail_bin() {
_MAIL_BIN=""
- for b in "$MAIL_BIN" sendmail ssmtp mutt mail msmtp; do
+ for b in $MAIL_BIN sendmail ssmtp mutt mail msmtp; do
if _exists "$b"; then
_MAIL_BIN="$b"
break
@@ -131,6 +131,7 @@ _mail_body() {
echo "To: $MAIL_TO"
echo "Subject: $subject"
echo "Content-Type: $contenttype"
+ echo "MIME-Version: 1.0"
echo
;;
esac
diff --git a/notify/pushbullet.sh b/notify/pushbullet.sh
new file mode 100644
index 00000000..ca997c84
--- /dev/null
+++ b/notify/pushbullet.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env sh
+
+#Support for pushbullet.com's api. Push notification, notification sync and message platform for multiple platforms
+#PUSHBULLET_TOKEN="" Required, pushbullet application token
+#PUSHBULLET_DEVICE="" Optional, Specific device, ignore to send to all devices
+
+PUSHBULLET_URI="https://api.pushbullet.com/v2/pushes"
+pushbullet_send() {
+ _subject="$1"
+ _content="$2"
+ _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+ _debug "_statusCode" "$_statusCode"
+
+ PUSHBULLET_TOKEN="${PUSHBULLET_TOKEN:-$(_readaccountconf_mutable PUSHBULLET_TOKEN)}"
+ if [ -z "$PUSHBULLET_TOKEN" ]; then
+ PUSHBULLET_TOKEN=""
+ _err "You didn't specify a Pushbullet application token yet."
+ return 1
+ fi
+ _saveaccountconf_mutable PUSHBULLET_TOKEN "$PUSHBULLET_TOKEN"
+
+ PUSHBULLET_DEVICE="${PUSHBULLET_DEVICE:-$(_readaccountconf_mutable PUSHBULLET_DEVICE)}"
+ if [ -z "$PUSHBULLET_DEVICE" ]; then
+ _clearaccountconf_mutable PUSHBULLET_DEVICE
+ else
+ _saveaccountconf_mutable PUSHBULLET_DEVICE "$PUSHBULLET_DEVICE"
+ fi
+
+ export _H1="Content-Type: application/json"
+ export _H2="Access-Token: ${PUSHBULLET_TOKEN}"
+ _content="$(printf "*%s*\n" "$_content" | _json_encode)"
+ _subject="$(printf "*%s*\n" "$_subject" | _json_encode)"
+ _data="{\"type\": \"note\",\"title\": \"${_subject}\",\"body\": \"${_content}\",\"device_iden\": \"${PUSHBULLET_DEVICE}\"}"
+ response="$(_post "$_data" "$PUSHBULLET_URI")"
+
+ if [ "$?" != "0" ] || _contains "$response" "\"error_code\""; then
+ _err "PUSHBULLET send error."
+ _err "$response"
+ return 1
+ fi
+
+ _info "PUSHBULLET send success."
+ return 0
+}
diff --git a/notify/sendgrid.sh b/notify/sendgrid.sh
index 0d5ea3b3..82d3f6c6 100644
--- a/notify/sendgrid.sh
+++ b/notify/sendgrid.sh
@@ -37,11 +37,19 @@ sendgrid_send() {
fi
_saveaccountconf_mutable SENDGRID_FROM "$SENDGRID_FROM"
+ SENDGRID_FROM_NAME="${SENDGRID_FROM_NAME:-$(_readaccountconf_mutable SENDGRID_FROM_NAME)}"
+ _saveaccountconf_mutable SENDGRID_FROM_NAME "$SENDGRID_FROM_NAME"
+
export _H1="Authorization: Bearer $SENDGRID_API_KEY"
export _H2="Content-Type: application/json"
_content="$(echo "$_content" | _json_encode)"
- _data="{\"personalizations\": [{\"to\": [{\"email\": \"$SENDGRID_TO\"}]}],\"from\": {\"email\": \"$SENDGRID_FROM\"},\"subject\": \"$_subject\",\"content\": [{\"type\": \"text/plain\", \"value\": \"$_content\"}]}"
+
+ if [ -z "$SENDGRID_FROM_NAME" ]; then
+ _data="{\"personalizations\": [{\"to\": [{\"email\": \"$SENDGRID_TO\"}]}],\"from\": {\"email\": \"$SENDGRID_FROM\"},\"subject\": \"$_subject\",\"content\": [{\"type\": \"text/plain\", \"value\": \"$_content\"}]}"
+ else
+ _data="{\"personalizations\": [{\"to\": [{\"email\": \"$SENDGRID_TO\"}]}],\"from\": {\"email\": \"$SENDGRID_FROM\", \"name\": \"$SENDGRID_FROM_NAME\"},\"subject\": \"$_subject\",\"content\": [{\"type\": \"text/plain\", \"value\": \"$_content\"}]}"
+ fi
response="$(_post "$_data" "https://api.sendgrid.com/v3/mail/send")"
if [ "$?" = "0" ] && [ -z "$response" ]; then
diff --git a/notify/smtp.sh b/notify/smtp.sh
index 6aa37ca3..293c665e 100644
--- a/notify/smtp.sh
+++ b/notify/smtp.sh
@@ -2,14 +2,398 @@
# support smtp
-smtp_send() {
- _subject="$1"
- _content="$2"
- _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
- _debug "_subject" "$_subject"
- _debug "_content" "$_content"
- _debug "_statusCode" "$_statusCode"
+# Please report bugs to https://github.com/acmesh-official/acme.sh/issues/3358
- _err "Not implemented yet."
- return 1
+# This implementation uses either curl or Python (3 or 2.7).
+# (See also the "mail" notify hook, which supports other ways to send mail.)
+
+# SMTP_FROM="from@example.com" # required
+# SMTP_TO="to@example.com" # required
+# SMTP_HOST="smtp.example.com" # required
+# SMTP_PORT="25" # defaults to 25, 465 or 587 depending on SMTP_SECURE
+# SMTP_SECURE="tls" # one of "none", "ssl" (implicit TLS, TLS Wrapper), "tls" (explicit TLS, STARTTLS)
+# SMTP_USERNAME="" # set if SMTP server requires login
+# SMTP_PASSWORD="" # set if SMTP server requires login
+# SMTP_TIMEOUT="30" # seconds for SMTP operations to timeout
+# SMTP_BIN="/path/to/python_or_curl" # default finds first of python3, python2.7, python, pypy3, pypy, curl on PATH
+
+SMTP_SECURE_DEFAULT="tls"
+SMTP_TIMEOUT_DEFAULT="30"
+
+# subject content statuscode
+smtp_send() {
+ SMTP_SUBJECT="$1"
+ SMTP_CONTENT="$2"
+ # UNUSED: _statusCode="$3" # 0: success, 1: error 2($RENEW_SKIP): skipped
+
+ # Load and validate config:
+ SMTP_BIN="$(_readaccountconf_mutable_default SMTP_BIN)"
+ if [ -n "$SMTP_BIN" ] && ! _exists "$SMTP_BIN"; then
+ _err "SMTP_BIN '$SMTP_BIN' does not exist."
+ return 1
+ fi
+ if [ -z "$SMTP_BIN" ]; then
+ # Look for a command that can communicate with an SMTP server.
+ # (Please don't add sendmail, ssmtp, mutt, mail, or msmtp here.
+ # Those are already handled by the "mail" notify hook.)
+ for cmd in python3 python2.7 python pypy3 pypy curl; do
+ if _exists "$cmd"; then
+ SMTP_BIN="$cmd"
+ break
+ fi
+ done
+ if [ -z "$SMTP_BIN" ]; then
+ _err "The smtp notify-hook requires curl or Python, but can't find any."
+ _err 'If you have one of them, define SMTP_BIN="/path/to/curl_or_python".'
+ _err 'Otherwise, see if you can use the "mail" notify-hook instead.'
+ return 1
+ fi
+ fi
+ _debug SMTP_BIN "$SMTP_BIN"
+ _saveaccountconf_mutable_default SMTP_BIN "$SMTP_BIN"
+
+ SMTP_FROM="$(_readaccountconf_mutable_default SMTP_FROM)"
+ SMTP_FROM="$(_clean_email_header "$SMTP_FROM")"
+ if [ -z "$SMTP_FROM" ]; then
+ _err "You must define SMTP_FROM as the sender email address."
+ return 1
+ fi
+ if _email_has_display_name "$SMTP_FROM"; then
+ _err "SMTP_FROM must be only a simple email address (sender@example.com)."
+ _err "Change your SMTP_FROM='$SMTP_FROM' to remove the display name."
+ return 1
+ fi
+ _debug SMTP_FROM "$SMTP_FROM"
+ _saveaccountconf_mutable_default SMTP_FROM "$SMTP_FROM"
+
+ SMTP_TO="$(_readaccountconf_mutable_default SMTP_TO)"
+ SMTP_TO="$(_clean_email_header "$SMTP_TO")"
+ if [ -z "$SMTP_TO" ]; then
+ _err "You must define SMTP_TO as the recipient email address(es)."
+ return 1
+ fi
+ if _email_has_display_name "$SMTP_TO"; then
+ _err "SMTP_TO must be only simple email addresses (to@example.com,to2@example.com)."
+ _err "Change your SMTP_TO='$SMTP_TO' to remove the display name(s)."
+ return 1
+ fi
+ _debug SMTP_TO "$SMTP_TO"
+ _saveaccountconf_mutable_default SMTP_TO "$SMTP_TO"
+
+ SMTP_HOST="$(_readaccountconf_mutable_default SMTP_HOST)"
+ if [ -z "$SMTP_HOST" ]; then
+ _err "You must define SMTP_HOST as the SMTP server hostname."
+ return 1
+ fi
+ _debug SMTP_HOST "$SMTP_HOST"
+ _saveaccountconf_mutable_default SMTP_HOST "$SMTP_HOST"
+
+ SMTP_SECURE="$(_readaccountconf_mutable_default SMTP_SECURE "$SMTP_SECURE_DEFAULT")"
+ case "$SMTP_SECURE" in
+ "none") smtp_port_default="25" ;;
+ "ssl") smtp_port_default="465" ;;
+ "tls") smtp_port_default="587" ;;
+ *)
+ _err "Invalid SMTP_SECURE='$SMTP_SECURE'. It must be 'ssl', 'tls' or 'none'."
+ return 1
+ ;;
+ esac
+ _debug SMTP_SECURE "$SMTP_SECURE"
+ _saveaccountconf_mutable_default SMTP_SECURE "$SMTP_SECURE" "$SMTP_SECURE_DEFAULT"
+
+ SMTP_PORT="$(_readaccountconf_mutable_default SMTP_PORT "$smtp_port_default")"
+ case "$SMTP_PORT" in
+ *[!0-9]*)
+ _err "Invalid SMTP_PORT='$SMTP_PORT'. It must be a port number."
+ return 1
+ ;;
+ esac
+ _debug SMTP_PORT "$SMTP_PORT"
+ _saveaccountconf_mutable_default SMTP_PORT "$SMTP_PORT" "$smtp_port_default"
+
+ SMTP_USERNAME="$(_readaccountconf_mutable_default SMTP_USERNAME)"
+ _debug SMTP_USERNAME "$SMTP_USERNAME"
+ _saveaccountconf_mutable_default SMTP_USERNAME "$SMTP_USERNAME"
+
+ SMTP_PASSWORD="$(_readaccountconf_mutable_default SMTP_PASSWORD)"
+ _secure_debug SMTP_PASSWORD "$SMTP_PASSWORD"
+ _saveaccountconf_mutable_default SMTP_PASSWORD "$SMTP_PASSWORD"
+
+ SMTP_TIMEOUT="$(_readaccountconf_mutable_default SMTP_TIMEOUT "$SMTP_TIMEOUT_DEFAULT")"
+ _debug SMTP_TIMEOUT "$SMTP_TIMEOUT"
+ _saveaccountconf_mutable_default SMTP_TIMEOUT "$SMTP_TIMEOUT" "$SMTP_TIMEOUT_DEFAULT"
+
+ SMTP_X_MAILER="$(_clean_email_header "$PROJECT_NAME $VER --notify-hook smtp")"
+
+ # Run with --debug 2 (or above) to echo the transcript of the SMTP session.
+ # Careful: this may include SMTP_PASSWORD in plaintext!
+ if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_2" ]; then
+ SMTP_SHOW_TRANSCRIPT="True"
+ else
+ SMTP_SHOW_TRANSCRIPT=""
+ fi
+
+ SMTP_SUBJECT=$(_clean_email_header "$SMTP_SUBJECT")
+ _debug SMTP_SUBJECT "$SMTP_SUBJECT"
+ _debug SMTP_CONTENT "$SMTP_CONTENT"
+
+ # Send the message:
+ case "$(basename "$SMTP_BIN")" in
+ curl) _smtp_send=_smtp_send_curl ;;
+ py*) _smtp_send=_smtp_send_python ;;
+ *)
+ _err "Can't figure out how to invoke '$SMTP_BIN'."
+ _err "Check your SMTP_BIN setting."
+ return 1
+ ;;
+ esac
+
+ if ! smtp_output="$($_smtp_send)"; then
+ _err "Error sending message with $SMTP_BIN."
+ if [ -n "$smtp_output" ]; then
+ _err "$smtp_output"
+ fi
+ return 1
+ fi
+
+ return 0
+}
+
+# Strip CR and NL from text to prevent MIME header injection
+# text
+_clean_email_header() {
+ printf "%s" "$(echo "$1" | tr -d "\r\n")"
+}
+
+# Simple check for display name in an email address (< > or ")
+# email
+_email_has_display_name() {
+ _email="$1"
+ expr "$_email" : '^.*[<>"]' >/dev/null
+}
+
+##
+## curl smtp sending
+##
+
+# Send the message via curl using SMTP_* variables
+_smtp_send_curl() {
+ # Build curl args in $@
+ case "$SMTP_SECURE" in
+ none)
+ set -- --url "smtp://${SMTP_HOST}:${SMTP_PORT}"
+ ;;
+ ssl)
+ set -- --url "smtps://${SMTP_HOST}:${SMTP_PORT}"
+ ;;
+ tls)
+ set -- --url "smtp://${SMTP_HOST}:${SMTP_PORT}" --ssl-reqd
+ ;;
+ *)
+ # This will only occur if someone adds a new SMTP_SECURE option above
+ # without updating this code for it.
+ _err "Unhandled SMTP_SECURE='$SMTP_SECURE' in _smtp_send_curl"
+ _err "Please re-run with --debug and report a bug."
+ return 1
+ ;;
+ esac
+
+ set -- "$@" \
+ --upload-file - \
+ --mail-from "$SMTP_FROM" \
+ --max-time "$SMTP_TIMEOUT"
+
+ # Burst comma-separated $SMTP_TO into individual --mail-rcpt args.
+ _to="${SMTP_TO},"
+ while [ -n "$_to" ]; do
+ _rcpt="${_to%%,*}"
+ _to="${_to#*,}"
+ set -- "$@" --mail-rcpt "$_rcpt"
+ done
+
+ _smtp_login="${SMTP_USERNAME}:${SMTP_PASSWORD}"
+ if [ "$_smtp_login" != ":" ]; then
+ set -- "$@" --user "$_smtp_login"
+ fi
+
+ if [ "$SMTP_SHOW_TRANSCRIPT" = "True" ]; then
+ set -- "$@" --verbose
+ else
+ set -- "$@" --silent --show-error
+ fi
+
+ raw_message="$(_smtp_raw_message)"
+
+ _debug2 "curl command:" "$SMTP_BIN" "$*"
+ _debug2 "raw_message:\n$raw_message"
+
+ echo "$raw_message" | "$SMTP_BIN" "$@"
+}
+
+# Output an RFC-822 / RFC-5322 email message using SMTP_* variables.
+# (This assumes variables have already been cleaned for use in email headers.)
+_smtp_raw_message() {
+ echo "From: $SMTP_FROM"
+ echo "To: $SMTP_TO"
+ echo "Subject: $(_mime_encoded_word "$SMTP_SUBJECT")"
+ echo "Date: $(_rfc2822_date)"
+ echo "Content-Type: text/plain; charset=utf-8"
+ echo "X-Mailer: $SMTP_X_MAILER"
+ echo
+ echo "$SMTP_CONTENT"
+}
+
+# Convert text to RFC-2047 MIME "encoded word" format if it contains non-ASCII chars
+# text
+_mime_encoded_word() {
+ _text="$1"
+ # (regex character ranges like [a-z] can be locale-dependent; enumerate ASCII chars to avoid that)
+ _ascii='] $`"'"[!#%&'()*+,./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ~^_abcdefghijklmnopqrstuvwxyz{|}~-"
+ if expr "$_text" : "^.*[^$_ascii]" >/dev/null; then
+ # At least one non-ASCII char; convert entire thing to encoded word
+ printf "%s" "=?UTF-8?B?$(printf "%s" "$_text" | _base64)?="
+ else
+ # Just printable ASCII, no conversion needed
+ printf "%s" "$_text"
+ fi
+}
+
+# Output current date in RFC-2822 Section 3.3 format as required in email headers
+# (e.g., "Mon, 15 Feb 2021 14:22:01 -0800")
+_rfc2822_date() {
+ # Notes:
+ # - this is deliberately not UTC, because it "SHOULD express local time" per spec
+ # - the spec requires weekday and month in the C locale (English), not localized
+ # - this date format specifier has been tested on Linux, Mac, Solaris and FreeBSD
+ _old_lc_time="$LC_TIME"
+ LC_TIME=C
+ date +'%a, %-d %b %Y %H:%M:%S %z'
+ LC_TIME="$_old_lc_time"
+}
+
+##
+## Python smtp sending
+##
+
+# Send the message via Python using SMTP_* variables
+_smtp_send_python() {
+ _debug "Python version" "$("$SMTP_BIN" --version 2>&1)"
+
+ # language=Python
+ "$SMTP_BIN" </dev/null; then
+ # shellcheck disable=SC2154
+ _message=$(printf "%s\n" "$response" | sed -n 's/.*"ok":\([^,]*\).*/\1/p')
+ if [ "$_message" = "true" ]; then
+ _info "telegram send success."
+ return 0
+ fi
+ fi
+ _err "telegram send error."
+ _err "$response"
+ return 1
+}
diff --git a/notify/weixin_work.sh b/notify/weixin_work.sh
new file mode 100644
index 00000000..bf3e9ad6
--- /dev/null
+++ b/notify/weixin_work.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env sh
+
+#Support weixin work webhooks api
+
+#WEIXIN_WORK_WEBHOOK="xxxx"
+
+#optional
+#WEIXIN_WORK_KEYWORD="yyyy"
+
+#`WEIXIN_WORK_SIGNING_KEY`="SEC08ffdbd403cbc3fc8a65xxxxxxxxxxxxxxxxxxxx"
+
+# subject content statusCode
+weixin_work_send() {
+ _subject="$1"
+ _content="$2"
+ _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+ _debug "_subject" "$_subject"
+ _debug "_content" "$_content"
+ _debug "_statusCode" "$_statusCode"
+
+ WEIXIN_WORK_WEBHOOK="${WEIXIN_WORK_WEBHOOK:-$(_readaccountconf_mutable WEIXIN_WORK_WEBHOOK)}"
+ if [ -z "$WEIXIN_WORK_WEBHOOK" ]; then
+ WEIXIN_WORK_WEBHOOK=""
+ _err "You didn't specify a weixin_work webhooks WEIXIN_WORK_WEBHOOK yet."
+ _err "You can get yours from https://work.weixin.qq.com/api/doc/90000/90136/91770"
+ return 1
+ fi
+ _saveaccountconf_mutable WEIXIN_WORK_WEBHOOK "$WEIXIN_WORK_WEBHOOK"
+
+ WEIXIN_WORK_KEYWORD="${WEIXIN_WORK_KEYWORD:-$(_readaccountconf_mutable WEIXIN_WORK_KEYWORD)}"
+ if [ "$WEIXIN_WORK_KEYWORD" ]; then
+ _saveaccountconf_mutable WEIXIN_WORK_KEYWORD "$WEIXIN_WORK_KEYWORD"
+ fi
+
+ _content=$(echo "$_content" | _json_encode)
+ _subject=$(echo "$_subject" | _json_encode)
+ _data="{\"msgtype\": \"text\", \"text\": {\"content\": \"[$WEIXIN_WORK_KEYWORD]\n$_subject\n$_content\"}}"
+
+ response="$(_post "$_data" "$WEIXIN_WORK_WEBHOOK" "" "POST" "application/json")"
+
+ if [ "$?" = "0" ] && _contains "$response" "errmsg\":\"ok"; then
+ _info "weixin_work webhooks event fired success."
+ return 0
+ fi
+
+ _err "weixin_work webhooks event fired error."
+ _err "$response"
+ return 1
+}