Browse Source

Merge remote-tracking branch 'upstream/master' into ssh-deploy

David Kerr 8 years ago
parent
commit
94e9844179
14 changed files with 1340 additions and 73 deletions
  1. 1 1
      .travis.yml
  2. 5 3
      Dockerfile
  3. 8 1
      README.md
  4. 211 57
      acme.sh
  5. 100 0
      deploy/unifi.sh
  6. 99 0
      dnsapi/README.md
  7. 1 1
      dnsapi/dns_aws.sh
  8. 91 0
      dnsapi/dns_duckdns.sh
  9. 339 0
      dnsapi/dns_dyn.sh
  10. 175 0
      dnsapi/dns_he.sh
  11. 8 8
      dnsapi/dns_infoblox.sh
  12. 2 2
      dnsapi/dns_linode.sh
  13. 193 0
      dnsapi/dns_namecom.sh
  14. 107 0
      dnsapi/dns_yandex.sh

+ 1 - 1
.travis.yml

@@ -40,7 +40,7 @@ script:
   - if [ "$TRAVIS_OS_NAME" = "linux" ]; then ~/shfmt -l -w -i 2 . ; fi
   - if [ "$TRAVIS_OS_NAME" = "linux" ]; then ~/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 **/*.sh && echo "shellcheck OK" ; fi
+  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -e SC2181 **/*.sh && echo "shellcheck OK" ; fi
   - cd ..
   - cd ..
   - git clone https://github.com/Neilpang/acmetest.git && cp -r acme.sh acmetest/ && cd acmetest
   - git clone 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" ./letest.sh ; fi
   - if [ "$TRAVIS_OS_NAME" = "linux" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ./letest.sh ; fi

+ 5 - 3
Dockerfile

@@ -16,7 +16,7 @@ ADD ./ /install_acme.sh/
 RUN cd /install_acme.sh && ([ -f /install_acme.sh/acme.sh ] && /install_acme.sh/acme.sh --install || curl https://get.acme.sh | sh) && rm -rf /install_acme.sh/
 RUN cd /install_acme.sh && ([ -f /install_acme.sh/acme.sh ] && /install_acme.sh/acme.sh --install || curl https://get.acme.sh | sh) && rm -rf /install_acme.sh/
 
 
 
 
-RUN ln -s  /root/.acme.sh/acme.sh  /usr/local/bin/acme.sh
+RUN ln -s  /root/.acme.sh/acme.sh  /usr/local/bin/acme.sh && crontab -l | sed 's#> /dev/null##' | crontab -
 
 
 RUN for verb in help \ 
 RUN for verb in help \ 
   version \
   version \
@@ -44,15 +44,17 @@ RUN for verb in help \
   create-domain-key \
   create-domain-key \
   createCSR \
   createCSR \
   deactivate \
   deactivate \
+  deactivate-account \
   ; do \
   ; do \
     printf -- "%b" "#!/usr/bin/env sh\n/root/.acme.sh/acme.sh --${verb} --config-home /acme.sh \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \
     printf -- "%b" "#!/usr/bin/env sh\n/root/.acme.sh/acme.sh --${verb} --config-home /acme.sh \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \
   ; done
   ; done
 
 
 RUN printf "%b" '#!'"/usr/bin/env sh\n \
 RUN printf "%b" '#!'"/usr/bin/env sh\n \
 if [ \"\$1\" = \"daemon\" ];  then \n \
 if [ \"\$1\" = \"daemon\" ];  then \n \
- crond -f\n \
+ trap \"echo stop && killall crond && exit 0\" SIGTERM SIGINT \n \
+ crond && while true; do sleep 1; done;\n \
 else \n \
 else \n \
- /root/.acme.sh/acme.sh --config-home /acme.sh \"\$@\"\n \
+ exec -- \"\$@\"\n \
 fi" >/entry.sh && chmod +x /entry.sh
 fi" >/entry.sh && chmod +x /entry.sh
 
 
 VOLUME /acme.sh
 VOLUME /acme.sh

+ 8 - 1
README.md

@@ -296,6 +296,9 @@ acme.sh --renew -d example.com
 
 
 Ok, it's finished.
 Ok, it's finished.
 
 
+**Take care, this is dns manual mode, it can not be renewed automatically. you will have to add a new txt record to your domain by your hand when you renew your cert.**
+
+**Please use dns api mode instead.**
 
 
 # 9. Automatic DNS API integration
 # 9. Automatic DNS API integration
 
 
@@ -331,7 +334,11 @@ You don't have to do anything manually!
 1. Dynu API (https://www.dynu.com)
 1. Dynu API (https://www.dynu.com)
 1. DNSimple API
 1. DNSimple API
 1. NS1.com API
 1. NS1.com API
-
+1. DuckDNS.org API
+1. Name.com API
+1. Dyn Managed DNS API
+1. Yandex PDD API (https://pdd.yandex.ru)
+1. Hurricane Electric DNS service (https://dns.he.net)
 
 
 
 
 And: 
 And: 

+ 211 - 57
acme.sh

@@ -366,6 +366,7 @@ _hasfield() {
   return 1 #not contains
   return 1 #not contains
 }
 }
 
 
+# str index [sep]
 _getfield() {
 _getfield() {
   _str="$1"
   _str="$1"
   _findex="$2"
   _findex="$2"
@@ -453,7 +454,7 @@ if [ "$(printf '\x41')" != 'A' ]; then
 fi
 fi
 
 
 _ESCAPE_XARGS=""
 _ESCAPE_XARGS=""
-if [ "$(printf %s '\\x41' | xargs printf)" = 'A' ]; then
+if _exists xargs && [ "$(printf %s '\\x41' | xargs printf)" = 'A' ]; then
   _ESCAPE_XARGS=1
   _ESCAPE_XARGS=1
 fi
 fi
 
 
@@ -925,7 +926,7 @@ _sign() {
 
 
 }
 }
 
 
-#keylength
+#keylength or isEcc flag (empty str => not ecc)
 _isEccKey() {
 _isEccKey() {
   _length="$1"
   _length="$1"
 
 
@@ -1138,7 +1139,12 @@ _readKeyLengthFromCSR() {
     echo "$_outcsr" | tr "\t" " " | _egrep_o "^ *ASN1 OID:.*" | cut -d ':' -f 2 | tr -d ' '
     echo "$_outcsr" | tr "\t" " " | _egrep_o "^ *ASN1 OID:.*" | cut -d ':' -f 2 | tr -d ' '
   else
   else
     _debug "RSA CSR"
     _debug "RSA CSR"
-    echo "$_outcsr" | tr "\t" " " | (_egrep_o "^ *Public.Key:.*" || _egrep_o "RSA Public.Key:.*") | cut -d '(' -f 2 | cut -d ' ' -f 1
+    _rkl="$(echo "$_outcsr" | tr "\t" " " | _egrep_o "^ *Public.Key:.*" | cut -d '(' -f 2 | cut -d ' ' -f 1)"
+    if [ "$_rkl" ]; then
+      echo "$_rkl"
+    else
+      echo "$_outcsr" | tr "\t" " " | _egrep_o "RSA Public.Key:.*" | cut -d '(' -f 2 | cut -d ' ' -f 1
+    fi
   fi
   fi
 }
 }
 
 
@@ -1147,7 +1153,7 @@ _ss() {
 
 
   if _exists "ss"; then
   if _exists "ss"; then
     _debug "Using: ss"
     _debug "Using: ss"
-    ss -ntpl | grep ":$_port "
+    ss -ntpl 2>/dev/null | grep ":$_port "
     return 0
     return 0
   fi
   fi
 
 
@@ -1176,6 +1182,28 @@ _ss() {
   return 1
   return 1
 }
 }
 
 
+#outfile key cert cacert [password [name [caname]]]
+_toPkcs() {
+  _cpfx="$1"
+  _ckey="$2"
+  _ccert="$3"
+  _cca="$4"
+  pfxPassword="$5"
+  pfxName="$6"
+  pfxCaname="$7"
+
+  if [ "$pfxCaname" ]; then
+    ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:$pfxPassword" -name "$pfxName" -caname "$pfxCaname"
+  elif [ "$pfxName" ]; then
+    ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:$pfxPassword" -name "$pfxName"
+  elif [ "$pfxPassword" ]; then
+    ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:$pfxPassword"
+  else
+    ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca"
+  fi
+
+}
+
 #domain [password] [isEcc]
 #domain [password] [isEcc]
 toPkcs() {
 toPkcs() {
   domain="$1"
   domain="$1"
@@ -1189,11 +1217,7 @@ toPkcs() {
 
 
   _initpath "$domain" "$_isEcc"
   _initpath "$domain" "$_isEcc"
 
 
-  if [ "$pfxPassword" ]; then
+  _toPkcs "$CERT_PFX_PATH" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$pfxPassword"
-    ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH" -password "pass:$pfxPassword"
-  else
-    ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH"
-  fi
 
 
   if [ "$?" = "0" ]; then
   if [ "$?" = "0" ]; then
     _info "Success, Pfx is exported to: $CERT_PFX_PATH"
     _info "Success, Pfx is exported to: $CERT_PFX_PATH"
@@ -1276,7 +1300,7 @@ createDomainKey() {
 
 
   _initpath "$domain" "$_cdl"
   _initpath "$domain" "$_cdl"
 
 
-  if [ ! -f "$CERT_KEY_PATH" ] || ([ "$FORCE" ] && ! [ "$IS_RENEW" ]); then
+  if [ ! -f "$CERT_KEY_PATH" ] || ([ "$FORCE" ] && ! [ "$IS_RENEW" ]) || [ "$Le_ForceNewDomainKey" = "1" ]; then
     if _createkey "$_cdl" "$CERT_KEY_PATH"; then
     if _createkey "$_cdl" "$CERT_KEY_PATH"; then
       _savedomainconf Le_Keylength "$_cdl"
       _savedomainconf Le_Keylength "$_cdl"
       _info "The domain key is here: $(__green $CERT_KEY_PATH)"
       _info "The domain key is here: $(__green $CERT_KEY_PATH)"
@@ -2191,7 +2215,9 @@ _initAPI() {
     export ACME_KEY_CHANGE="https://acme-v01.api.letsencrypt.org/acme/key-change"
     export ACME_KEY_CHANGE="https://acme-v01.api.letsencrypt.org/acme/key-change"
     export ACME_NEW_AUTHZ="https://acme-v01.api.letsencrypt.org/acme/new-authz"
     export ACME_NEW_AUTHZ="https://acme-v01.api.letsencrypt.org/acme/new-authz"
     export ACME_NEW_ORDER="https://acme-v01.api.letsencrypt.org/acme/new-cert"
     export ACME_NEW_ORDER="https://acme-v01.api.letsencrypt.org/acme/new-cert"
+    export ACME_NEW_ORDER_RES="new-cert"
     export ACME_NEW_ACCOUNT="https://acme-v01.api.letsencrypt.org/acme/new-reg"
     export ACME_NEW_ACCOUNT="https://acme-v01.api.letsencrypt.org/acme/new-reg"
+    export ACME_NEW_ACCOUNT_RES="new-reg"
     export ACME_REVOKE_CERT="https://acme-v01.api.letsencrypt.org/acme/revoke-cert"
     export ACME_REVOKE_CERT="https://acme-v01.api.letsencrypt.org/acme/revoke-cert"
   fi
   fi
 
 
@@ -2211,16 +2237,22 @@ _initAPI() {
     export ACME_NEW_AUTHZ
     export ACME_NEW_AUTHZ
 
 
     ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'new-cert" *: *"[^"]*"' | cut -d '"' -f 3)
     ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'new-cert" *: *"[^"]*"' | cut -d '"' -f 3)
+    ACME_NEW_ORDER_RES="new-cert"
     if [ -z "$ACME_NEW_ORDER" ]; then
     if [ -z "$ACME_NEW_ORDER" ]; then
       ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'new-order" *: *"[^"]*"' | cut -d '"' -f 3)
       ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'new-order" *: *"[^"]*"' | cut -d '"' -f 3)
+      ACME_NEW_ORDER_RES="new-order"
     fi
     fi
     export ACME_NEW_ORDER
     export ACME_NEW_ORDER
+    export ACME_NEW_ORDER_RES
 
 
     ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'new-reg" *: *"[^"]*"' | cut -d '"' -f 3)
     ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'new-reg" *: *"[^"]*"' | cut -d '"' -f 3)
+    ACME_NEW_ACCOUNT_RES="new-reg"
     if [ -z "$ACME_NEW_ACCOUNT" ]; then
     if [ -z "$ACME_NEW_ACCOUNT" ]; then
       ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'new-account" *: *"[^"]*"' | cut -d '"' -f 3)
       ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'new-account" *: *"[^"]*"' | cut -d '"' -f 3)
+      ACME_NEW_ACCOUNT_RES="new-account"
     fi
     fi
     export ACME_NEW_ACCOUNT
     export ACME_NEW_ACCOUNT
+    export ACME_NEW_ACCOUNT_RES
 
 
     ACME_REVOKE_CERT=$(echo "$response" | _egrep_o 'revoke-cert" *: *"[^"]*"' | cut -d '"' -f 3)
     ACME_REVOKE_CERT=$(echo "$response" | _egrep_o 'revoke-cert" *: *"[^"]*"' | cut -d '"' -f 3)
     export ACME_REVOKE_CERT
     export ACME_REVOKE_CERT
@@ -2237,7 +2269,7 @@ _initAPI() {
   _debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT"
   _debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT"
 }
 }
 
 
-#[domain]  [keylength]
+#[domain]  [keylength or isEcc flag]
 _initpath() {
 _initpath() {
 
 
   __initHome
   __initHome
@@ -2994,9 +3026,9 @@ _on_issue_err() {
   fi
   fi
 
 
   #trigger the validation to flush the pending authz
   #trigger the validation to flush the pending authz
+  _debug2 "_chk_vlist" "$_chk_vlist"
   if [ "$_chk_vlist" ]; then
   if [ "$_chk_vlist" ]; then
     (
     (
-      _debug2 "_chk_vlist" "$_chk_vlist"
       _debug2 "start to deactivate authz"
       _debug2 "start to deactivate authz"
       ventries=$(echo "$_chk_vlist" | tr "$dvsep" ' ')
       ventries=$(echo "$_chk_vlist" | tr "$dvsep" ' ')
       for ventry in $ventries; do
       for ventry in $ventries; do
@@ -3068,14 +3100,13 @@ _regAccount() {
   _initpath
   _initpath
   _reg_length="$1"
   _reg_length="$1"
 
 
+  mkdir -p "$CA_DIR"
   if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then
   if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then
-    mkdir -p "$CA_DIR"
     _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH"
     _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH"
     mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH"
     mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH"
   fi
   fi
 
 
   if [ ! -f "$ACCOUNT_JSON_PATH" ] && [ -f "$_OLD_ACCOUNT_JSON" ]; then
   if [ ! -f "$ACCOUNT_JSON_PATH" ] && [ -f "$_OLD_ACCOUNT_JSON" ]; then
-    mkdir -p "$CA_DIR"
     _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH"
     _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH"
     mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH"
     mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH"
   fi
   fi
@@ -3092,7 +3123,7 @@ _regAccount() {
   fi
   fi
   _initAPI
   _initAPI
   _updateTos=""
   _updateTos=""
-  _reg_res="new-reg"
+  _reg_res="$ACME_NEW_ACCOUNT_RES"
   while true; do
   while true; do
     _debug AGREEMENT "$AGREEMENT"
     _debug AGREEMENT "$AGREEMENT"
 
 
@@ -3122,7 +3153,7 @@ _regAccount() {
 
 
       _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"
-
+      _savecaconf "ACCOUNT_URL" "$_accUri"
       _tos="$(echo "$responseHeaders" | grep "^Link:.*rel=\"terms-of-service\"" | _head_n 1 | _egrep_o "<.*>" | tr -d '<>')"
       _tos="$(echo "$responseHeaders" | grep "^Link:.*rel=\"terms-of-service\"" | _head_n 1 | _egrep_o "<.*>" | tr -d '<>')"
       _debug "_tos" "$_tos"
       _debug "_tos" "$_tos"
       if [ -z "$_tos" ]; then
       if [ -z "$_tos" ]; then
@@ -3143,11 +3174,14 @@ _regAccount() {
         return 1
         return 1
       fi
       fi
       if [ "$code" = '202' ]; then
       if [ "$code" = '202' ]; then
-        _info "Update success."
+        _info "Update account tos info success."
 
 
         CA_KEY_HASH="$(__calcAccountKeyHash)"
         CA_KEY_HASH="$(__calcAccountKeyHash)"
         _debug "Calc CA_KEY_HASH" "$CA_KEY_HASH"
         _debug "Calc CA_KEY_HASH" "$CA_KEY_HASH"
         _savecaconf CA_KEY_HASH "$CA_KEY_HASH"
         _savecaconf CA_KEY_HASH "$CA_KEY_HASH"
+      elif [ "$code" = '403' ]; then
+        _err "It seems that the account key is already deactivated, please use a new account key."
+        return 1
       else
       else
         _err "Update account error."
         _err "Update account error."
         return 1
         return 1
@@ -3160,6 +3194,68 @@ _regAccount() {
 
 
 }
 }
 
 
+#Implement deactivate account
+deactivateaccount() {
+  _initpath
+
+  if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then
+    _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH"
+    mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH"
+  fi
+
+  if [ ! -f "$ACCOUNT_JSON_PATH" ] && [ -f "$_OLD_ACCOUNT_JSON" ]; then
+    _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH"
+    mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH"
+  fi
+
+  if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
+    _err "Account key is not found at: $ACCOUNT_KEY_PATH"
+    return 1
+  fi
+
+  _accUri=$(_readcaconf "ACCOUNT_URL")
+  _debug _accUri "$_accUri"
+
+  if [ -z "$_accUri" ]; then
+    _err "The account url is empty, please run '--update-account' first to update the account info first,"
+    _err "Then try again."
+    return 1
+  fi
+
+  if ! _calcjwk "$ACCOUNT_KEY_PATH"; then
+    return 1
+  fi
+  _initAPI
+
+  if _send_signed_request "$_accUri" "{\"resource\": \"reg\", \"status\":\"deactivated\"}" && _contains "$response" '"deactivated"'; then
+    _info "Deactivate account success for $_accUri."
+    _accid=$(echo "$response" | _egrep_o "\"id\" *: *[^,]*," | cut -d : -f 2 | tr -d ' ,')
+  elif [ "$code" = "403" ]; then
+    _info "The account is already deactivated."
+    _accid=$(_getfield "$_accUri" "999" "/")
+  else
+    _err "Deactivate: account failed for $_accUri."
+    return 1
+  fi
+
+  _debug "Account id: $_accid"
+  if [ "$_accid" ]; then
+    _deactivated_account_path="$CA_DIR/deactivated/$_accid"
+    _debug _deactivated_account_path "$_deactivated_account_path"
+    if mkdir -p "$_deactivated_account_path"; then
+      _info "Moving deactivated account info to $_deactivated_account_path/"
+      mv "$CA_CONF" "$_deactivated_account_path/"
+      mv "$ACCOUNT_JSON_PATH" "$_deactivated_account_path/"
+      mv "$ACCOUNT_KEY_PATH" "$_deactivated_account_path/"
+    else
+      _err "Can not create dir: $_deactivated_account_path, try to remove the deactivated account key."
+      rm -f "$CA_CONF"
+      rm -f "$ACCOUNT_JSON_PATH"
+      rm -f "$ACCOUNT_KEY_PATH"
+    fi
+  fi
+}
+
 # domain folder  file
 # domain folder  file
 _findHook() {
 _findHook() {
   _hookdomain="$1"
   _hookdomain="$1"
@@ -3350,7 +3446,7 @@ issue() {
   else
   else
     _key=$(_readdomainconf Le_Keylength)
     _key=$(_readdomainconf Le_Keylength)
     _debug "Read key length:$_key"
     _debug "Read key length:$_key"
-    if [ ! -f "$CERT_KEY_PATH" ] || [ "$_key_length" != "$_key" ]; then
+    if [ ! -f "$CERT_KEY_PATH" ] || [ "$_key_length" != "$_key" ] || [ "$Le_ForceNewDomainKey" = "1" ]; then
       if ! createDomainKey "$_main_domain" "$_key_length"; then
       if ! createDomainKey "$_main_domain" "$_key_length"; then
         _err "Create domain key error."
         _err "Create domain key error."
         _clearup
         _clearup
@@ -3465,11 +3561,11 @@ issue() {
         if [ "$d_api" ]; then
         if [ "$d_api" ]; then
           _info "Found domain api file: $d_api"
           _info "Found domain api file: $d_api"
         else
         else
-          _err "Add the following TXT record:"
+          _info "$(__red "Add the following TXT record:")"
-          _err "Domain: '$(__green "$txtdomain")'"
+          _info "$(__red "Domain: '$(__green "$txtdomain")'")"
-          _err "TXT value: '$(__green "$txt")'"
+          _info "$(__red "TXT value: '$(__green "$txt")'")"
-          _err "Please be aware that you prepend _acme-challenge. before your domain"
+          _info "$(__red "Please be aware that you prepend _acme-challenge. before your domain")"
-          _err "so the resulting subdomain will be: $txtdomain"
+          _info "$(__red "so the resulting subdomain will be: $txtdomain")"
           continue
           continue
         fi
         fi
 
 
@@ -3493,7 +3589,7 @@ issue() {
 
 
         if [ "$?" != "0" ]; then
         if [ "$?" != "0" ]; then
           _clearup
           _clearup
-          _on_issue_err "$_post_hook"
+          _on_issue_err "$_post_hook" "$vlist"
           return 1
           return 1
         fi
         fi
         dnsadded='1'
         dnsadded='1'
@@ -3756,7 +3852,7 @@ issue() {
   _info "Verify finished, start to sign."
   _info "Verify finished, start to sign."
   der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _url_replace)"
   der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _url_replace)"
 
 
-  if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"new-cert\", \"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."
     _on_issue_err "$_post_hook"
     _on_issue_err "$_post_hook"
     return 1
     return 1
@@ -3880,6 +3976,12 @@ issue() {
     _cleardomainconf Le_Listen_V4
     _cleardomainconf Le_Listen_V4
   fi
   fi
 
 
+  if [ "$Le_ForceNewDomainKey" = "1" ]; then
+    _savedomainconf "Le_ForceNewDomainKey" "$Le_ForceNewDomainKey"
+  else
+    _cleardomainconf Le_ForceNewDomainKey
+  fi
+
   Le_NextRenewTime=$(_math "$Le_CertCreateTime" + "$Le_RenewalDays" \* 24 \* 60 \* 60)
   Le_NextRenewTime=$(_math "$Le_CertCreateTime" + "$Le_RenewalDays" \* 24 \* 60 \* 60)
 
 
   Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
   Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
@@ -3949,6 +4051,11 @@ renew() {
     return "$RENEW_SKIP"
     return "$RENEW_SKIP"
   fi
   fi
 
 
+  if [ "$IN_CRON" = "1" ] && [ -z "$Le_CertCreateTime" ]; then
+    _info "Skip invalid cert for: $Le_Domain"
+    return 0
+  fi
+
   IS_RENEW="1"
   IS_RENEW="1"
   issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress"
   issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress"
   res="$?"
   res="$?"
@@ -4474,26 +4581,51 @@ _deactivate() {
   _d_type="$2"
   _d_type="$2"
   _initpath
   _initpath
 
 
-  _d_i=0
+  if ! __get_domain_new_authz "$_d_domain"; then
-  _d_max_retry=9
+    _err "Can not get domain new authz token."
-  while [ "$_d_i" -lt "$_d_max_retry" ]; do
+    return 1
-    _info "Deactivate: $_d_domain"
+  fi
-    _d_i="$(_math $_d_i + 1)"
 
 
-    if ! __get_domain_new_authz "$_d_domain"; then
+  authzUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
-      _err "Can not get domain new authz token."
+  _debug "authzUri" "$authzUri"
-      return 1
-    fi
 
 
-    authzUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
+  if [ "$code" ] && [ ! "$code" = '201' ]; then
-    _debug "authzUri" "$authzUri"
+    _err "new-authz error: $response"
+    return 1
+  fi
 
 
-    if [ ! -z "$code" ] && [ ! "$code" = '201' ]; then
+  entries="$(echo "$response" | _egrep_o '{ *"type":"[^"]*", *"status": *"valid", *"uri"[^}]*')"
-      _err "new-authz error: $response"
+  if [ -z "$entries" ]; then
+    _info "No valid entries found."
+    if [ -z "$thumbprint" ]; then
+      thumbprint="$(__calc_account_thumbprint)"
+    fi
+    _debug "Trigger validation."
+    vtype="$VTYPE_HTTP"
+    entry="$(printf "%s\n" "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
+    _debug entry "$entry"
+    if [ -z "$entry" ]; then
+      _err "Error, can not get domain token $d"
       return 1
       return 1
     fi
     fi
+    token="$(printf "%s\n" "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
+    _debug token "$token"
+
+    uri="$(printf "%s\n" "$entry" | _egrep_o '"uri":"[^"]*' | cut -d : -f 2,3 | tr -d '"')"
+    _debug uri "$uri"
+
+    keyauthorization="$token.$thumbprint"
+    _debug keyauthorization "$keyauthorization"
+    __trigger_validation "$uri" "$keyauthorization"
 
 
-    entry="$(printf "%s\n" "$response" | _egrep_o '{"type":"[^"]*","status":"valid","uri"[^}]*')"
+  fi
+
+  _d_i=0
+  _d_max_retry=$(echo "$entries" | wc -l)
+  while [ "$_d_i" -lt "$_d_max_retry" ]; do
+    _info "Deactivate: $_d_domain"
+    _d_i="$(_math $_d_i + 1)"
+    entry="$(echo "$entries" | sed -n "${_d_i}p")"
     _debug entry "$entry"
     _debug entry "$entry"
 
 
     if [ -z "$entry" ]; then
     if [ -z "$entry" ]; then
@@ -4515,16 +4647,16 @@ _deactivate() {
 
 
     _info "Deactivate: $_vtype"
     _info "Deactivate: $_vtype"
 
 
-    if ! _send_signed_request "$authzUri" "{\"resource\": \"authz\", \"status\":\"deactivated\"}"; then
+    if _send_signed_request "$authzUri" "{\"resource\": \"authz\", \"status\":\"deactivated\"}" && _contains "$response" '"deactivated"'; then
+      _info "Deactivate: $_vtype success."
+    else
       _err "Can not deactivate $_vtype."
       _err "Can not deactivate $_vtype."
-      return 1
+      break
     fi
     fi
 
 
-    _info "Deactivate: $_vtype success."
-
   done
   done
   _debug "$_d_i"
   _debug "$_d_i"
-  if [ "$_d_i" -lt "$_d_max_retry" ]; then
+  if [ "$_d_i" -eq "$_d_max_retry" ]; then
     _info "Deactivated success!"
     _info "Deactivated success!"
   else
   else
     _err "Deactivate failed."
     _err "Deactivate failed."
@@ -4584,9 +4716,7 @@ _detect_profile() {
     fi
     fi
   fi
   fi
 
 
-  if [ ! -z "$DETECTED_PROFILE" ]; then
+  echo "$DETECTED_PROFILE"
-    echo "$DETECTED_PROFILE"
-  fi
 }
 }
 
 
 _initconf() {
 _initconf() {
@@ -4674,6 +4804,8 @@ _installalias() {
   _setopt "$_envfile" "export LE_WORKING_DIR" "=" "\"$LE_WORKING_DIR\""
   _setopt "$_envfile" "export LE_WORKING_DIR" "=" "\"$LE_WORKING_DIR\""
   if [ "$_c_home" ]; then
   if [ "$_c_home" ]; then
     _setopt "$_envfile" "export LE_CONFIG_HOME" "=" "\"$LE_CONFIG_HOME\""
     _setopt "$_envfile" "export LE_CONFIG_HOME" "=" "\"$LE_CONFIG_HOME\""
+  else
+    _sed_i "/^export LE_CONFIG_HOME/d" "$_envfile"
   fi
   fi
   _setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
   _setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
 
 
@@ -4695,6 +4827,8 @@ _installalias() {
     _setopt "$_cshfile" "setenv LE_WORKING_DIR" " " "\"$LE_WORKING_DIR\""
     _setopt "$_cshfile" "setenv LE_WORKING_DIR" " " "\"$LE_WORKING_DIR\""
     if [ "$_c_home" ]; then
     if [ "$_c_home" ]; then
       _setopt "$_cshfile" "setenv LE_CONFIG_HOME" " " "\"$LE_CONFIG_HOME\""
       _setopt "$_cshfile" "setenv LE_CONFIG_HOME" " " "\"$LE_CONFIG_HOME\""
+    else
+      _sed_i "/^setenv LE_CONFIG_HOME/d" "$_cshfile"
     fi
     fi
     _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
     _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
     _setopt "$_csh_profile" "source \"$_cshfile\""
     _setopt "$_csh_profile" "source \"$_cshfile\""
@@ -4759,20 +4893,24 @@ install() {
 
 
   _info "Installing to $LE_WORKING_DIR"
   _info "Installing to $LE_WORKING_DIR"
 
 
-  if ! mkdir -p "$LE_WORKING_DIR"; then
+  if [ ! -d "$LE_WORKING_DIR" ]; then
-    _err "Can not create working dir: $LE_WORKING_DIR"
+    if ! mkdir -p "$LE_WORKING_DIR"; then
-    return 1
+      _err "Can not create working dir: $LE_WORKING_DIR"
+      return 1
+    fi
+
+    chmod 700 "$LE_WORKING_DIR"
   fi
   fi
 
 
-  chmod 700 "$LE_WORKING_DIR"
+  if [ ! -d "$LE_CONFIG_HOME" ]; then
+    if ! mkdir -p "$LE_CONFIG_HOME"; then
+      _err "Can not create config dir: $LE_CONFIG_HOME"
+      return 1
+    fi
 
 
-  if ! mkdir -p "$LE_CONFIG_HOME"; then
+    chmod 700 "$LE_CONFIG_HOME"
-    _err "Can not create config dir: $LE_CONFIG_HOME"
-    return 1
   fi
   fi
 
 
-  chmod 700 "$LE_CONFIG_HOME"
-
   cp "$PROJECT_ENTRY" "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/$PROJECT_ENTRY"
   cp "$PROJECT_ENTRY" "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/$PROJECT_ENTRY"
 
 
   if [ "$?" != "0" ]; then
   if [ "$?" != "0" ]; then
@@ -4930,6 +5068,7 @@ Commands:
   --toPkcs8                Convert to pkcs8 format.
   --toPkcs8                Convert to pkcs8 format.
   --update-account         Update account info.
   --update-account         Update account info.
   --register-account       Register account key.
   --register-account       Register account key.
+  --deactivate-account     Deactivate the account.
   --create-account-key     Create an account private key, professional use.
   --create-account-key     Create an account private key, professional use.
   --create-domain-key      Create an domain private key, professional use.
   --create-domain-key      Create an domain private key, professional use.
   --createCSR, -ccsr       Create CSR , professional use.
   --createCSR, -ccsr       Create CSR , professional use.
@@ -4990,6 +5129,7 @@ Parameters:
   --renew-hook                      Command to be run once for each successfully renewed certificate.
   --renew-hook                      Command to be run once for each successfully renewed certificate.
   --deploy-hook                     The hook file to deploy cert
   --deploy-hook                     The hook file to deploy cert
   --ocsp-must-staple, --ocsp        Generate ocsp must Staple extension.
   --ocsp-must-staple, --ocsp        Generate ocsp must Staple extension.
+  --always-force-new-domain-key     Generate new domain key when renewal. Otherwise, the domain key is not changed by default.
   --auto-upgrade   [0|1]            Valid for '--upgrade' command, indicating whether to upgrade automatically in future.
   --auto-upgrade   [0|1]            Valid for '--upgrade' command, indicating whether to upgrade automatically in future.
   --listen-v4                       Force standalone/tls server to listen at ipv4.
   --listen-v4                       Force standalone/tls server to listen at ipv4.
   --listen-v6                       Force standalone/tls server to listen at ipv6.
   --listen-v6                       Force standalone/tls server to listen at ipv6.
@@ -5209,6 +5349,9 @@ _process() {
       --registeraccount | --register-account)
       --registeraccount | --register-account)
         _CMD="registeraccount"
         _CMD="registeraccount"
         ;;
         ;;
+      --deactivate-account)
+        _CMD="deactivateaccount"
+        ;;
       --domain | -d)
       --domain | -d)
         _dvalue="$2"
         _dvalue="$2"
 
 
@@ -5315,7 +5458,7 @@ _process() {
         ;;
         ;;
       --dns)
       --dns)
         wvalue="dns"
         wvalue="dns"
-        if ! _startswith "$2" "-"; then
+        if [ "$2" ] && ! _startswith "$2" "-"; then
           wvalue="$2"
           wvalue="$2"
           shift
           shift
         fi
         fi
@@ -5470,6 +5613,14 @@ _process() {
       --ocsp-must-staple | --ocsp)
       --ocsp-must-staple | --ocsp)
         Le_OCSP_Staple="1"
         Le_OCSP_Staple="1"
         ;;
         ;;
+      --always-force-new-domain-key)
+        if [ -z "$2" ] || _startswith "$2" "-"; then
+          Le_ForceNewDomainKey=1
+        else
+          Le_ForceNewDomainKey="$2"
+          shift
+        fi
+        ;;
       --log | --logfile)
       --log | --logfile)
         _log="1"
         _log="1"
         _logfile="$2"
         _logfile="$2"
@@ -5616,6 +5767,9 @@ _process() {
     updateaccount)
     updateaccount)
       updateaccount
       updateaccount
       ;;
       ;;
+    deactivateaccount)
+      deactivateaccount
+      ;;
     list)
     list)
       list "$_listraw"
       list "$_listraw"
       ;;
       ;;

+ 100 - 0
deploy/unifi.sh

@@ -0,0 +1,100 @@
+#!/usr/bin/env sh
+
+#Here is a script to deploy cert to unifi server.
+
+#returns 0 means success, otherwise error.
+
+#DEPLOY_UNIFI_KEYSTORE="/usr/lib/unifi/data/keystore"
+#DEPLOY_UNIFI_KEYPASS="aircontrolenterprise"
+#DEPLOY_UNIFI_RELOAD="service unifi restart"
+
+########  Public functions #####################
+
+#domain keyfile certfile cafile fullchain
+unifi_deploy() {
+  _cdomain="$1"
+  _ckey="$2"
+  _ccert="$3"
+  _cca="$4"
+  _cfullchain="$5"
+
+  _debug _cdomain "$_cdomain"
+  _debug _ckey "$_ckey"
+  _debug _ccert "$_ccert"
+  _debug _cca "$_cca"
+  _debug _cfullchain "$_cfullchain"
+
+  if ! _exists keytool; then
+    _err "keytool not found"
+    return 1
+  fi
+
+  DEFAULT_UNIFI_KEYSTORE="/usr/lib/unifi/data/keystore"
+  _unifi_keystore="${DEPLOY_UNIFI_KEYSTORE:-$DEFAULT_UNIFI_KEYSTORE}"
+  DEFAULT_UNIFI_KEYPASS="aircontrolenterprise"
+  _unifi_keypass="${DEPLOY_UNIFI_KEYPASS:-$DEFAULT_UNIFI_KEYPASS}"
+  DEFAULT_UNIFI_RELOAD="service unifi restart"
+  _reload="${DEPLOY_UNIFI_RELOAD:-$DEFAULT_UNIFI_RELOAD}"
+
+  _debug _unifi_keystore "$_unifi_keystore"
+  if [ ! -f "$_unifi_keystore" ]; then
+    if [ -z "$DEPLOY_UNIFI_KEYSTORE" ]; then
+      _err "unifi keystore is not found, please define DEPLOY_UNIFI_KEYSTORE"
+      return 1
+    else
+      _err "It seems that the specified unifi keystore is not valid, please check."
+      return 1
+    fi
+  fi
+  if [ ! -w "$_unifi_keystore" ]; then
+    _err "The file $_unifi_keystore is not writable, please change the permission."
+    return 1
+  fi
+
+  _info "Generate import pkcs12"
+  _import_pkcs12="$(_mktemp)"
+  _toPkcs "$_import_pkcs12" "$_ckey" "$_ccert" "$_cca" "$_unifi_keypass" unifi root
+  if [ "$?" != "0" ]; then
+    _err "Oops, error creating import pkcs12, please report bug to us."
+    return 1
+  fi
+
+  _info "Modify unifi keystore: $_unifi_keystore"
+  if keytool -importkeystore \
+    -deststorepass "$_unifi_keypass" -destkeypass "$_unifi_keypass" -destkeystore "$_unifi_keystore" \
+    -srckeystore "$_import_pkcs12" -srcstoretype PKCS12 -srcstorepass "$_unifi_keypass" \
+    -alias unifi -noprompt; then
+    _info "Import keystore success!"
+    rm "$_import_pkcs12"
+  else
+    _err "Import unifi keystore error, please report bug to us."
+    rm "$_import_pkcs12"
+    return 1
+  fi
+
+  _info "Run reload: $_reload"
+  if eval "$_reload"; then
+    _info "Reload success!"
+    if [ "$DEPLOY_UNIFI_KEYSTORE" ]; then
+      _savedomainconf DEPLOY_UNIFI_KEYSTORE "$DEPLOY_UNIFI_KEYSTORE"
+    else
+      _cleardomainconf DEPLOY_UNIFI_KEYSTORE
+    fi
+    if [ "$DEPLOY_UNIFI_KEYPASS" ]; then
+      _savedomainconf DEPLOY_UNIFI_KEYPASS "$DEPLOY_UNIFI_KEYPASS"
+    else
+      _cleardomainconf DEPLOY_UNIFI_KEYPASS
+    fi
+    if [ "$DEPLOY_UNIFI_RELOAD" ]; then
+      _savedomainconf DEPLOY_UNIFI_RELOAD "$DEPLOY_UNIFI_RELOAD"
+    else
+      _cleardomainconf DEPLOY_UNIFI_RELOAD
+    fi
+    return 0
+  else
+    _err "Reload error"
+    return 1
+  fi
+  return 0
+
+}

+ 99 - 0
dnsapi/README.md

@@ -505,6 +505,105 @@ Ok, let's issue a cert now:
 acme.sh --issue --dns dns_nsone -d example.com -d www.example.com
 acme.sh --issue --dns dns_nsone -d example.com -d www.example.com
 ```
 ```
 
 
+## 27. Use DuckDNS.org API
+
+```
+export DuckDNS_Token="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
+```
+
+Please note that since DuckDNS uses StartSSL as their cert provider, thus 
+--insecure must be used when issuing certs:
+```
+acme.sh --insecure --issue --dns dns_duckdns -d mydomain.duckdns.org
+```
+
+Also, DuckDNS uses the domain name as username for recording changing, so the
+account file will always store the lastly used domain name.
+
+For issues, please report to https://github.com/raidenii/acme.sh/issues.
+
+## 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.
+
+```
+export Namecom_Username="testuser"
+export Namecom_Token="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+```
+
+And now you can issue certs with:
+
+```
+acme.sh --issue --dns dns_namecom -d example.com -d www.example.com
+```
+
+For issues, please report to https://github.com/raidenii/acme.sh/issues.
+
+## 29. Use Dyn Managed DNS API to automatically issue cert
+
+First, login to your Dyn Managed DNS account: https://portal.dynect.net/login/
+
+It is recommended to add a new user specific for API access.
+
+The minimum "Zones & Records Permissions" required are:
+```
+RecordAdd
+RecordUpdate
+RecordDelete
+RecordGet
+ZoneGet
+ZoneAddNode
+ZoneRemoveNode
+ZonePublish
+```
+
+Pass the API user credentials to the environment:
+```
+export DYN_Customer="customer"
+export DYN_Username="apiuser"
+export DYN_Password="secret"
+```
+
+Ok, let's issue a cert now:
+```
+acme.sh --issue --dns dns_dyn -d example.com -d www.example.com
+```
+
+The `DYN_Customer`, `DYN_Username` and `DYN_Password` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+## 30. Use pdd.yandex.ru API
+
+```
+export PDD_Token="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+```
+
+Follow these instructions to get the token for your domain https://tech.yandex.com/domain/doc/concepts/access-docpage/
+```
+acme.sh --issue --dns dns_yandex -d mydomain.example.org
+```
+
+For issues, please report to https://github.com/non7top/acme.sh/issues.
+
+## 31. Use Hurricane Electric
+
+Hurricane Electric doesn't have an API so just set your login credentials like so:
+
+```
+export HE_Username="yourusername"
+export HE_Password="password"
+```
+
+Then you can issue your certificate:
+
+```
+acme.sh --issue --dns dns_he -d example.com -d www.example.com
+```
+
+The `HE_Username` and `HE_Password` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+Please report any issues to https://github.com/angel333/acme.sh or to <me@ondrejsimek.com>.
+
 # Use custom API
 # Use custom API
 
 
 If your API is not supported yet, you can write your own DNS API.
 If your API is not supported yet, you can write your own DNS API.

+ 1 - 1
dnsapi/dns_aws.sh

@@ -208,7 +208,7 @@ aws_rest() {
   kServiceH="$(printf "$Service%s" | _hmac "$Hash" "$kRegionH" hex)"
   kServiceH="$(printf "$Service%s" | _hmac "$Hash" "$kRegionH" hex)"
   _debug2 kServiceH "$kServiceH"
   _debug2 kServiceH "$kServiceH"
 
 
-  kSigningH="$(printf "aws4_request%s" | _hmac "$Hash" "$kServiceH" hex)"
+  kSigningH="$(printf "%s" "aws4_request" | _hmac "$Hash" "$kServiceH" hex)"
   _debug2 kSigningH "$kSigningH"
   _debug2 kSigningH "$kSigningH"
 
 
   signature="$(printf "$StringToSign%s" | _hmac "$Hash" "$kSigningH" hex)"
   signature="$(printf "$StringToSign%s" | _hmac "$Hash" "$kSigningH" hex)"

+ 91 - 0
dnsapi/dns_duckdns.sh

@@ -0,0 +1,91 @@
+#!/usr/bin/env sh
+
+#Created by RaidenII, to use DuckDNS's API to add/remove text records
+#06/27/2017
+
+# Currently only support single domain access
+# Due to the fact that DuckDNS uses StartSSL as cert provider, --insecure must be used with acme.sh
+
+DuckDNS_API="https://www.duckdns.org/update"
+API_Params="domains=$DuckDNS_Domain&token=$DuckDNS_Token"
+
+########  Public functions #####################
+
+#Usage: dns_duckdns_add _acme-challenge.domain.duckdns.org "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_duckdns_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  # We'll extract the domain/username from full domain
+  DuckDNS_Domain=$(echo "$fulldomain" | _lower_case | _egrep_o '.[^.]*.duckdns.org' | cut -d . -f 2)
+
+  if [ -z "$DuckDNS_Domain" ]; then
+    _err "Error extracting the domain."
+    return 1
+  fi
+
+  if [ -z "$DuckDNS_Token" ]; then
+    DuckDNS_Token=""
+    _err "The token for your DuckDNS account is necessary."
+    _err "You can look it up in your DuckDNS account."
+    return 1
+  fi
+
+  # Now save the credentials.
+  _saveaccountconf DuckDNS_Domain "$DuckDNS_Domain"
+  _saveaccountconf DuckDNS_Token "$DuckDNS_Token"
+
+  # Unfortunately, DuckDNS does not seems to support lookup domain through API
+  # So I assume your credentials (which are your domain and token) are correct
+  # If something goes wrong, we will get a KO response from DuckDNS
+
+  # Now add the TXT record to DuckDNS
+  _info "Trying to add TXT record"
+  if _duckdns_rest GET "$API_Params&txt=$txtvalue" && [ "$response" = "OK" ]; then
+    _info "TXT record has been successfully added to your DuckDNS domain."
+    _info "Note that all subdomains under this domain uses the same TXT record."
+    return 0
+  else
+    _err "Errors happened during adding the TXT record."
+    return 1
+  fi
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_duckdns_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  # Now remove the TXT record from DuckDNS
+  _info "Trying to remove TXT record"
+  if _duckdns_rest GET "$API_Params&txt=&clear=true" && [ "$response" = "OK" ]; then
+    _info "TXT record has been successfully removed from your DuckDNS domain."
+    return 0
+  else
+    _err "Errors happened during removing the TXT record."
+    return 1
+  fi
+}
+
+####################  Private functions below ##################################
+
+#Usage: method URI
+_duckdns_rest() {
+  method=$1
+  param="$2"
+  _debug param "$param"
+  url="$DuckDNS_API?$param"
+  _debug url "$url"
+
+  # DuckDNS uses GET to update domain info
+  if [ "$method" = "GET" ]; then
+    response="$(_get "$url")"
+  else
+    _err "Unsupported method"
+    return 1
+  fi
+
+  _debug2 response "$response"
+  return 0
+}

+ 339 - 0
dnsapi/dns_dyn.sh

@@ -0,0 +1,339 @@
+#!/usr/bin/env sh
+#
+# Dyn.com Domain API
+#
+# Author: Gerd Naschenweng
+# https://github.com/magicdude4eva
+#
+# Dyn Managed DNS API
+# https://help.dyn.com/dns-api-knowledge-base/
+#
+# It is recommended to add a "Dyn Managed DNS" user specific for API access.
+# The "Zones & Records Permissions" required by this script are:
+# --
+# RecordAdd
+# RecordUpdate
+# RecordDelete
+# RecordGet
+# ZoneGet
+# ZoneAddNode
+# ZoneRemoveNode
+# ZonePublish
+# --
+#
+# Pass credentials before "acme.sh --issue --dns dns_dyn ..."
+# --
+# export DYN_Customer="customer"
+# export DYN_Username="apiuser"
+# export DYN_Password="secret"
+# --
+
+DYN_API="https://api.dynect.net/REST"
+
+#REST_API
+########  Public functions #####################
+
+#Usage: add  _acme-challenge.www.domain.com   "Challenge-code"
+dns_dyn_add() {
+  fulldomain="$1"
+  txtvalue="$2"
+
+  DYN_Customer="${DYN_Customer:-$(_readaccountconf_mutable DYN_Customer)}"
+  DYN_Username="${DYN_Username:-$(_readaccountconf_mutable DYN_Username)}"
+  DYN_Password="${DYN_Password:-$(_readaccountconf_mutable DYN_Password)}"
+  if [ -z "$DYN_Customer" ] || [ -z "$DYN_Username" ] || [ -z "$DYN_Password" ]; then
+    DYN_Customer=""
+    DYN_Username=""
+    DYN_Password=""
+    _err "You must export variables: DYN_Customer, DYN_Username and DYN_Password"
+    return 1
+  fi
+
+  #save the config variables to the account conf file.
+  _saveaccountconf_mutable DYN_Customer "$DYN_Customer"
+  _saveaccountconf_mutable DYN_Username "$DYN_Username"
+  _saveaccountconf_mutable DYN_Password "$DYN_Password"
+
+  if ! _dyn_get_authtoken; then
+    return 1
+  fi
+
+  if [ -z "$_dyn_authtoken" ]; then
+    _dyn_end_session
+    return 1
+  fi
+
+  if ! _dyn_get_zone; then
+    _dyn_end_session
+    return 1
+  fi
+
+  if ! _dyn_add_record; then
+    _dyn_end_session
+    return 1
+  fi
+
+  if ! _dyn_publish_zone; then
+    _dyn_end_session
+    return 1
+  fi
+
+  _dyn_end_session
+
+  return 0
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_dyn_rm() {
+  fulldomain="$1"
+  txtvalue="$2"
+
+  DYN_Customer="${DYN_Customer:-$(_readaccountconf_mutable DYN_Customer)}"
+  DYN_Username="${DYN_Username:-$(_readaccountconf_mutable DYN_Username)}"
+  DYN_Password="${DYN_Password:-$(_readaccountconf_mutable DYN_Password)}"
+  if [ -z "$DYN_Customer" ] || [ -z "$DYN_Username" ] || [ -z "$DYN_Password" ]; then
+    DYN_Customer=""
+    DYN_Username=""
+    DYN_Password=""
+    _err "You must export variables: DYN_Customer, DYN_Username and DYN_Password"
+    return 1
+  fi
+
+  if ! _dyn_get_authtoken; then
+    return 1
+  fi
+
+  if [ -z "$_dyn_authtoken" ]; then
+    _dyn_end_session
+    return 1
+  fi
+
+  if ! _dyn_get_zone; then
+    _dyn_end_session
+    return 1
+  fi
+
+  if ! _dyn_get_record_id; then
+    _dyn_end_session
+    return 1
+  fi
+
+  if [ -z "$_dyn_record_id" ]; then
+    _dyn_end_session
+    return 1
+  fi
+
+  if ! _dyn_rm_record; then
+    _dyn_end_session
+    return 1
+  fi
+
+  if ! _dyn_publish_zone; then
+    _dyn_end_session
+    return 1
+  fi
+
+  _dyn_end_session
+
+  return 0
+}
+
+####################  Private functions below ##################################
+
+#get Auth-Token
+_dyn_get_authtoken() {
+
+  _info "Start Dyn API Session"
+
+  data="{\"customer_name\":\"$DYN_Customer\", \"user_name\":\"$DYN_Username\", \"password\":\"$DYN_Password\"}"
+  dyn_url="$DYN_API/Session/"
+  method="POST"
+
+  _debug data "$data"
+  _debug dyn_url "$dyn_url"
+
+  export _H1="Content-Type: application/json"
+
+  response="$(_post "$data" "$dyn_url" "" "$method")"
+  sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')"
+
+  _debug response "$response"
+  _debug sessionstatus "$sessionstatus"
+
+  if [ "$sessionstatus" = "success" ]; then
+    _dyn_authtoken="$(printf "%s\n" "$response" | _egrep_o '"token" *: *"[^"]*' | _head_n 1 | sed 's#^"token" *: *"##')"
+    _info "Token received"
+    _debug _dyn_authtoken "$_dyn_authtoken"
+    return 0
+  fi
+
+  _dyn_authtoken=""
+  _err "get token failed"
+  return 1
+}
+
+#fulldomain=_acme-challenge.www.domain.com
+#returns
+# _dyn_zone=domain.com
+_dyn_get_zone() {
+  i=2
+  while true; do
+    domain="$(printf "%s" "$fulldomain" | cut -d . -f "$i-100")"
+    if [ -z "$domain" ]; then
+      break
+    fi
+
+    dyn_url="$DYN_API/Zone/$domain/"
+
+    export _H1="Auth-Token: $_dyn_authtoken"
+    export _H2="Content-Type: application/json"
+
+    response="$(_get "$dyn_url" "" "")"
+    sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')"
+
+    _debug dyn_url "$dyn_url"
+    _debug response "$response"
+    _debug sessionstatus "$sessionstatus"
+
+    if [ "$sessionstatus" = "success" ]; then
+      _dyn_zone="$domain"
+      return 0
+    fi
+    i=$(_math "$i" + 1)
+  done
+
+  _dyn_zone=""
+  _err "get zone failed"
+  return 1
+}
+
+#add TXT record
+_dyn_add_record() {
+
+  _info "Adding TXT record"
+
+  data="{\"rdata\":{\"txtdata\":\"$txtvalue\"},\"ttl\":\"300\"}"
+  dyn_url="$DYN_API/TXTRecord/$_dyn_zone/$fulldomain/"
+  method="POST"
+
+  export _H1="Auth-Token: $_dyn_authtoken"
+  export _H2="Content-Type: application/json"
+
+  response="$(_post "$data" "$dyn_url" "" "$method")"
+  sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')"
+
+  _debug response "$response"
+  _debug sessionstatus "$sessionstatus"
+
+  if [ "$sessionstatus" = "success" ]; then
+    _info "TXT Record successfully added"
+    return 0
+  fi
+
+  _err "add TXT record failed"
+  return 1
+}
+
+#publish the zone
+_dyn_publish_zone() {
+
+  _info "Publishing zone"
+
+  data="{\"publish\":\"true\"}"
+  dyn_url="$DYN_API/Zone/$_dyn_zone/"
+  method="PUT"
+
+  export _H1="Auth-Token: $_dyn_authtoken"
+  export _H2="Content-Type: application/json"
+
+  response="$(_post "$data" "$dyn_url" "" "$method")"
+  sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')"
+
+  _debug response "$response"
+  _debug sessionstatus "$sessionstatus"
+
+  if [ "$sessionstatus" = "success" ]; then
+    _info "Zone published"
+    return 0
+  fi
+
+  _err "publish zone failed"
+  return 1
+}
+
+#get record_id of TXT record so we can delete the record
+_dyn_get_record_id() {
+
+  _info "Getting record_id of TXT record"
+
+  dyn_url="$DYN_API/TXTRecord/$_dyn_zone/$fulldomain/"
+
+  export _H1="Auth-Token: $_dyn_authtoken"
+  export _H2="Content-Type: application/json"
+
+  response="$(_get "$dyn_url" "" "")"
+  sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')"
+
+  _debug response "$response"
+  _debug sessionstatus "$sessionstatus"
+
+  if [ "$sessionstatus" = "success" ]; then
+    _dyn_record_id="$(printf "%s\n" "$response" | _egrep_o "\"data\" *: *\[\"/REST/TXTRecord/$_dyn_zone/$fulldomain/[^\"]*" | _head_n 1 | sed "s#^\"data\" *: *\[\"/REST/TXTRecord/$_dyn_zone/$fulldomain/##")"
+    _debug _dyn_record_id "$_dyn_record_id"
+    return 0
+  fi
+
+  _dyn_record_id=""
+  _err "getting record_id failed"
+  return 1
+}
+
+#delete TXT record
+_dyn_rm_record() {
+
+  _info "Deleting TXT record"
+
+  dyn_url="$DYN_API/TXTRecord/$_dyn_zone/$fulldomain/$_dyn_record_id/"
+  method="DELETE"
+
+  _debug dyn_url "$dyn_url"
+
+  export _H1="Auth-Token: $_dyn_authtoken"
+  export _H2="Content-Type: application/json"
+
+  response="$(_post "" "$dyn_url" "" "$method")"
+  sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')"
+
+  _debug response "$response"
+  _debug sessionstatus "$sessionstatus"
+
+  if [ "$sessionstatus" = "success" ]; then
+    _info "TXT record successfully deleted"
+    return 0
+  fi
+
+  _err "delete TXT record failed"
+  return 1
+}
+
+#logout
+_dyn_end_session() {
+
+  _info "End Dyn API Session"
+
+  dyn_url="$DYN_API/Session/"
+  method="DELETE"
+
+  _debug dyn_url "$dyn_url"
+
+  export _H1="Auth-Token: $_dyn_authtoken"
+  export _H2="Content-Type: application/json"
+
+  response="$(_post "" "$dyn_url" "" "$method")"
+
+  _debug response "$response"
+
+  _dyn_authtoken=""
+  return 0
+}

+ 175 - 0
dnsapi/dns_he.sh

@@ -0,0 +1,175 @@
+#!/usr/bin/env sh
+
+########################################################################
+# Hurricane Electric hook script for acme.sh
+#
+# Environment variables:
+#
+#  - $HE_Username  (your dns.he.net username)
+#  - $HE_Password  (your dns.he.net password)
+#
+# Author: Ondrej Simek <me@ondrejsimek.com>
+# Git repo: https://github.com/angel333/acme.sh
+
+#-- dns_he_add() - Add TXT record --------------------------------------
+# Usage: dns_he_add _acme-challenge.subdomain.domain.com "XyZ123..."
+
+dns_he_add() {
+  _full_domain=$1
+  _txt_value=$2
+  _info "Using DNS-01 Hurricane Electric hook"
+
+  if [ -z "$HE_Username" ] || [ -z "$HE_Password" ]; then
+    HE_Username=
+    HE_Password=
+    _err "No auth details provided. Please set user credentials using the \$HE_Username and \$HE_Password envoronment variables."
+    return 1
+  fi
+  _saveaccountconf HE_Username "$HE_Username"
+  _saveaccountconf HE_Password "$HE_Password"
+
+  # Fills in the $_zone_id
+  _find_zone "$_full_domain" || return 1
+  _debug "Zone id \"$_zone_id\" will be used."
+
+  body="email=${HE_Username}&pass=${HE_Password}"
+  body="$body&account="
+  body="$body&menu=edit_zone"
+  body="$body&Type=TXT"
+  body="$body&hosted_dns_zoneid=$_zone_id"
+  body="$body&hosted_dns_recordid="
+  body="$body&hosted_dns_editzone=1"
+  body="$body&Priority="
+  body="$body&Name=$_full_domain"
+  body="$body&Content=$_txt_value"
+  body="$body&TTL=300"
+  body="$body&hosted_dns_editrecord=Submit"
+  response="$(_post "$body" "https://dns.he.net/")"
+  exit_code="$?"
+  if [ "$exit_code" -eq 0 ]; then
+    _info "TXT record added successfuly."
+  else
+    _err "Couldn't add the TXT record."
+  fi
+  _debug2 response "$response"
+  return "$exit_code"
+}
+
+#-- dns_he_rm() - Remove TXT record ------------------------------------
+# Usage: dns_he_rm _acme-challenge.subdomain.domain.com "XyZ123..."
+
+dns_he_rm() {
+  _full_domain=$1
+  _txt_value=$2
+  _info "Cleaning up after DNS-01 Hurricane Electric hook"
+
+  # fills in the $_zone_id
+  _find_zone "$_full_domain" || return 1
+  _debug "Zone id \"$_zone_id\" will be used."
+
+  # Find the record id to clean
+  body="email=${HE_Username}&pass=${HE_Password}"
+  body="$body&hosted_dns_zoneid=$_zone_id"
+  body="$body&menu=edit_zone"
+  body="$body&hosted_dns_editzone="
+  domain_regex="$(echo "$_full_domain" | sed 's/\./\\./g')" # escape dots
+  _record_id=$(_post "$body" "https://dns.he.net/" \
+    | tr -d '\n' \
+    | _egrep_o "data=\"&quot;${_txt_value}&quot;([^>]+>){6}[^<]+<[^;]+;deleteRecord\('[0-9]+','${domain_regex}','TXT'\)" \
+    | _egrep_o "[0-9]+','${domain_regex}','TXT'\)$" \
+    | _egrep_o "^[0-9]+"
+  )
+  # The series of egreps above could have been done a bit shorter but
+  #  I wanted to double-check whether it's the correct record (in case
+  #  HE changes their website somehow).
+
+  # Remove the record
+  body="email=${HE_Username}&pass=${HE_Password}"
+  body="$body&menu=edit_zone"
+  body="$body&hosted_dns_zoneid=$_zone_id"
+  body="$body&hosted_dns_recordid=$_record_id"
+  body="$body&hosted_dns_editzone=1"
+  body="$body&hosted_dns_delrecord=1"
+  body="$body&hosted_dns_delconfirm=delete"
+  _post "$body" "https://dns.he.net/" \
+    | grep '<div id="dns_status" onClick="hideThis(this);">Successfully removed record.</div>' \
+      >/dev/null
+  exit_code="$?"
+  if [ "$exit_code" -eq 0 ]; then
+    _info "Record removed successfuly."
+  else
+    _err "Could not clean (remove) up the record. Please go to HE administration interface and clean it by hand."
+    return "$exit_code"
+  fi
+}
+
+########################## PRIVATE FUNCTIONS ###########################
+
+#-- _find_zone() -------------------------------------------------------
+# Returns the most specific zone found in administration interface.
+#
+# Example:
+#
+# _find_zone first.second.third.co.uk
+#
+# ... will return the first zone that exists in admin out of these:
+# - "first.second.third.co.uk"
+# - "second.third.co.uk"
+# - "third.co.uk"
+# - "co.uk" <-- unlikely
+# - "uk"    <-'
+#
+# (another approach would be something like this:
+#   https://github.com/hlandau/acme/blob/master/_doc/dns.hook
+#   - that's better if there are multiple pages. It's so much simpler.
+# )
+
+_find_zone() {
+
+  _domain="$1"
+
+  body="email=${HE_Username}&pass=${HE_Password}"
+  _matches=$(_post "$body" "https://dns.he.net/" \
+    | _egrep_o "delete_dom.*name=\"[^\"]+\" value=\"[0-9]+"
+  )
+  # Zone names and zone IDs are in same order
+  _zone_ids=$(echo "$_matches" | cut -d '"' -f 5)
+  _zone_names=$(echo "$_matches" | cut -d '"' -f 3)
+  _debug2 "These are the zones on this HE account:"
+  _debug2 "$_zone_names"
+  _debug2 "And these are their respective IDs:"
+  _debug2 "$_zone_ids"
+
+  # Walk through all possible zone names
+  _strip_counter=1
+  while true; do
+    _attempted_zone=$(echo "$_domain" | cut -d . -f ${_strip_counter}-)
+
+    # All possible zone names have been tried
+    if [ -z "$_attempted_zone" ]; then
+      _err "No zone for domain \"$_domain\" found."
+      return 1
+    fi
+
+    _debug "Looking for zone \"${_attempted_zone}\""
+
+    # Take care of "." and only match whole lines. Note that grep -F
+    # cannot be used because there's no way to make it match whole
+    # lines.
+    regex="^$(echo "$_attempted_zone" | sed 's/\./\\./g')$"
+    line_num=$(echo "$_zone_names" \
+      | grep -n "$regex" \
+      | cut -d : -f 1
+    )
+
+    if [ -n "$line_num" ]; then
+      _zone_id=$(echo "$_zone_ids" | sed "${line_num}q;d")
+      _debug "Found relevant zone \"$_attempted_zone\" with id \"$_zone_id\" - will be used for domain \"$_domain\"."
+      return 0
+    fi
+
+    _debug "Zone \"$_attempted_zone\" doesn't exist, let's try a less specific zone."
+    _strip_counter=$(_math "$_strip_counter" + 1)
+  done
+}
+# vim: et:ts=2:sw=2:

+ 8 - 8
dnsapi/dns_infoblox.sh

@@ -41,10 +41,10 @@ dns_infoblox_add() {
   export _H2="Authorization: Basic $Infoblox_CredsEncoded"
   export _H2="Authorization: Basic $Infoblox_CredsEncoded"
 
 
   ## Add the challenge record to the Infoblox grid member
   ## Add the challenge record to the Infoblox grid member
-  result=$(_post "" "$baseurlnObject" "" "POST")
+  result="$(_post "" "$baseurlnObject" "" "POST")"
 
 
   ## Let's see if we get something intelligible back from the unit
   ## Let's see if we get something intelligible back from the unit
-  if echo "$result" | egrep "record:txt/.*:.*/$Infoblox_View"; then
+  if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/$Infoblox_View")" ]; then
     _info "Successfully created the txt record"
     _info "Successfully created the txt record"
     return 0
     return 0
   else
   else
@@ -66,7 +66,7 @@ dns_infoblox_rm() {
   _debug txtvalue "$txtvalue"
   _debug txtvalue "$txtvalue"
 
 
   ## Base64 encode the credentials
   ## Base64 encode the credentials
-  Infoblox_CredsEncoded=$(printf "%b" "$Infoblox_Creds" | _base64)
+  Infoblox_CredsEncoded="$(printf "%b" "$Infoblox_Creds" | _base64)"
 
 
   ## Construct the HTTP Authorization header
   ## Construct the HTTP Authorization header
   export _H1="Accept-Language:en-US"
   export _H1="Accept-Language:en-US"
@@ -74,17 +74,17 @@ dns_infoblox_rm() {
 
 
   ## Does the record exist?  Let's check.
   ## Does the record exist?  Let's check.
   baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=$Infoblox_View&_return_type=xml-pretty"
   baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=$Infoblox_View&_return_type=xml-pretty"
-  result=$(_get "$baseurlnObject")
+  result="$(_get "$baseurlnObject")"
 
 
   ## Let's see if we get something intelligible back from the grid
   ## Let's see if we get something intelligible back from the grid
-  if echo "$result" | egrep "record:txt/.*:.*/$Infoblox_View"; then
+  if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/$Infoblox_View")" ]; then
     ## Extract the object reference
     ## Extract the object reference
-    objRef=$(printf "%b" "$result" | _egrep_o "record:txt/.*:.*/$Infoblox_View")
+    objRef="$(printf "%b" "$result" | _egrep_o "record:txt/.*:.*/$Infoblox_View")"
     objRmUrl="https://$Infoblox_Server/wapi/v2.2.2/$objRef"
     objRmUrl="https://$Infoblox_Server/wapi/v2.2.2/$objRef"
     ## Delete them! All the stale records!
     ## Delete them! All the stale records!
-    rmResult=$(_post "" "$objRmUrl" "" "DELETE")
+    rmResult="$(_post "" "$objRmUrl" "" "DELETE")"
     ## Let's see if that worked
     ## Let's see if that worked
-    if echo "$rmResult" | egrep "record:txt/.*:.*/$Infoblox_View"; then
+    if [ "$(echo "$rmResult" | _egrep_o "record:txt/.*:.*/$Infoblox_View")" ]; then
       _info "Successfully deleted $objRef"
       _info "Successfully deleted $objRef"
       return 0
       return 0
     else
     else

+ 2 - 2
dnsapi/dns_linode.sh

@@ -68,7 +68,7 @@ dns_linode_rm() {
   _parameters="&DomainID=$_domain_id"
   _parameters="&DomainID=$_domain_id"
 
 
   if _rest GET "domain.resource.list" "$_parameters" && [ -n "$response" ]; then
   if _rest GET "domain.resource.list" "$_parameters" && [ -n "$response" ]; then
-    response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')"
+    response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")"
 
 
     resource="$(echo "$response" | _egrep_o "{.*\"NAME\":\s*\"$_sub_domain\".*}")"
     resource="$(echo "$response" | _egrep_o "{.*\"NAME\":\s*\"$_sub_domain\".*}")"
     if [ "$resource" ]; then
     if [ "$resource" ]; then
@@ -128,7 +128,7 @@ _get_root() {
   p=1
   p=1
 
 
   if _rest GET "domain.list"; then
   if _rest GET "domain.list"; then
-    response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')"
+    response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")"
     while true; do
     while true; do
       h=$(printf "%s" "$domain" | cut -d . -f $i-100)
       h=$(printf "%s" "$domain" | cut -d . -f $i-100)
       _debug h "$h"
       _debug h "$h"

+ 193 - 0
dnsapi/dns_namecom.sh

@@ -0,0 +1,193 @@
+#!/usr/bin/env sh
+
+#Author: RaidneII
+#Created 06/28/2017
+#Utilize name.com API to finish dns-01 verifications.
+########  Public functions #####################
+
+Namecom_API="https://api.name.com/api"
+
+#Usage: dns_namecom_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_namecom_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  # First we need name.com credentials.
+  if [ -z "$Namecom_Username" ]; then
+    Namecom_Username=""
+    _err "Username for name.com is missing."
+    _err "Please specify that in your environment variable."
+    return 1
+  fi
+
+  if [ -z "$Namecom_Token" ]; then
+    Namecom_Token=""
+    _err "API token for name.com is missing."
+    _err "Please specify that in your environment variable."
+    return 1
+  fi
+
+  # Save them in configuration.
+  _saveaccountconf Namecom_Username "$Namecom_Username"
+  _saveaccountconf Namecom_Token "$Namecom_Token"
+
+  # Login in using API
+  if ! _namecom_login; then
+    return 1
+  fi
+
+  # Find domain in domain list.
+  if ! _namecom_get_root "$fulldomain"; then
+    _err "Unable to find domain specified."
+    _namecom_logout
+    return 1
+  fi
+
+  # Add TXT record.
+  _namecom_addtxt_json="{\"hostname\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":\"300\",\"priority\":\"10\"}"
+  if _namecom_rest POST "dns/create/$_domain" "$_namecom_addtxt_json"; then
+    retcode=$(printf "%s\n" "$response" | _egrep_o "\"code\":100")
+    if [ "$retcode" ]; then
+      _info "Successfully added TXT record, ready for validation."
+      _namecom_logout
+      return 0
+    else
+      _err "Unable to add the DNS record."
+      _namecom_logout
+      return 1
+    fi
+  fi
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_namecom_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if ! _namecom_login; then
+    return 1
+  fi
+
+  # Find domain in domain list.
+  if ! _namecom_get_root "$fulldomain"; then
+    _err "Unable to find domain specified."
+    _namecom_logout
+    return 1
+  fi
+
+  # Get the record id.
+  if _namecom_rest GET "dns/list/$_domain"; then
+    retcode=$(printf "%s\n" "$response" | _egrep_o "\"code\":100")
+    if [ "$retcode" ]; then
+      _record_id=$(printf "%s\n" "$response" | _egrep_o "\"record_id\":\"[0-9]+\",\"name\":\"$fulldomain\",\"type\":\"TXT\"" | cut -d \" -f 4)
+      _debug record_id "$_record_id"
+      _info "Successfully retrieved the record id for ACME challenge."
+    else
+      _err "Unable to retrieve the record id."
+      _namecom_logout
+      return 1
+    fi
+  fi
+
+  # Remove the DNS record using record id.
+  _namecom_rmtxt_json="{\"record_id\":\"$_record_id\"}"
+  if _namecom_rest POST "dns/delete/$_domain" "$_namecom_rmtxt_json"; then
+    retcode=$(printf "%s\n" "$response" | _egrep_o "\"code\":100")
+    if [ "$retcode" ]; then
+      _info "Successfully removed the TXT record."
+      _namecom_logout
+      return 0
+    else
+      _err "Unable to remove the DNS record."
+      _namecom_logout
+      return 1
+    fi
+  fi
+}
+
+####################  Private functions below ##################################
+_namecom_rest() {
+  method=$1
+  param=$2
+  data=$3
+
+  export _H1="Content-Type: application/json"
+  export _H2="Api-Session-Token: $sessionkey"
+  if [ "$method" != "GET" ]; then
+    response="$(_post "$data" "$Namecom_API/$param" "" "$method")"
+  else
+    response="$(_get "$Namecom_API/$param")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $param"
+    return 1
+  fi
+
+  _debug2 response "$response"
+  return 0
+}
+
+_namecom_login() {
+  namecom_login_json="{\"username\":\"$Namecom_Username\",\"api_token\":\"$Namecom_Token\"}"
+
+  if _namecom_rest POST "login" "$namecom_login_json"; then
+    retcode=$(printf "%s\n" "$response" | _egrep_o "\"code\":100")
+    if [ "$retcode" ]; then
+      _info "Successfully logged in. Fetching session token..."
+      sessionkey=$(printf "%s\n" "$response" | _egrep_o "\"session_token\":\".+" | cut -d \" -f 4)
+      if [ ! -z "$sessionkey" ]; then
+        _debug sessionkey "$sessionkey"
+        _info "Session key obtained."
+      else
+        _err "Unable to get session key."
+        return 1
+      fi
+    else
+      _err "Logging in failed."
+      return 1
+    fi
+  fi
+}
+
+_namecom_logout() {
+  if _namecom_rest GET "logout"; then
+    retcode=$(printf "%s\n" "$response" | _egrep_o "\"code\":100")
+    if [ "$retcode" ]; then
+      _info "Successfully logged out."
+    else
+      _err "Error logging out."
+      return 1
+    fi
+  fi
+}
+
+_namecom_get_root() {
+  domain=$1
+  i=2
+  p=1
+
+  if ! _namecom_rest GET "domain/list"; then
+    return 1
+  fi
+
+  # Need to exclude the last field (tld)
+  numfields=$(echo "$domain" | _egrep_o "\." | wc -l)
+  while [ $i -le "$numfields" ]; do
+    host=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug host "$host"
+    if [ -z "$host" ]; then
+      return 1
+    fi
+
+    if _contains "$response" "$host"; then
+      _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+      _domain="$host"
+      return 0
+    fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}

+ 107 - 0
dnsapi/dns_yandex.sh

@@ -0,0 +1,107 @@
+#!/usr/bin/env sh
+# Author: non7top@gmail.com
+# 07 Jul 2017
+# report bugs at https://github.com/non7top/acme.sh
+
+# Values to export:
+# export PDD_Token="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+
+########  Public functions #####################
+
+#Usage: dns_myapi_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_yandex_add() {
+  fulldomain="${1}"
+  txtvalue="${2}"
+  _debug "Calling: dns_yandex_add() '${fulldomain}' '${txtvalue}'"
+  _PDD_credentials || return 1
+  export _H1="PddToken: $PDD_Token"
+
+  curDomain=$(_PDD_get_domain "$fulldomain")
+  _debug "Found suitable domain in pdd: $curDomain"
+  curSubdomain="$(echo "${fulldomain}" | sed -e "s@.${curDomain}\$@@")"
+  curData="domain=${curDomain}&type=TXT&subdomain=${curSubdomain}&ttl=360&content=${txtvalue}"
+  curUri="https://pddimp.yandex.ru/api2/admin/dns/add"
+  curResult="$(_post "${curData}" "${curUri}")"
+  _debug "Result: $curResult"
+}
+
+#Usage: dns_myapi_rm   _acme-challenge.www.domain.com
+dns_yandex_rm() {
+  fulldomain="${1}"
+  _debug "Calling: dns_yandex_rm() '${fulldomain}'"
+  _PDD_credentials || return 1
+  export _H1="PddToken: $PDD_Token"
+  record_id=$(pdd_get_record_id "${fulldomain}")
+  _debug "Result: $record_id"
+
+  curDomain=$(_PDD_get_domain "$fulldomain")
+  _debug "Found suitable domain in pdd: $curDomain"
+  curSubdomain="$(echo "${fulldomain}" | sed -e "s@.${curDomain}\$@@")"
+
+  curUri="https://pddimp.yandex.ru/api2/admin/dns/del"
+  curData="domain=${curDomain}&record_id=${record_id}"
+  curResult="$(_post "${curData}" "${curUri}")"
+  _debug "Result: $curResult"
+}
+
+####################  Private functions below ##################################
+
+_PDD_get_domain() {
+  fulldomain="${1}"
+  __page=1
+  __last=0
+  while [ $__last -eq 0 ]; do
+    uri1="https://pddimp.yandex.ru/api2/admin/domain/domains?page=${__page}&on_page=20"
+    res1=$(_get "$uri1" | _normalizeJson)
+    #_debug "$res1"
+    __found=$(echo "$res1" | sed -n -e 's#.* "found": \([^,]*\),.*#\1#p')
+    _debug "found: $__found results on page"
+    if [ "$__found" -lt 20 ]; then
+      _debug "last page: $__page"
+      __last=1
+    fi
+
+    __all_domains="$__all_domains $(echo "$res1" | sed -e "s@,@\n@g" | grep '"name"' | cut -d: -f2 | sed -e 's@"@@g')"
+
+    __page=$(_math $__page + 1)
+  done
+
+  k=2
+  while [ $k -lt 10 ]; do
+    __t=$(echo "$fulldomain" | cut -d . -f $k-100)
+    _debug "finding zone for domain $__t"
+    for d in $__all_domains; do
+      if [ "$d" = "$__t" ]; then
+        echo "$__t"
+        return
+      fi
+    done
+    k=$(_math $k + 1)
+  done
+  _err "No suitable domain found in your account"
+  return 1
+}
+
+_PDD_credentials() {
+  if [ -z "${PDD_Token}" ]; then
+    PDD_Token=""
+    _err "You need to export PDD_Token=xxxxxxxxxxxxxxxxx"
+    _err "You can get it at https://pddimp.yandex.ru/api2/admin/get_token"
+    return 1
+  else
+    _saveaccountconf PDD_Token "${PDD_Token}"
+  fi
+}
+
+pdd_get_record_id() {
+  fulldomain="${1}"
+
+  curDomain=$(_PDD_get_domain "$fulldomain")
+  _debug "Found suitable domain in pdd: $curDomain"
+  curSubdomain="$(echo "${fulldomain}" | sed -e "s@.${curDomain}\$@@")"
+
+  curUri="https://pddimp.yandex.ru/api2/admin/dns/list?domain=${curDomain}"
+  curResult="$(_get "${curUri}" | _normalizeJson)"
+  _debug "Result: $curResult"
+  echo "$curResult" | _egrep_o "{[^{]*\"content\":[^{]*\"subdomain\":\"${curSubdomain}\"" | sed -n -e 's#.* "record_id": \(.*\),[^,]*#\1#p'
+}