Browse Source

Merge pull request #2571 from Neilpang/dev

sync
neil 6 years ago
parent
commit
3d2df3ba93

+ 1 - 1
.github/ISSUE_TEMPLATE.md

@@ -5,7 +5,7 @@
 如何调试 https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh
 
 If it is a bug report:
-- make sure you are able to repro it on the latest released version. 
+- make sure you are able to repro it on the latest released version.
 You can install the latest version by: `acme.sh --upgrade`
 
 - Search the existing issues.

+ 3 - 3
.travis.yml

@@ -28,11 +28,11 @@ script:
   - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -V ; fi
   - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -e SC2181 **/*.sh && echo "shellcheck OK" ; fi
   - cd ..
-  - git clone https://github.com/Neilpang/acmetest.git && cp -r acme.sh acmetest/ && cd acmetest
+  - git clone --depth 1 https://github.com/Neilpang/acmetest.git && cp -r acme.sh acmetest/ && cd acmetest
   - if [ "$TRAVIS_OS_NAME" = "linux" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ./rundocker.sh testplat ubuntu:latest ; fi
   - if [ "$TRAVIS_OS_NAME" = "osx" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ACME_OPENSSL_BIN="$ACME_OPENSSL_BIN" ./letest.sh ; fi
 
 matrix:
   fast_finish: true
-  
-  
+
+

+ 1 - 0
Dockerfile

@@ -3,6 +3,7 @@ FROM alpine:3.10
 RUN apk update -f \
   && apk --no-cache add -f \
   openssl \
+  openssh-client \
   coreutils \
   bind-tools \
   curl \

+ 55 - 10
acme.sh

@@ -153,7 +153,7 @@ fi
 
 __green() {
   if [ "${__INTERACTIVE}${ACME_NO_COLOR:-0}" = "10" -o "${ACME_FORCE_COLOR}" = "1" ]; then
-    printf '\033[1;31;32m%b\033[0m' "$1"
+    printf '\33[1;32m%b\33[0m' "$1"
     return
   fi
   printf -- "%b" "$1"
@@ -161,7 +161,7 @@ __green() {
 
 __red() {
   if [ "${__INTERACTIVE}${ACME_NO_COLOR:-0}" = "10" -o "${ACME_FORCE_COLOR}" = "1" ]; then
-    printf '\033[1;31;40m%b\033[0m' "$1"
+    printf '\33[1;31m%b\33[0m' "$1"
     return
   fi
   printf -- "%b" "$1"
@@ -178,7 +178,7 @@ _printargs() {
     printf -- "%s" "$1='$2'"
   fi
   printf "\n"
-  # return the saved exit status 
+  # return the saved exit status
   return "$_exitstatus"
 }
 
@@ -265,6 +265,37 @@ _usage() {
   printf "\n" >&2
 }
 
+__debug_bash_helper() {
+  # At this point only do for --debug 3
+  if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -lt "$DEBUG_LEVEL_3" ]; then
+    echo ""
+    return
+  fi
+  # Return extra debug info when running with bash, otherwise return empty
+  # string.
+  if [ -z "${BASH_VERSION}" ]; then
+    echo ""
+    return
+  fi
+  # We are a bash shell at this point, return the filename, function name, and
+  # line number as a string
+  _dbh_saveIFS=$IFS
+  IFS=" "
+  # Must use eval or syntax error happens under dash
+  # Use 'caller 1' as we want one level up the stack as we should be called
+  # by one of the _debug* functions
+  eval "_dbh_called=($(caller 1))"
+  IFS=$_dbh_saveIFS
+  _dbh_file=${_dbh_called[2]}
+  if [ -n "${_script_home}" ]; then
+    # Trim off the _script_home directory name
+    _dbh_file=${_dbh_file#$_script_home/}
+  fi
+  _dbh_function=${_dbh_called[1]}
+  _dbh_lineno=${_dbh_called[0]}
+  printf "%-40s " "$_dbh_file:${_dbh_function}:${_dbh_lineno}"
+}
+
 _debug() {
   if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_1" ]; then
     _log "$@"
@@ -273,7 +304,8 @@ _debug() {
     _syslog "$SYSLOG_DEBUG" "$@"
   fi
   if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_1" ]; then
-    _printargs "$@" >&2
+    _bash_debug=$(__debug_bash_helper)
+    _printargs "${_bash_debug}$@" >&2
   fi
 }
 
@@ -306,7 +338,8 @@ _debug2() {
     _syslog "$SYSLOG_DEBUG" "$@"
   fi
   if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_2" ]; then
-    _printargs "$@" >&2
+    _bash_debug=$(__debug_bash_helper)
+    _printargs "${_bash_debug}$@" >&2
   fi
 }
 
@@ -338,7 +371,8 @@ _debug3() {
     _syslog "$SYSLOG_DEBUG" "$@"
   fi
   if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_3" ]; then
-    _printargs "$@" >&2
+    _bash_debug=$(__debug_bash_helper)
+    _printargs "${_bash_debug}$@" >&2
   fi
 }
 
@@ -3678,7 +3712,7 @@ _ns_purge_cf() {
 
 #checks if cf server is available
 _ns_is_available_cf() {
-  if _get "https://cloudflare-dns.com"; then
+  if _get "https://cloudflare-dns.com" >/dev/null 2>&1; then
     return 0
   else
     return 1
@@ -4047,7 +4081,18 @@ $_authorizations_map"
       fi
 
       if [ "$ACME_VERSION" = "2" ]; then
-        response="$(echo "$_authorizations_map" | grep "^$(_idn "$d")," | sed "s/$d,//")"
+        _idn_d="$(_idn "$d")"
+        _candindates="$(echo "$_authorizations_map" | grep "^$_idn_d,")"
+        _debug2 _candindates "$_candindates"
+        if [ "$(echo "$_candindates" | wc -l)" -gt 1 ]; then
+          for _can in $_candindates; do
+            if _startswith "$(echo "$_can" | tr '.' '|')" "$(echo "$_idn_d" | tr '.' '|'),"; then
+              _candindates="$_can"
+              break
+            fi
+          done
+        fi
+        response="$(echo "$_candindates" | sed "s/$_idn_d,//")"
         _debug2 "response" "$response"
         if [ -z "$response" ]; then
           _err "get to authz error."
@@ -6059,7 +6104,7 @@ _send_notify() {
 _set_notify_hook() {
   _nhooks="$1"
 
-  _test_subject="Hello, this is notification from $PROJECT_NAME"
+  _test_subject="Hello, this is a notification from $PROJECT_NAME"
   _test_content="If you receive this message, your notification works."
 
   _send_notify "$_test_subject" "$_test_content" "$_nhooks" 0
@@ -6215,7 +6260,7 @@ Parameters:
   --branch, -b                      Only valid for '--upgrade' command, specifies the branch name to upgrade to.
 
   --notify-level  0|1|2|3           Set the notification level:  Default value is $NOTIFY_LEVEL_DEFAULT.
-                                     0: disabled, no notification will be sent. 
+                                     0: disabled, no notification will be sent.
                                      1: send notifications only when there is an error.
                                      2: send notifications when a cert is successfully renewed, or there is an error.
                                      3: send notifications when a cert is skipped, renewed, or error.

+ 4 - 4
deploy/gcore_cdn.sh

@@ -77,15 +77,15 @@ gcore_cdn_deploy() {
   _debug _regex "$_regex"
   _resource=$(echo "$_response" | sed 's/},{/},\n{/g' | _egrep_o "$_regex")
   _debug _resource "$_resource"
-  _regex=".*\"id\":\([0-9]*\),.*$"
+  _regex=".*\"id\":\([0-9]*\).*\"rules\".*$"
   _debug _regex "$_regex"
   _resourceId=$(echo "$_resource" | sed -n "s/$_regex/\1/p")
   _debug _resourceId "$_resourceId"
-  _regex=".*\"sslData\":\([0-9]*\)}.*$"
+  _regex=".*\"sslData\":\([0-9]*\).*$"
   _debug _regex "$_regex"
   _sslDataOld=$(echo "$_resource" | sed -n "s/$_regex/\1/p")
   _debug _sslDataOld "$_sslDataOld"
-  _regex=".*\"originGroup\":\([0-9]*\),.*$"
+  _regex=".*\"originGroup\":\([0-9]*\).*$"
   _debug _regex "$_regex"
   _originGroup=$(echo "$_resource" | sed -n "s/$_regex/\1/p")
   _debug _originGroup "$_originGroup"
@@ -101,7 +101,7 @@ gcore_cdn_deploy() {
   _debug _request "$_request"
   _response=$(_post "$_request" "https://api.gcdn.co/sslData")
   _debug _response "$_response"
-  _regex=".*\"id\":\([0-9]*\),.*$"
+  _regex=".*\"id\":\([0-9]*\).*$"
   _debug _regex "$_regex"
   _sslDataAdd=$(echo "$_response" | sed -n "s/$_regex/\1/p")
   _debug _sslDataAdd "$_sslDataAdd"

+ 1 - 1
deploy/qiniu.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 
-# Script to create certificate to qiniu.com 
+# Script to create certificate to qiniu.com
 #
 # This deployment required following variables
 # export QINIU_AK="QINIUACCESSKEY"

+ 1 - 1
deploy/routeros.sh

@@ -85,7 +85,7 @@ routeros_deploy() {
   scp "$_ckey" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.key"
   _info "Trying to push cert '$_cfullchain' to router"
   scp "$_cfullchain" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.cer"
-  DEPLOY_SCRIPT_CMD="/system script add name=\"LE Cert Deploy - $_cdomain\" owner=admin policy=ftp,read,write,password,sensitive 
+  DEPLOY_SCRIPT_CMD="/system script add name=\"LE Cert Deploy - $_cdomain\" owner=admin policy=ftp,read,write,password,sensitive
 source=\"## generated by routeros deploy script in acme.sh
 \n/certificate remove [ find name=$_cdomain.cer_0 ]
 \n/certificate remove [ find name=$_cdomain.cer_1 ]

+ 2 - 2
deploy/vault_cli.sh

@@ -2,10 +2,10 @@
 
 # Here is a script to deploy cert to hashicorp vault
 # (https://www.vaultproject.io/)
-# 
+#
 # it requires the vault binary to be available in PATH, and the following
 # environment variables:
-# 
+#
 # VAULT_PREFIX - this contains the prefix path in vault
 # VAULT_ADDR - vault requires this to find your vault server
 #

+ 12 - 2
dnsapi/dns_aws.sh

@@ -6,6 +6,8 @@
 #AWS_SECRET_ACCESS_KEY="xxxxxxx"
 
 #This is the Amazon Route53 api wrapper for acme.sh
+#All `_sleep` commands are included to avoid Route53 throttling, see
+#https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests
 
 AWS_HOST="route53.amazonaws.com"
 AWS_URL="https://$AWS_HOST"
@@ -43,6 +45,7 @@ dns_aws_add() {
   _debug "First detect the root zone"
   if ! _get_root "$fulldomain"; then
     _err "invalid domain"
+    _sleep 1
     return 1
   fi
   _debug _domain_id "$_domain_id"
@@ -51,6 +54,7 @@ dns_aws_add() {
 
   _info "Getting existing records for $fulldomain"
   if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then
+    _sleep 1
     return 1
   fi
 
@@ -63,6 +67,7 @@ dns_aws_add() {
 
   if [ "$_resource_record" ] && _contains "$response" "$txtvalue"; then
     _info "The TXT record already exists. Skipping."
+    _sleep 1
     return 0
   fi
 
@@ -72,9 +77,10 @@ dns_aws_add() {
 
   if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then
     _info "TXT record updated successfully."
+    _sleep 1
     return 0
   fi
-
+  _sleep 1
   return 1
 }
 
@@ -93,6 +99,7 @@ dns_aws_rm() {
   _debug "First detect the root zone"
   if ! _get_root "$fulldomain"; then
     _err "invalid domain"
+    _sleep 1
     return 1
   fi
   _debug _domain_id "$_domain_id"
@@ -101,6 +108,7 @@ dns_aws_rm() {
 
   _info "Getting existing records for $fulldomain"
   if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then
+    _sleep 1
     return 1
   fi
 
@@ -109,6 +117,7 @@ dns_aws_rm() {
     _debug "_resource_record" "$_resource_record"
   else
     _debug "no records exist, skip"
+    _sleep 1
     return 0
   fi
 
@@ -116,9 +125,10 @@ dns_aws_rm() {
 
   if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then
     _info "TXT record deleted successfully."
+    _sleep 1
     return 0
   fi
-
+  _sleep 1
   return 1
 
 }

+ 1 - 1
dnsapi/dns_da.sh

@@ -9,7 +9,7 @@
 #
 # User must provide login data and URL to DirectAdmin incl. port.
 # You can create login key, by using the Login Keys function
-# ( https://da.example.com:8443/CMD_LOGIN_KEYS ), which only has access to 
+# ( https://da.example.com:8443/CMD_LOGIN_KEYS ), which only has access to
 # - CMD_API_DNS_CONTROL
 # - CMD_API_SHOW_DOMAINS
 #

+ 3 - 3
dnsapi/dns_doapi.sh

@@ -1,11 +1,11 @@
 #!/usr/bin/env sh
 
 # Official Let's Encrypt API for do.de / Domain-Offensive
-# 
+#
 # This is different from the dns_do adapter, because dns_do is only usable for enterprise customers
 # This API is also available to private customers/individuals
-# 
-# Provide the required LetsEncrypt token like this: 
+#
+# Provide the required LetsEncrypt token like this:
 # DO_LETOKEN="FmD408PdqT1E269gUK57"
 
 DO_API="https://www.do.de/api/letsencrypt"

+ 5 - 5
dnsapi/dns_durabledns.sh

@@ -147,11 +147,11 @@ _dd_soap() {
 
   # build SOAP XML
   _xml='<?xml version="1.0" encoding="utf-8"?>
-<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
-xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 
-xmlns:tns="urn:'$_urn'" 
-xmlns:types="urn:'$_urn'/encodedTypes" 
-xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
+xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
+xmlns:tns="urn:'$_urn'"
+xmlns:types="urn:'$_urn'/encodedTypes"
+xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'"$body"'</soap:Body>
 </soap:Envelope>'

+ 1 - 1
dnsapi/dns_euserv.sh

@@ -127,7 +127,7 @@ dns_euserv_rm() {
   else
     # find XML block where txtvalue is in. The record_id is allways prior this line!
     _endLine=$(echo "$response" | grep -n '>dns_record_content<.*>'"$txtvalue"'<' | cut -d ':' -f 1)
-    # record_id is the last <name> Tag with a number before the row _endLine, identified by </name><value><struct> 
+    # record_id is the last <name> Tag with a number before the row _endLine, identified by </name><value><struct>
     _record_id=$(echo "$response" | sed -n '1,'"$_endLine"'p' | grep '</name><value><struct>' | _tail_n 1 | sed 's/.*<name>\([0-9]*\)<\/name>.*/\1/')
     _info "Deleting record"
     _euserv_delete_record "$_record_id"

