浏览代码

Merge with latest dev branch.

Martin Kammerlander 7 年之前
父节点
当前提交
c34aadfbf7

+ 1 - 10
.travis.yml

@@ -13,12 +13,6 @@ env:
   global:
   global:
     - SHFMT_URL=https://github.com/mvdan/sh/releases/download/v0.4.0/shfmt_v0.4.0_linux_amd64
     - SHFMT_URL=https://github.com/mvdan/sh/releases/download/v0.4.0/shfmt_v0.4.0_linux_amd64
 
 
-addons:
-  apt:
-    sources:
-    - debian-sid    # Grab shellcheck from the Debian repo (o_O)
-    packages:
-    - shellcheck
 
 
 install:
 install:
   - if [ "$TRAVIS_OS_NAME" = 'osx' ]; then
   - if [ "$TRAVIS_OS_NAME" = 'osx' ]; then
@@ -29,9 +23,7 @@ install:
 script:
 script:
   - echo "NGROK_TOKEN=$(echo "$NGROK_TOKEN" | wc -c)"
   - echo "NGROK_TOKEN=$(echo "$NGROK_TOKEN" | wc -c)"
   - command -V openssl && openssl version
   - command -V openssl && openssl version
-  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then curl -sSL $SHFMT_URL -o ~/shfmt ; fi
-  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then chmod +x ~/shfmt ; fi
-  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then ~/shfmt -l -w -i 2 . ; fi
+  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then curl -sSL $SHFMT_URL -o ~/shfmt && chmod +x ~/shfmt && ~/shfmt -l -w -i 2 . ; fi
   - if [ "$TRAVIS_OS_NAME" = "linux" ]; then git diff --exit-code && echo "shfmt OK" ; fi
   - if [ "$TRAVIS_OS_NAME" = "linux" ]; then git diff --exit-code && echo "shfmt OK" ; fi
   - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -V ; fi
   - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -V ; fi
   - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -e SC2181 **/*.sh && echo "shellcheck OK" ; fi
   - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -e SC2181 **/*.sh && echo "shellcheck OK" ; fi
@@ -40,7 +32,6 @@ script:
   - 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" = "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
   - 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:
 matrix:
   fast_finish: true
   fast_finish: true
   
   

+ 1 - 0
Dockerfile

@@ -3,6 +3,7 @@ FROM alpine:3.6
 RUN apk update -f \
 RUN apk update -f \
   && apk --no-cache add -f \
   && apk --no-cache add -f \
   openssl \
   openssl \
