Browse Source

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

David Kerr 7 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 git diff --exit-code && echo "shfmt OK" ; 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 ..
   - 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

+ 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 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 \ 
   version \
@@ -44,15 +44,17 @@ RUN for verb in help \
   create-domain-key \
   createCSR \
   deactivate \
+  deactivate-account \
   ; 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} \
   ; done
 
 RUN printf "%b" '#!'"/usr/bin/env sh\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 \
- /root/.acme.sh/acme.sh --config-home /acme.sh \"\$@\"\n \
+ exec -- \"\$@\"\n \
 fi" >/entry.sh && chmod +x /entry.sh
 
 VOLUME /acme.sh

+ 8 - 1
README.md

@@ -296,6 +296,9 @@ acme.sh --renew -d example.com
 
 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
 
@@ -331,7 +334,11 @@ You don't have to do anything manually!
 1. Dynu API (https://www.dynu.com)
 1. DNSimple 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: 

+ 211 - 57
acme.sh

@@ -366,6 +366,7 @@ _hasfield() {
   return 1 #not contains
 }
 
+# str index [sep]
 _getfield() {
   _str="$1"
   _findex="$2"
@@ -453,7 +454,7 @@ if [ "$(printf '\x41')" != 'A' ]; then
 fi
 
 _ESCAPE_XARGS=""
-if [ "$(printf %s '\\x41' | xargs printf)" = 'A' ]; then
+if _exists xargs && [ "$(printf %s '\\x41' | xargs printf)" = 'A' ]; then
   _ESCAPE_XARGS=1
 fi
 
@@ -925,7 +926,7 @@ _sign() {
 
 }
 
-#keylength
+#keylength or isEcc flag (empty str => not ecc)
 _isEccKey() {
   _length="$1"
 
@@ -1138,7 +1139,12 @@ _readKeyLengthFromCSR() {
     echo "$_outcsr" | tr "\t" " " | _egrep_o "^ *ASN1 OID:.*" | cut -d ':' -f 2 | tr -d ' '
   else
     _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
 }
 
@@ -1147,7 +1153,7 @@ _ss() {
 
   if _exists "ss"; then
     _debug "Using: ss"
-    ss -ntpl | grep ":$_port "
+    ss -ntpl 2>/dev/null | grep ":$_port "
     return 0
   fi
 
@@ -1176,6 +1182,28 @@ _ss() {
   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]
 toPkcs() {
   domain="$1"
@@ -1189,11 +1217,7 @@ toPkcs() {
 
   _initpath "$domain" "$_isEcc"
 
-  if [ "$pfxPassword" ]; then
-    ${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
+  _toPkcs "$CERT_PFX_PATH" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$pfxPassword"
 
   if [ "$?" = "0" ]; then
     _info "Success, Pfx is exported to: $CERT_PFX_PATH"
@@ -1276,7 +1300,7 @@ createDomainKey() {
 
   _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
       _savedomainconf Le_Keylength "$_cdl"
       _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_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_RES="new-cert"
     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"
   fi
 
@@ -2211,16 +2237,22 @@ _initAPI() {
     export ACME_NEW_AUTHZ
 
     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
       ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'new-order" *: *"[^"]*"' | cut -d '"' -f 3)
+      ACME_NEW_ORDER_RES="new-order"
     fi
     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_RES="new-reg"
     if [ -z "$ACME_NEW_ACCOUNT" ]; then
       ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'new-account" *: *"[^"]*"' | cut -d '"' -f 3)
+      ACME_NEW_ACCOUNT_RES="new-account"
     fi
     export ACME_NEW_ACCOUNT
+    export ACME_NEW_ACCOUNT_RES
 
     ACME_REVOKE_CERT=$(echo "$response" | _egrep_o 'revoke-cert" *: *"[^"]*"' | cut -d '"' -f 3)
     export ACME_REVOKE_CERT
@@ -2237,7 +2269,7 @@ _initAPI() {
   _debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT"
 }
 
-#[domain]  [keylength]
+#[domain]  [keylength or isEcc flag]
 _initpath() {
 
   __initHome
@@ -2994,9 +3026,9 @@ _on_issue_err() {
   fi
 
   #trigger the validation to flush the pending authz
+  _debug2 "_chk_vlist" "$_chk_vlist"
   if [ "$_chk_vlist" ]; then
     (
-      _debug2 "_chk_vlist" "$_chk_vlist"
       _debug2 "start to deactivate authz"
       ventries=$(echo "$_chk_vlist" | tr "$dvsep" ' ')
       for ventry in $ventries; do
@@ -3068,14 +3100,13 @@ _regAccount() {
   _initpath
   _reg_length="$1"
 
+  mkdir -p "$CA_DIR"
   if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then
-    mkdir -p "$CA_DIR"
     _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
-    mkdir -p "$CA_DIR"
     _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH"
     mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH"
   fi
@@ -3092,7 +3123,7 @@ _regAccount() {
   fi
   _initAPI
   _updateTos=""
-  _reg_res="new-reg"
+  _reg_res="$ACME_NEW_ACCOUNT_RES"
   while true; do
     _debug AGREEMENT "$AGREEMENT"
 
@@ -3122,7 +3153,7 @@ _regAccount() {
 
       _accUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
       _debug "_accUri" "$_accUri"
-
+      _savecaconf "ACCOUNT_URL" "$_accUri"
       _tos="$(echo "$responseHeaders" | grep "^Link:.*rel=\"terms-of-service\"" | _head_n 1 | _egrep_o "<.*>" | tr -d '<>')"
       _debug "_tos" "$_tos"
       if [ -z "$_tos" ]; then
@@ -3143,11 +3174,14 @@ _regAccount() {
         return 1
       fi
       if [ "$code" = '202' ]; then
-        _info "Update success."
+        _info "Update account tos info success."
 
         CA_KEY_HASH="$(__calcAccountKeyHash)"
         _debug "Calc 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
         _err "Update account error."
         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
 _findHook() {
   _hookdomain="$1"
@@ -3350,7 +3446,7 @@ issue() {
   else
     _key=$(_readdomainconf Le_Keylength)
     _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
         _err "Create domain key error."
         _clearup
@@ -3465,11 +3561,11 @@ issue() {
         if [ "$d_api" ]; then
           _info "Found domain api file: $d_api"
         else
-          _err "Add the following TXT record:"
-          _err "Domain: '$(__green "$txtdomain")'"
-          _err "TXT value: '$(__green "$txt")'"
-          _err "Please be aware that you prepend _acme-challenge. before your domain"
-          _err "so the resulting subdomain will be: $txtdomain"
+          _info "$(__red "Add the following TXT record:")"
+          _info "$(__red "Domain: '$(__green "$txtdomain")'")"
+          _info "$(__red "TXT value: '$(__green "$txt")'")"
+          _info "$(__red "Please be aware that you prepend _acme-challenge. before your domain")"
+          _info "$(__red "so the resulting subdomain will be: $txtdomain")"
           continue
         fi
 
@@ -3493,7 +3589,7 @@ issue() {
 
         if [ "$?" != "0" ]; then
           _clearup
-          _on_issue_err "$_post_hook"
+          _on_issue_err "$_post_hook" "$vlist"
           return 1
         fi
         dnsadded='1'
@@ -3756,7 +3852,7 @@ issue() {
   _info "Verify finished, start to sign."
   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."
     _on_issue_err "$_post_hook"
     return 1
@@ -3880,6 +3976,12 @@ issue() {
     _cleardomainconf Le_Listen_V4
   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_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
@@ -3949,6 +4051,11 @@ renew() {
     return "$RENEW_SKIP"
   fi
 
+  if [ "$IN_CRON" = "1" ] && [ -z "$Le_CertCreateTime" ]; then
+    _info "Skip invalid cert for: $Le_Domain"
+    return 0
+  fi
+
   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"
   res="$?"
@@ -4474,26 +4581,51 @@ _deactivate() {
   _d_type="$2"
   _initpath
 
-  _d_i=0
-  _d_max_retry=9
-  while [ "$_d_i" -lt "$_d_max_retry" ]; do
-    _info "Deactivate: $_d_domain"
-    _d_i="$(_math $_d_i + 1)"
+  if ! __get_domain_new_authz "$_d_domain"; then
+    _err "Can not get domain new authz token."
+    return 1
+  fi
 
-    if ! __get_domain_new_authz "$_d_domain"; then
-      _err "Can not get domain new authz token."
-      return 1
-    fi
+  authzUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
+  _debug "authzUri" "$authzUri"
 
-    authzUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
-    _debug "authzUri" "$authzUri"
+  if [ "$code" ] && [ ! "$code" = '201' ]; then
+    _err "new-authz error: $response"
+    return 1
+  fi
 
-    if [ ! -z "$code" ] && [ ! "$code" = '201' ]; then
-      _err "new-authz error: $response"
+  entries="$(echo "$response" | _egrep_o '{ *"type":"[^"]*", *"status": *"valid", *"uri"[^}]*')"
+  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
     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"
 
     if [ -z "$entry" ]; then
@@ -4515,16 +4647,16 @@ _deactivate() {
 
     _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."
-      return 1
+      break
     fi
 
-    _info "Deactivate: $_vtype success."
-
   done
   _debug "$_d_i"
-  if [ "$_d_i" -lt "$_d_max_retry" ]; then
+  if [ "$_d_i" -eq "$_d_max_retry" ]; then
     _info "Deactivated success!"
   else
     _err "Deactivate failed."
@@ -4584,9 +4716,7 @@ _detect_profile() {
     fi
   fi
 
-  if [ ! -z "$DETECTED_PROFILE" ]; then
-    echo "$DETECTED_PROFILE"
-  fi
+  echo "$DETECTED_PROFILE"
 }
 
 _initconf() {
@@ -4674,6 +4804,8 @@ _installalias() {
   _setopt "$_envfile" "export LE_WORKING_DIR" "=" "\"$LE_WORKING_DIR\""
   if [ "$_c_home" ]; then
     _setopt "$_envfile" "export LE_CONFIG_HOME" "=" "\"$LE_CONFIG_HOME\""
+  else
+    _sed_i "/^export LE_CONFIG_HOME/d" "$_envfile"
   fi
   _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\""
     if [ "$_c_home" ]; then
       _setopt "$_cshfile" "setenv LE_CONFIG_HOME" " " "\"$LE_CONFIG_HOME\""
+    else
+      _sed_i "/^setenv LE_CONFIG_HOME/d" "$_cshfile"
     fi
     _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
     _setopt "$_csh_profile" "source \"$_cshfile\""
@@ -4759,20 +4893,24 @@ install() {
 
   _info "Installing to $LE_WORKING_DIR"
 
-  if ! mkdir -p "$LE_WORKING_DIR"; then
-    _err "Can not create working dir: $LE_WORKING_DIR"
-    return 1
+  if [ ! -d "$LE_WORKING_DIR" ]; then
+    if ! mkdir -p "$LE_WORKING_DIR"; then
+      _err "Can not create working dir: $LE_WORKING_DIR"
+      return 1
+    fi
+
+    chmod 700 "$LE_WORKING_DIR"
   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
-    _err "Can not create config dir: $LE_CONFIG_HOME"
-    return 1
+    chmod 700 "$LE_CONFIG_HOME"
   fi
 
-  chmod 700 "$LE_CONFIG_HOME"
-
   cp "$PROJECT_ENTRY" "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/$PROJECT_ENTRY"
 
   if [ "$?" != "0" ]; then
@@ -4930,6 +5068,7 @@ Commands:
   --toPkcs8                Convert to pkcs8 format.
   --update-account         Update account info.
   --register-account       Register account key.
+  --deactivate-account     Deactivate the account.
   --create-account-key     Create an account private key, professional use.
   --create-domain-key      Create an domain private key, 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.
   --deploy-hook                     The hook file to deploy cert
   --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.
   --listen-v4                       Force standalone/tls server to listen at ipv4.
   --listen-v6                       Force standalone/tls server to listen at ipv6.
@@ -5209,6 +5349,9 @@ _process() {
       --registeraccount | --register-account)
         _CMD="registeraccount"
         ;;
+      --deactivate-account)
+        _CMD="deactivateaccount"
+        ;;
       --domain | -d)
         _dvalue="$2"
 
@@ -5315,7 +5458,7 @@ _process() {
         ;;
       --dns)
         wvalue="dns"
-        if ! _startswith "$2" "-"; then
+        if [ "$2" ] && ! _startswith "$2" "-"; then
           wvalue="$2"
           shift
         fi
@@ -5470,6 +5613,14 @@ _process() {
       --ocsp-must-staple | --ocsp)
         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="1"
         _logfile="$2"
@@ -5616,6 +5767,9 @@ _process() {
     updateaccount)
       updateaccount
       ;;
+    deactivateaccount)
+      deactivateaccount
+      ;;
     list)
       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
 ```
 
+## 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
 
 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)"
   _debug2 kServiceH "$kServiceH"
 
-  kSigningH="$(printf "aws4_request%s" | _hmac "$Hash" "$kServiceH" hex)"
+  kSigningH="$(printf "%s" "aws4_request" | _hmac "$Hash" "$kServiceH" hex)"
   _debug2 kSigningH "$kSigningH"
 
   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"
 
   ## 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
-  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"
     return 0
   else
@@ -66,7 +66,7 @@ dns_infoblox_rm() {
   _debug txtvalue "$txtvalue"
 
   ## Base64 encode the credentials
-  Infoblox_CredsEncoded=$(printf "%b" "$Infoblox_Creds" | _base64)
+  Infoblox_CredsEncoded="$(printf "%b" "$Infoblox_Creds" | _base64)"
 
   ## Construct the HTTP Authorization header
   export _H1="Accept-Language:en-US"
@@ -74,17 +74,17 @@ dns_infoblox_rm() {
 
   ## 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"
-  result=$(_get "$baseurlnObject")
+  result="$(_get "$baseurlnObject")"
 
   ## 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
-    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"
     ## Delete them! All the stale records!
-    rmResult=$(_post "" "$objRmUrl" "" "DELETE")
+    rmResult="$(_post "" "$objRmUrl" "" "DELETE")"
     ## 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"
       return 0
     else

+ 2 - 2
dnsapi/dns_linode.sh

@@ -68,7 +68,7 @@ dns_linode_rm() {
   _parameters="&DomainID=$_domain_id"
 
   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\".*}")"
     if [ "$resource" ]; then
@@ -128,7 +128,7 @@ _get_root() {
   p=1
 
   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
       h=$(printf "%s" "$domain" | cut -d . -f $i-100)
       _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'
+}