+ 5 - 5
dnsapi/dns_freedns.sh

@@ -303,9 +303,9 @@ _freedns_domain_id() {
       return 1
     fi
 
-    domain_id="$(echo "$htmlpage" | tr -d "[:space:]" | sed 's/<tr>/@<tr>/g' | tr '@' '\n' \
+    domain_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's/<tr>/@<tr>/g' | tr '@' '\n' \
       | grep "<td>$search_domain</td>\|<td>$search_domain(.*)</td>" \
-      | _egrep_o "edit\.php\?edit_domain_id=[0-9a-zA-Z]+" \
+      | sed -n 's/.*\(edit\.php?edit_domain_id=[0-9a-zA-Z]*\).*/\1/p' \
       | cut -d = -f 2)"
     # The above beauty extracts domain ID from the html page...
     # strip out all blank space and new lines. Then insert newlines
@@ -349,17 +349,17 @@ _freedns_data_id() {
       return 1
     fi
 
-    data_id="$(echo "$htmlpage" | tr -d "[:space:]" | sed 's/<tr>/@<tr>/g' | tr '@' '\n' \
+    data_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's/<tr>/@<tr>/g' | tr '@' '\n' \
       | grep "<td[a-zA-Z=#]*>$record_type</td>" \
       | grep "<ahref.*>$search_domain</a>" \
-      | _egrep_o "edit\.php\?data_id=[0-9a-zA-Z]+" \
+      | sed -n 's/.*\(edit\.php?data_id=[0-9a-zA-Z]*\).*/\1/p' \
       | cut -d = -f 2)"
     # The above beauty extracts data ID from the html page...
     # strip out all blank space and new lines. Then insert newlines
     # before each table row <tr>
     # search for the record type withing each row (e.g. TXT)
     # search for the domain within each row (which is within a <a..>
-    # </a> anchor. And finally extract the domain ID.         
+    # </a> anchor. And finally extract the domain ID.
     if [ -n "$data_id" ]; then
       printf "%s" "$data_id"
       return 0

+ 149 - 0
dnsapi/dns_leaseweb.sh

@@ -0,0 +1,149 @@
+#!/usr/bin/env sh
+
+#Author: Rolph Haspers <[email protected]>
+#Utilize leaseweb.com API to finish dns-01 verifications.
+#Requires a Leaseweb API Key (export LSW_Key="Your Key")
+#See http://developer.leaseweb.com for more information.
+########  Public functions #####################
+
+LSW_API="https://api.leaseweb.com/hosting/v2/domains/"
+
+#Usage: dns_leaseweb_add   _acme-challenge.www.domain.com
+dns_leaseweb_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  LSW_Key="${LSW_Key:-$(_readaccountconf_mutable LSW_Key)}"
+  if [ -z "$LSW_Key" ]; then
+    LSW_Key=""
+    _err "You don't specify Leaseweb api key yet."
+    _err "Please create your key and try again."
+    return 1
+  fi
+
+  #save the api key to the account conf file.
+  _saveaccountconf_mutable LSW_Key "$LSW_Key"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+
+  _debug _root_domain "$_domain"
+  _debug _domain "$fulldomain"
+
+  if _lsw_api "POST" "$_domain" "$fulldomain" "$txtvalue"; then
+    if [ "$_code" = "201" ]; then
+      _info "Added, OK"
+      return 0
+    else
+      _err "Add txt record error, invalid code. Code: $_code"
+      return 1
+    fi
+  fi
+  _err "Add txt record error."
+
+  return 1
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_leaseweb_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  LSW_Key="${LSW_Key:-$(_readaccountconf_mutable LSW_Key)}"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+
+  _debug _root_domain "$_domain"
+  _debug _domain "$fulldomain"
+
+  if _lsw_api "DELETE" "$_domain" "$fulldomain" "$txtvalue"; then
+    if [ "$_code" = "204" ]; then
+      _info "Deleted, OK"
+      return 0
+    else
+      _err "Delete txt record error."
+      return 1
+    fi
+  fi
+  _err "Delete txt record error."
+
+  return 1
+}
+
+####################  Private functions below ##################################
+# _acme-challenge.www.domain.com
+# returns
+# _domain=domain.com
+_get_root() {
+  rdomain=$1
+  i="$(echo "$rdomain" | tr '.' ' ' | wc -w)"
+  i=$(_math "$i" - 1)
+
+  while true; do
+    h=$(printf "%s" "$rdomain" | cut -d . -f "$i"-100)
+    _debug h "$h"
+    if [ -z "$h" ]; then
+      return 1 #not valid domain
+    fi
+
+    #Check API if domain exists
+    if _lsw_api "GET" "$h"; then
+      if [ "$_code" = "200" ]; then
+        _domain="$h"
+        return 0
+      fi
+    fi
+    i=$(_math "$i" - 1)
+    if [ "$i" -lt 2 ]; then
+      return 1 #not found, no need to check _acme-challenge.sub.domain in leaseweb api.
+    fi
+  done
+
+  return 1
+}
+
+_lsw_api() {
+  cmd=$1
+  d=$2
+  fd=$3
+  tvalue=$4
+
+  # Construct the HTTP Authorization header
+  export _H2="Content-Type: application/json"
+  export _H1="X-Lsw-Auth: ${LSW_Key}"
+
+  if [ "$cmd" = "GET" ]; then
+    response="$(_get "$LSW_API/$d")"
+    _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+    _debug "http response code $_code"
+    _debug response "$response"
+    return 0
+  fi
+
+  if [ "$cmd" = "POST" ]; then
+    data="{\"name\": \"$fd.\",\"type\": \"TXT\",\"content\": [\"$tvalue\"],\"ttl\": 60}"
+    response="$(_post "$data" "$LSW_API/$d/resourceRecordSets" "$data" "POST")"
+    _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+    _debug "http response code $_code"
+    _debug response "$response"
+    return 0
+  fi
+
+  if [ "$cmd" = "DELETE" ]; then
+    response="$(_post "" "$LSW_API/$d/resourceRecordSets/$fd/TXT" "" "DELETE")"
+    _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+    _debug "http response code $_code"
+    _debug response "$response"
+    return 0
+  fi
+
+  return 1
+}

+ 1 - 1
dnsapi/dns_me.sh

@@ -2,7 +2,7 @@
 
 # bug reports to [email protected]
 
-# ME_Key=qmlkdjflmkqdjf	
+# ME_Key=qmlkdjflmkqdjf
 # ME_Secret=qmsdlkqmlksdvnnpae
 
 ME_Api=https://api.dnsmadeeasy.com/V2.0/dns/managed

+ 210 - 0
dnsapi/dns_miab.sh

@@ -0,0 +1,210 @@
+#!/usr/bin/env sh
+
+# Name: dns_miab.sh
+#
+# Authors:
+#    Darven Dissek 2018
+#    William Gertz 2019
+#
+#     Thanks to Neil Pang and other developers here for code reused from acme.sh from DNS-01
+#     used to communicate with the MailinaBox Custom DNS API
+# Report Bugs here:
+#    https://github.com/billgertz/MIAB_dns_api (for dns_miab.sh)
+#    https://github.com/Neilpang/acme.sh       (for acme.sh)
+#
+########  Public functions #####################
+
+#Usage: dns_miab_add  _acme-challenge.www.domain.com  "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_miab_add() {
+  fulldomain=$1
+  txtvalue=$2
+  _info "Using miab challange add"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  #retrieve MIAB environemt vars
+  if ! _retrieve_miab_env; then
+    return 1
+  fi
+
+  #check domain and seperate into doamin and host
+  if ! _get_root "$fulldomain"; then
+    _err "Cannot find any part of ${fulldomain} is hosted on ${MIAB_Server}"
+    return 1
+  fi
+
+  _debug2 _sub_domain "$_sub_domain"
+  _debug2 _domain "$_domain"
+
+  #add the challenge record
+  _api_path="custom/${fulldomain}/txt"
+  _miab_rest "$txtvalue" "$_api_path" "POST"
+
+  #check if result was good
+  if _contains "$response" "updated DNS"; then
+    _info "Successfully created the txt record"
+    return 0
+  else
+    _err "Error encountered during record add"
+    _err "$response"
+    return 1
+  fi
+}
+
+#Usage: dns_miab_rm  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_miab_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  _info "Using miab challage delete"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  #retrieve MIAB environemt vars
+  if ! _retrieve_miab_env; then
+    return 1
+  fi
+
+  #check domain and seperate into doamin and host
+  if ! _get_root "$fulldomain"; then
+    _err "Cannot find any part of ${fulldomain} is hosted on ${MIAB_Server}"
+    return 1
+  fi
+
+  _debug2 _sub_domain "$_sub_domain"
+  _debug2 _domain "$_domain"
+
+  #Remove the challenge record
+  _api_path="custom/${fulldomain}/txt"
+  _miab_rest "$txtvalue" "$_api_path" "DELETE"
+
+  #check if result was good
+  if _contains "$response" "updated DNS"; then
+    _info "Successfully removed the txt record"
+    return 0
+  else
+    _err "Error encountered during record remove"
+    _err "$response"
+    return 1
+  fi
+}
+
+####################  Private functions below ##################################
+#
+#Usage: _get_root  _acme-challenge.www.domain.com
+#Returns:
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+  _passed_domain=$1
+  _debug _passed_domain "$_passed_domain"
+  _i=2
+  _p=1
+
+  #get the zones hosed on MIAB server, must be a json stream
+  _miab_rest "" "zones" "GET"
+
+  if ! _is_json "$response"; then
+    _err "ERROR fetching domain list"
+    _err "$response"
+    return 1
+  fi
+
+  #cycle through the passed domain seperating out a test domain discarding
+  #   the subdomain by marching thorugh the dots
+  while true; do
+    _test_domain=$(printf "%s" "$_passed_domain" | cut -d . -f ${_i}-100)
+    _debug _test_domain "$_test_domain"
+
+    if [ -z "$_test_domain" ]; then
+      return 1
+    fi
+
+    #report found if the test domain is in the json response and
+    #   report the subdomain
+    if _contains "$response" "\"$_test_domain\""; then
+      _sub_domain=$(printf "%s" "$_passed_domain" | cut -d . -f 1-${_p})
+      _domain=${_test_domain}
+      return 0
+    fi
+
+    #cycle to the next dot in the passed domain
+    _p=${_i}
+    _i=$(_math "$_i" + 1)
+  done
+
+  return 1
+}
+
+#Usage: _retrieve_miab_env
+#Returns (from store or environment variables):
+# MIAB_Username
+# MIAB_Password
+# MIAB_Server
+#retrieve MIAB environment variables, report errors and quit if problems
+_retrieve_miab_env() {
+  MIAB_Username="${MIAB_Username:-$(_readaccountconf_mutable MIAB_Username)}"
+  MIAB_Password="${MIAB_Password:-$(_readaccountconf_mutable MIAB_Password)}"
+  MIAB_Server="${MIAB_Server:-$(_readaccountconf_mutable MIAB_Server)}"
+
+  #debug log the environmental variables
+  _debug MIAB_Username "$MIAB_Username"
+  _debug MIAB_Password "$MIAB_Password"
+  _debug MIAB_Server "$MIAB_Server"
+
+  #check if MIAB environemt vars set and quit if not
+  if [ -z "$MIAB_Username" ] || [ -z "$MIAB_Password" ] || [ -z "$MIAB_Server" ]; then
+    _err "You didn't specify one or more of MIAB_Username, MIAB_Password or MIAB_Server."
+    _err "Please check these environment variables and try again."
+    return 1
+  fi
+
+  #save the credentials to the account conf file.
+  _saveaccountconf_mutable MIAB_Username "$MIAB_Username"
+  _saveaccountconf_mutable MIAB_Password "$MIAB_Password"
+  _saveaccountconf_mutable MIAB_Server "$MIAB_Server"
+}
+
+#Useage: _miab_rest  "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"  "custom/_acme-challenge.www.domain.com/txt  "POST"
+#Returns: "updated DNS: domain.com"
+#rest interface MIAB dns
+_miab_rest() {
+  _data="$1"
+  _api_path="$2"
+  _httpmethod="$3"
+
+  #encode username and password for basic authentication
+  _credentials="$(printf "%s" "$MIAB_Username:$MIAB_Password" | _base64)"
+  export _H1="Authorization: Basic $_credentials"
+  _url="https://${MIAB_Server}/admin/dns/${_api_path}"
+
+  _debug2 _data "$_data"
+  _debug _api_path "$_api_path"
+  _debug2 _url "$_url"
+  _debug2 _credentails "$_credentials"
+  _debug _httpmethod "$_httpmethod"
+
+  if [ "$_httpmethod" = "GET" ]; then
+    response="$(_get "$_url")"
+  else
+    response="$(_post "$_data" "$_url" "" "$_httpmethod")"
+  fi
+
+  _retcode="$?"
+
+  if [ "$_retcode" != "0" ]; then
+    _err "MIAB REST authentication failed on $_httpmethod"
+    return 1
+  fi
+
+  _debug response "$response"
+  return 0
+}
+
+#Usage: _is_json  "\[\n   "mydomain.com"\n]"
+#Reurns "\[\n   "mydomain.com"\n]"
+#returns the string if it begins and ends with square braces
+_is_json() {
+  _str="$(echo "$1" | _normalizeJson)"
+  echo "$_str" | grep '^\[.*\]$' >/dev/null 2>&1
+}