+  coreutils \
   curl \
   curl \
   socat \
   socat \
   && rm -rf /var/cache/apk/*
   && rm -rf /var/cache/apk/*

+ 9 - 3
README.md

@@ -315,8 +315,12 @@ You don't have to do anything manually!
 1. zonomi.com DNS API
 1. zonomi.com DNS API
 1. DreamHost.com API
 1. DreamHost.com API
 1. DirectAdmin API
 1. DirectAdmin API
-1. All-inkl/Kasserver API
-
+1. KingHost (https://www.kinghost.com.br/)
+1. Zilore (https://zilore.com)
+1. Loopia.se API
+1. acme-dns (https://github.com/joohoi/acme-dns)
+1. TELE3 (https://www.tele3.cz)
+1. All-inkl/Kasserver API (https://all-inkl.com)
 
 
 And: 
 And: 
 
 
@@ -332,6 +336,8 @@ For more details: [How to use DNS API](dnsapi)
 
 
 # 8. Use DNS manual mode:
 # 8. Use DNS manual mode:
 
 
+See: https://github.com/Neilpang/acme.sh/wiki/dns-manual-mode first.
+
 If your dns provider doesn't support any api access, you can add the txt record by your hand.
 If your dns provider doesn't support any api access, you can add the txt record by your hand.
 
 
 ```bash
 ```bash
@@ -401,7 +407,7 @@ Valid values are:
 It's simple, just give a wildcard domain as the `-d` parameter.
 It's simple, just give a wildcard domain as the `-d` parameter.
 
 
 ```sh
 ```sh
-acme.sh  --issue -d example.com  -d *.example.com  --dns dns_cf
+acme.sh  --issue -d example.com  -d '*.example.com'  --dns dns_cf
 ```
 ```
 
 
 
 

+ 79 - 33
acme.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 #!/usr/bin/env sh
 
 
-VER=2.7.8
+VER=2.7.9
 
 
 PROJECT_NAME="acme.sh"
 PROJECT_NAME="acme.sh"
 
 
@@ -110,31 +110,35 @@ _STATELESS_WIKI="https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode"
 
 
 _DNS_ALIAS_WIKI="https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode"
 _DNS_ALIAS_WIKI="https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode"
 
 
+_DNS_MANUAL_WIKI="https://github.com/Neilpang/acme.sh/wiki/dns-manual-mode"
+
 _DNS_MANUAL_ERR="The dns manual mode can not renew automatically, you must issue it again manually. You'd better use the other modes instead."
 _DNS_MANUAL_ERR="The dns manual mode can not renew automatically, you must issue it again manually. You'd better use the other modes instead."
 
 
 _DNS_MANUAL_WARN="It seems that you are using dns manual mode. please take care: $_DNS_MANUAL_ERR"
 _DNS_MANUAL_WARN="It seems that you are using dns manual mode. please take care: $_DNS_MANUAL_ERR"
 
 
+_DNS_MANUAL_ERROR="It seems that you are using dns manual mode. Read this link first: $_DNS_MANUAL_WIKI"
+
 __INTERACTIVE=""
 __INTERACTIVE=""
 if [ -t 1 ]; then
 if [ -t 1 ]; then
   __INTERACTIVE="1"
   __INTERACTIVE="1"
 fi
 fi
 
 
 __green() {
 __green() {
-  if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" ]; then
+  if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" -o "${ACME_FORCE_COLOR}" = "1" ]; then
     printf '\033[1;31;32m'
     printf '\033[1;31;32m'
   fi
   fi
   printf -- "%b" "$1"
   printf -- "%b" "$1"
-  if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" ]; then
+  if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" -o "${ACME_FORCE_COLOR}" = "1" ]; then
     printf '\033[0m'
     printf '\033[0m'
   fi
   fi
 }
 }
 
 
 __red() {
 __red() {
-  if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" ]; then
+  if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" -o "${ACME_FORCE_COLOR}" = "1" ]; then
     printf '\033[1;31;40m'
     printf '\033[1;31;40m'
   fi
   fi
   printf -- "%b" "$1"
   printf -- "%b" "$1"
-  if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" ]; then
+  if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" -o "${ACME_FORCE_COLOR}" = "1" ]; then
     printf '\033[0m'
     printf '\033[0m'
   fi
   fi
 }
 }
@@ -1617,6 +1621,7 @@ _post() {
   _debug $httpmethod
   _debug $httpmethod
   _debug "_post_url" "$_post_url"
   _debug "_post_url" "$_post_url"
   _debug2 "body" "$body"
   _debug2 "body" "$body"
+  _debug2 "_postContentType" "$_postContentType"
 
 
   _inithttp
   _inithttp
 
 
@@ -1625,14 +1630,19 @@ _post() {
     if [ "$HTTPS_INSECURE" ]; then
     if [ "$HTTPS_INSECURE" ]; then
       _CURL="$_CURL --insecure  "
       _CURL="$_CURL --insecure  "
     fi
     fi
-    if [ "$_postContentType" ]; then
-      _CURL="$_CURL -H \"Content-Type: $_postContentType\" "
-    fi
     _debug "_CURL" "$_CURL"
     _debug "_CURL" "$_CURL"
     if [ "$needbase64" ]; then
     if [ "$needbase64" ]; then
-      response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)"
+      if [ "$_postContentType" ]; then
+        response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)"
+      else
+        response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)"
+      fi
     else
     else
-      response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")"
+      if [ "$_postContentType" ]; then
+        response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")"
+      else
+        response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")"
+      fi
     fi
     fi
     _ret="$?"
     _ret="$?"
     if [ "$_ret" != "0" ]; then
     if [ "$_ret" != "0" ]; then
@@ -1785,19 +1795,25 @@ _send_signed_request() {
     return 1
     return 1
   fi
   fi
 
 
+  if [ "$ACME_VERSION" = "2" ]; then
+    __request_conent_type="$CONTENT_TYPE_JSON"
+  else
+    __request_conent_type=""
+  fi
   payload64=$(printf "%s" "$payload" | _base64 | _url_replace)
   payload64=$(printf "%s" "$payload" | _base64 | _url_replace)
   _debug3 payload64 "$payload64"
   _debug3 payload64 "$payload64"
 
 
   MAX_REQUEST_RETRY_TIMES=5
   MAX_REQUEST_RETRY_TIMES=5
   _request_retry_times=0
   _request_retry_times=0
   while [ "${_request_retry_times}" -lt "$MAX_REQUEST_RETRY_TIMES" ]; do
   while [ "${_request_retry_times}" -lt "$MAX_REQUEST_RETRY_TIMES" ]; do
+    _request_retry_times=$(_math "$_request_retry_times" + 1)
     _debug3 _request_retry_times "$_request_retry_times"
     _debug3 _request_retry_times "$_request_retry_times"
     if [ -z "$_CACHED_NONCE" ]; then
     if [ -z "$_CACHED_NONCE" ]; then
       _headers=""
       _headers=""
       if [ "$ACME_NEW_NONCE" ]; then
       if [ "$ACME_NEW_NONCE" ]; then
         _debug2 "Get nonce. ACME_NEW_NONCE" "$ACME_NEW_NONCE"
         _debug2 "Get nonce. ACME_NEW_NONCE" "$ACME_NEW_NONCE"
         nonceurl="$ACME_NEW_NONCE"
         nonceurl="$ACME_NEW_NONCE"
-        if _post "" "$nonceurl" "" "HEAD" "$CONTENT_TYPE_JSON"; then
+        if _post "" "$nonceurl" "" "HEAD" "$__request_conent_type"; then
           _headers="$(cat "$HTTP_HEADER")"
           _headers="$(cat "$HTTP_HEADER")"
         fi
         fi
       fi
       fi
@@ -1821,7 +1837,11 @@ _send_signed_request() {
     fi
     fi
     nonce="$_CACHED_NONCE"
     nonce="$_CACHED_NONCE"
     _debug2 nonce "$nonce"
     _debug2 nonce "$nonce"
-
+    if [ -z "$nonce" ]; then
+      _info "Could not get nonce, let's try again."
+      _sleep 2
+      continue
+    fi
     if [ "$ACME_VERSION" = "2" ]; then
     if [ "$ACME_VERSION" = "2" ]; then
       if [ "$url" = "$ACME_NEW_ACCOUNT" ] || [ "$url" = "$ACME_REVOKE_CERT" ]; then
       if [ "$url" = "$ACME_NEW_ACCOUNT" ] || [ "$url" = "$ACME_REVOKE_CERT" ]; then
         protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
         protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
@@ -1852,7 +1872,7 @@ _send_signed_request() {
     fi
     fi
     _debug3 body "$body"
     _debug3 body "$body"
 
 
-    response="$(_post "$body" "$url" "$needbase64" "POST" "$CONTENT_TYPE_JSON")"
+    response="$(_post "$body" "$url" "$needbase64" "POST" "$__request_conent_type")"
     _CACHED_NONCE=""
     _CACHED_NONCE=""
 
 
     if [ "$?" != "0" ]; then
     if [ "$?" != "0" ]; then
@@ -1879,7 +1899,6 @@ _send_signed_request() {
 
 
     if _contains "$_body" "JWS has invalid anti-replay nonce"; then
     if _contains "$_body" "JWS has invalid anti-replay nonce"; then
       _info "It seems the CA server is busy now, let's wait and retry."
       _info "It seems the CA server is busy now, let's wait and retry."
-      _request_retry_times=$(_math "$_request_retry_times" + 1)
       _sleep 5
       _sleep 5
       continue
       continue
     fi
     fi
@@ -3247,10 +3266,16 @@ _regAccount() {
     return 1
     return 1
   fi
   fi
 
 
+  _debug2 responseHeaders "$responseHeaders"
   _accUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
   _accUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
   _debug "_accUri" "$_accUri"
   _debug "_accUri" "$_accUri"
+  if [ -z "$_accUri" ]; then
+    _err "Can not find account id url."
+    _err "$responseHeaders"
+    return 1
+  fi
   _savecaconf "ACCOUNT_URL" "$_accUri"
   _savecaconf "ACCOUNT_URL" "$_accUri"
-  export ACCOUNT_URL="$ACCOUNT_URL"
+  export ACCOUNT_URL="$_accUri"
 
 
   CA_KEY_HASH="$(__calcAccountKeyHash)"
   CA_KEY_HASH="$(__calcAccountKeyHash)"
   _debug "Calc CA_KEY_HASH" "$CA_KEY_HASH"
   _debug "Calc CA_KEY_HASH" "$CA_KEY_HASH"
@@ -3460,6 +3485,11 @@ issue() {
     mkdir -p "$DOMAIN_PATH"
     mkdir -p "$DOMAIN_PATH"
   fi
   fi
 
 
+  if _hasfield "$_web_roots" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then
+    _err "$_DNS_MANUAL_ERROR"
+    return 1
+  fi
+
   _debug "Using ACME_DIRECTORY: $ACME_DIRECTORY"
   _debug "Using ACME_DIRECTORY: $ACME_DIRECTORY"
 
 
   _initAPI
   _initAPI
@@ -3521,7 +3551,7 @@ issue() {
   _saved_account_key_hash="$(_readcaconf "CA_KEY_HASH")"
   _saved_account_key_hash="$(_readcaconf "CA_KEY_HASH")"
   _debug2 _saved_account_key_hash "$_saved_account_key_hash"
   _debug2 _saved_account_key_hash "$_saved_account_key_hash"
 
 
-  if [ -z "$_saved_account_key_hash" ] || [ "$_saved_account_key_hash" != "$(__calcAccountKeyHash)" ]; then
+  if [ -z "$ACCOUNT_URL" ] || [ -z "$_saved_account_key_hash" ] || [ "$_saved_account_key_hash" != "$(__calcAccountKeyHash)" ]; then
     if ! _regAccount "$_accountkeylength"; then
     if ! _regAccount "$_accountkeylength"; then
       _on_issue_err "$_post_hook"
       _on_issue_err "$_post_hook"
       return 1
       return 1
@@ -4083,13 +4113,15 @@ $_authorizations_map"
     fi
     fi
     if [ "$code" != "200" ]; then
     if [ "$code" != "200" ]; then
       _err "Sign failed, code is not 200."
       _err "Sign failed, code is not 200."
+      _err "$response"
       _on_issue_err "$_post_hook"
       _on_issue_err "$_post_hook"
       return 1
       return 1
     fi
     fi
     Le_LinkCert="$(echo "$response" | tr -d '\r\n' | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
     Le_LinkCert="$(echo "$response" | tr -d '\r\n' | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
 
 
     if ! _get "$Le_LinkCert" >"$CERT_PATH"; then
     if ! _get "$Le_LinkCert" >"$CERT_PATH"; then
-      _err "Sign failed, code is not 200."
+      _err "Sign failed, can not download cert:$Le_LinkCert."
+      _err "$response"
       _on_issue_err "$_post_hook"
       _on_issue_err "$_post_hook"
       return 1
       return 1
     fi
     fi
@@ -4105,12 +4137,12 @@ $_authorizations_map"
     fi
     fi
   else
   else
     if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"$ACME_NEW_ORDER_RES\", \"csr\": \"$der\"}" "needbase64"; then
     if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"$ACME_NEW_ORDER_RES\", \"csr\": \"$der\"}" "needbase64"; then
-      _err "Sign failed."
+      _err "Sign failed. $response"
       _on_issue_err "$_post_hook"
       _on_issue_err "$_post_hook"
       return 1
       return 1
     fi
     fi
     _rcert="$response"
     _rcert="$response"
-    Le_LinkCert="$(grep -i '^Location.*$' "$HTTP_HEADER" | _head_n 1 | tr -d "\r\n" | cut -d " " -f 2)"
+    Le_LinkCert="$(grep -i '^Location.*$' "$HTTP_HEADER" | _tail_n 1 | tr -d "\r\n" | cut -d " " -f 2)"
     echo "$BEGIN_CERT" >"$CERT_PATH"
     echo "$BEGIN_CERT" >"$CERT_PATH"
 
 
     #if ! _get "$Le_LinkCert" | _base64 "multiline"  >> "$CERT_PATH" ; then
     #if ! _get "$Le_LinkCert" | _base64 "multiline"  >> "$CERT_PATH" ; then
@@ -4255,20 +4287,21 @@ $_authorizations_map"
   Le_NextRenewTime=$(_math "$Le_NextRenewTime" - 86400)
   Le_NextRenewTime=$(_math "$Le_NextRenewTime" - 86400)
   _savedomainconf "Le_NextRenewTime" "$Le_NextRenewTime"
   _savedomainconf "Le_NextRenewTime" "$Le_NextRenewTime"
 
 
-  if ! _on_issue_success "$_post_hook" "$_renew_hook"; then
-    _err "Call hook error."
-    return 1
-  fi
-
   if [ "$_real_cert$_real_key$_real_ca$_reload_cmd$_real_fullchain" ]; then
   if [ "$_real_cert$_real_key$_real_ca$_reload_cmd$_real_fullchain" ]; then
     _savedomainconf "Le_RealCertPath" "$_real_cert"
     _savedomainconf "Le_RealCertPath" "$_real_cert"
     _savedomainconf "Le_RealCACertPath" "$_real_ca"
     _savedomainconf "Le_RealCACertPath" "$_real_ca"
     _savedomainconf "Le_RealKeyPath" "$_real_key"
     _savedomainconf "Le_RealKeyPath" "$_real_key"
     _savedomainconf "Le_ReloadCmd" "$_reload_cmd"
     _savedomainconf "Le_ReloadCmd" "$_reload_cmd"
     _savedomainconf "Le_RealFullChainPath" "$_real_fullchain"
     _savedomainconf "Le_RealFullChainPath" "$_real_fullchain"
-    _installcert "$_main_domain" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd"
+    if ! _installcert "$_main_domain" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd"; then
+      return 1
+    fi
   fi
   fi
 
 
+  if ! _on_issue_success "$_post_hook" "$_renew_hook"; then
+    _err "Call hook error."
+    return 1
+  fi
 }
 }
 
 
 #domain  [isEcc]
 #domain  [isEcc]
@@ -4643,19 +4676,19 @@ _installcert() {
     if [ -f "$_real_cert" ] && [ ! "$IS_RENEW" ]; then
     if [ -f "$_real_cert" ] && [ ! "$IS_RENEW" ]; then
       cp "$_real_cert" "$_backup_path/cert.bak"
       cp "$_real_cert" "$_backup_path/cert.bak"
     fi
     fi
-    cat "$CERT_PATH" >"$_real_cert"
+    cat "$CERT_PATH" >"$_real_cert" || return 1
   fi
   fi
 
 
   if [ "$_real_ca" ]; then
   if [ "$_real_ca" ]; then
     _info "Installing CA to:$_real_ca"
     _info "Installing CA to:$_real_ca"
     if [ "$_real_ca" = "$_real_cert" ]; then
     if [ "$_real_ca" = "$_real_cert" ]; then
       echo "" >>"$_real_ca"
       echo "" >>"$_real_ca"
-      cat "$CA_CERT_PATH" >>"$_real_ca"
+      cat "$CA_CERT_PATH" >>"$_real_ca" || return 1
     else
     else
       if [ -f "$_real_ca" ] && [ ! "$IS_RENEW" ]; then
       if [ -f "$_real_ca" ] && [ ! "$IS_RENEW" ]; then
         cp "$_real_ca" "$_backup_path/ca.bak"
         cp "$_real_ca" "$_backup_path/ca.bak"
       fi
       fi
-      cat "$CA_CERT_PATH" >"$_real_ca"
+      cat "$CA_CERT_PATH" >"$_real_ca" || return 1
     fi
     fi
   fi
   fi
 
 
@@ -4665,9 +4698,9 @@ _installcert() {
       cp "$_real_key" "$_backup_path/key.bak"
       cp "$_real_key" "$_backup_path/key.bak"
     fi
     fi
     if [ -f "$_real_key" ]; then
     if [ -f "$_real_key" ]; then
-      cat "$CERT_KEY_PATH" >"$_real_key"
+      cat "$CERT_KEY_PATH" >"$_real_key" || return 1
     else
     else
-      cat "$CERT_KEY_PATH" >"$_real_key"
+      cat "$CERT_KEY_PATH" >"$_real_key" || return 1
       chmod 600 "$_real_key"
       chmod 600 "$_real_key"
     fi
     fi
   fi
   fi
@@ -4677,7 +4710,7 @@ _installcert() {
     if [ -f "$_real_fullchain" ] && [ ! "$IS_RENEW" ]; then
     if [ -f "$_real_fullchain" ] && [ ! "$IS_RENEW" ]; then
       cp "$_real_fullchain" "$_backup_path/fullchain.bak"
       cp "$_real_fullchain" "$_backup_path/fullchain.bak"
     fi
     fi
-    cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain"
+    cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain" || return 1
   fi
   fi
 
 
   if [ "$_reload_cmd" ]; then
   if [ "$_reload_cmd" ]; then
@@ -5456,8 +5489,8 @@ Parameters:
   --cert-home                       Specifies the home dir to save all the certs, only valid for '--install' command.
   --cert-home                       Specifies the home dir to save all the certs, only valid for '--install' command.
   --config-home                     Specifies the home dir to save all the configurations.
   --config-home                     Specifies the home dir to save all the configurations.
   --useragent                       Specifies the user agent string. it will be saved for future use too.
   --useragent                       Specifies the user agent string. it will be saved for future use too.
-  --accountemail                    Specifies the account email for registering, Only valid for the '--install' command.
-  --accountkey                      Specifies the account key path, Only valid for the '--install' command.
+  --accountemail                    Specifies the account email, only valid for the '--install' and '--update-account' command.
+  --accountkey                      Specifies the account key path, only valid for the '--install' command.
   --days                            Specifies the days to renew the cert when using '--issue' command. The max value is $MAX_RENEW days.
   --days                            Specifies the days to renew the cert when using '--issue' command. The max value is $MAX_RENEW days.
   --httpport                        Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer.
   --httpport                        Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer.
   --local-address                   Specifies the standalone/tls server listening address, in case you have multiple ip addresses.
   --local-address                   Specifies the standalone/tls server listening address, in case you have multiple ip addresses.
@@ -5468,6 +5501,7 @@ Parameters:
   --ca-path                         Specifies directory containing CA certificates in PEM format, used by wget or curl.
   --ca-path                         Specifies directory containing CA certificates in PEM format, used by wget or curl.
   --nocron                          Only valid for '--install' command, which means: do not install the default cron job. In this case, the certs will not be renewed automatically.
   --nocron                          Only valid for '--install' command, which means: do not install the default cron job. In this case, the certs will not be renewed automatically.
   --no-color                        Do not output color text.
   --no-color                        Do not output color text.
+  --force-color                     Force output of color text. Useful for non-interactive use with the aha tool for HTML E-Mails.
   --ecc                             Specifies to use the ECC cert. Valid for '--install-cert', '--renew', '--revoke', '--toPkcs' and '--createCSR'
   --ecc                             Specifies to use the ECC cert. Valid for '--install-cert', '--renew', '--revoke', '--toPkcs' and '--createCSR'
   --csr                             Specifies the input csr.
   --csr                             Specifies the input csr.
   --pre-hook                        Command to be run before obtaining any certificates.
   --pre-hook                        Command to be run before obtaining any certificates.
@@ -5481,6 +5515,8 @@ Parameters:
   --listen-v6                       Force standalone/tls server to listen at ipv6.
   --listen-v6                       Force standalone/tls server to listen at ipv6.
   --openssl-bin                     Specifies a custom openssl bin location.
   --openssl-bin                     Specifies a custom openssl bin location.
   --use-wget                        Force to use wget, if you have both curl and wget installed.
   --use-wget                        Force to use wget, if you have both curl and wget installed.
+  --yes-I-know-dns-manual-mode-enough-go-ahead-please  Force to use dns manual mode: $_DNS_MANUAL_WIKI
+  --branch, -b                      Only valid for '--upgrade' command, specifies the branch name to upgrade to.
   "
   "
 }
 }
 
 
@@ -5931,6 +5967,9 @@ _process() {
       --no-color)
       --no-color)
         export ACME_NO_COLOR=1
         export ACME_NO_COLOR=1
         ;;
         ;;
+      --force-color)
+        export ACME_FORCE_COLOR=1
+        ;;
       --ecc)
       --ecc)
         _ecc="isEcc"
         _ecc="isEcc"
         ;;
         ;;
@@ -5969,6 +6008,9 @@ _process() {
           shift
           shift
         fi
         fi
         ;;
         ;;
+      --yes-I-know-dns-manual-mode-enough-go-ahead-please)
+        export FORCE_DNS_MANUAL=1
+        ;;
       --log | --logfile)
       --log | --logfile)
         _log="1"
         _log="1"
         _logfile="$2"
         _logfile="$2"
@@ -6022,6 +6064,10 @@ _process() {
         _use_wget="1"
         _use_wget="1"
         ACME_USE_WGET="1"
         ACME_USE_WGET="1"
         ;;
         ;;
+      --branch | -b)
+        export BRANCH="$2"
+        shift
+        ;;
       *)
       *)
         _err "Unknown parameter : $1"
         _err "Unknown parameter : $1"
         return 1
         return 1

+ 0 - 6
deploy/keychain.sh

@@ -1,11 +1,5 @@
 #!/usr/bin/env sh
 #!/usr/bin/env sh
 
 
-#Here is a sample custom api script.
-#This file name is "myapi.sh"
-#So, here must be a method   myapi_deploy()
-#Which will be called by acme.sh to deploy the cert
-#returns 0 means success, otherwise error.
-
 ########  Public functions #####################
 ########  Public functions #####################
 
 
 #domain keyfile certfile cafile fullchain
 #domain keyfile certfile cafile fullchain

+ 1 - 0
deploy/vault_cli.sh

@@ -51,6 +51,7 @@ vault_cli_deploy() {
 
 
   $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1
   $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1
   $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1
   $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1
+  $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1
   $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1
   $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1
 
 
 }
 }

+ 97 - 4
dnsapi/README.md

@@ -1,5 +1,9 @@
 # How to use DNS API
 # How to use DNS API
 
 
+If your dns provider doesn't provide api access, you can use our dns alias mode: 
+
+https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode
+
 ## 1. Use CloudFlare domain API to automatically issue cert
 ## 1. Use CloudFlare domain API to automatically issue cert
 
 
 First you need to login to your CloudFlare account to get your API key.
 First you need to login to your CloudFlare account to get your API key.
@@ -325,6 +329,8 @@ The `CY_Username`, `CY_Password` and `CY_OTP_Secret` will be saved in `~/.acme.s
 
 
 ## 17. Use Domain-Offensive/Resellerinterface/Domainrobot API
 ## 17. Use Domain-Offensive/Resellerinterface/Domainrobot API
 
 
+ATTENTION: You need to be a registered Reseller to be able to use the ResellerInterface. As a normal user you can not use this method.
+
 You will need your login credentials (Partner ID+Password) to the Resellerinterface, and export them before you run `acme.sh`:
 You will need your login credentials (Partner ID+Password) to the Resellerinterface, and export them before you run `acme.sh`:
 ```
 ```
 export DO_PID="KD-1234567"
 export DO_PID="KD-1234567"
@@ -525,8 +531,9 @@ For issues, please report to https://github.com/raidenii/acme.sh/issues.
 
 
 ## 28. Use Name.com API
 ## 28. Use Name.com API
 
 
-You'll need to fill out the form at https://www.name.com/reseller/apply to apply
-for API username and token.
+Create your API token here: https://www.name.com/account/settings/api
+
+Note: `Namecom_Username` should be your Name.com username and not the token name.  If you accidentally run the script with the token name as the username see `~/.acme.sh/account.conf` to fix the issue
 
 
 ```
 ```
 export Namecom_Username="testuser"
 export Namecom_Username="testuser"
@@ -638,6 +645,14 @@ acme.sh --issue --dns dns_inwx -d example.com -d www.example.com
 
 
 The `INWX_User` and `INWX_Password` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 The `INWX_User` and `INWX_Password` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
 
+If your account is secured by mobile tan you have also defined the shared secret.
+
+```
+export INWX_Shared_Secret="shared secret"
+```
+
+You may need to re-enable the mobile tan to gain the shared secret.
+
 ## 34. User Servercow API v1
 ## 34. User Servercow API v1
 
 
 Create a new user from the servercow control center. Don't forget to activate **DNS API** for this user.
 Create a new user from the servercow control center. Don't forget to activate **DNS API** for this user.
@@ -750,7 +765,7 @@ DNS API keys may be created at https://panel.dreamhost.com/?tree=home.api.
 Ensure the created key has add and remove privelages.
 Ensure the created key has add and remove privelages.
 
 
 ```
 ```
-export DH_API_Key="<api key>"
+export DH_API_KEY="<api key>"
 acme.sh --issue --dns dns_dreamhost -d example.com -d www.example.com
 acme.sh --issue --dns dns_dreamhost -d example.com -d www.example.com
 ```
 ```
 
 
@@ -784,7 +799,85 @@ acme.sh --issue --dns dns_da -d example.com -d www.example.com
 
 
 The `DA_Api` and `DA_Api_Insecure` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 The `DA_Api` and `DA_Api_Insecure` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
 
-## 42. Use All-inkl Kasserver API
+## 42. Use KingHost DNS API
+
+API access must be enabled at https://painel.kinghost.com.br/painel.api.php
+
+```
+export KINGHOST_Username="yourusername"
+export KINGHOST_Password="yourpassword"
+acme.sh --issue --dns dns_kinghost -d example.com -d *.example.com
+```
+
+The `KINGHOST_username` and `KINGHOST_Password` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+## 43. Use Zilore DNS API
+
+First, get your API key at https://my.zilore.com/account/api
+
+```
+export Zilore_Key="5dcad3a2-36cb-50e8-cb92-000002f9"
+```
+
+Ok, let's issue a cert now:
+```
+acme.sh --issue --dns dns_zilore -d example.com -d *.example.com
+```
+
+The `Zilore_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+## 44. Use Loopia.se API
+User must provide login credentials to the Loopia API.
+The user needs the following permissions:
+
+- addSubdomain
+- updateZoneRecord
+- getDomains
+- removeSubdomain
+
+Set the login credentials:
+```
+export LOOPIA_User="user@loopiaapi"
+export LOOPIA_Password="password"
+```
+
+And to issue a cert:
+```
+acme.sh --issue --dns dns_loopia -d example.com -d *.example.com
+```
+
+The username and password will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+## 45. Use ACME DNS API
+
+ACME DNS is a limited DNS server with RESTful HTTP API to handle ACME DNS challenges easily and securely. 
+https://github.com/joohoi/acme-dns
+
+```
+export ACMEDNS_UPDATE_URL="https://auth.acme-dns.io/update"
+export ACMEDNS_USERNAME="<username>"
+export ACMEDNS_PASSWORD="<password>"
+export ACMEDNS_SUBDOMAIN="<subdomain>"
+
+acme.sh --issue --dns dns_acmedns -d example.com -d www.example.com
+```
+
+The credentials will be saved in `~/.acme.sh/account.conf` and will
+be reused when needed.
+## 46. Use TELE3 API
+
+First you need to login to your TELE3 account to set your API-KEY.
+https://www.tele3.cz/system-acme-api.html
+
+```
+export TELE3_Key="MS2I4uPPaI..."
+export TELE3_Secret="kjhOIHGJKHg"
+
+acme.sh --issue --dns dns_tele3 -d example.com -d *.example.com
+```
+
+The TELE3_Key and TELE3_Secret will be saved in ~/.acme.sh/account.conf and will be reused when needed.
+
+## 47. Use All-inkl Kasserver API
 
 
 All-inkl Kasserver API (https://kasapi.kasserver.com/dokumentation) needs you to set your Login credentials like so:
 All-inkl Kasserver API (https://kasapi.kasserver.com/dokumentation) needs you to set your Login credentials like so:
 
 

+ 55 - 0
dnsapi/dns_acmedns.sh

@@ -0,0 +1,55 @@
+#!/usr/bin/env sh
+#
+#Author: Wolfgang Ebner
+#Report Bugs here: https://github.com/webner/acme.sh
+#
+########  Public functions #####################
+
+#Usage: dns_acmedns_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_acmedns_add() {
+  fulldomain=$1
+  txtvalue=$2
+  _info "Using acme-dns"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  ACMEDNS_UPDATE_URL="${ACMEDNS_UPDATE_URL:-$(_readaccountconf_mutable ACMEDNS_UPDATE_URL)}"
+  ACMEDNS_USERNAME="${ACMEDNS_USERNAME:-$(_readaccountconf_mutable ACMEDNS_USERNAME)}"
+  ACMEDNS_PASSWORD="${ACMEDNS_PASSWORD:-$(_readaccountconf_mutable ACMEDNS_PASSWORD)}"
+  ACMEDNS_SUBDOMAIN="${ACMEDNS_SUBDOMAIN:-$(_readaccountconf_mutable ACMEDNS_SUBDOMAIN)}"
+
+  if [ "$ACMEDNS_UPDATE_URL" = "" ]; then
+    ACMEDNS_UPDATE_URL="https://auth.acme-dns.io/update"
+  fi
+
+  _saveaccountconf_mutable ACMEDNS_UPDATE_URL "$ACMEDNS_UPDATE_URL"
+  _saveaccountconf_mutable ACMEDNS_USERNAME "$ACMEDNS_USERNAME"
+  _saveaccountconf_mutable ACMEDNS_PASSWORD "$ACMEDNS_PASSWORD"
+  _saveaccountconf_mutable ACMEDNS_SUBDOMAIN "$ACMEDNS_SUBDOMAIN"
+
+  export _H1="X-Api-User: $ACMEDNS_USERNAME"
+  export _H2="X-Api-Key: $ACMEDNS_PASSWORD"
+  data="{\"subdomain\":\"$ACMEDNS_SUBDOMAIN\", \"txt\": \"$txtvalue\"}"
+
+  _debug data "$data"
+  response="$(_post "$data" "$ACMEDNS_UPDATE_URL" "" "POST")"
+  _debug response "$response"
+
+  if ! echo "$response" | grep "\"$txtvalue\"" >/dev/null; then
+    _err "invalid response of acme-dns"
+    return 1
+  fi
+
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_acmedns_rm() {
+  fulldomain=$1
+  txtvalue=$2
+  _info "Using acme-dns"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+}
+
+####################  Private functions below ##################################

+ 12 - 7
dnsapi/dns_azure.sh

@@ -76,10 +76,10 @@ dns_azure_add() {
   values="{\"value\":[\"$txtvalue\"]}"
   values="{\"value\":[\"$txtvalue\"]}"
   timestamp="$(_time)"
   timestamp="$(_time)"
   if [ "$_code" = "200" ]; then
   if [ "$_code" = "200" ]; then
-    vlist="$(echo "$response" | _egrep_o "\"value\"\s*:\s*\[\s*\"[^\"]*\"\s*]" | cut -d : -f 2 | tr -d "[]\"")"
+    vlist="$(echo "$response" | _egrep_o "\"value\"\\s*:\\s*\\[\\s*\"[^\"]*\"\\s*]" | cut -d : -f 2 | tr -d "[]\"")"
     _debug "existing TXT found"
     _debug "existing TXT found"
     _debug "$vlist"
     _debug "$vlist"
-    existingts="$(echo "$response" | _egrep_o "\"acmetscheck\"\s*:\s*\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d "\"")"
+    existingts="$(echo "$response" | _egrep_o "\"acmetscheck\"\\s*:\\s*\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d "\"")"
     if [ -z "$existingts" ]; then
     if [ -z "$existingts" ]; then
       # the record was not created by acme.sh. Copy the exisiting entires
       # the record was not created by acme.sh. Copy the exisiting entires
       existingts=$timestamp
       existingts=$timestamp
@@ -172,7 +172,7 @@ dns_azure_rm() {
   _azure_rest GET "$acmeRecordURI" "" "$accesstoken"
   _azure_rest GET "$acmeRecordURI" "" "$accesstoken"
   timestamp="$(_time)"
   timestamp="$(_time)"
   if [ "$_code" = "200" ]; then
   if [ "$_code" = "200" ]; then
-    vlist="$(echo "$response" | _egrep_o "\"value\"\s*:\s*\[\s*\"[^\"]*\"\s*]" | cut -d : -f 2 | tr -d "[]\"" | grep -v "$txtvalue")"
+    vlist="$(echo "$response" | _egrep_o "\"value\"\\s*:\\s*\\[\\s*\"[^\"]*\"\\s*]" | cut -d : -f 2 | tr -d "[]\"" | grep -v "$txtvalue")"
     values=""
     values=""
     comma=""
     comma=""
     for v in $vlist; do
     for v in $vlist; do
@@ -230,7 +230,7 @@ _azure_rest() {
     fi
     fi
     _ret="$?"
     _ret="$?"
     _secure_debug2 "response $response"
     _secure_debug2 "response $response"
-    _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
+    _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
     _debug "http response code $_code"
     _debug "http response code $_code"
     if [ "$_code" = "401" ]; then
     if [ "$_code" = "401" ]; then
       # we have an invalid access token set to expired
       # we have an invalid access token set to expired
@@ -308,7 +308,7 @@ _get_root() {
   domain=$1
   domain=$1
   subscriptionId=$2
   subscriptionId=$2
   accesstoken=$3
   accesstoken=$3
-  i=2
+  i=1
   p=1
   p=1
 
 
   ## Ref: https://docs.microsoft.com/en-us/rest/api/dns/zones/list
   ## Ref: https://docs.microsoft.com/en-us/rest/api/dns/zones/list
@@ -328,9 +328,14 @@ _get_root() {
     fi
     fi
 
 
     if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
     if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
-      _domain_id=$(echo "$response" | _egrep_o "\{\"id\":\"[^\"]*$h\"" | head -n 1 | cut -d : -f 2 | tr -d \")
+      _domain_id=$(echo "$response" | _egrep_o "\\{\"id\":\"[^\"]*$h\"" | head -n 1 | cut -d : -f 2 | tr -d \")
       if [ "$_domain_id" ]; then
       if [ "$_domain_id" ]; then
-        _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+        if [ "$i" = 1 ]; then
+          #create the record at the domain apex (@) if only the domain name was provided as --domain-alias
+          _sub_domain="@"
+        else
+          _sub_domain=$(echo "$domain" | cut -d . -f 1-$p)
+        fi
         _domain=$h
         _domain=$h
         return 0
         return 0
       fi
       fi

+ 18 - 35
dnsapi/dns_dnsimple.sh

@@ -39,34 +39,17 @@ dns_dnsimple_add() {
 
 
   _get_records "$_account_id" "$_domain" "$_sub_domain"
   _get_records "$_account_id" "$_domain" "$_sub_domain"
 
 
-  if [ "$_records_count" = "0" ]; then
-    _info "Adding record"
-    if _dnsimple_rest POST "$_account_id/zones/$_domain/records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
-      if printf -- "%s" "$response" | grep "\"name\":\"$_sub_domain\"" >/dev/null; then
-        _info "Added"
-        return 0
-      else
-        _err "Unexpected response while adding text record."
-        return 1
-      fi
-    fi
-    _err "Add txt record error."
-  else
-    _info "Updating record"
-    _extract_record_id "$_records" "$_sub_domain"
-
-    if _dnsimple_rest \
-      PATCH \
-      "$_account_id/zones/$_domain/records/$_record_id" \
-      "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
-
-      _info "Updated!"
+  _info "Adding record"
+  if _dnsimple_rest POST "$_account_id/zones/$_domain/records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
+    if printf -- "%s" "$response" | grep "\"name\":\"$_sub_domain\"" >/dev/null; then
+      _info "Added"
       return 0
       return 0
+    else
+      _err "Unexpected response while adding text record."
+      return 1
     fi
     fi
-
-    _err "Update error"
-    return 1
   fi
   fi
+  _err "Add txt record error."
 }
 }
 
 
 # fulldomain
 # fulldomain
@@ -84,19 +67,19 @@ dns_dnsimple_rm() {
   fi
   fi
 
 
   _get_records "$_account_id" "$_domain" "$_sub_domain"
   _get_records "$_account_id" "$_domain" "$_sub_domain"
-  _extract_record_id "$_records" "$_sub_domain"
 
 
+  _extract_record_id "$_records" "$_sub_domain"
   if [ "$_record_id" ]; then
   if [ "$_record_id" ]; then
-
-    if _dnsimple_rest DELETE "$_account_id/zones/$_domain/records/$_record_id"; then
-      _info "removed record" "$_record_id"
-      return 0
-    fi
+    echo "$_record_id" | while read -r item; do
+      if _dnsimple_rest DELETE "$_account_id/zones/$_domain/records/$item"; then
+        _info "removed record" "$item"
+        return 0
+      else
+        _err "failed to remove record" "$item"
+        return 1
+      fi
+    done
   fi
   fi
-
-  _err "failed to remove record" "$_record_id"
-  return 1
-
 }
 }
 
 
 ####################  Private functions bellow ##################################
 ####################  Private functions bellow ##################################

+ 1 - 1
dnsapi/dns_freedns.sh

@@ -279,7 +279,7 @@ _freedns_add_txt_record() {
   domain_id="$2"
   domain_id="$2"
   subdomain="$3"
   subdomain="$3"
   value="$(printf '%s' "$4" | _url_encode)"
   value="$(printf '%s' "$4" | _url_encode)"
-  url="http://freedns.afraid.org/subdomain/save.php?step=2"
+  url="https://freedns.afraid.org/subdomain/save.php?step=2"
 
 
   htmlpage="$(_post "type=TXT&domain_id=$domain_id&subdomain=$subdomain&address=%22$value%22&send=Save%21" "$url")"
   htmlpage="$(_post "type=TXT&domain_id=$domain_id&subdomain=$subdomain&address=%22$value%22&send=Save%21" "$url")"
 
 

+ 5 - 11
dnsapi/dns_gd.sh

@@ -59,19 +59,13 @@ dns_gd_add() {
 
 
   _info "Adding record"
   _info "Adding record"
   if _gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]"; then
   if _gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]"; then
-    if [ "$response" = "{}" ]; then
-      _info "Added, sleeping 10 seconds"
-      _sleep 10
-      #todo: check if the record takes effect
-      return 0
-    else
-      _err "Add txt record error."
-      _err "$response"
-      return 1
-    fi
+    _info "Added, sleeping 10 seconds"
+    _sleep 10
+    #todo: check if the record takes effect
+    return 0
   fi
   fi
   _err "Add txt record error."
   _err "Add txt record error."
-
+  return 1
 }
 }
 
 
 #fulldomain
 #fulldomain

+ 14 - 5
dnsapi/dns_he.sh

@@ -33,8 +33,9 @@ dns_he_add() {
   # Fills in the $_zone_id
   # Fills in the $_zone_id
   _find_zone "$_full_domain" || return 1
   _find_zone "$_full_domain" || return 1
   _debug "Zone id \"$_zone_id\" will be used."
   _debug "Zone id \"$_zone_id\" will be used."
-
-  body="email=${HE_Username}&pass=${HE_Password}"
+  username_encoded="$(printf "%s" "${HE_Username}" | _url_encode)"
+  password_encoded="$(printf "%s" "${HE_Password}" | _url_encode)"
+  body="email=${username_encoded}&pass=${password_encoded}"
   body="$body&account="
   body="$body&account="
   body="$body&menu=edit_zone"
   body="$body&menu=edit_zone"
   body="$body&Type=TXT"
   body="$body&Type=TXT"
@@ -71,7 +72,9 @@ dns_he_rm() {
   _debug "Zone id \"$_zone_id\" will be used."
   _debug "Zone id \"$_zone_id\" will be used."
 
 
   # Find the record id to clean
   # Find the record id to clean
-  body="email=${HE_Username}&pass=${HE_Password}"
+  username_encoded="$(printf "%s" "${HE_Username}" | _url_encode)"
+  password_encoded="$(printf "%s" "${HE_Password}" | _url_encode)"
+  body="email=${username_encoded}&pass=${password_encoded}"
   body="$body&hosted_dns_zoneid=$_zone_id"
   body="$body&hosted_dns_zoneid=$_zone_id"
   body="$body&menu=edit_zone"
   body="$body&menu=edit_zone"
   body="$body&hosted_dns_editzone="
   body="$body&hosted_dns_editzone="
@@ -112,9 +115,15 @@ dns_he_rm() {
 
 
 _find_zone() {
 _find_zone() {
   _domain="$1"
   _domain="$1"
-  body="email=${HE_Username}&pass=${HE_Password}"
+  username_encoded="$(printf "%s" "${HE_Username}" | _url_encode)"
+  password_encoded="$(printf "%s" "${HE_Password}" | _url_encode)"
+  body="email=${username_encoded}&pass=${password_encoded}"
   response="$(_post "$body" "https://dns.he.net/")"
   response="$(_post "$body" "https://dns.he.net/")"
   _debug2 response "$response"
   _debug2 response "$response"
+  if _contains "$response" '>Incorrect<'; then
+    _err "Unable to login to dns.he.net please check username and password"
+    return 1
+  fi
   _table="$(echo "$response" | tr -d "#" | sed "s/<table/#<table/g" | tr -d "\n" | tr "#" "\n" | grep 'id="domains_table"')"
   _table="$(echo "$response" | tr -d "#" | sed "s/<table/#<table/g" | tr -d "\n" | tr "#" "\n" | grep 'id="domains_table"')"
   _debug2 _table "$_table"
   _debug2 _table "$_table"
   _matches="$(echo "$_table" | sed "s/<tr/#<tr/g" | tr "#" "\n" | grep 'alt="edit"' | tr -d " " | sed "s/<td/#<td/g" | tr "#" "\n" | grep 'hosted_dns_zoneid')"
   _matches="$(echo "$_table" | sed "s/<tr/#<tr/g" | tr "#" "\n" | grep 'alt="edit"' | tr -d " " | sed "s/<td/#<td/g" | tr "#" "\n" | grep 'hosted_dns_zoneid')"
@@ -143,7 +152,7 @@ _find_zone() {
 
 
     _debug "Looking for zone \"${_attempted_zone}\""
     _debug "Looking for zone \"${_attempted_zone}\""
 
 
-    line_num="$(echo "$_zone_names" | grep -n "$_attempted_zone" | cut -d : -f 1)"
+    line_num="$(echo "$_zone_names" | grep -n "^$_attempted_zone" | cut -d : -f 1)"
 
 
     if [ "$line_num" ]; then
     if [ "$line_num" ]; then
       _zone_id=$(echo "$_zone_ids" | sed -n "${line_num}p")
       _zone_id=$(echo "$_zone_ids" | sed -n "${line_num}p")

+ 47 - 3
dnsapi/dns_inwx.sh

@@ -4,6 +4,10 @@
 #INWX_User="username"
 #INWX_User="username"
 #
 #
 #INWX_Password="password"
 #INWX_Password="password"
+#
+# Dependencies:
+# -------------
+# - oathtool (When using 2 Factor Authentication)
 
 
 INWX_Api="https://api.domrobot.com/xmlrpc/"
 INWX_Api="https://api.domrobot.com/xmlrpc/"
 
 
@@ -16,6 +20,7 @@ dns_inwx_add() {
 
 
   INWX_User="${INWX_User:-$(_readaccountconf_mutable INWX_User)}"
   INWX_User="${INWX_User:-$(_readaccountconf_mutable INWX_User)}"
   INWX_Password="${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}"
   INWX_Password="${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}"
+  INWX_Shared_Secret="${INWX_Shared_Secret:-$(_readaccountconf_mutable INWX_Shared_Secret)}"
   if [ -z "$INWX_User" ] || [ -z "$INWX_Password" ]; then
   if [ -z "$INWX_User" ] || [ -z "$INWX_Password" ]; then
     INWX_User=""
     INWX_User=""
     INWX_Password=""
     INWX_Password=""
@@ -27,6 +32,7 @@ dns_inwx_add() {
   #save the api key and email to the account conf file.
   #save the api key and email to the account conf file.
   _saveaccountconf_mutable INWX_User "$INWX_User"
   _saveaccountconf_mutable INWX_User "$INWX_User"
   _saveaccountconf_mutable INWX_Password "$INWX_Password"
   _saveaccountconf_mutable INWX_Password "$INWX_Password"
+  _saveaccountconf_mutable INWX_Shared_Secret "$INWX_Shared_Secret"
 
 
   _debug "First detect the root zone"
   _debug "First detect the root zone"
   if ! _get_root "$fulldomain"; then
   if ! _get_root "$fulldomain"; then
@@ -148,8 +154,46 @@ _inwx_login() {
   </methodCall>' $INWX_User $INWX_Password)
   </methodCall>' $INWX_User $INWX_Password)
 
 
   response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
   response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
+  _H1=$(printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')")
+  export _H1
 
 
-  printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')"
+  #https://github.com/inwx/php-client/blob/master/INWX/Domrobot.php#L71
+  if _contains "$response" "tfa"; then
+    if [ -z "$INWX_Shared_Secret" ]; then
+      _err "Mobile TAN detected."
+      _err "Please define a shared secret."
+      return 1
+    fi
+
+    if ! _exists oathtool; then
+      _err "Please install oathtool to use 2 Factor Authentication."
+      _err ""
+      return 1
+    fi
+
+    tan="$(oathtool --base32 --totp "${INWX_Shared_Secret}" 2>/dev/null)"
+
+    xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+    <methodCall>
+    <methodName>account.unlock</methodName>
+    <params>
+     <param>
+      <value>
+       <struct>
+        <member>
+         <name>tan</name>
+         <value>
+          <string>%s</string>
+         </value>
+        </member>
+       </struct>
+      </value>
+     </param>
+    </params>
+    </methodCall>' "$tan")
+
+    response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
+  fi
 
 
 }
 }
 
 
@@ -161,8 +205,8 @@ _get_root() {
   i=2
   i=2
   p=1
   p=1
 
 
-  _H1=$(_inwx_login)
-  export _H1
+  _inwx_login
+
   xml_content='<?xml version="1.0" encoding="UTF-8"?>
   xml_content='<?xml version="1.0" encoding="UTF-8"?>
   <methodCall>
   <methodCall>
   <methodName>nameserver.list</methodName>
   <methodName>nameserver.list</methodName>

+ 107 - 0
dnsapi/dns_kinghost.sh

@@ -0,0 +1,107 @@
+#!/usr/bin/env sh
+
+############################################################
+# KingHost API support                                     #
+# http://api.kinghost.net/doc/                             #
+#                                                          #
+# Author: Felipe Keller Braz <[email protected]>  #
+# Report Bugs here: https://github.com/kinghost/acme.sh    #
+#                                                          #
+# Values to export:                                        #
+# export KINGHOST_Username="[email protected]"            #
+# export KINGHOST_Password="xxxxxxxxxx"                    #
+############################################################
+
+KING_Api="https://api.kinghost.net/acme"
+
+# Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Used to add txt record
+dns_kinghost_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  KINGHOST_Username="${KINGHOST_Username:-$(_readaccountconf_mutable KINGHOST_Username)}"
+  KINGHOST_Password="${KINGHOST_Password:-$(_readaccountconf_mutable KINGHOST_Password)}"
+  if [ -z "$KINGHOST_Username" ] || [ -z "$KINGHOST_Password" ]; then
+    KINGHOST_Username=""
+    KINGHOST_Password=""
+    _err "You don't specify KingHost api password and email yet."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
+  #save the credentials to the account conf file.
+  _saveaccountconf_mutable KINGHOST_Username "$KINGHOST_Username"
+  _saveaccountconf_mutable KINGHOST_Password "$KINGHOST_Password"
+
+  _debug "Getting txt records"
+  _kinghost_rest GET "dns" "name=$fulldomain&content=$txtvalue"
+
+  #This API call returns "status":"ok" if dns record does not exists
+  #We are creating a new txt record here, so we expect the "ok" status
+  if ! echo "$response" | grep '"status":"ok"' >/dev/null; then
+    _err "Error"
+    _err "$response"
+    return 1
+  fi
+
+  _kinghost_rest POST "dns" "name=$fulldomain&content=$txtvalue"
+  if ! echo "$response" | grep '"status":"ok"' >/dev/null; then
+    _err "Error"
+    _err "$response"
+    return 1
+  fi
+
+  return 0
+}
+
+# Usage: fulldomain txtvalue
+# Used to remove the txt record after validation
+dns_kinghost_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  KINGHOST_Password="${KINGHOST_Password:-$(_readaccountconf_mutable KINGHOST_Password)}"
+  KINGHOST_Username="${KINGHOST_Username:-$(_readaccountconf_mutable KINGHOST_Username)}"
+  if [ -z "$KINGHOST_Password" ] || [ -z "$KINGHOST_Username" ]; then
+    KINGHOST_Password=""
+    KINGHOST_Username=""
+    _err "You don't specify KingHost api key and email yet."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
+  _kinghost_rest DELETE "dns" "name=$fulldomain&content=$txtvalue"
+  if ! echo "$response" | grep '"status":"ok"' >/dev/null; then
+    _err "Error"
+    _err "$response"
+    return 1
+  fi
+
+  return 0
+}
+
+####################  Private functions below ##################################
+_kinghost_rest() {
+  method=$1
+  uri="$2"
+  data="$3"
+  _debug "$uri"
+
+  export _H1="X-Auth-Email: $KINGHOST_Username"
+  export _H2="X-Auth-Key: $KINGHOST_Password"
+
+  if [ "$method" != "GET" ]; then
+    _debug data "$data"
+    response="$(_post "$data" "$KING_Api/$uri.json" "" "$method")"
+  else
+    response="$(_get "$KING_Api/$uri.json?$data")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $uri"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}

+ 227 - 0
dnsapi/dns_loopia.sh

@@ -0,0 +1,227 @@
+#!/usr/bin/env sh
+
+#
+#LOOPIA_User="username"
+#
+#LOOPIA_Password="password"
+
+LOOPIA_Api="https://api.loopia.se/RPCSERV"
+
+########  Public functions #####################
+
+#Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_loopia_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  LOOPIA_User="${LOOPIA_User:-$(_readaccountconf_mutable LOOPIA_User)}"
+  LOOPIA_Password="${LOOPIA_Password:-$(_readaccountconf_mutable LOOPIA_Password)}"
+  if [ -z "$LOOPIA_User" ] || [ -z "$LOOPIA_Password" ]; then
+    LOOPIA_User=""
+    LOOPIA_Password=""
+    _err "You don't specify loopia user and password 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 LOOPIA_User "$LOOPIA_User"
+  _saveaccountconf_mutable LOOPIA_Password "$LOOPIA_Password"
+
+  _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"
+
+  _loopia_add_record "$_domain" "$_sub_domain"
+  _loopia_update_record "$_domain" "$_sub_domain" "$txtvalue"
+
+}
+
+dns_loopia_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  LOOPIA_User="${LOOPIA_User:-$(_readaccountconf_mutable LOOPIA_User)}"
+  LOOPIA_Password="${LOOPIA_Password:-$(_readaccountconf_mutable LOOPIA_Password)}"
+  if [ -z "$LOOPIA_User" ] || [ -z "$LOOPIA_Password" ]; then
+    LOOPIA_User=""
+    LOOPIA_Password=""
+    _err "You don't specify LOOPIA user and password 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 LOOPIA_User "$LOOPIA_User"
+  _saveaccountconf_mutable LOOPIA_Password "$LOOPIA_Password"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+    <methodName>removeSubdomain</methodName>
+    <params>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+    </params>
+  </methodCall>' $LOOPIA_User $LOOPIA_Password "$_domain" "$_sub_domain")
+
+  response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
+
+  if ! _contains "$response" "OK"; then
+    _err "Error could not get txt records"
+    return 1
+  fi
+}
+
+####################  Private functions below ##################################
+
+_get_root() {
+  domain=$1
+  _debug "get root"
+
+  domain=$1
+  i=2
+  p=1
+
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+  <methodName>getDomains</methodName>
+  <params>
+   <param>
+    <value><string>%s</string></value>
+   </param>
+   <param>
+    <value><string>%s</string></value>
+   </param>
+  </params>
+  </methodCall>' $LOOPIA_User $LOOPIA_Password)
+
+  response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
+  while true; do
+    h=$(echo "$domain" | cut -d . -f $i-100)
+    if [ -z "$h" ]; then
+      #not valid
+      return 1
+    fi
+
+    if _contains "$response" "$h"; then
+      _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+      _domain="$h"
+      return 0
+    fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+  return 1
+
+}
+
+_loopia_update_record() {
+  domain=$1
+  sub_domain=$2
+  txtval=$3
+
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+    <methodName>updateZoneRecord</methodName>
+    <params>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <struct>
+          <member>
+            <name>type</name>
+            <value><string>TXT</string></value>
+          </member>
+          <member>
+            <name>priority</name>
+            <value><int>0</int></value>
+          </member>
+          <member>
+            <name>ttl</name>
+            <value><int>60</int></value>
+          </member>
+          <member>
+            <name>rdata</name>
+            <value><string>%s</string></value>
+          </member>
+          <member>
+            <name>record_id</name>
+            <value><int>0</int></value>
+          </member>
+        </struct>
+      </param>
+    </params>
+  </methodCall>' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain" "$txtval")
+
+  response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
+
+  if ! _contains "$response" "OK"; then
+    _err "Error"
+    return 1
+  fi
+  return 0
+}
+
+_loopia_add_record() {
+  domain=$1
+  sub_domain=$2
+
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+    <methodName>addSubdomain</methodName>
+    <params>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+    </params>
+  </methodCall>' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain")
+
+  response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
+
+  if ! _contains "$response" "OK"; then
+    _err "Error"
+    return 1
+  fi
+  return 0
+}

+ 1 - 1
dnsapi/dns_namecom.sh

@@ -123,7 +123,7 @@ _namecom_login() {
   # Auth string
   # Auth string
   # Name.com API v4 uses http basic auth to authenticate
   # Name.com API v4 uses http basic auth to authenticate
   # need to convert the token for http auth
   # need to convert the token for http auth
-  _namecom_auth=$(printf "%s:%s" "$Namecom_Username" "$Namecom_Token" | base64)
+  _namecom_auth=$(printf "%s:%s" "$Namecom_Username" "$Namecom_Token" | _base64)
 
 
   if _namecom_rest GET "hello"; then
   if _namecom_rest GET "hello"; then
     retcode=$(printf "%s\n" "$response" | _egrep_o "\"username\"\:\"$Namecom_Username\"")
     retcode=$(printf "%s\n" "$response" | _egrep_o "\"username\"\:\"$Namecom_Username\"")

+ 5 - 2
dnsapi/dns_nsupdate.sh

@@ -8,12 +8,14 @@ dns_nsupdate_add() {
   txtvalue=$2
   txtvalue=$2
   _checkKeyFile || return 1
   _checkKeyFile || return 1
   [ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost"
   [ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost"
+  [ -n "${NSUPDATE_SERVER_PORT}" ] || NSUPDATE_SERVER_PORT=53
   # save the dns server and key to the account conf file.
   # save the dns server and key to the account conf file.
   _saveaccountconf NSUPDATE_SERVER "${NSUPDATE_SERVER}"
   _saveaccountconf NSUPDATE_SERVER "${NSUPDATE_SERVER}"
+  _saveaccountconf NSUPDATE_SERVER_PORT "${NSUPDATE_SERVER_PORT}"
   _saveaccountconf NSUPDATE_KEY "${NSUPDATE_KEY}"
   _saveaccountconf NSUPDATE_KEY "${NSUPDATE_KEY}"
   _info "adding ${fulldomain}. 60 in txt \"${txtvalue}\""
   _info "adding ${fulldomain}. 60 in txt \"${txtvalue}\""
   nsupdate -k "${NSUPDATE_KEY}" <<EOF
   nsupdate -k "${NSUPDATE_KEY}" <<EOF
-server ${NSUPDATE_SERVER}
+server ${NSUPDATE_SERVER}  ${NSUPDATE_SERVER_PORT} 
 update add ${fulldomain}. 60 in txt "${txtvalue}"
 update add ${fulldomain}. 60 in txt "${txtvalue}"
 send
 send
 EOF
 EOF
@@ -30,9 +32,10 @@ dns_nsupdate_rm() {
   fulldomain=$1
   fulldomain=$1
   _checkKeyFile || return 1
   _checkKeyFile || return 1
   [ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost"
   [ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost"
+  [ -n "${NSUPDATE_SERVER_PORT}" ] || NSUPDATE_SERVER_PORT=53
   _info "removing ${fulldomain}. txt"
   _info "removing ${fulldomain}. txt"
   nsupdate -k "${NSUPDATE_KEY}" <<EOF
   nsupdate -k "${NSUPDATE_KEY}" <<EOF
-server ${NSUPDATE_SERVER}
+server ${NSUPDATE_SERVER}  ${NSUPDATE_SERVER_PORT} 
 update delete ${fulldomain}. txt
 update delete ${fulldomain}. txt
 send
 send
 EOF
 EOF

+ 62 - 14
dnsapi/dns_pdns.sh

@@ -69,15 +69,21 @@ dns_pdns_add() {
 #fulldomain
 #fulldomain
 dns_pdns_rm() {
 dns_pdns_rm() {
   fulldomain=$1
   fulldomain=$1
+  txtvalue=$2
+
+  if [ -z "$PDNS_Ttl" ]; then
+    PDNS_Ttl="$DEFAULT_PDNS_TTL"
+  fi
 
 
   _debug "Detect root zone"
   _debug "Detect root zone"
   if ! _get_root "$fulldomain"; then
   if ! _get_root "$fulldomain"; then
     _err "invalid domain"
     _err "invalid domain"
     return 1
     return 1
   fi
   fi
+
   _debug _domain "$_domain"
   _debug _domain "$_domain"
 
 
-  if ! rm_record "$_domain" "$fulldomain"; then
+  if ! rm_record "$_domain" "$fulldomain" "$txtvalue"; then
     return 1
     return 1
   fi
   fi
 
 
@@ -88,9 +94,16 @@ set_record() {
   _info "Adding record"
   _info "Adding record"
   root=$1
   root=$1
   full=$2
   full=$2
-  txtvalue=$3
+  new_challenge=$3
 
 
-  if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root." "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [{\"name\": \"$full.\", \"type\": \"TXT\", \"content\": \"\\\"$txtvalue\\\"\", \"disabled\": false, \"ttl\": $PDNS_Ttl}]}]}"; then
+  _record_string=""
+  _build_record_string "$new_challenge"
+  _list_existingchallenges
+  for oldchallenge in $_existing_challenges; do
+    _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
     _err "Set txt record error."
     _err "Set txt record error."
     return 1
     return 1
   fi
   fi
@@ -106,14 +119,37 @@ rm_record() {
   _info "Remove record"
   _info "Remove record"
   root=$1
   root=$1
   full=$2
   full=$2
+  txtvalue=$3
 
 
-  if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root." "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}"; then
-    _err "Delete txt record error."
-    return 1
-  fi
+  #Enumerate existing acme challenges
+  _list_existingchallenges
 
 
-  if ! notify_slaves "$root"; then
-    return 1
+  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
+      _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 ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}"; then
+        _err "Set txt record error."
+        return 1
+      fi
+    fi
+    if ! notify_slaves "$root"; then
+      return 1
+    fi
+  else
+    _info "Record not found, nothing to remove"
   fi
   fi
 
 
   return 0
   return 0
@@ -122,7 +158,7 @@ rm_record() {
 notify_slaves() {
 notify_slaves() {
   root=$1
   root=$1
 
 
-  if ! _pdns_rest "PUT" "/api/v1/servers/$PDNS_ServerId/zones/$root./notify"; then
+  if ! _pdns_rest "PUT" "/api/v1/servers/$PDNS_ServerId/zones/$root/notify"; then
     _err "Notify slaves error."
     _err "Notify slaves error."
     return 1
     return 1
   fi
   fi
@@ -144,15 +180,18 @@ _get_root() {
 
 
   while true; do
   while true; do
     h=$(printf "%s" "$domain" | cut -d . -f $i-100)
     h=$(printf "%s" "$domain" | cut -d . -f $i-100)
-    if [ -z "$h" ]; then
-      return 1
-    fi
 
 
     if _contains "$_zones_response" "\"name\": \"$h.\""; then
     if _contains "$_zones_response" "\"name\": \"$h.\""; then
-      _domain="$h"
+      _domain="$h."
+      if [ -z "$h" ]; then
+        _domain="=2E"
+      fi
       return 0
       return 0
     fi
     fi
 
 
+    if [ -z "$h" ]; then
+      return 1
+    fi
     i=$(_math $i + 1)
     i=$(_math $i + 1)
   done
   done
   _debug "$domain not found"
   _debug "$domain not found"
@@ -182,3 +221,12 @@ _pdns_rest() {
 
 
   return 0
   return 0
 }
 }
+
+_build_record_string() {
+  _record_string="${_record_string:+${_record_string}, }{\"content\": \"\\\"${1}\\\"\", \"disabled\": false}"
+}
+
+_list_existingchallenges() {
+  _pdns_rest "GET" "/api/v1/servers/$PDNS_ServerId/zones/$root"
+  _existing_challenges=$(echo "$response" | _normalizeJson | _egrep_o "\"name\":\"${fulldomain}[^]]*}" | _egrep_o 'content\":\"\\"[^\\]*' | sed -n 's/^content":"\\"//p')
+}

+ 69 - 0
dnsapi/dns_tele3.sh

@@ -0,0 +1,69 @@
+#!/usr/bin/env sh
+#
+# tele3.cz DNS API
+#
+# Author: Roman Blizik
+# Report Bugs here: https://github.com/par-pa/acme.sh
+#
+# --
+# export TELE3_Key="MS2I4uPPaI..."
+# export TELE3_Secret="kjhOIHGJKHg"
+# --
+
+TELE3_API="https://www.tele3.cz/acme/"
+
+########  Public functions  #####################
+
+dns_tele3_add() {
+  _info "Using TELE3 DNS"
+  data="\"ope\":\"add\", \"domain\":\"$1\", \"value\":\"$2\""
+  if ! _tele3_call; then
+    _err "Publish zone failed"
+    return 1
+  fi
+
+  _info "Zone published"
+}
+
+dns_tele3_rm() {
+  _info "Using TELE3 DNS"
+  data="\"ope\":\"rm\", \"domain\":\"$1\", \"value\":\"$2\""
+  if ! _tele3_call; then
+    _err "delete TXT record failed"
+    return 1
+  fi
+
+  _info "TXT record successfully deleted"
+}
+
+####################  Private functions below  ##################################
+
+_tele3_init() {
+  TELE3_Key="${TELE3_Key:-$(_readaccountconf_mutable TELE3_Key)}"
+  TELE3_Secret="${TELE3_Secret:-$(_readaccountconf_mutable TELE3_Secret)}"
+  if [ -z "$TELE3_Key" ] || [ -z "$TELE3_Secret" ]; then
+    TELE3_Key=""
+    TELE3_Secret=""
+    _err "You must export variables: TELE3_Key and TELE3_Secret"
+    return 1
+  fi
+
+  #save the config variables to the account conf file.
+  _saveaccountconf_mutable TELE3_Key "$TELE3_Key"
+  _saveaccountconf_mutable TELE3_Secret "$TELE3_Secret"
+}
+
+_tele3_call() {
+  _tele3_init
+  data="{\"key\":\"$TELE3_Key\", \"secret\":\"$TELE3_Secret\", $data}"
+
+  _debug data "$data"
+
+  response="$(_post "$data" "$TELE3_API" "" "POST")"
+  _debug response "$response"
+
+  if [ "$response" != "success" ]; then
+    _err "$response"
+    return 1
+  fi
+}

+ 3 - 3
dnsapi/dns_yandex.sh

@@ -50,9 +50,9 @@ _PDD_get_domain() {
   __last=0
   __last=0
   while [ $__last -eq 0 ]; do
   while [ $__last -eq 0 ]; do
     uri1="https://pddimp.yandex.ru/api2/admin/domain/domains?page=${__page}&on_page=20"
     uri1="https://pddimp.yandex.ru/api2/admin/domain/domains?page=${__page}&on_page=20"
-    res1=$(_get "$uri1" | _normalizeJson)
-    #_debug "$res1"
-    __found=$(echo "$res1" | sed -n -e 's#.* "found": \([^,]*\),.*#\1#p')
+    res1="$(_get "$uri1" | _normalizeJson)"
+    _debug2 "res1" "$res1"
+    __found="$(echo "$res1" | sed -n -e 's#.* "found": \([^,]*\),.*#\1#p')"
     _debug "found: $__found results on page"
     _debug "found: $__found results on page"
     if [ "$__found" -lt 20 ]; then
     if [ "$__found" -lt 20 ]; then
       _debug "last page: $__page"
       _debug "last page: $__page"

+ 139 - 0
dnsapi/dns_zilore.sh

@@ -0,0 +1,139 @@
+#!/usr/bin/env sh
+
+Zilore_API="https://api.zilore.com/dns/v1"
+# Zilore_Key="YOUR-ZILORE-API-KEY"
+
+########  Public functions #####################
+
+dns_zilore_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  _info "Using Zilore"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  Zilore_Key="${Zilore_Key:-$(_readaccountconf_mutable Zilore_Key)}"
+  if [ -z "$Zilore_Key" ]; then
+    Zilore_Key=""
+    _err "Please define Zilore API key"
+    return 1
+  fi
+  _saveaccountconf_mutable Zilore_Key "$Zilore_Key"
+
+  if ! _get_root "$fulldomain"; then
+    _err "Unable to determine root domain"
+    return 1
+  else
+    _debug _domain "$_domain"
+  fi
+
+  if _zilore_rest POST "domains/$_domain/records?record_type=TXT&record_ttl=600&record_name=$fulldomain&record_value=\"$txtvalue\""; then
+    if _contains "$response" '"added"' >/dev/null; then
+      _info "Added TXT record, waiting for validation"
+      return 0
+    else
+      _debug response "$response"
+      _err "Error while adding DNS records"
+      return 1
+    fi
+  fi
+
+  return 1
+}
+
+dns_zilore_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  _info "Using Zilore"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  Zilore_Key="${Zilore_Key:-$(_readaccountconf_mutable Zilore_Key)}"
+  if [ -z "$Zilore_Key" ]; then
+    Zilore_Key=""
+    _err "Please define Zilore API key"
+    return 1
+  fi
+  _saveaccountconf_mutable Zilore_Key "$Zilore_Key"
+
+  if ! _get_root "$fulldomain"; then
+    _err "Unable to determine root domain"
+    return 1
+  else
+    _debug _domain "$_domain"
+  fi
+
+  _debug "Getting TXT records"
+  _zilore_rest GET "domains/${_domain}/records?search_text=$txtvalue&search_record_type=TXT"
+  _debug response "$response"
+
+  if ! _contains "$response" '"ok"' >/dev/null; then
+    _err "Error while getting records list"
+    return 1
+  else
+    _record_id=$(printf "%s\n" "$response" | _egrep_o "\"record_id\":\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1)
+    if [ -z "$_record_id" ]; then
+      _err "Cannot determine _record_id"
+      return 1
+    else
+      _debug _record_id "$_record_id"
+    fi
+    if ! _zilore_rest DELETE "domains/${_domain}/records?record_id=$_record_id"; then
+      _err "Error while deleting chosen record"
+      return 1
+    fi
+    _contains "$response" '"ok"'
+  fi
+}
+
+####################  Private functions below ##################################
+
+_get_root() {
+  domain=$1
+  i=2
+  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 ! _zilore_rest GET "domains?search_text=$h"; then
+      return 1
+    fi
+
+    if _contains "$response" "\"$h\"" >/dev/null; then
+      _domain=$h
+      return 0
+    else
+      _debug "$h not found"
+    fi
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+_zilore_rest() {
+  method=$1
+  param=$2
+  data=$3
+
+  export _H1="X-Auth-Key: $Zilore_Key"
+
+  if [ "$method" != "GET" ]; then
+    response="$(_post "$data" "$Zilore_API/$param" "" "$method")"
+  else
+    response="$(_get "$Zilore_API/$param")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $param"
+    return 1
+  fi
+
+  _debug2 response "$response"
+  return 0
+}