Browse Source

Merge branch 'dev' of https://github.com/Neilpang/acme.sh into dev

# Conflicts:
#	README.md
#	dnsapi/README.md
Ne-Lexa 6 years ago
parent
commit
412f85b665

+ 35 - 15
README.md

@@ -70,11 +70,16 @@ For all build statuses, check our [weekly build project](https://github.com/Neil
 
 
 https://github.com/Neilpang/acmetest
 https://github.com/Neilpang/acmetest
 
 
+# Supported CA
+
+- Letsencrypt.org CA(default)
+- [BuyPass.com CA](https://github.com/Neilpang/acme.sh/wiki/BuyPass.com-CA)
 
 
 # Supported modes
 # Supported modes
 
 
 - Webroot mode
 - Webroot mode
 - Standalone mode
 - Standalone mode
+- Standalone tls-alpn mode
 - Apache mode
 - Apache mode
 - Nginx mode
 - Nginx mode
 - DNS mode
 - DNS mode
@@ -221,8 +226,20 @@ acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com
 
 
 More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
 More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
 
 
+# 5. Use Standalone ssl server to issue cert
+
+**(requires you to be root/sudoer or have permission to listen on port 443 (TCP))**
+
+Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again.
+
+```bash
+acme.sh --issue --alpn -d example.com -d www.example.com -d cp.example.com
+```
+
+More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
+
 
 
-# 5. Use Apache mode
+# 6. Use Apache mode
 
 
 **(requires you to be root/sudoer, since it is required to interact with Apache server)**
 **(requires you to be root/sudoer, since it is required to interact with Apache server)**
 
 
@@ -242,7 +259,7 @@ We don't want to mess your apache server, don't worry.**
 
 
 More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
 More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
 
 
-# 6. Use Nginx mode
+# 7. Use Nginx mode
 
 
 **(requires you to be root/sudoer, since it is required to interact with Nginx server)**
 **(requires you to be root/sudoer, since it is required to interact with Nginx server)**
 
 
@@ -266,7 +283,7 @@ We don't want to mess your nginx server, don't worry.**
 
 
 More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
 More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
 
 
-# 7. Automatic DNS API integration
+# 8. Automatic DNS API integration
 
 
 If your DNS provider supports API access, we can use that API to automatically issue the certs.
 If your DNS provider supports API access, we can use that API to automatically issue the certs.
 
 
@@ -331,7 +348,10 @@ You don't have to do anything manually!
 1. hosting.de (https://www.hosting.de)
 1. hosting.de (https://www.hosting.de)
 1. Neodigit.net API (https://www.neodigit.net)
 1. Neodigit.net API (https://www.neodigit.net)
 1. Exoscale.com API (https://www.exoscale.com/)
 1. Exoscale.com API (https://www.exoscale.com/)
-1. Internet.bs API (https://internetbs.net/)
+1. PointDNS API (https://pointhq.com/)
+1. Active24.cz API (https://www.active24.cz/)
+1. do.de API (https://www.do.de/)
+1. internetbs.net API (https://internetbs.net/)
 
 
 And:
 And:
 
 
@@ -345,7 +365,7 @@ If your DNS provider is not on the supported list above, you can write your own
 
 
 For more details: [How to use DNS API](dnsapi)
 For more details: [How to use DNS API](dnsapi)
 
 
-# 8. Use DNS manual mode:
+# 9. Use DNS manual mode:
 
 
 See: https://github.com/Neilpang/acme.sh/wiki/dns-manual-mode first.
 See: https://github.com/Neilpang/acme.sh/wiki/dns-manual-mode first.
 
 
@@ -381,7 +401,7 @@ Ok, it's done.
 
 
 **Please use dns api mode instead.**
 **Please use dns api mode instead.**
 
 
-# 9. Issue ECC certificates
+# 10. Issue ECC certificates
 
 
 `Let's Encrypt` can now issue **ECDSA** certificates.
 `Let's Encrypt` can now issue **ECDSA** certificates.
 
 
@@ -413,7 +433,7 @@ Valid values are:
 
 
 
 
 
 
-# 10. Issue Wildcard certificates
+# 11. Issue Wildcard certificates
 
 
 It's simple, just give a wildcard domain as the `-d` parameter.
 It's simple, just give a wildcard domain as the `-d` parameter.
 
 
@@ -423,7 +443,7 @@ acme.sh  --issue -d example.com  -d '*.example.com'  --dns dns_cf
 
 
 
 
 
 
-# 11. How to renew the certs
+# 12. How to renew the certs
 
 
 No, you don't need to renew the certs manually. All the certs will be renewed automatically every **60** days.
 No, you don't need to renew the certs manually. All the certs will be renewed automatically every **60** days.
 
 
@@ -440,7 +460,7 @@ acme.sh --renew -d example.com --force --ecc
 ```
 ```
 
 
 
 
-# 12. How to stop cert renewal
+# 13. How to stop cert renewal
 
 
 To stop renewal of a cert, you can execute the following to remove the cert from the renewal list:
 To stop renewal of a cert, you can execute the following to remove the cert from the renewal list:
 
 
@@ -453,7 +473,7 @@ The cert/key file is not removed from the disk.
 You can remove the respective directory (e.g. `~/.acme.sh/example.com`) by yourself.
 You can remove the respective directory (e.g. `~/.acme.sh/example.com`) by yourself.
 
 
 
 
-# 13. How to upgrade `acme.sh`
+# 14. How to upgrade `acme.sh`
 
 
 acme.sh is in constant development, so it's strongly recommended to use the latest code.
 acme.sh is in constant development, so it's strongly recommended to use the latest code.
 
 
@@ -478,25 +498,25 @@ acme.sh --upgrade --auto-upgrade 0
 ```
 ```
 
 
 
 
-# 14. Issue a cert from an existing CSR
+# 15. Issue a cert from an existing CSR
 
 
 https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR
 https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR
 
 
 
 
-# 15. Under the Hood
+# 16. Under the Hood
 
 
 Speak ACME language using shell, directly to "Let's Encrypt".
 Speak ACME language using shell, directly to "Let's Encrypt".
 
 
 TODO:
 TODO:
 
 
 
 
-# 16. Acknowledgments
+# 17. Acknowledgments
 
 
 1. Acme-tiny: https://github.com/diafygi/acme-tiny
 1. Acme-tiny: https://github.com/diafygi/acme-tiny
 2. ACME protocol: https://github.com/ietf-wg-acme/acme
 2. ACME protocol: https://github.com/ietf-wg-acme/acme
 
 
 
 
-# 17. License & Others
+# 18. License & Others
 
 
 License is GPLv3
 License is GPLv3
 
 
@@ -505,7 +525,7 @@ Please Star and Fork me.
 [Issues](https://github.com/Neilpang/acme.sh/issues) and [pull requests](https://github.com/Neilpang/acme.sh/pulls) are welcome.
 [Issues](https://github.com/Neilpang/acme.sh/issues) and [pull requests](https://github.com/Neilpang/acme.sh/pulls) are welcome.
 
 
 
 
-# 18. Donate
+# 19. Donate
 Your donation makes **acme.sh** better:
 Your donation makes **acme.sh** better:
 
 
 1. PayPal/Alipay(支付宝)/Wechat(微信): [https://donate.acme.sh/](https://donate.acme.sh/)
 1. PayPal/Alipay(支付宝)/Wechat(微信): [https://donate.acme.sh/](https://donate.acme.sh/)

+ 36 - 71
acme.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 #!/usr/bin/env sh
 
 
-VER=2.8.0
+VER=2.8.1
 
 
 PROJECT_NAME="acme.sh"
 PROJECT_NAME="acme.sh"
 
 
@@ -35,19 +35,16 @@ _OLD_STAGE_CA_HOST="https://acme-staging.api.letsencrypt.org"
 
 
 VTYPE_HTTP="http-01"
 VTYPE_HTTP="http-01"
 VTYPE_DNS="dns-01"
 VTYPE_DNS="dns-01"
-VTYPE_TLS="tls-sni-01"
-VTYPE_TLS2="tls-sni-02"
 VTYPE_ALPN="tls-alpn-01"
 VTYPE_ALPN="tls-alpn-01"
 
 
 LOCAL_ANY_ADDRESS="0.0.0.0"
 LOCAL_ANY_ADDRESS="0.0.0.0"
 
 
-MAX_RENEW=60
+DEFAULT_RENEW=60
 
 
 DEFAULT_DNS_SLEEP=120
 DEFAULT_DNS_SLEEP=120
 
 
 NO_VALUE="no"
 NO_VALUE="no"
 
 
-W_TLS="tls"
 W_DNS="dns"
 W_DNS="dns"
 W_ALPN="alpn"
 W_ALPN="alpn"
 DNS_ALIAS_PREFIX="="
 DNS_ALIAS_PREFIX="="
@@ -1090,7 +1087,7 @@ _createcsr() {
   fi
   fi
 
 
   if [ "$acmeValidationv1" ]; then
   if [ "$acmeValidationv1" ]; then
-    printf "\n1.3.6.1.5.5.7.1.30.1=critical,DER:04:20:${acmeValidationv1}" >>"${csrconf}"
+    printf "\n1.3.6.1.5.5.7.1.31=critical,DER:04:20:${acmeValidationv1}" >>"${csrconf}"
   fi
   fi
 
 
   _csr_cn="$(_idn "$domain")"
   _csr_cn="$(_idn "$domain")"
@@ -1875,11 +1872,7 @@ _send_signed_request() {
     sig="$(printf "%s" "$_sig_t" | _url_replace)"
     sig="$(printf "%s" "$_sig_t" | _url_replace)"
     _debug3 sig "$sig"
     _debug3 sig "$sig"
 
 
-    if [ "$ACME_VERSION" = "2" ]; then
-      body="{\"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}"
-    else
-      body="{\"header\": $JWK_HEADER, \"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}"
-    fi
+    body="{\"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}"
     _debug3 body "$body"
     _debug3 body "$body"
 
 
     response="$(_post "$body" "$url" "$needbase64" "POST" "$__request_conent_type")"
     response="$(_post "$body" "$url" "$needbase64" "POST" "$__request_conent_type")"
@@ -2926,7 +2919,10 @@ _clearup() {
 
 
 _clearupdns() {
 _clearupdns() {
   _debug "_clearupdns"
   _debug "_clearupdns"
-  if [ "$dnsadded" != 1 ] || [ -z "$vlist" ]; then
+  _debug "dnsadded" "$dnsadded"
+  _debug "vlist" "$vlist"
+  #dnsadded is "0" or "1" means dns-01 method was used for at least one domain
+  if [ -z "$dnsadded" ] || [ -z "$vlist" ]; then
     _debug "skip dns."
     _debug "skip dns."
     return
     return
   fi
   fi
@@ -3082,8 +3078,8 @@ _on_before_issue() {
         _savedomainconf "Le_HTTPPort" "$Le_HTTPPort"
         _savedomainconf "Le_HTTPPort" "$Le_HTTPPort"
       fi
       fi
       _checkport="$Le_HTTPPort"
       _checkport="$Le_HTTPPort"
-    elif [ "$_currentRoot" = "$W_TLS" ] || [ "$_currentRoot" = "$W_ALPN" ]; then
-      _info "Standalone tls/alpn mode."
+    elif [ "$_currentRoot" = "$W_ALPN" ]; then
+      _info "Standalone alpn mode."
       if [ -z "$Le_TLSPort" ]; then
       if [ -z "$Le_TLSPort" ]; then
         Le_TLSPort=443
         Le_TLSPort=443
       else
       else
@@ -3443,15 +3439,17 @@ __get_domain_new_authz() {
 
 
 #uri keyAuthorization
 #uri keyAuthorization
 __trigger_validation() {
 __trigger_validation() {
-  _debug2 "tigger domain validation."
+  _debug2 "Trigger domain validation."
   _t_url="$1"
   _t_url="$1"
   _debug2 _t_url "$_t_url"
   _debug2 _t_url "$_t_url"
   _t_key_authz="$2"
   _t_key_authz="$2"
   _debug2 _t_key_authz "$_t_key_authz"
   _debug2 _t_key_authz "$_t_key_authz"
+  _t_vtype="$3"
+  _debug2 _t_vtype "$_t_vtype"
   if [ "$ACME_VERSION" = "2" ]; then
   if [ "$ACME_VERSION" = "2" ]; then
     _send_signed_request "$_t_url" "{\"keyAuthorization\": \"$_t_key_authz\"}"
     _send_signed_request "$_t_url" "{\"keyAuthorization\": \"$_t_key_authz\"}"
   else
   else
-    _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"keyAuthorization\": \"$_t_key_authz\"}"
+    _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"type\": \"$_t_vtype\", \"keyAuthorization\": \"$_t_key_authz\"}"
   fi
   fi
 }
 }
 
 
@@ -3654,7 +3652,7 @@ issue() {
       _authorizations_map=""
       _authorizations_map=""
       for _authz_url in $(echo "$_authorizations_seg" | tr ',' ' '); do
       for _authz_url in $(echo "$_authorizations_seg" | tr ',' ' '); do
         _debug2 "_authz_url" "$_authz_url"
         _debug2 "_authz_url" "$_authz_url"
-        if ! response="$(_get "$_authz_url")"; then
+        if ! _send_signed_request "$_authz_url"; then
           _err "get to authz error."
           _err "get to authz error."
           _err "_authorizations_seg" "$_authorizations_seg"
           _err "_authorizations_seg" "$_authorizations_seg"
           _err "_authz_url" "$_authz_url"
           _err "_authz_url" "$_authz_url"
@@ -3701,14 +3699,6 @@ $_authorizations_map"
         vtype="$VTYPE_DNS"
         vtype="$VTYPE_DNS"
       fi
       fi
 
 
-      if [ "$_currentRoot" = "$W_TLS" ]; then
-        if [ "$ACME_VERSION" = "2" ]; then
-          vtype="$VTYPE_TLS2"
-        else
-          vtype="$VTYPE_TLS"
-        fi
-      fi
-
       if [ "$_currentRoot" = "$W_ALPN" ]; then
       if [ "$_currentRoot" = "$W_ALPN" ]; then
         vtype="$VTYPE_ALPN"
         vtype="$VTYPE_ALPN"
       fi
       fi
@@ -3861,8 +3851,8 @@ $_authorizations_map"
         )
         )
 
 
         if [ "$?" != "0" ]; then
         if [ "$?" != "0" ]; then
-          _clearup
           _on_issue_err "$_post_hook" "$vlist"
           _on_issue_err "$_post_hook" "$vlist"
+          _clearup
           return 1
           return 1
         fi
         fi
         dnsadded='1'
         dnsadded='1'
@@ -3873,8 +3863,8 @@ $_authorizations_map"
       _savedomainconf "Le_Vlist" "$vlist"
       _savedomainconf "Le_Vlist" "$vlist"
       _debug "Dns record not added yet, so, save to $DOMAIN_CONF and exit."
       _debug "Dns record not added yet, so, save to $DOMAIN_CONF and exit."
       _err "Please add the TXT records to the domains, and re-run with --renew."
       _err "Please add the TXT records to the domains, and re-run with --renew."
-      _clearup
       _on_issue_err "$_post_hook"
       _on_issue_err "$_post_hook"
+      _clearup
       return 1
       return 1
     fi
     fi
 
 
@@ -3908,7 +3898,7 @@ $_authorizations_map"
       continue
       continue
     fi
     fi
 
 
-    _info "Verifying:$d"
+    _info "Verifying: $d"
     _debug "d" "$d"
     _debug "d" "$d"
     _debug "keyauthorization" "$keyauthorization"
     _debug "keyauthorization" "$keyauthorization"
     _debug "uri" "$uri"
     _debug "uri" "$uri"
@@ -3992,40 +3982,6 @@ $_authorizations_map"
         fi
         fi
 
 
       fi
       fi
-
-    elif [ "$vtype" = "$VTYPE_TLS" ]; then
-      #create A
-      #_hash_A="$(printf "%s" $token | _digest "sha256" "hex" )"
-      #_debug2 _hash_A "$_hash_A"
-      #_x="$(echo $_hash_A | cut -c 1-32)"
-      #_debug2 _x "$_x"
-      #_y="$(echo $_hash_A | cut -c 33-64)"
-      #_debug2 _y "$_y"
-      #_SAN_A="$_x.$_y.token.acme.invalid"
-      #_debug2 _SAN_A "$_SAN_A"
-
-      #create B
-      _hash_B="$(printf "%s" "$keyauthorization" | _digest "sha256" "hex")"
-      _debug2 _hash_B "$_hash_B"
-      _x="$(echo "$_hash_B" | cut -c 1-32)"
-      _debug2 _x "$_x"
-      _y="$(echo "$_hash_B" | cut -c 33-64)"
-      _debug2 _y "$_y"
-
-      #_SAN_B="$_x.$_y.ka.acme.invalid"
-
-      _SAN_B="$_x.$_y.acme.invalid"
-      _debug2 _SAN_B "$_SAN_B"
-
-      _ncaddr="$(_getfield "$_local_addr" "$_ncIndex")"
-      _ncIndex="$(_math "$_ncIndex" + 1)"
-      if ! _starttlsserver "$_SAN_B" "$_SAN_A" "$Le_TLSPort" "$keyauthorization" "$_ncaddr"; then
-        _err "Start tls server error."
-        _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
-        _clearup
-        _on_issue_err "$_post_hook" "$vlist"
-        return 1
-      fi
     elif [ "$vtype" = "$VTYPE_ALPN" ]; then
     elif [ "$vtype" = "$VTYPE_ALPN" ]; then
       acmevalidationv1="$(printf "%s" "$keyauthorization" | _digest "sha256" "hex")"
       acmevalidationv1="$(printf "%s" "$keyauthorization" | _digest "sha256" "hex")"
       _debug acmevalidationv1 "$acmevalidationv1"
       _debug acmevalidationv1 "$acmevalidationv1"
@@ -4038,7 +3994,7 @@ $_authorizations_map"
       fi
       fi
     fi
     fi
 
 
-    if ! __trigger_validation "$uri" "$keyauthorization"; then
+    if ! __trigger_validation "$uri" "$keyauthorization" "$vtype"; then
       _err "$d:Can not get challenge: $response"
       _err "$d:Can not get challenge: $response"
       _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
       _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
       _clearup
       _clearup
@@ -4047,7 +4003,7 @@ $_authorizations_map"
     fi
     fi
 
 
     if [ "$code" ] && [ "$code" != '202' ]; then
     if [ "$code" ] && [ "$code" != '202' ]; then
-      if [ "$ACME_VERSION" = "2" ] && [ "$code" = '200' ]; then
+      if [ "$code" = '200' ]; then
         _debug "trigger validation code: $code"
         _debug "trigger validation code: $code"
       else
       else
         _err "$d:Challenge error: $response"
         _err "$d:Challenge error: $response"
@@ -4076,7 +4032,11 @@ $_authorizations_map"
       _debug "sleep 2 secs to verify"
       _debug "sleep 2 secs to verify"
       sleep 2
       sleep 2
       _debug "checking"
       _debug "checking"
-      response="$(_get "$uri")"
+      if [ "$ACME_VERSION" = "2" ]; then
+        _send_signed_request "$uri"
+      else
+        response="$(_get "$uri")"
+      fi
       if [ "$?" != "0" ]; then
       if [ "$?" != "0" ]; then
         _err "$d:Verify error:$response"
         _err "$d:Verify error:$response"
         _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
         _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
@@ -4152,13 +4112,16 @@ $_authorizations_map"
     fi
     fi
     Le_LinkCert="$(echo "$response" | tr -d '\r\n' | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
     Le_LinkCert="$(echo "$response" | tr -d '\r\n' | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
 
 
-    if ! _get "$Le_LinkCert" >"$CERT_PATH"; then
+    _tempSignedResponse="$response"
+    if ! _send_signed_request "$Le_LinkCert" "" "needbase64"; then
       _err "Sign failed, can not download cert:$Le_LinkCert."
       _err "Sign failed, can not download cert:$Le_LinkCert."
       _err "$response"
       _err "$response"
       _on_issue_err "$_post_hook"
       _on_issue_err "$_post_hook"
       return 1
       return 1
     fi
     fi
 
 
+    echo "$response" | _dbase64 "multiline" >"$CERT_PATH"
+
     if [ "$(grep -- "$BEGIN_CERT" "$CERT_PATH" | wc -l)" -gt "1" ]; then
     if [ "$(grep -- "$BEGIN_CERT" "$CERT_PATH" | wc -l)" -gt "1" ]; then
       _debug "Found cert chain"
       _debug "Found cert chain"
       cat "$CERT_PATH" >"$CERT_FULLCHAIN_PATH"
       cat "$CERT_PATH" >"$CERT_FULLCHAIN_PATH"
@@ -4168,6 +4131,7 @@ $_authorizations_map"
       _end_n="$(_math $_end_n + 1)"
       _end_n="$(_math $_end_n + 1)"
       sed -n "${_end_n},9999p" "$CERT_FULLCHAIN_PATH" >"$CA_CERT_PATH"
       sed -n "${_end_n},9999p" "$CERT_FULLCHAIN_PATH" >"$CA_CERT_PATH"
     fi
     fi
+    response="$_tempSignedResponse"
   else
   else
     if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"$ACME_NEW_ORDER_RES\", \"csr\": \"$der\"}" "needbase64"; then
     if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"$ACME_NEW_ORDER_RES\", \"csr\": \"$der\"}" "needbase64"; then
       _err "Sign failed. $response"
       _err "Sign failed. $response"
@@ -4238,7 +4202,8 @@ $_authorizations_map"
       while [ "$_link_issuer_retry" -lt "$_MAX_ISSUER_RETRY" ]; do
       while [ "$_link_issuer_retry" -lt "$_MAX_ISSUER_RETRY" ]; do
         _debug _link_issuer_retry "$_link_issuer_retry"
         _debug _link_issuer_retry "$_link_issuer_retry"
         if [ "$ACME_VERSION" = "2" ]; then
         if [ "$ACME_VERSION" = "2" ]; then
-          if _get "$Le_LinkIssuer" >"$CA_CERT_PATH"; then
+          if _send_signed_request "$Le_LinkIssuer"; then
+            echo "$response" >"$CA_CERT_PATH"
             break
             break
           fi
           fi
         else
         else
@@ -4274,8 +4239,8 @@ $_authorizations_map"
   Le_CertCreateTimeStr=$(date -u)
   Le_CertCreateTimeStr=$(date -u)
   _savedomainconf "Le_CertCreateTimeStr" "$Le_CertCreateTimeStr"
   _savedomainconf "Le_CertCreateTimeStr" "$Le_CertCreateTimeStr"
 
 
-  if [ -z "$Le_RenewalDays" ] || [ "$Le_RenewalDays" -lt "0" ] || [ "$Le_RenewalDays" -gt "$MAX_RENEW" ]; then
-    Le_RenewalDays="$MAX_RENEW"
+  if [ -z "$Le_RenewalDays" ] || [ "$Le_RenewalDays" -lt "0" ]; then
+    Le_RenewalDays="$DEFAULT_RENEW"
   else
   else
     _savedomainconf "Le_RenewalDays" "$Le_RenewalDays"
     _savedomainconf "Le_RenewalDays" "$Le_RenewalDays"
   fi
   fi
@@ -4964,7 +4929,7 @@ _deactivate() {
 
 
     authzUri="$_authorizations_seg"
     authzUri="$_authorizations_seg"
     _debug2 "authzUri" "$authzUri"
     _debug2 "authzUri" "$authzUri"
-    if ! response="$(_get "$authzUri")"; then
+    if ! _send_signed_request "$authzUri"; then
       _err "get to authz error."
       _err "get to authz error."
       _err "_authorizations_seg" "$_authorizations_seg"
       _err "_authorizations_seg" "$_authorizations_seg"
       _err "authzUri" "$authzUri"
       _err "authzUri" "$authzUri"
@@ -5527,7 +5492,7 @@ Parameters:
   --useragent                       Specifies the user agent string. it will be saved for future use too.
   --useragent                       Specifies the user agent string. it will be saved for future use too.
   --accountemail                    Specifies the account email, only valid for the '--install' and '--update-account' command.
   --accountemail                    Specifies the account email, only valid for the '--install' and '--update-account' command.
   --accountkey                      Specifies the account key path, only valid for the '--install' command.
   --accountkey                      Specifies the account key path, only valid for the '--install' command.
-  --days                            Specifies the days to renew the cert when using '--issue' command. The max value is $MAX_RENEW days.
+  --days                            Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days.
   --httpport                        Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer.
   --httpport                        Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer.
   --tlsport                         Specifies the standalone tls listening port. Only valid if the server is behind a reverse proxy or load balancer.
   --tlsport                         Specifies the standalone tls listening port. Only valid if the server is behind a reverse proxy or load balancer.
   --local-address                   Specifies the standalone/tls server listening address, in case you have multiple ip addresses.
   --local-address                   Specifies the standalone/tls server listening address, in case you have multiple ip addresses.

+ 49 - 0
deploy/README.md

@@ -332,3 +332,52 @@ variable to anything (ex: "1") before running `acme.sh`:
 ```sh
 ```sh
 export FABIO="1"
 export FABIO="1"
 ```
 ```
+
+## 13. Deploy your certificate to Qiniu.com
+
+使用 acme.sh 部署到七牛之前,需要确保部署的域名已打开 HTTPS 功能,您可以访问[融合 CDN - 域名管理](https://portal.qiniu.com/cdn/domain) 设置。
+另外还需要先导出 AK/SK 环境变量,您可以访问[密钥管理](https://portal.qiniu.com/user/key) 获得。
+
+```sh
+$ export QINIU_AK="foo"
+$ export QINIU_SK="bar"
+```
+
+完成准备工作之后,您就可以通过下面的命令开始部署 SSL 证书到七牛上:
+
+```sh
+$ acme.sh --deploy -d example.com --deploy-hook qiniu
+```
+
+假如您部署的证书为泛域名证书,您还需要设置 `QINIU_CDN_DOMAIN` 变量,指定实际需要部署的域名:
+
+```sh
+$ export QINIU_CDN_DOMAIN="cdn.example.com"
+$ acme.sh --deploy -d example.com --deploy-hook qiniu
+```
+
+### English version
+
+You should create AccessKey/SecretKey pair in https://portal.qiniu.com/user/key 
+before deploying your certificate, and please ensure you have enabled HTTPS for
+your domain name. You can enable it in https://portal.qiniu.com/cdn/domain.
+
+```sh
+$ export QINIU_AK="foo"
+$ export QINIU_SK="bar"
+```
+
+then you can deploy certificate by following command:
+
+```sh
+$ acme.sh --deploy -d example.com --deploy-hook qiniu
+```
+
+(Optional), If you are using wildcard certificate,
+you may need export `QINIU_CDN_DOMAIN` to specify which domain
+you want to update:
+
+```sh
+$ export QINIU_CDN_DOMAIN="cdn.example.com"
+$ acme.sh --deploy -d example.com --deploy-hook qiniu
+```

+ 92 - 0
deploy/qiniu.sh

@@ -0,0 +1,92 @@
+#!/usr/bin/env sh
+
+# Script to create certificate to qiniu.com 
+#
+# This deployment required following variables
+# export QINIU_AK="QINIUACCESSKEY"
+# export QINIU_SK="QINIUSECRETKEY"
+# export QINIU_CDN_DOMAIN="cdn.example.com"
+
+QINIU_API_BASE="https://api.qiniu.com"
+
+qiniu_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 [ -z "$QINIU_AK" ]; then
+    _err "QINIU_AK is not defined."
+    return 1
+  else
+    _savedomainconf QINIU_AK "$QINIU_AK"
+  fi
+
+  if [ -z "$QINIU_SK" ]; then
+    _err "QINIU_SK is not defined."
+    return 1
+  else
+    _savedomainconf QINIU_SK "$QINIU_SK"
+  fi
+
+  if [ "$QINIU_CDN_DOMAIN" ]; then
+    _savedomainconf QINIU_CDN_DOMAIN "$QINIU_CDN_DOMAIN"
+  else
+    QINIU_CDN_DOMAIN="$_cdomain"
+  fi
+
+  ## upload certificate
+  string_fullchain=$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')
+  string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
+
+  sslcert_path="/sslcert"
+  sslcerl_body="{\"name\":\"$_cdomain\",\"common_name\":\"$QINIU_CDN_DOMAIN\",\"ca\":\"$string_fullchain\",\"pri\":\"$string_key\"}"
+  sslcert_access_token="$(_make_access_token "$sslcert_path")"
+  _debug sslcert_access_token "$sslcert_access_token"
+  export _H1="Authorization: QBox $sslcert_access_token"
+  sslcert_response=$(_post "$sslcerl_body" "$QINIU_API_BASE$sslcert_path" 0 "POST" "application/json" | _dbase64 "multiline")
+
+  if ! _contains "$sslcert_response" "certID"; then
+    _err "Error in creating certificate:"
+    _err "$sslcert_response"
+    return 1
+  fi
+
+  _debug sslcert_response "$sslcert_response"
+  _info "Certificate successfully uploaded, updating domain $_cdomain"
+
+  ## extract certId
+  _certId="$(printf "%s" "$sslcert_response" | _normalizeJson | _egrep_o "certID\": *\"[^\"]*\"" | cut -d : -f 2)"
+  _debug certId "$_certId"
+
+  ## update domain ssl config
+  update_path="/domain/$QINIU_CDN_DOMAIN/httpsconf"
+  update_body="{\"certid\":$_certId,\"forceHttps\":false}"
+  update_access_token="$(_make_access_token "$update_path")"
+  _debug update_access_token "$update_access_token"
+  export _H1="Authorization: QBox $update_access_token"
+  update_response=$(_post "$update_body" "$QINIU_API_BASE$update_path" 0 "PUT" "application/json" | _dbase64 "multiline")
+
+  if _contains "$update_response" "error"; then
+    _err "Error in updating domain httpsconf:"
+    _err "$update_response"
+    return 1
+  fi
+
+  _debug update_response "$update_response"
+  _info "Certificate successfully deployed"
+
+  return 0
+}
+
+_make_access_token() {
+  _token="$(printf "%s\n" "$1" | _hmac "sha1" "$(printf "%s" "$QINIU_SK" | _hex_dump | tr -d " ")" | _base64)"
+  echo "$QINIU_AK:$_token"
+}

+ 50 - 4
dnsapi/README.md

@@ -1122,21 +1122,67 @@ export EXOSCALE_SECRET_KEY='xxx'
 
 
 Now, let's issue a cert:
 Now, let's issue a cert:
 ```
 ```
-acme.sh --issue --dns dns_netcup -d example.com -d www.example.com
+acme.sh --issue --dns dns_exoscale -d example.com -d www.example.com
 ```
 ```
 
 
 The `EXOSCALE_API_KEY` and `EXOSCALE_SECRET_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 The `EXOSCALE_API_KEY` and `EXOSCALE_SECRET_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
 
-## 58. Use Internet.bs
+## 58. Using PointHQ API to issue certs
+
+Log into [PointHQ account management](https://app.pointhq.com/profile) and copy the API key from the page there.
+
+```export PointHQ_Key="apikeystringgoeshere"
+exportPointHQ_Email="accountemail@yourdomain.com"
+```
+
+You can then issue certs by using:
+```acme.sh --issue --dns dns_pointhq -d example.com -d www.example.com
+```
+
+## 59. Use Active24 API
 
 
-First you need to create/obtain API credentials on your Internet.bs (https://internetbs.net) account. Go to the "Get my API Key" section in the "My Domains" section.
+Create an API token in the Active24 account section, documentation on https://faq.active24.com/cz/790131-REST-API-rozhran%C3%AD.
 
 
+Set your API token:
+
+```
+export ACTIVE24_Token='xxx'
+```
+
+Now, let's issue a cert, set `dnssleep` for propagation new DNS record:
+```
+acme.sh --issue --dns dns_active24 -d example.com -d www.example.com --dnssleep 1000
+```
+
+The `ACTIVE24_Token` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+## 60. Use do.de API
+
+Create an API token in your do.de account.
+
+Set your API token:
+```
+export DO_LETOKEN='FmD408PdqT1E269gUK57'
+```
+
+To issue a certificate run:
+```
+acme.sh --issue --dns dns_doapi -d example.com -d *.example.com
+```
+
+The API token will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+## 61. Use internetbs.net API
+
+Create an API token in your internetbs.net account.
+
+Set your API token:
 ```
 ```
 export INTERNETBS_API_KEY="..."
 export INTERNETBS_API_KEY="..."
 export INTERNETBS_API_PASSWORD="..."
 export INTERNETBS_API_PASSWORD="..."
 ```
 ```
 
 
-Ok, let's issue a cert now:
+To issue a certificate run:
 ```
 ```
 acme.sh --issue --dns dns_internetbs -d example.com -d www.example.com
 acme.sh --issue --dns dns_internetbs -d example.com -d www.example.com
 ```
 ```

+ 141 - 0
dnsapi/dns_active24.sh

@@ -0,0 +1,141 @@
+#!/usr/bin/env sh
+
+#ACTIVE24_Token="sdfsdfsdfljlbjkljlkjsdfoiwje"
+
+ACTIVE24_Api="https://api.active24.com"
+
+########  Public functions #####################
+
+# Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Used to add txt record
+dns_active24_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  _active24_init
+
+  _info "Adding txt record"
+  if _active24_rest POST "dns/$_domain/txt/v1" "{\"name\":\"$_sub_domain\",\"text\":\"$txtvalue\",\"ttl\":0}"; then
+    if _contains "$response" "errors"; then
+      _err "Add txt record error."
+      return 1
+    else
+      _info "Added, OK"
+      return 0
+    fi
+  fi
+  _err "Add txt record error."
+  return 1
+}
+
+# Usage: fulldomain txtvalue
+# Used to remove the txt record after validation
+dns_active24_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  _active24_init
+
+  _debug "Getting txt records"
+  _active24_rest GET "dns/$_domain/records/v1"
+
+  if _contains "$response" "errors"; then
+    _err "Error"
+    return 1
+  fi
+
+  hash_ids=$(echo "$response" | _egrep_o "[^{]+${txtvalue}[^}]+" | _egrep_o "hashId\":\"[^\"]+" | cut -c10-)
+
+  for hash_id in $hash_ids; do
+    _debug "Removing hash_id" "$hash_id"
+    if _active24_rest DELETE "dns/$_domain/$hash_id/v1" ""; then
+      if _contains "$response" "errors"; then
+        _err "Unable to remove txt record."
+        return 1
+      else
+        _info "Removed txt record."
+        return 0
+      fi
+    fi
+  done
+
+  _err "No txt records found."
+  return 1
+}
+
+####################  Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_get_root() {
+  domain=$1
+
+  if ! _active24_rest GET "dns/domains/v1"; then
+    return 1
+  fi
+
+  i=2
+  p=1
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug "h" "$h"
+    if [ -z "$h" ]; then
+      #not valid
+      return 1
+    fi
+
+    if _contains "$response" "\"$h\"" >/dev/null; then
+      _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+      _domain=$h
+      return 0
+    fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+_active24_rest() {
+  m=$1
+  ep="$2"
+  data="$3"
+  _debug "$ep"
+
+  export _H1="Authorization: Bearer $ACTIVE24_Token"
+
+  if [ "$m" != "GET" ]; then
+    _debug "data" "$data"
+    response="$(_post "$data" "$ACTIVE24_Api/$ep" "" "$m" "application/json")"
+  else
+    response="$(_get "$ACTIVE24_Api/$ep")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}
+
+_active24_init() {
+  ACTIVE24_Token="${ACTIVE24_Token:-$(_readaccountconf_mutable ACTIVE24_Token)}"
+  if [ -z "$ACTIVE24_Token" ]; then
+    ACTIVE24_Token=""
+    _err "You didn't specify a Active24 api token yet."
+    _err "Please create the token and try again."
+    return 1
+  fi
+
+  _saveaccountconf_mutable ACTIVE24_Token "ACTIVE24_Token"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+}

+ 10 - 29
dnsapi/dns_cf.sh

@@ -19,8 +19,8 @@ dns_cf_add() {
   if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then
   if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then
     CF_Key=""
     CF_Key=""
     CF_Email=""
     CF_Email=""
-    _err "You didn't specify a cloudflare api key and email yet."
-    _err "Please create the key and try again."
+    _err "You didn't specify a Cloudflare api key and email yet."
+    _err "You can get yours from here https://dash.cloudflare.com/profile."
     return 1
     return 1
   fi
   fi
 
 
@@ -34,9 +34,6 @@ dns_cf_add() {
   _saveaccountconf_mutable CF_Key "$CF_Key"
   _saveaccountconf_mutable CF_Key "$CF_Key"
   _saveaccountconf_mutable CF_Email "$CF_Email"
   _saveaccountconf_mutable CF_Email "$CF_Email"
 
 
-  _DOMAIN_CF_ZONES_CACHE_NAME_="$(echo "${CF_Email}_CF_ZONES_" | tr '@.' '__')"
-  _cleardomainconf "$_DOMAIN_CF_ZONES_CACHE_NAME_"
-
   _debug "First detect the root zone"
   _debug "First detect the root zone"
   if ! _get_root "$fulldomain"; then
   if ! _get_root "$fulldomain"; then
     _err "invalid domain"
     _err "invalid domain"
@@ -100,21 +97,16 @@ dns_cf_rm() {
   if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then
   if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then
     CF_Key=""
     CF_Key=""
     CF_Email=""
     CF_Email=""
-    _err "You didn't specify a cloudflare api key and email yet."
-    _err "Please create the key and try again."
+    _err "You didn't specify a Cloudflare api key and email yet."
+    _err "You can get yours from here https://dash.cloudflare.com/profile."
     return 1
     return 1
   fi
   fi
 
 
-  _DOMAIN_CF_ZONES_CACHE_NAME_="$(echo "${CF_Email}_CF_ZONES_" | tr '@.' '__')"
-
   _debug "First detect the root zone"
   _debug "First detect the root zone"
   if ! _get_root "$fulldomain"; then
   if ! _get_root "$fulldomain"; then
-    _cleardomainconf "$_DOMAIN_CF_ZONES_CACHE_NAME_"
     _err "invalid domain"
     _err "invalid domain"
     return 1
     return 1
   fi
   fi
-  _cleardomainconf "$_DOMAIN_CF_ZONES_CACHE_NAME_"
-
   _debug _domain_id "$_domain_id"
   _debug _domain_id "$_domain_id"
   _debug _sub_domain "$_sub_domain"
   _debug _sub_domain "$_sub_domain"
   _debug _domain "$_domain"
   _debug _domain "$_domain"
@@ -154,21 +146,6 @@ dns_cf_rm() {
 # _domain=domain.com
 # _domain=domain.com
 # _domain_id=sdjkglgdfewsdfg
 # _domain_id=sdjkglgdfewsdfg
 _get_root() {
 _get_root() {
-
-  _cf_zones="$(_readdomainconf "$_DOMAIN_CF_ZONES_CACHE_NAME_")"
-  _debug2 "_cf_zones" "$_cf_zones"
-  if [ -z "$_cf_zones" ]; then
-    _debug "$_DOMAIN_CF_ZONES_CACHE_NAME_ is none, so get it."
-    if ! _cf_rest GET "zones"; then
-      return 1
-    fi
-    _cf_zones="$response"
-    _savedomainconf "$_DOMAIN_CF_ZONES_CACHE_NAME_" "$(echo "$_cf_zones" | _base64)"
-  else
-    _debug "$_DOMAIN_CF_ZONES_CACHE_NAME_ found"
-    _cf_zones="$(echo "$_cf_zones" | _dbase64)"
-  fi
-
   domain=$1
   domain=$1
   i=2
   i=2
   p=1
   p=1
@@ -180,8 +157,12 @@ _get_root() {
       return 1
       return 1
     fi
     fi
 
 
-    if _contains "$_cf_zones" "\"name\":\"$h\"" >/dev/null; then
-      _domain_id=$(echo "$_cf_zones" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "^\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
+    if ! _cf_rest GET "zones?name=$h"; then
+      return 1
+    fi
+
+    if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
+      _domain_id=$(echo "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
       if [ "$_domain_id" ]; then
       if [ "$_domain_id" ]; then
         _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
         _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
         _domain=$h
         _domain=$h

+ 89 - 64
dnsapi/dns_dgon.sh

@@ -104,47 +104,59 @@ dns_dgon_rm() {
   ## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}}
   ## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}}
   GURL="https://api.digitalocean.com/v2/domains/$_domain/records"
   GURL="https://api.digitalocean.com/v2/domains/$_domain/records"
 
 
-  ## while we dont have a record ID we keep going
-  while [ -z "$record" ]; do
+  ## Get all the matching records
+  while true; do
     ## 1) get the URL
     ## 1) get the URL
     ## the create request - get
     ## the create request - get
     ## args: URL, [onlyheader, timeout]
     ## args: URL, [onlyheader, timeout]
     domain_list="$(_get "$GURL")"
     domain_list="$(_get "$GURL")"
-    ## 2) find record
-    ## check for what we are looing for: "type":"A","name":"$_sub_domain"
-    record="$(echo "$domain_list" | _egrep_o "\"id\"\s*\:\s*\"*[0-9]+\"*[^}]*\"name\"\s*\:\s*\"$_sub_domain\"[^}]*\"data\"\s*\:\s*\"$txtvalue\"")"
-    ## 3) check record and get next page
-    if [ -z "$record" ]; then
-      ## find the next page if we dont have a match
-      nextpage="$(echo "$domain_list" | _egrep_o "\"links\".*" | _egrep_o "\"next\".*" | _egrep_o "http.*page\=[0-9]+")"
-      if [ -z "$nextpage" ]; then
-        _err "no record and no nextpage in digital ocean DNS removal"
-        return 1
-      fi
-      _debug2 nextpage "$nextpage"
-      GURL="$nextpage"
+
+    ## check response
+    if [ "$?" != "0" ]; then
+      _err "error in domain_list response: $domain_list"
+      return 1
     fi
     fi
-    ## we break out of the loop when we have a record
-  done
+    _debug2 domain_list "$domain_list"
 
 
-  ## we found the record
-  rec_id="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")"
-  _debug rec_id "$rec_id"
+    ## 2) find records
+    ## check for what we are looking for: "type":"A","name":"$_sub_domain"
+    record="$(echo "$domain_list" | _egrep_o "\"id\"\s*\:\s*\"*[0-9]+\"*[^}]*\"name\"\s*\:\s*\"$_sub_domain\"[^}]*\"data\"\s*\:\s*\"$txtvalue\"")"
 
 
-  ## delete the record
-  ## delete URL for removing the one we dont want
-  DURL="https://api.digitalocean.com/v2/domains/$_domain/records/$rec_id"
+    if [ ! -z "$record" ]; then
+
+      ## we found records
+      rec_ids="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")"
+      _debug rec_ids "$rec_ids"
+      if [ ! -z "$rec_ids" ]; then
+        echo "$rec_ids" | while IFS= read -r rec_id; do
+          ## delete the record
+          ## delete URL for removing the one we dont want
+          DURL="https://api.digitalocean.com/v2/domains/$_domain/records/$rec_id"
+
+          ## the create request - delete
+          ## args: BODY, URL, [need64, httpmethod]
+          response="$(_post "" "$DURL" "" "DELETE")"
+
+          ## check response (sort of)
+          if [ "$?" != "0" ]; then
+            _err "error in remove response: $response"
+            return 1
+          fi
+          _debug2 response "$response"
+
+        done
+      fi
+    fi
 
 
-  ## the create request - delete
-  ## args: BODY, URL, [need64, httpmethod]
-  response="$(_post "" "$DURL" "" "DELETE")"
+    ## 3) find the next page
+    nextpage="$(echo "$domain_list" | _egrep_o "\"links\".*" | _egrep_o "\"next\".*" | _egrep_o "http.*page\=[0-9]+")"
+    if [ -z "$nextpage" ]; then
+      break
+    fi
+    _debug2 nextpage "$nextpage"
+    GURL="$nextpage"
 
 
-  ## check response (sort of)
-  if [ "$?" != "0" ]; then
-    _err "error in remove response: $response"
-    return 1
-  fi
-  _debug2 response "$response"
+  done
 
 
   ## finished correctly
   ## finished correctly
   return 0
   return 0
@@ -178,44 +190,57 @@ _get_base_domain() {
   export _H2="Authorization: Bearer $DO_API_KEY"
   export _H2="Authorization: Bearer $DO_API_KEY"
   _debug DO_API_KEY "$DO_API_KEY"
   _debug DO_API_KEY "$DO_API_KEY"
   ## get URL for the list of domains
   ## get URL for the list of domains
-  ## havent seen this request paginated, tested with 18 domains (more requires manual requests with DO)
+  ## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}}
   DOMURL="https://api.digitalocean.com/v2/domains"
   DOMURL="https://api.digitalocean.com/v2/domains"
 
 
-  ## get the domain list (DO gives basically a full XFER!)
-  domain_list="$(_get "$DOMURL")"
+  ## while we dont have a matching domain we keep going
+  while [ -z "$found" ]; do
+    ## get the domain list (current page)
+    domain_list="$(_get "$DOMURL")"
 
 
-  ## check response
-  if [ "$?" != "0" ]; then
-    _err "error in domain_list response: $domain_list"
-    return 1
-  fi
-  _debug2 domain_list "$domain_list"
-
-  ## for each shortening of our $fulldomain, check if it exists in the $domain_list
-  ## can never start on 1 (aka whole $fulldomain) as $fulldomain starts with "_acme-challenge"
-  i=2
-  while [ $i -gt 0 ]; do
-    ## get next longest domain
-    _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM")
-    ## check we got something back from our cut (or are we at the end)
-    if [ -z "$_domain" ]; then
-      ## we got to the end of the domain - invalid domain
-      _err "domain not found in DigitalOcean account"
+    ## check response
+    if [ "$?" != "0" ]; then
+      _err "error in domain_list response: $domain_list"
       return 1
       return 1
     fi
     fi
-    ## we got part of a domain back - grep it out
-    found="$(echo "$domain_list" | _egrep_o "\"name\"\s*\:\s*\"$_domain\"")"
-    ## check if it exists
-    if [ ! -z "$found" ]; then
-      ## exists - exit loop returning the parts
-      sub_point=$(_math $i - 1)
-      _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point")
-      _debug _domain "$_domain"
-      _debug _sub_domain "$_sub_domain"
-      return 0
+    _debug2 domain_list "$domain_list"
+
+    ## for each shortening of our $fulldomain, check if it exists in the $domain_list
+    ## can never start on 1 (aka whole $fulldomain) as $fulldomain starts with "_acme-challenge"
+    i=2
+    while [ $i -gt 0 ]; do
+      ## get next longest domain
+      _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM")
+      ## check we got something back from our cut (or are we at the end)
+      if [ -z "$_domain" ]; then
+        break
+      fi
+      ## we got part of a domain back - grep it out
+      found="$(echo "$domain_list" | _egrep_o "\"name\"\s*\:\s*\"$_domain\"")"
+      ## check if it exists
+      if [ ! -z "$found" ]; then
+        ## exists - exit loop returning the parts
+        sub_point=$(_math $i - 1)
+        _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point")
+        _debug _domain "$_domain"
+        _debug _sub_domain "$_sub_domain"
+        return 0
+      fi
+      ## increment cut point $i
+      i=$(_math $i + 1)
+    done
+
+    if [ -z "$found" ]; then
+      ## find the next page if we dont have a match
+      nextpage="$(echo "$domain_list" | _egrep_o "\"links\".*" | _egrep_o "\"next\".*" | _egrep_o "http.*page\=[0-9]+")"
+      if [ -z "$nextpage" ]; then
+        _err "no record and no nextpage in digital ocean DNS removal"
+        return 1
+      fi
+      _debug2 nextpage "$nextpage"
+      DOMURL="$nextpage"
     fi
     fi
-    ## increment cut point $i
-    i=$(_math $i + 1)
+
   done
   done
 
 
   ## we went through the entire domain zone list and dint find one that matched
   ## we went through the entire domain zone list and dint find one that matched

+ 1 - 1
dnsapi/dns_dnsimple.sh

@@ -152,7 +152,7 @@ _get_records() {
   sub_domain=$3
   sub_domain=$3
 
 
   _debug "fetching txt records"
   _debug "fetching txt records"
-  _dnsimple_rest GET "$account_id/zones/$domain/records?per_page=100"
+  _dnsimple_rest GET "$account_id/zones/$domain/records?per_page=5000&sort=id:desc"
 
 
   if ! _contains "$response" "\"id\":"; then
   if ! _contains "$response" "\"id\":"; then
     _err "failed to retrieve records"
     _err "failed to retrieve records"

+ 59 - 0
dnsapi/dns_doapi.sh

@@ -0,0 +1,59 @@
+#!/usr/bin/env sh
+
+# Official Let's Encrypt API for do.de / Domain-Offensive
+# 
+# This is different from the dns_do adapter, because dns_do is only usable for enterprise customers
+# This API is also available to private customers/individuals
+# 
+# Provide the required LetsEncrypt token like this: 
+# DO_LETOKEN="FmD408PdqT1E269gUK57"
+
+DO_API="https://www.do.de/api/letsencrypt"
+
+########  Public functions #####################
+
+#Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_doapi_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  DO_LETOKEN="${DO_LETOKEN:-$(_readaccountconf_mutable DO_LETOKEN)}"
+  if [ -z "$DO_LETOKEN" ]; then
+    DO_LETOKEN=""
+    _err "You didn't configure a do.de API token yet."
+    _err "Please set DO_LETOKEN and try again."
+    return 1
+  fi
+  _saveaccountconf_mutable DO_LETOKEN "$DO_LETOKEN"
+
+  _info "Adding TXT record to ${fulldomain}"
+  response="$(_get "$DO_API?token=$DO_LETOKEN&domain=${fulldomain}&value=${txtvalue}")"
+  if _contains "${response}" 'success'; then
+    return 0
+  fi
+  _err "Could not create resource record, check logs"
+  _err "${response}"
+  return 1
+}
+
+dns_doapi_rm() {
+  fulldomain=$1
+
+  DO_LETOKEN="${DO_LETOKEN:-$(_readaccountconf_mutable DO_LETOKEN)}"
+  if [ -z "$DO_LETOKEN" ]; then
+    DO_LETOKEN=""
+    _err "You didn't configure a do.de API token yet."
+    _err "Please set DO_LETOKEN and try again."
+    return 1
+  fi
+  _saveaccountconf_mutable DO_LETOKEN "$DO_LETOKEN"
+
+  _info "Deleting resource record $fulldomain"
+  response="$(_get "$DO_API?token=$DO_LETOKEN&domain=${fulldomain}&action=delete")"
+  if _contains "${response}" 'success'; then
+    return 0
+  fi
+  _err "Could not delete resource record, check logs"
+  _err "${response}"
+  return 1
+}

+ 1 - 1
dnsapi/dns_dp.sh

@@ -63,7 +63,7 @@ dns_dp_rm() {
     return 0
     return 0
   fi
   fi
 
 
-  record_id=$(echo "$response" | _egrep_o '{[^{]*"value":"'"$txtvalue"'"' | cut -d , -f 1 | cut -d : -f 2 | tr -d \")
+  record_id=$(echo "$response" | tr "{" "\n" | grep "$txtvalue" | grep '^"id"' | cut -d : -f 2 | cut -d '"' -f 2)
   _debug record_id "$record_id"
   _debug record_id "$record_id"
   if [ -z "$record_id" ]; then
   if [ -z "$record_id" ]; then
     _err "Can not get record id."
     _err "Can not get record id."

+ 13 - 13
dnsapi/dns_dynu.sh

@@ -10,7 +10,7 @@
 Dynu_Token=""
 Dynu_Token=""
 #
 #
 #Endpoint
 #Endpoint
-Dynu_EndPoint="https://api.dynu.com/v1"
+Dynu_EndPoint="https://api.dynu.com/v2"
 #
 #
 #Author: Dynu Systems, Inc.
 #Author: Dynu Systems, Inc.
 #Report Bugs here: https://github.com/shar0119/acme.sh
 #Report Bugs here: https://github.com/shar0119/acme.sh
@@ -51,11 +51,11 @@ dns_dynu_add() {
   _debug _domain_name "$_domain_name"
   _debug _domain_name "$_domain_name"
 
 
   _info "Creating TXT record."
   _info "Creating TXT record."
-  if ! _dynu_rest POST "dns/record/add" "{\"domain_name\":\"$_domain_name\",\"node_name\":\"$_node\",\"record_type\":\"TXT\",\"text_data\":\"$txtvalue\",\"state\":true,\"ttl\":90}"; then
+  if ! _dynu_rest POST "dns/$dnsId/record" "{\"domainId\":\"$dnsId\",\"nodeName\":\"$_node\",\"recordType\":\"TXT\",\"textData\":\"$txtvalue\",\"state\":true,\"ttl\":90}"; then
     return 1
     return 1
   fi
   fi
 
 
-  if ! _contains "$response" "text_data"; then
+  if ! _contains "$response" "200"; then
     _err "Could not add TXT record."
     _err "Could not add TXT record."
     return 1
     return 1
   fi
   fi
@@ -132,11 +132,12 @@ _get_root() {
       return 1
       return 1
     fi
     fi
 
 
-    if ! _dynu_rest GET "dns/get/$h"; then
+    if ! _dynu_rest GET "dns/getroot/$h"; then
       return 1
       return 1
     fi
     fi
 
 
-    if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
+    if _contains "$response" "\"domainName\":\"$h\"" >/dev/null; then
+      dnsId=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 2 | cut -d : -f 2)
       _domain_name=$h
       _domain_name=$h
       _node=$(printf "%s" "$domain" | cut -d . -f 1-$p)
       _node=$(printf "%s" "$domain" | cut -d . -f 1-$p)
       return 0
       return 0
@@ -152,7 +153,7 @@ _get_recordid() {
   fulldomain=$1
   fulldomain=$1
   txtvalue=$2
   txtvalue=$2
 
 
-  if ! _dynu_rest GET "dns/record/get?hostname=$fulldomain&rrtype=TXT"; then
+  if ! _dynu_rest GET "dns/$dnsId/record"; then
     return 1
     return 1
   fi
   fi
 
 
@@ -161,19 +162,18 @@ _get_recordid() {
     return 0
     return 0
   fi
   fi
 
 
-  _dns_record_id=$(printf "%s" "$response" | _egrep_o "{[^}]*}" | grep "\"text_data\":\"$txtvalue\"" | _egrep_o ",[^,]*," | grep ',"id":' | tr -d ",," | cut -d : -f 2)
-
+  _dns_record_id=$(printf "%s" "$response" | sed -e 's/[^{]*\({[^}]*}\)[^{]*/\1\n/g' | grep "\"textData\":\"$txtvalue\"" | sed -e 's/.*"id":\([^,]*\).*/\1/')
   return 0
   return 0
 }
 }
 
 
 _delete_txt_record() {
 _delete_txt_record() {
   _dns_record_id=$1
   _dns_record_id=$1
 
 
-  if ! _dynu_rest GET "dns/record/delete/$_dns_record_id"; then
+  if ! _dynu_rest DELETE "dns/$dnsId/record/$_dns_record_id"; then
     return 1
     return 1
   fi
   fi
 
 
-  if ! _contains "$response" "true"; then
+  if ! _contains "$response" "200"; then
     return 1
     return 1
   fi
   fi
 
 
@@ -189,7 +189,7 @@ _dynu_rest() {
   export _H1="Authorization: Bearer $Dynu_Token"
   export _H1="Authorization: Bearer $Dynu_Token"
   export _H2="Content-Type: application/json"
   export _H2="Content-Type: application/json"
 
 
-  if [ "$data" ]; then
+  if [ "$data" ] || [ "$m" = "DELETE" ]; then
     _debug data "$data"
     _debug data "$data"
     response="$(_post "$data" "$Dynu_EndPoint/$ep" "" "$m")"
     response="$(_post "$data" "$Dynu_EndPoint/$ep" "" "$m")"
   else
   else
@@ -216,8 +216,8 @@ _dynu_authentication() {
     _err "Authentication failed."
     _err "Authentication failed."
     return 1
     return 1
   fi
   fi
-  if _contains "$response" "accessToken"; then
-    Dynu_Token=$(printf "%s" "$response" | tr -d "[]" | cut -d , -f 2 | cut -d : -f 2 | cut -d '"' -f 2)
+  if _contains "$response" "access_token"; then
+    Dynu_Token=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 1 | cut -d : -f 2 | cut -d '"' -f 2)
   fi
   fi
   if _contains "$Dynu_Token" "null"; then
   if _contains "$Dynu_Token" "null"; then
     Dynu_Token=""
     Dynu_Token=""

+ 57 - 5
dnsapi/dns_gandi_livedns.sh

@@ -7,6 +7,7 @@
 # Requires GANDI API KEY set in GANDI_LIVEDNS_KEY set as environment variable
 # Requires GANDI API KEY set in GANDI_LIVEDNS_KEY set as environment variable
 #
 #
 #Author: Frédéric Crozat <fcrozat@suse.com>
 #Author: Frédéric Crozat <fcrozat@suse.com>
+#        Dominik Röttsches <drott@google.com>
 #Report Bugs here: https://github.com/fcrozat/acme.sh
 #Report Bugs here: https://github.com/fcrozat/acme.sh
 #
 #
 ########  Public functions #####################
 ########  Public functions #####################
@@ -36,9 +37,7 @@ dns_gandi_livedns_add() {
   _debug domain "$_domain"
   _debug domain "$_domain"
   _debug sub_domain "$_sub_domain"
   _debug sub_domain "$_sub_domain"
 
 
-  _gandi_livedns_rest PUT "domains/$_domain/records/$_sub_domain/TXT" "{\"rrset_ttl\": 300, \"rrset_values\":[\"$txtvalue\"]}" \
-    && _contains "$response" '{"message": "DNS Record Created"}' \
-    && _info "Add $(__green "success")"
+  _dns_gandi_append_record "$_domain" "$_sub_domain" "$txtvalue"
 }
 }
 
 
 #Usage: fulldomain txtvalue
 #Usage: fulldomain txtvalue
@@ -56,9 +55,23 @@ dns_gandi_livedns_rm() {
   _debug fulldomain "$fulldomain"
   _debug fulldomain "$fulldomain"
   _debug domain "$_domain"
   _debug domain "$_domain"
   _debug sub_domain "$_sub_domain"
   _debug sub_domain "$_sub_domain"
+  _debug txtvalue "$txtvalue"
 
 
-  _gandi_livedns_rest DELETE "domains/$_domain/records/$_sub_domain/TXT" ""
-
+  if ! _dns_gandi_existing_rrset_values "$_domain" "$_sub_domain"; then
+    return 1
+  fi
+  _new_rrset_values=$(echo "$_rrset_values" | sed "s/...$txtvalue...//g")
+  # Cleanup dangling commata.
+  _new_rrset_values=$(echo "$_new_rrset_values" | sed "s/, ,/ ,/g")
+  _new_rrset_values=$(echo "$_new_rrset_values" | sed "s/, *\]/\]/g")
+  _new_rrset_values=$(echo "$_new_rrset_values" | sed "s/\[ *,/\[/g")
+  _debug "New rrset_values" "$_new_rrset_values"
+
+  _gandi_livedns_rest PUT \
+    "domains/$_domain/records/$_sub_domain/TXT" \
+    "{\"rrset_ttl\": 300, \"rrset_values\": $_new_rrset_values}" \
+    && _contains "$response" '{"message": "DNS Record Created"}' \
+    && _info "Removing record $(__green "success")"
 }
 }
 
 
 ####################  Private functions below ##################################
 ####################  Private functions below ##################################
@@ -98,6 +111,45 @@ _get_root() {
   return 1
   return 1
 }
 }
 
 
+_dns_gandi_append_record() {
+  domain=$1
+  sub_domain=$2
+  txtvalue=$3
+
+  if _dns_gandi_existing_rrset_values "$domain" "$sub_domain"; then
+    _debug "Appending new value"
+    _rrset_values=$(echo "$_rrset_values" | sed "s/\"]/\",\"$txtvalue\"]/")
+  else
+    _debug "Creating new record" "$_rrset_values"
+    _rrset_values="[\"$txtvalue\"]"
+  fi
+  _debug new_rrset_values "$_rrset_values"
+  _gandi_livedns_rest PUT "domains/$_domain/records/$sub_domain/TXT" \
+    "{\"rrset_ttl\": 300, \"rrset_values\": $_rrset_values}" \
+    && _contains "$response" '{"message": "DNS Record Created"}' \
+    && _info "Adding record $(__green "success")"
+}
+
+_dns_gandi_existing_rrset_values() {
+  domain=$1
+  sub_domain=$2
+  if ! _gandi_livedns_rest GET "domains/$domain/records/$sub_domain"; then
+    return 1
+  fi
+  if ! _contains "$response" '"rrset_type": "TXT"'; then
+    _debug "Does not have a _acme-challenge TXT record yet."
+    return 1
+  fi
+  if _contains "$response" '"rrset_values": \[\]'; then
+    _debug "Empty rrset_values for TXT record, no previous TXT record."
+    return 1
+  fi
+  _debug "Already has TXT record."
+  _rrset_values=$(echo "$response" | _egrep_o 'rrset_values.*\[.*\]' \
+    | _egrep_o '\[".*\"]')
+  return 0
+}
+
 _gandi_livedns_rest() {
 _gandi_livedns_rest() {
   m=$1
   m=$1
   ep="$2"
   ep="$2"

+ 42 - 4
dnsapi/dns_hostingde.sh

@@ -59,9 +59,22 @@ _hostingde_getZoneConfig() {
     if _contains "${curResult}" '"totalEntries": 1'; then
     if _contains "${curResult}" '"totalEntries": 1'; then
       _info "Retrieved zone data."
       _info "Retrieved zone data."
       _debug "Zone data: '${curResult}'"
       _debug "Zone data: '${curResult}'"
-
-      # read ZoneConfigId for later update
       zoneConfigId=$(echo "${curResult}" | _egrep_o '"id":.*' | cut -d ':' -f 2 | cut -d '"' -f 2)
       zoneConfigId=$(echo "${curResult}" | _egrep_o '"id":.*' | cut -d ':' -f 2 | cut -d '"' -f 2)
+      zoneConfigName=$(echo "${curResult}" | _egrep_o '"name":.*' | cut -d ':' -f 2 | cut -d '"' -f 2)
+      zoneConfigType=$(echo "${curResult}" | grep -v "FindZoneConfigsResult" | _egrep_o '"type":.*' | cut -d ':' -f 2 | cut -d '"' -f 2)
+      zoneConfigExpire=$(echo "${curResult}" | _egrep_o '"expire":.*' | cut -d ':' -f 2 | cut -d '"' -f 2 | cut -d ',' -f 1)
+      zoneConfigNegativeTtl=$(echo "${curResult}" | _egrep_o '"negativeTtl":.*' | cut -d ':' -f 2 | cut -d '"' -f 2 | cut -d ',' -f 1)
+      zoneConfigRefresh=$(echo "${curResult}" | _egrep_o '"refresh":.*' | cut -d ':' -f 2 | cut -d '"' -f 2 | cut -d ',' -f 1)
+      zoneConfigRetry=$(echo "${curResult}" | _egrep_o '"retry":.*' | cut -d ':' -f 2 | cut -d '"' -f 2 | cut -d ',' -f 1)
+      zoneConfigTtl=$(echo "${curResult}" | _egrep_o '"ttl":.*' | cut -d ':' -f 2 | cut -d '"' -f 2 | cut -d ',' -f 1)
+      zoneConfigDnsServerGroupId=$(echo "${curResult}" | _egrep_o '"dnsServerGroupId":.*' | cut -d ':' -f 2 | cut -d '"' -f 2)
+      zoneConfigEmailAddress=$(echo "${curResult}" | _egrep_o '"emailAddress":.*' | cut -d ':' -f 2 | cut -d '"' -f 2)
+      zoneConfigDnsSecMode=$(echo "${curResult}" | _egrep_o '"dnsSecMode":.*' | cut -d ':' -f 2 | cut -d '"' -f 2)
+      if [ "${zoneConfigType}" != "NATIVE" ]; then
+        _err "Zone is not native"
+        returnCode=1
+        break
+      fi
       _debug "zoneConfigId '${zoneConfigId}'"
       _debug "zoneConfigId '${zoneConfigId}'"
       returnCode=0
       returnCode=0
       break
       break
@@ -74,9 +87,27 @@ _hostingde_getZoneConfig() {
   return $returnCode
   return $returnCode
 }
 }
 
 
+_hostingde_getZoneStatus() {
+  _debug "Checking Zone status"
+  curData="{\"filter\":{\"field\":\"zoneConfigId\",\"value\":\"${zoneConfigId}\"},\"limit\":1,\"authToken\":\"${HOSTINGDE_APIKEY}\"}"
+  curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zonesFind")"
+  _debug "Calling zonesFind '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zonesFind'"
+  _debug "Result of zonesFind '$curResult'"
+  zoneStatus=$(echo "${curResult}" | grep -v success | _egrep_o '"status":.*' | cut -d ':' -f 2 | cut -d '"' -f 2)
+  _debug "zoneStatus '${zoneStatus}'"
+  return 0
+}
+
 _hostingde_addRecord() {
 _hostingde_addRecord() {
   _info "Adding record to zone"
   _info "Adding record to zone"
-  curData="{\"authToken\":\"${HOSTINGDE_APIKEY}\",\"zoneConfig\":{\"id\":\"${zoneConfigId}\"},\"recordsToAdd\":[{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"\\\"${txtvalue}\\\"\",\"ttl\":3600}]}"
+  _hostingde_getZoneStatus
+  _debug "Result of zoneStatus: '${zoneStatus}'"
+  while [ "${zoneStatus}" != "active" ]; do
+    _sleep 5
+    _hostingde_getZoneStatus
+    _debug "Result of zoneStatus: '${zoneStatus}'"
+  done
+  curData="{\"authToken\":\"${HOSTINGDE_APIKEY}\",\"zoneConfig\":{\"id\":\"${zoneConfigId}\",\"name\":\"${zoneConfigName}\",\"type\":\"${zoneConfigType}\",\"dnsServerGroupId\":\"${zoneConfigDnsServerGroupId}\",\"dnsSecMode\":\"${zoneConfigDnsSecMode}\",\"emailAddress\":\"${zoneConfigEmailAddress}\",\"soaValues\":{\"expire\":${zoneConfigExpire},\"negativeTtl\":${zoneConfigNegativeTtl},\"refresh\":${zoneConfigRefresh},\"retry\":${zoneConfigRetry},\"ttl\":${zoneConfigTtl}}},\"recordsToAdd\":[{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"\\\"${txtvalue}\\\"\",\"ttl\":3600}]}"
   curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate")"
   curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate")"
   _debug "Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'"
   _debug "Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'"
   _debug "Result of zoneUpdate: '$curResult'"
   _debug "Result of zoneUpdate: '$curResult'"
@@ -93,7 +124,14 @@ _hostingde_addRecord() {
 
 
 _hostingde_removeRecord() {
 _hostingde_removeRecord() {
   _info "Removing record from zone"
   _info "Removing record from zone"
-  curData="{\"authToken\":\"${HOSTINGDE_APIKEY}\",\"zoneConfig\":{\"id\":\"${zoneConfigId}\"},\"recordsToDelete\":[{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"\\\"${txtvalue}\\\"\"}]}"
+  _hostingde_getZoneStatus
+  _debug "Result of zoneStatus: '$zoneStatus'"
+  while [ "$zoneStatus" != "active" ]; do
+    _sleep 5
+    _hostingde_getZoneStatus
+    _debug "Result of zoneStatus: '$zoneStatus'"
+  done
+  curData="{\"authToken\":\"${HOSTINGDE_APIKEY}\",\"zoneConfig\":{\"id\":\"${zoneConfigId}\",\"name\":\"${zoneConfigName}\",\"type\":\"${zoneConfigType}\",\"dnsServerGroupId\":\"${zoneConfigDnsServerGroupId}\",\"dnsSecMode\":\"${zoneConfigDnsSecMode}\",\"emailAddress\":\"${zoneConfigEmailAddress}\",\"soaValues\":{\"expire\":${zoneConfigExpire},\"negativeTtl\":${zoneConfigNegativeTtl},\"refresh\":${zoneConfigRefresh},\"retry\":${zoneConfigRetry},\"ttl\":${zoneConfigTtl}}},\"recordsToDelete\":[{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"\\\"${txtvalue}\\\"\"}]}"
   curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate")"
   curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate")"
   _debug "Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'"
   _debug "Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'"
   _debug "Result of zoneUpdate: '$curResult'"
   _debug "Result of zoneUpdate: '$curResult'"

+ 2 - 1
dnsapi/dns_inwx.sh

@@ -158,7 +158,8 @@ _inwx_login() {
   export _H1
   export _H1
 
 
   #https://github.com/inwx/php-client/blob/master/INWX/Domrobot.php#L71
   #https://github.com/inwx/php-client/blob/master/INWX/Domrobot.php#L71
-  if _contains "$response" "tfa"; then
+  if _contains "$response" "<member><name>code</name><value><int>1000</int></value></member>" \
+    && _contains "$response" "<member><name>tfa</name><value><string>GOOGLE-AUTH</string></value></member>"; then
     if [ -z "$INWX_Shared_Secret" ]; then
     if [ -z "$INWX_Shared_Secret" ]; then
       _err "Mobile TAN detected."
       _err "Mobile TAN detected."
       _err "Please define a shared secret."
       _err "Please define a shared secret."

+ 2 - 2
dnsapi/dns_linode_v4.sh

@@ -8,7 +8,7 @@ LINODE_V4_API_URL="https://api.linode.com/v4/domains"
 ########  Public functions #####################
 ########  Public functions #####################
 
 
 #Usage: dns_linode_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
 #Usage: dns_linode_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
-dns_linode_add() {
+dns_linode_v4_add() {
   fulldomain="${1}"
   fulldomain="${1}"
   txtvalue="${2}"
   txtvalue="${2}"
 
 
@@ -51,7 +51,7 @@ dns_linode_add() {
 }
 }
 
 
 #Usage: dns_linode_rm   _acme-challenge.www.domain.com
 #Usage: dns_linode_rm   _acme-challenge.www.domain.com
-dns_linode_rm() {
+dns_linode_v4_rm() {
   fulldomain="${1}"
   fulldomain="${1}"
 
 
   if ! _Linode_API; then
   if ! _Linode_API; then

+ 68 - 9
dnsapi/dns_loopia.sh

@@ -38,8 +38,8 @@ dns_loopia_add() {
 
 
   _info "Adding record"
   _info "Adding record"
 
 
-  _loopia_add_record "$_domain" "$_sub_domain"
-  _loopia_update_record "$_domain" "$_sub_domain" "$txtvalue"
+  _loopia_add_sub_domain "$_domain" "$_sub_domain"
+  _loopia_add_record "$_domain" "$_sub_domain" "$txtvalue"
 
 
 }
 }
 
 
@@ -96,6 +96,37 @@ dns_loopia_rm() {
 
 
 ####################  Private functions below ##################################
 ####################  Private functions below ##################################
 
 
+_loopia_get_records() {
+  domain=$1
+  sub_domain=$2
+
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+    <methodName>getZoneRecords</methodName>
+    <params>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+    </params>
+  </methodCall>' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain")
+
+  response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
+  if ! _contains "$response" "<array>"; then
+    _err "Error"
+    return 1
+  fi
+  return 0
+}
+
 _get_root() {
 _get_root() {
   domain=$1
   domain=$1
   _debug "get root"
   _debug "get root"
@@ -137,14 +168,14 @@ _get_root() {
 
 
 }
 }
 
 
-_loopia_update_record() {
+_loopia_add_record() {
   domain=$1
   domain=$1
   sub_domain=$2
   sub_domain=$2
   txtval=$3
   txtval=$3
 
 
   xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
   xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
   <methodCall>
   <methodCall>
-    <methodName>updateZoneRecord</methodName>
+    <methodName>addZoneRecord</methodName>
     <params>
     <params>
       <param>
       <param>
         <value><string>%s</string></value>
         <value><string>%s</string></value>
@@ -176,10 +207,6 @@ _loopia_update_record() {
             <name>rdata</name>
             <name>rdata</name>
             <value><string>%s</string></value>
             <value><string>%s</string></value>
           </member>
           </member>
-          <member>
-            <name>record_id</name>
-            <value><int>0</int></value>
-          </member>
         </struct>
         </struct>
       </param>
       </param>
     </params>
     </params>
@@ -194,10 +221,42 @@ _loopia_update_record() {
   return 0
   return 0
 }
 }
 
 
-_loopia_add_record() {
+_sub_domain_exists() {
   domain=$1
   domain=$1
   sub_domain=$2
   sub_domain=$2
 
 
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+    <methodName>getSubdomains</methodName>
+    <params>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+      <param>
+        <value><string>%s</string></value>
+      </param>
+    </params>
+  </methodCall>' $LOOPIA_User $LOOPIA_Password "$domain")
+
+  response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
+
+  if _contains "$response" "$sub_domain"; then
+    return 0
+  fi
+  return 1
+}
+
+_loopia_add_sub_domain() {
+  domain=$1
+  sub_domain=$2
+
+  if _sub_domain_exists "$domain" "$sub_domain"; then
+    return 0
+  fi
+
   xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
   xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
   <methodCall>
   <methodCall>
     <methodName>addSubdomain</methodName>
     <methodName>addSubdomain</methodName>

+ 75 - 21
dnsapi/dns_namecheap.sh

@@ -3,16 +3,15 @@
 # Namecheap API
 # Namecheap API
 # https://www.namecheap.com/support/api/intro.aspx
 # https://www.namecheap.com/support/api/intro.aspx
 #
 #
-# Requires Namecheap API key set in NAMECHEAP_API_KEY, NAMECHEAP_SOURCEIP and NAMECHEAP_USERNAME set as environment variable
+# Requires Namecheap API key set in 
+#NAMECHEAP_API_KEY, 
+#NAMECHEAP_USERNAME,
+#NAMECHEAP_SOURCEIP 
 # Due to Namecheap's API limitation all the records of your domain will be read and re applied, make sure to have a backup of your records you could apply if any issue would arise.
 # Due to Namecheap's API limitation all the records of your domain will be read and re applied, make sure to have a backup of your records you could apply if any issue would arise.
 
 
 ########  Public functions #####################
 ########  Public functions #####################
 
 
-if [ "$STAGE" -eq 1 ]; then
-  NAMECHEAP_API="https://api.sandbox.namecheap.com/xml.response"
-else
-  NAMECHEAP_API="https://api.namecheap.com/xml.response"
-fi
+NAMECHEAP_API="https://api.namecheap.com/xml.response"
 
 
 #Usage: dns_namecheap_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
 #Usage: dns_namecheap_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
 dns_namecheap_add() {
 dns_namecheap_add() {
@@ -144,7 +143,7 @@ _namecheap_set_publicip() {
 _namecheap_post() {
 _namecheap_post() {
   command=$1
   command=$1
   data="ApiUser=${NAMECHEAP_USERNAME}&ApiKey=${NAMECHEAP_API_KEY}&ClientIp=${_publicip}&UserName=${NAMECHEAP_USERNAME}&Command=${command}"
   data="ApiUser=${NAMECHEAP_USERNAME}&ApiKey=${NAMECHEAP_API_KEY}&ClientIp=${_publicip}&UserName=${NAMECHEAP_USERNAME}&Command=${command}"
-
+  _debug2 "_namecheap_post data" "$data"
   response="$(_post "$data" "$NAMECHEAP_API" "" "POST")"
   response="$(_post "$data" "$NAMECHEAP_API" "" "POST")"
   _debug2 response "$response"
   _debug2 response "$response"
 
 
@@ -161,12 +160,12 @@ _namecheap_parse_host() {
   _host=$1
   _host=$1
   _debug _host "$_host"
   _debug _host "$_host"
 
 
-  _hostid=$(echo "$_host" | _egrep_o '\sHostId="[^"]*' | cut -d '"' -f 2)
-  _hostname=$(echo "$_host" | _egrep_o '\sName="[^"]*' | cut -d '"' -f 2)
-  _hosttype=$(echo "$_host" | _egrep_o '\sType="[^"]*' | cut -d '"' -f 2)
-  _hostaddress=$(echo "$_host" | _egrep_o '\sAddress="[^"]*' | cut -d '"' -f 2)
-  _hostmxpref=$(echo "$_host" | _egrep_o '\sMXPref="[^"]*' | cut -d '"' -f 2)
-  _hostttl=$(echo "$_host" | _egrep_o '\sTTL="[^"]*' | cut -d '"' -f 2)
+  _hostid=$(echo "$_host" | _egrep_o ' HostId="[^"]*' | cut -d '"' -f 2)
+  _hostname=$(echo "$_host" | _egrep_o ' Name="[^"]*' | cut -d '"' -f 2)
+  _hosttype=$(echo "$_host" | _egrep_o ' Type="[^"]*' | cut -d '"' -f 2)
+  _hostaddress=$(echo "$_host" | _egrep_o ' Address="[^"]*' | cut -d '"' -f 2)
+  _hostmxpref=$(echo "$_host" | _egrep_o ' MXPref="[^"]*' | cut -d '"' -f 2)
+  _hostttl=$(echo "$_host" | _egrep_o ' TTL="[^"]*' | cut -d '"' -f 2)
 
 
   _debug hostid "$_hostid"
   _debug hostid "$_hostid"
   _debug hostname "$_hostname"
   _debug hostname "$_hostname"
@@ -199,9 +198,12 @@ _namecheap_check_config() {
 _set_namecheap_TXT() {
 _set_namecheap_TXT() {
   subdomain=$2
   subdomain=$2
   txt=$3
   txt=$3
-  tld=$(echo "$1" | cut -d '.' -f 2)
-  sld=$(echo "$1" | cut -d '.' -f 1)
-  request="namecheap.domains.dns.getHosts&SLD=$sld&TLD=$tld"
+
+  if ! _namecheap_set_tld_sld "$1"; then
+    return 1
+  fi
+
+  request="namecheap.domains.dns.getHosts&SLD=${_sld}&TLD=${_tld}"
 
 
   if ! _namecheap_post "$request"; then
   if ! _namecheap_post "$request"; then
     _err "$error"
     _err "$error"
@@ -221,6 +223,12 @@ _set_namecheap_TXT() {
   while read -r host; do
   while read -r host; do
     if _contains "$host" "<host"; then
     if _contains "$host" "<host"; then
       _namecheap_parse_host "$host"
       _namecheap_parse_host "$host"
+      _debug2 _hostname "_hostname"
+      _debug2 _hosttype "_hosttype"
+      _debug2 _hostaddress "_hostaddress"
+      _debug2 _hostmxpref "_hostmxpref"
+      _hostaddress="$(printf "%s" "$_hostaddress" | _url_encode)"
+      _debug2 "encoded _hostaddress" "_hostaddress"
       _namecheap_add_host "$_hostname" "$_hosttype" "$_hostaddress" "$_hostmxpref" "$_hostttl"
       _namecheap_add_host "$_hostname" "$_hosttype" "$_hostaddress" "$_hostmxpref" "$_hostttl"
     fi
     fi
   done <<EOT
   done <<EOT
@@ -231,7 +239,7 @@ EOT
 
 
   _debug hostrequestfinal "$_hostrequest"
   _debug hostrequestfinal "$_hostrequest"
 
 
-  request="namecheap.domains.dns.setHosts&SLD=${sld}&TLD=${tld}${_hostrequest}"
+  request="namecheap.domains.dns.setHosts&SLD=${_sld}&TLD=${_tld}${_hostrequest}"
 
 
   if ! _namecheap_post "$request"; then
   if ! _namecheap_post "$request"; then
     _err "$error"
     _err "$error"
@@ -244,9 +252,12 @@ EOT
 _del_namecheap_TXT() {
 _del_namecheap_TXT() {
   subdomain=$2
   subdomain=$2
   txt=$3
   txt=$3
-  tld=$(echo "$1" | cut -d '.' -f 2)
-  sld=$(echo "$1" | cut -d '.' -f 1)
-  request="namecheap.domains.dns.getHosts&SLD=$sld&TLD=$tld"
+
+  if ! _namecheap_set_tld_sld "$1"; then
+    return 1
+  fi
+
+  request="namecheap.domains.dns.getHosts&SLD=${_sld}&TLD=${_tld}"
 
 
   if ! _namecheap_post "$request"; then
   if ! _namecheap_post "$request"; then
     _err "$error"
     _err "$error"
@@ -272,6 +283,7 @@ _del_namecheap_TXT() {
         _debug "TXT entry found"
         _debug "TXT entry found"
         found=1
         found=1
       else
       else
+        _hostaddress="$(printf "%s" "$_hostaddress" | _url_encode)"
         _namecheap_add_host "$_hostname" "$_hosttype" "$_hostaddress" "$_hostmxpref" "$_hostttl"
         _namecheap_add_host "$_hostname" "$_hosttype" "$_hostaddress" "$_hostmxpref" "$_hostttl"
       fi
       fi
     fi
     fi
@@ -286,7 +298,7 @@ EOT
 
 
   _debug hostrequestfinal "$_hostrequest"
   _debug hostrequestfinal "$_hostrequest"
 
 
-  request="namecheap.domains.dns.setHosts&SLD=${sld}&TLD=${tld}${_hostrequest}"
+  request="namecheap.domains.dns.setHosts&SLD=${_sld}&TLD=${_tld}${_hostrequest}"
 
 
   if ! _namecheap_post "$request"; then
   if ! _namecheap_post "$request"; then
     _err "$error"
     _err "$error"
@@ -306,3 +318,45 @@ _namecheap_add_host() {
   _hostindex=$(_math "$_hostindex" + 1)
   _hostindex=$(_math "$_hostindex" + 1)
   _hostrequest=$(printf '%s&HostName%d=%s&RecordType%d=%s&Address%d=%s&MXPref%d=%d&TTL%d=%d' "$_hostrequest" "$_hostindex" "$1" "$_hostindex" "$2" "$_hostindex" "$3" "$_hostindex" "$4" "$_hostindex" "$5")
   _hostrequest=$(printf '%s&HostName%d=%s&RecordType%d=%s&Address%d=%s&MXPref%d=%d&TTL%d=%d' "$_hostrequest" "$_hostindex" "$1" "$_hostindex" "$2" "$_hostindex" "$3" "$_hostindex" "$4" "$_hostindex" "$5")
 }
 }
+
+_namecheap_set_tld_sld() {
+  domain=$1
+  _tld=""
+  _sld=""
+
+  i=2
+
+  while true; do
+
+    _tld=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug tld "$_tld"
+
+    if [ -z "$_tld" ]; then
+      _debug "invalid tld"
+      return 1
+    fi
+
+    j=$(_math "$i" - 1)
+
+    _sld=$(printf "%s" "$domain" | cut -d . -f 1-"$j")
+    _debug sld "$_sld"
+
+    if [ -z "$_sld" ]; then
+      _debug "invalid sld"
+      return 1
+    fi
+
+    request="namecheap.domains.dns.getHosts&SLD=$_sld&TLD=$_tld"
+
+    if ! _namecheap_post "$request"; then
+      _debug "sld($_sld)/tld($_tld) not found"
+    else
+      _debug "sld($_sld)/tld($_tld) found"
+      return 0
+    fi
+
+    i=$(_math "$i" + 1)
+
+  done
+
+}

+ 164 - 0
dnsapi/dns_pointhq.sh

@@ -0,0 +1,164 @@
+#!/usr/bin/env sh
+
+#
+#PointHQ_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
+#
+#PointHQ_Email="xxxx@sss.com"
+
+PointHQ_Api="https://api.pointhq.com"
+
+########  Public functions #####################
+
+#Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_pointhq_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  PointHQ_Key="${PointHQ_Key:-$(_readaccountconf_mutable PointHQ_Key)}"
+  PointHQ_Email="${PointHQ_Email:-$(_readaccountconf_mutable PointHQ_Email)}"
+  if [ -z "$PointHQ_Key" ] || [ -z "$PointHQ_Email" ]; then
+    PointHQ_Key=""
+    PointHQ_Email=""
+    _err "You didn't specify a PointHQ API key and email yet."
+    _err "Please create the key and try again."
+    return 1
+  fi
+
+  if ! _contains "$PointHQ_Email" "@"; then
+    _err "It seems that the PointHQ_Email=$PointHQ_Email is not a valid email address."
+    _err "Please check and retry."
+    return 1
+  fi
+
+  #save the api key and email to the account conf file.
+  _saveaccountconf_mutable PointHQ_Key "$PointHQ_Key"
+  _saveaccountconf_mutable PointHQ_Email "$PointHQ_Email"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _info "Adding record"
+  if _pointhq_rest POST "zones/$_domain/records" "{\"zone_record\": {\"name\":\"$_sub_domain\",\"record_type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":3600}}"; then
+    if printf -- "%s" "$response" | grep "$fulldomain" >/dev/null; then
+      _info "Added, OK"
+      return 0
+    else
+      _err "Add txt record error."
+      return 1
+    fi
+  fi
+  _err "Add txt record error."
+  return 1
+}
+
+#fulldomain txtvalue
+dns_pointhq_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  PointHQ_Key="${PointHQ_Key:-$(_readaccountconf_mutable PointHQ_Key)}"
+  PointHQ_Email="${PointHQ_Email:-$(_readaccountconf_mutable PointHQ_Email)}"
+  if [ -z "$PointHQ_Key" ] || [ -z "$PointHQ_Email" ]; then
+    PointHQ_Key=""
+    PointHQ_Email=""
+    _err "You didn't specify a PointHQ API key and email yet."
+    _err "Please create the key and try again."
+    return 1
+  fi
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _debug "Getting txt records"
+  _pointhq_rest GET "zones/${_domain}/records?record_type=TXT&name=$_sub_domain"
+
+  if ! printf "%s" "$response" | grep "^\[" >/dev/null; then
+    _err "Error"
+    return 1
+  fi
+
+  if [ "$response" = "[]" ]; then
+    _info "No records to remove."
+  else
+    record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | cut -d : -f 2 | tr -d \" | head -n 1)
+    _debug "record_id" "$record_id"
+    if [ -z "$record_id" ]; then
+      _err "Can not get record id to remove."
+      return 1
+    fi
+    if ! _pointhq_rest DELETE "zones/$_domain/records/$record_id"; then
+      _err "Delete record error."
+      return 1
+    fi
+    _contains "$response" '"status":"OK"'
+  fi
+}
+
+####################  Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+  domain=$1
+  i=2
+  p=1
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug h "$h"
+    if [ -z "$h" ]; then
+      #not valid
+      return 1
+    fi
+
+    if ! _pointhq_rest GET "zones"; then
+      return 1
+    fi
+
+    if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
+      _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+      _domain=$h
+      return 0
+    fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+_pointhq_rest() {
+  m=$1
+  ep="$2"
+  data="$3"
+  _debug "$ep"
+
+  _pointhq_auth=$(printf "%s:%s" "$PointHQ_Email" "$PointHQ_Key" | _base64)
+
+  export _H1="Authorization: Basic $_pointhq_auth"
+  export _H2="Content-Type: application/json"
+  export _H3="Accept: application/json"
+
+  if [ "$m" != "GET" ]; then
+    _debug data "$data"
+    response="$(_post "$data" "$PointHQ_Api/$ep" "" "$m")"
+  else
+    response="$(_get "$PointHQ_Api/$ep")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}