+ 3 - 3
dnsapi/dns_namecheap.sh

@@ -3,10 +3,10 @@
 # Namecheap API
 # https://www.namecheap.com/support/api/intro.aspx
 #
-# Requires Namecheap API key set in 
-#NAMECHEAP_API_KEY, 
+# Requires Namecheap API key set in
+#NAMECHEAP_API_KEY,
 #NAMECHEAP_USERNAME,
-#NAMECHEAP_SOURCEIP 
+#NAMECHEAP_SOURCEIP
 # Due to Namecheap's API limitation all the records of your domain will be read and re applied, make sure to have a backup of your records you could apply if any issue would arise.
 
 ########  Public functions #####################

+ 185 - 0
dnsapi/dns_nic.sh

@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+#NIC_Token="sdfsdfsdfljlbjkljlkjsdfoiwjedfglgkdlfgkfgldfkg"
+#
+#NIC_Username="000000/NIC-D"
+
+#NIC_Password="xxxxxxx"
+
+NIC_Api="https://api.nic.ru"
+
+dns_nic_add() {
+  fulldomain="${1}"
+  txtvalue="${2}"
+
+  NIC_Token="${NIC_Token:-$(_readaccountconf_mutable NIC_Token)}"
+  NIC_Username="${NIC_Username:-$(_readaccountconf_mutable NIC_Username)}"
+  NIC_Password="${NIC_Password:-$(_readaccountconf_mutable NIC_Password)}"
+  if [ -z "$NIC_Token" ] || [ -z "$NIC_Username" ] || [ -z "$NIC_Password" ]; then
+    NIC_Token=""
+    NIC_Username=""
+    NIC_Password=""
+    _err "You must export variables: NIC_Token, NIC_Username and NIC_Password"
+    return 1
+  fi
+
+  _saveaccountconf_mutable NIC_Customer "$NIC_Token"
+  _saveaccountconf_mutable NIC_Username "$NIC_Username"
+  _saveaccountconf_mutable NIC_Password "$NIC_Password"
+
+  if ! _nic_get_authtoken "$NIC_Username" "$NIC_Password" "$NIC_Token"; then
+    _err "get NIC auth token failed"
+    return 1
+  fi
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "Invalid domain"
+    return 1
+  fi
+
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+  _debug _service "$_service"
+
+  _info "Adding record"
+  if ! _nic_rest PUT "services/$_service/zones/$_domain/records" "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><request><rr-list><rr><name>$_sub_domain</name><type>TXT</type><txt><string>$txtvalue</string></txt></rr></rr-list></request>"; then
+    _err "Add TXT record error"
+    return 1
+  fi
+
+  if ! _nic_rest POST "services/$_service/zones/$_domain/commit" ""; then
+    return 1
+  fi
+  _info "Added, OK"
+}
+
+dns_nic_rm() {
+  fulldomain="${1}"
+  txtvalue="${2}"
+
+  NIC_Token="${NIC_Token:-$(_readaccountconf_mutable NIC_Token)}"
+  NIC_Username="${NIC_Username:-$(_readaccountconf_mutable NIC_Username)}"
+  NIC_Password="${NIC_Password:-$(_readaccountconf_mutable NIC_Password)}"
+  if [ -z "$NIC_Token" ] || [ -z "$NIC_Username" ] || [ -z "$NIC_Password" ]; then
+    NIC_Token=""
+    NIC_Username=""
+    NIC_Password=""
+    _err "You must export variables: NIC_Token, NIC_Username and NIC_Password"
+    return 1
+  fi
+
+  if ! _nic_get_authtoken "$NIC_Username" "$NIC_Password" "$NIC_Token"; then
+    _err "get NIC auth token failed"
+    return 1
+  fi
+
+  if ! _get_root "$fulldomain"; then
+    _err "Invalid domain"
+    return 1
+  fi
+
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+  _debug _service "$_service"
+
+  if ! _nic_rest GET "services/$_service/zones/$_domain/records"; then
+    _err "Get records error"
+    return 1
+  fi
+
+  _domain_id=$(printf "%s" "$response" | grep "$_sub_domain" | grep "$txtvalue" | sed -r "s/.*<rr id=\"(.*)\".*/\1/g")
+
+  if ! _nic_rest DELETE "services/$_service/zones/$_domain/records/$_domain_id"; then
+    _err "Delete record error"
+    return 1
+  fi
+
+  if ! _nic_rest POST "services/$_service/zones/$_domain/commit" ""; then
+    return 1
+  fi
+}
+
+####################  Private functions below ##################################
+
+_nic_get_authtoken() {
+  username="$1"
+  password="$2"
+  token="$3"
+
+  _info "Getting NIC auth token"
+
+  export _H1="Authorization: Basic $token"
+  export _H2="Content-Type: application/x-www-form-urlencoded"
+
+  res=$(_post "grant_type=password&username=$username&password=$password&scope=%28GET%7CPUT%7CPOST%7CDELETE%29%3A%2Fdns-master%2F.%2B" "$NIC_Api/oauth/token" "" "POST")
+  if _contains "$res" "access_token"; then
+    _auth_token=$(printf "%s" "$res" | cut -d , -f2 | tr -d "\"" | sed "s/access_token://")
+    _info "Token received"
+    _debug _auth_token "$_auth_token"
+    return 0
+  fi
+  return 1
+}
+
+_get_root() {
+  domain="$1"
+  i=1
+  p=1
+
+  if ! _nic_rest GET "zones"; then
+    return 1
+  fi
+
+  _all_domains=$(printf "%s" "$response" | grep "idn-name" | sed -r "s/.*idn-name=\"(.*)\" name=.*/\1/g")
+  _debug2 _all_domains "$_all_domains"
+
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
+    _debug h "$h"
+
+    if [ -z "$h" ]; then
+      return 1
+    fi
+
+    if _contains "$_all_domains" "^$h$"; then
+      _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+      _domain=$h
+      _service=$(printf "%s" "$response" | grep "$_domain" | sed -r "s/.*service=\"(.*)\".*$/\1/")
+      return 0
+    fi
+    p="$i"
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+_nic_rest() {
+  m="$1"
+  ep="$2"
+  data="$3"
+  _debug "$ep"
+
+  export _H1="Content-Type: application/xml"
+  export _H2="Authorization: Bearer $_auth_token"
+
+  if [ "$m" != "GET" ]; then
+    _debug data "$data"
+    response=$(_post "$data" "$NIC_Api/dns-master/$ep" "" "$m")
+  else
+    response=$(_get "$NIC_Api/dns-master/$ep")
+  fi
+
+  if _contains "$response" "<errors>"; then
+    error=$(printf "%s" "$response" | grep "error code" | sed -r "s/.*<error code=.*>(.*)<\/error>/\1/g")
+    _err "Error: $error"
+    return 1
+  fi
+
+  if ! _contains "$response" "<status>success</status>"; then
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}

+ 2 - 2
dnsapi/dns_nsupdate.sh

@@ -27,7 +27,7 @@ dns_nsupdate_add() {
   [ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_2" ] && nsdebug="-D"
   if [ -z "${NSUPDATE_ZONE}" ]; then
     nsupdate -k "${NSUPDATE_KEY}" $nsdebug <<EOF
-server ${NSUPDATE_SERVER}  ${NSUPDATE_SERVER_PORT} 
+server ${NSUPDATE_SERVER}  ${NSUPDATE_SERVER_PORT}
 update add ${fulldomain}. 60 in txt "${txtvalue}"
 send
 EOF
@@ -64,7 +64,7 @@ dns_nsupdate_rm() {
   [ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_2" ] && nsdebug="-D"
   if [ -z "${NSUPDATE_ZONE}" ]; then
     nsupdate -k "${NSUPDATE_KEY}" $nsdebug <<EOF
-server ${NSUPDATE_SERVER}  ${NSUPDATE_SERVER_PORT} 
+server ${NSUPDATE_SERVER}  ${NSUPDATE_SERVER_PORT}
 update delete ${fulldomain}. txt
 send
 EOF

+ 224 - 0
dnsapi/dns_rcode0.sh

@@ -0,0 +1,224 @@
+#!/usr/bin/env sh
+
+#Rcode0 API Integration
+#https://my.rcodezero.at/api-doc
+#
+# log into https://my.rcodezero.at/enableapi and  get your ACME API Token (the ACME API token has limited
+# access to the REST calls needed for acme.sh only)
+#
+#RCODE0_URL="https://my.rcodezero.at"
+#RCODE0_API_TOKEN="0123456789ABCDEF"
+#RCODE0_TTL=60
+
+DEFAULT_RCODE0_URL="https://my.rcodezero.at"
+DEFAULT_RCODE0_TTL=60
+
+########  Public functions #####################
+#Usage: add _acme-challenge.www.domain.com "123456789ABCDEF0000000000000000000000000000000000000"
+#fulldomain
+#txtvalue
+dns_rcode0_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  RCODE0_API_TOKEN="${RCODE0_API_TOKEN:-$(_readaccountconf_mutable RCODE0_API_TOKEN)}"
+  RCODE0_URL="${RCODE0_URL:-$(_readaccountconf_mutable RCODE0_URL)}"
+  RCODE0_TTL="${RCODE0_TTL:-$(_readaccountconf_mutable RCODE0_TTL)}"
+
+  if [ -z "$RCODE0_URL" ]; then
+    RCODE0_URL="$DEFAULT_RCODE0_URL"
+  fi
+
+  if [ -z "$RCODE0_API_TOKEN" ]; then
+    RCODE0_API_TOKEN=""
+    _err "Missing Rcode0 ACME API Token."
+    _err "Please login and create your token at httsp://my.rcodezero.at/enableapi and try again."
+    return 1
+  fi
+
+  if [ -z "$RCODE0_TTL" ]; then
+    RCODE0_TTL="$DEFAULT_RCODE0_TTL"
+  fi
+
+  #save the token to the account conf file.
+  _saveaccountconf_mutable RCODE0_API_TOKEN "$RCODE0_API_TOKEN"
+
+  if [ "$RCODE0_URL" != "$DEFAULT_RCODE0_URL" ]; then
+    _saveaccountconf_mutable RCODE0_URL "$RCODE0_URL"
+  fi
+
+  if [ "$RCODE0_TTL" != "$DEFAULT_RCODE0_TTL" ]; then
+    _saveaccountconf_mutable RCODE0_TTL "$RCODE0_TTL"
+  fi
+
+  _debug "Detect root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "No 'MASTER' zone for $fulldomain found at RcodeZero Anycast."
+    return 1
+  fi
+  _debug _domain "$_domain"
+
+  _debug "Adding record"
+
+  _record_string=""
+  _build_record_string "$txtvalue"
+  _list_existingchallenges
+  for oldchallenge in $_existing_challenges; do
+    _build_record_string "$oldchallenge"
+  done
+
+  _debug "Challenges: $_existing_challenges"
+
+  if [ -z "$_existing_challenges" ]; then
+    if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"add\", \"name\": \"$fulldomain.\", \"type\": \"TXT\", \"ttl\": $RCODE0_TTL, \"records\": [$_record_string]}]"; then
+      _err "Add txt record error."
+      return 1
+    fi
+  else
+    # try update in case a records exists (need for wildcard certs)
+    if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"update\", \"name\": \"$fulldomain.\", \"type\": \"TXT\", \"ttl\": $RCODE0_TTL, \"records\": [$_record_string]}]"; then
+      _err "Set txt record error."
+      return 1
+    fi
+  fi
+
+  return 0
+}
+
+#fulldomain txtvalue
+dns_rcode0_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  RCODE0_API_TOKEN="${RCODE0_API_TOKEN:-$(_readaccountconf_mutable RCODE0_API_TOKEN)}"
+  RCODE0_URL="${RCODE0_URL:-$(_readaccountconf_mutable RCODE0_URL)}"
+  RCODE0_TTL="${RCODE0_TTL:-$(_readaccountconf_mutable RCODE0_TTL)}"
+
+  if [ -z "$RCODE0_URL" ]; then
+    RCODE0_URL="$DEFAULT_RCODE0_URL"
+  fi
+
+  if [ -z "$RCODE0_API_TOKEN" ]; then
+    RCODE0_API_TOKEN=""
+    _err "Missing Rcode0 API Token."
+    _err "Please login and create your token at httsp://my.rcodezero.at/enableapi and try again."
+    return 1
+  fi
+
+  #save the api addr and key to the account conf file.
+  _saveaccountconf_mutable RCODE0_URL "$RCODE0_URL"
+  _saveaccountconf_mutable RCODE0_API_TOKEN "$RCODE0_API_TOKEN"
+
+  if [ "$RCODE0_TTL" != "$DEFAULT_RCODE0_TTL" ]; then
+    _saveaccountconf_mutable RCODE0_TTL "$RCODE0_TTL"
+  fi
+
+  if [ -z "$RCODE0_TTL" ]; then
+    RCODE0_TTL="$DEFAULT_RCODE0_TTL"
+  fi
+
+  _debug "Detect root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+
+  _debug "Remove record"
+
+  #Enumerate existing acme challenges
+  _list_existingchallenges
+
+  if _contains "$_existing_challenges" "$txtvalue"; then
+    #Delete all challenges (PowerDNS API does not allow to delete content)
+    if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"delete\", \"name\": \"$fulldomain.\", \"type\": \"TXT\"}]"; then
+      _err "Delete txt record error."
+      return 1
+    fi
+    _record_string=""
+    #If the only existing challenge was the challenge to delete: nothing to do
+    if ! [ "$_existing_challenges" = "$txtvalue" ]; then
+      for oldchallenge in $_existing_challenges; do
+        #Build up the challenges to re-add, ommitting the one what should be deleted
+        if ! [ "$oldchallenge" = "$txtvalue" ]; then
+          _build_record_string "$oldchallenge"
+        fi
+      done
+      #Recreate the existing challenges
+      if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"update\", \"name\": \"$fulldomain.\", \"type\": \"TXT\", \"ttl\": $RCODE0_TTL, \"records\": [$_record_string]}]"; then
+        _err "Set txt record error."
+        return 1
+      fi
+    fi
+  else
+    _info "Record not found, nothing to remove"
+  fi
+
+  return 0
+}
+
+####################  Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _domain=domain.com
+_get_root() {
+  domain=$1
+  i=1
+
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+
+    _debug "try to find: $h"
+    if _rcode0_rest "GET" "/api/v1/acme/zones/$h"; then
+      if [ "$response" = "[\"found\"]" ]; then
+        _domain="$h"
+        if [ -z "$h" ]; then
+          _domain="=2E"
+        fi
+        return 0
+      elif [ "$response" = "[\"not a master domain\"]" ]; then
+        return 1
+      fi
+    fi
+
+    if [ -z "$h" ]; then
+      return 1
+    fi
+    i=$(_math $i + 1)
+  done
+  _debug "no matching domain for $domain found"
+
+  return 1
+}
+
+_rcode0_rest() {
+  method=$1
+  ep=$2
+  data=$3
+
+  export _H1="Authorization: Bearer $RCODE0_API_TOKEN"
+
+  if [ ! "$method" = "GET" ]; then
+    _debug data "$data"
+    response="$(_post "$data" "$RCODE0_URL$ep" "" "$method")"
+  else
+    response="$(_get "$RCODE0_URL$ep")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+
+  return 0
+}
+
+_build_record_string() {
+  _record_string="${_record_string:+${_record_string}, }{\"content\": \"\\\"${1}\\\"\", \"disabled\": false}"
+}
+
+_list_existingchallenges() {
+  _rcode0_rest "GET" "/api/v1/acme/zones/$_domain/rrsets"
+  _existing_challenges=$(echo "$response" | _normalizeJson | _egrep_o "\"name\":\"${fulldomain}[^]]*}" | _egrep_o 'content\":\"\\"[^\\]*' | sed -n 's/^content":"\\"//p')
+  _debug2 "$_existing_challenges"
+}

+ 147 - 0
dnsapi/dns_variomedia.sh

@@ -0,0 +1,147 @@
+#!/usr/bin/env sh
+
+#
+#VARIOMEDIA_API_TOKEN=000011112222333344445555666677778888
+
+VARIOMEDIA_API="https://api.variomedia.de"
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_variomedia_add() {
+  fulldomain=$1
+  txtvalue=$2
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  VARIOMEDIA_API_TOKEN="${VARIOMEDIA_API_TOKEN:-$(_readaccountconf_mutable VARIOMEDIA_API_TOKEN)}"
+  if test -z "$VARIOMEDIA_API_TOKEN"; then
+    VARIOMEDIA_API_TOKEN=""
+    _err 'VARIOMEDIA_API_TOKEN was not exported'
+    return 1
+  fi
+
+  _saveaccountconf_mutable VARIOMEDIA_API_TOKEN "$VARIOMEDIA_API_TOKEN"
+
+  _debug 'First detect the root zone'
+  if ! _get_root "$fulldomain"; then
+    return 1
+  fi
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  if ! _variomedia_rest POST "dns-records" "{\"data\": {\"type\": \"dns-record\", \"attributes\": {\"record_type\": \"TXT\", \"name\": \"$_sub_domain\", \"domain\": \"$_domain\", \"data\": \"$txtvalue\", \"ttl\":300}}}"; then
+    _err "$response"
+    return 1
+  fi
+
+  _debug2 _response "$response"
+  return 0
+}
+
+#fulldomain txtvalue
+dns_variomedia_rm() {
+  fulldomain=$1
+  txtvalue=$2
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  VARIOMEDIA_API_TOKEN="${VARIOMEDIA_API_TOKEN:-$(_readaccountconf_mutable VARIOMEDIA_API_TOKEN)}"
+  if test -z "$VARIOMEDIA_API_TOKEN"; then
+    VARIOMEDIA_API_TOKEN=""
+    _err 'VARIOMEDIA_API_TOKEN was not exported'
+    return 1
+  fi
+
+  _saveaccountconf_mutable VARIOMEDIA_API_TOKEN "$VARIOMEDIA_API_TOKEN"
+
+  _debug 'First detect the root zone'
+  if ! _get_root "$fulldomain"; then
+    return 1
+  fi
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _debug 'Getting txt records'
+
+  if ! _variomedia_rest GET "dns-records?filter[domain]=$_domain"; then
+    _err 'Error'
+    return 1
+  fi
+
+  _record_id="$(echo "$response" | cut -d '[' -f2 | cut -d']' -f1 | sed 's/},[ \t]*{/\},§\{/g' | tr § '\n' | grep "$_sub_domain" | grep "$txtvalue" | sed 's/^{//;s/}[,]?$//' | tr , '\n' | tr -d '\"' | grep ^id | cut -d : -f2 | tr -d ' ')"
+  _debug _record_id "$_record_id"
+  if [ "$_record_id" ]; then
+    _info "Successfully retrieved the record id for ACME challenge."
+  else
+    _info "Empty record id, it seems no such record."
+    return 0
+  fi
+
+  if ! _variomedia_rest DELETE "/dns-records/$_record_id"; then
+    _err "$response"
+    return 1
+  fi
+
+  _debug2 _response "$response"
+  return 0
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+  fulldomain=$1
+  i=1
+  while true; do
+    h=$(printf "%s" "$fulldomain" | cut -d . -f $i-100)
+    _debug h "$h"
+    if [ -z "$h" ]; then
+      return 1
+    fi
+
+    if ! _variomedia_rest GET "domains/$h"; then
+      return 1
+    fi
+
+    if _startswith "$response" "\{\"data\":"; then
+      if _contains "$response" "\"id\": \"$h\""; then
+        _sub_domain="$(echo "$fulldomain" | sed "s/\\.$h\$//")"
+        _domain=$h
+        return 0
+      fi
+    fi
+    i=$(_math "$i" + 1)
+  done
+
+  _debug "root domain not found"
+  return 1
+}
+
+_variomedia_rest() {
+  m=$1
+  ep="$2"
+  data="$3"
+  _debug "$ep"
+
+  export _H1="Authorization: token $VARIOMEDIA_API_TOKEN"
+  export _H2="Content-Type: application/vnd.api+json"
+  export _H3="Accept: application/vnd.variomedia.v1+json"
+
+  if [ "$m" != "GET" ]; then
+    _debug data "$data"
+    response="$(_post "$data" "$VARIOMEDIA_API/$ep" "" "$m")"
+  else
+    response="$(_get "$VARIOMEDIA_API/$ep")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "Error $ep"
+    return 1
+  fi
+
+  _debug2 response "$response"
+  return 0
+}

+ 1 - 1
dnsapi/dns_vscale.sh

@@ -102,7 +102,7 @@ _get_root() {
         return 1
       fi
 
-      hostedzone="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$h\".*}")"
+      hostedzone="$(echo "$response" | tr "{" "\n" | _egrep_o "\"name\":\s*\"$h\".*}")"
       if [ "$hostedzone" ]; then
         _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
         if [ "$_domain_id" ]; then

+ 4 - 6
dnsapi/dns_vultr.sh

@@ -106,9 +106,9 @@ _get_root() {
   domain=$1
   i=1
   while true; do
-    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
-    _debug h "$h"
-    if [ -z "$h" ]; then
+    _domain=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug h "$_domain"
+    if [ -z "$_domain" ]; then
       return 1
     fi
 
@@ -119,11 +119,9 @@ _get_root() {
     if printf "%s\n" "$response" | grep '^\[.*\]' >/dev/null; then
       if _contains "$response" "\"domain\":\"$_domain\""; then
         _sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")"
-        _domain=$_domain
         return 0
       else
-        _err 'Invalid domain'
-        return 1
+        _debug "Go to next level of $_domain"
       fi
     else
       _err "$response"

+ 68 - 0
notify/dingtalk.sh

@@ -0,0 +1,68 @@
+#!/usr/bin/env sh
+
+#Support dingtalk webhooks api
+
+#DINGTALK_WEBHOOK="xxxx"
+
+#optional
+#DINGTALK_KEYWORD="yyyy"
+
+#DINGTALK_SIGNING_KEY="SEC08ffdbd403cbc3fc8a65xxxxxxxxxxxxxxxxxxxx"
+
+# subject  content statusCode
+dingtalk_send() {
+  _subject="$1"
+  _content="$2"
+  _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+  _debug "_subject" "$_subject"
+  _debug "_content" "$_content"
+  _debug "_statusCode" "$_statusCode"
+
+  DINGTALK_WEBHOOK="${DINGTALK_WEBHOOK:-$(_readaccountconf_mutable DINGTALK_WEBHOOK)}"
+  if [ -z "$DINGTALK_WEBHOOK" ]; then
+    DINGTALK_WEBHOOK=""
+    _err "You didn't specify a dingtalk webhooks DINGTALK_WEBHOOK yet."
+    _err "You can get yours from https://dingtalk.com"
+    return 1
+  fi
+  _saveaccountconf_mutable DINGTALK_WEBHOOK "$DINGTALK_WEBHOOK"
+
+  DINGTALK_KEYWORD="${DINGTALK_KEYWORD:-$(_readaccountconf_mutable DINGTALK_KEYWORD)}"
+  if [ "$DINGTALK_KEYWORD" ]; then
+    _saveaccountconf_mutable DINGTALK_KEYWORD "$DINGTALK_KEYWORD"
+  fi
+
+  #  DINGTALK_SIGNING_KEY="${DINGTALK_SIGNING_KEY:-$(_readaccountconf_mutable DINGTALK_SIGNING_KEY)}"
+  #  if [ -z "$DINGTALK_SIGNING_KEY" ]; then
+  #    DINGTALK_SIGNING_KEY="value1"
+  #    _info "The DINGTALK_SIGNING_KEY is not set, so use the default value1 as key."
+  #  elif ! _hasfield "$_IFTTT_AVAIL_MSG_KEYS" "$DINGTALK_SIGNING_KEY"; then
+  #    _err "The DINGTALK_SIGNING_KEY \"$DINGTALK_SIGNING_KEY\" is not available, should be one of $_IFTTT_AVAIL_MSG_KEYS"
+  #    DINGTALK_SIGNING_KEY=""
+  #    return 1
+  #  else
+  #    _saveaccountconf_mutable DINGTALK_SIGNING_KEY "$DINGTALK_SIGNING_KEY"
+  #  fi
+
+  #  if [ "$DINGTALK_SIGNING_KEY" = "$IFTTT_CONTENT_KEY" ]; then
+  #    DINGTALK_SIGNING_KEY=""
+  #    IFTTT_CONTENT_KEY=""
+  #    _err "The DINGTALK_SIGNING_KEY must not be same as IFTTT_CONTENT_KEY."
+  #    return 1
+  #  fi
+
+  _content=$(echo "$_content" | _json_encode)
+  _subject=$(echo "$_subject" | _json_encode)
+  _data="{\"msgtype\": \"text\", \"text\": {\"content\": \"[$DINGTALK_KEYWORD]\n$_subject\n$_content\"}}"
+
+  response="$(_post "$_data" "$DINGTALK_WEBHOOK" "" "POST" "application/json")"
+
+  if [ "$?" = "0" ] && _contains "$response" "errmsg\":\"ok"; then
+    _info "dingtalk webhooks event fired success."
+    return 0
+  fi
+
+  _err "dingtalk webhooks event fired error."
+  _err "$response"
+  return 1
+}