Browse Source

Merge pull request #2 from Neilpang/dev

Merging develop
Ivru 6 years ago
parent
commit
a5e4bf16d3

+ 32 - 14
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.
 
 
@@ -330,6 +347,7 @@ You don't have to do anything manually!
 1. MyDNS.JP API (https://www.mydns.jp/)
 1. MyDNS.JP API (https://www.mydns.jp/)
 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/)
 
 
 And:
 And:
 
 
@@ -343,7 +361,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.
 
 
@@ -379,7 +397,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.
 
 
@@ -411,7 +429,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.
 
 
@@ -421,7 +439,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.
 
 
@@ -438,7 +456,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:
 
 
@@ -451,7 +469,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.
 
 
@@ -476,25 +494,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
 
 
@@ -503,7 +521,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/)

+ 71 - 28
acme.sh

@@ -36,11 +36,11 @@ _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_TLS="tls-sni-01"
-VTYPE_TLS2="tls-sni-02"
+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
 
 
@@ -48,6 +48,7 @@ NO_VALUE="no"
 
 
 W_TLS="tls"
 W_TLS="tls"
 W_DNS="dns"
 W_DNS="dns"
+W_ALPN="alpn"
 DNS_ALIAS_PREFIX="="
 DNS_ALIAS_PREFIX="="
 
 
 MODE_STATELESS="stateless"
 MODE_STATELESS="stateless"
@@ -1046,7 +1047,7 @@ _idn() {
   fi
   fi
 }
 }
 
 
-#_createcsr  cn  san_list  keyfile csrfile conf
+#_createcsr  cn  san_list  keyfile csrfile conf acmeValidationv1
 _createcsr() {
 _createcsr() {
   _debug _createcsr
   _debug _createcsr
   domain="$1"
   domain="$1"
@@ -1054,6 +1055,7 @@ _createcsr() {
   csrkey="$3"
   csrkey="$3"
   csr="$4"
   csr="$4"
   csrconf="$5"
   csrconf="$5"
+  acmeValidationv1="$6"
   _debug2 domain "$domain"
   _debug2 domain "$domain"
   _debug2 domainlist "$domainlist"
   _debug2 domainlist "$domainlist"
   _debug2 csrkey "$csrkey"
   _debug2 csrkey "$csrkey"
@@ -1062,7 +1064,9 @@ _createcsr() {
 
 
   printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\nreq_extensions = v3_req\n[ v3_req ]\n\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment" >"$csrconf"
   printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\nreq_extensions = v3_req\n[ v3_req ]\n\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment" >"$csrconf"
 
 
-  if [ -z "$domainlist" ] || [ "$domainlist" = "$NO_VALUE" ]; then
+  if [ "$acmeValidationv1" ]; then
+    printf -- "\nsubjectAltName=DNS:$domainlist" >>"$csrconf"
+  elif [ -z "$domainlist" ] || [ "$domainlist" = "$NO_VALUE" ]; then
     #single domain
     #single domain
     _info "Single domain" "$domain"
     _info "Single domain" "$domain"
     printf -- "\nsubjectAltName=DNS:$domain" >>"$csrconf"
     printf -- "\nsubjectAltName=DNS:$domain" >>"$csrconf"
@@ -1084,6 +1088,10 @@ _createcsr() {
     printf -- "\nbasicConstraints = CA:FALSE\n1.3.6.1.5.5.7.1.24=DER:30:03:02:01:05" >>"$csrconf"
     printf -- "\nbasicConstraints = CA:FALSE\n1.3.6.1.5.5.7.1.24=DER:30:03:02:01:05" >>"$csrconf"
   fi
   fi
 
 
+  if [ "$acmeValidationv1" ]; then
+    printf "\n1.3.6.1.5.5.7.1.30.1=critical,DER:04:20:${acmeValidationv1}" >>"${csrconf}"
+  fi
+
   _csr_cn="$(_idn "$domain")"
   _csr_cn="$(_idn "$domain")"
   _debug2 _csr_cn "$_csr_cn"
   _debug2 _csr_cn "$_csr_cn"
   if _contains "$(uname -a)" "MINGW"; then
   if _contains "$(uname -a)" "MINGW"; then
@@ -1134,12 +1142,17 @@ _readSubjectAltNamesFromCSR() {
 
 
   if _contains "$_dnsAltnames," "DNS:$_csrsubj,"; then
   if _contains "$_dnsAltnames," "DNS:$_csrsubj,"; then
     _debug "AltNames contains subject"
     _debug "AltNames contains subject"
-    _dnsAltnames="$(printf "%s" "$_dnsAltnames," | sed "s/DNS:$_csrsubj,//g")"
+    _excapedAlgnames="$(echo "$_dnsAltnames" | tr '*' '#')"
+    _debug _excapedAlgnames "$_excapedAlgnames"
+    _escapedSubject="$(echo "$_csrsubj" | tr '*' '#')"
+    _debug _escapedSubject "$_escapedSubject"
+    _dnsAltnames="$(echo "$_excapedAlgnames," | sed "s/DNS:$_escapedSubject,//g" | tr '#' '*' | sed "s/,\$//g")"
+    _debug _dnsAltnames "$_dnsAltnames"
   else
   else
     _debug "AltNames doesn't contain subject"
     _debug "AltNames doesn't contain subject"
   fi
   fi
 
 
-  printf "%s" "$_dnsAltnames" | sed "s/DNS://g"
+  echo "$_dnsAltnames" | sed "s/DNS://g"
 }
 }
 
 
 #_csrfile
 #_csrfile
@@ -1516,7 +1529,8 @@ _calcjwk() {
     JWK_HEADERPLACE_PART1='{"nonce": "'
     JWK_HEADERPLACE_PART1='{"nonce": "'
     JWK_HEADERPLACE_PART2='", "alg": "ES'$__ECC_KEY_LEN'"'
     JWK_HEADERPLACE_PART2='", "alg": "ES'$__ECC_KEY_LEN'"'
   else
   else
-    _err "Only RSA or EC key is supported."
+    _err "Only RSA or EC key is supported. keyfile=$keyfile"
+    _debug2 "$(cat "$keyfile")"
     return 1
     return 1
   fi
   fi
 
 
@@ -1860,11 +1874,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")"
@@ -2101,7 +2111,7 @@ _sleep() {
   fi
   fi
 }
 }
 
 
-# _starttlsserver  san_a  san_b port content _ncaddr
+# _starttlsserver  san_a  san_b port content _ncaddr acmeValidationv1
 _starttlsserver() {
 _starttlsserver() {
   _info "Starting tls server."
   _info "Starting tls server."
   san_a="$1"
   san_a="$1"
@@ -2109,10 +2119,12 @@ _starttlsserver() {
   port="$3"
   port="$3"
   content="$4"
   content="$4"
   opaddr="$5"
   opaddr="$5"
+  acmeValidationv1="$6"
 
 
   _debug san_a "$san_a"
   _debug san_a "$san_a"
   _debug san_b "$san_b"
   _debug san_b "$san_b"
   _debug port "$port"
   _debug port "$port"
+  _debug acmeValidationv1 "$acmeValidationv1"
 
 
   #create key TLS_KEY
   #create key TLS_KEY
   if ! _createkey "2048" "$TLS_KEY"; then
   if ! _createkey "2048" "$TLS_KEY"; then
@@ -2125,7 +2137,7 @@ _starttlsserver() {
   if [ "$san_b" ]; then
   if [ "$san_b" ]; then
     alt="$alt,$san_b"
     alt="$alt,$san_b"
   fi
   fi
-  if ! _createcsr "tls.acme.sh" "$alt" "$TLS_KEY" "$TLS_CSR" "$TLS_CONF"; then
+  if ! _createcsr "tls.acme.sh" "$alt" "$TLS_KEY" "$TLS_CSR" "$TLS_CONF" "$acmeValidationv1"; then
     _err "Create tls validation csr error."
     _err "Create tls validation csr error."
     return 1
     return 1
   fi
   fi
@@ -2151,6 +2163,10 @@ _starttlsserver() {
     __S_OPENSSL="$__S_OPENSSL -6"
     __S_OPENSSL="$__S_OPENSSL -6"
   fi
   fi
 
 
+  if [ "$acmeValidationv1" ]; then
+    __S_OPENSSL="$__S_OPENSSL -alpn acme-tls/1"
+  fi
+
   _debug "$__S_OPENSSL"
   _debug "$__S_OPENSSL"
   if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
   if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
     $__S_OPENSSL -tlsextdebug &
     $__S_OPENSSL -tlsextdebug &
@@ -3061,8 +3077,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" ]; then
-      _info "Standalone tls mode."
+    elif [ "$_currentRoot" = "$W_TLS" ] || [ "$_currentRoot" = "$W_ALPN" ]; then
+      _info "Standalone tls/alpn mode."
       if [ -z "$Le_TLSPort" ]; then
       if [ -z "$Le_TLSPort" ]; then
         Le_TLSPort=443
         Le_TLSPort=443
       else
       else
@@ -3422,15 +3438,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
 }
 }
 
 
@@ -3681,11 +3699,11 @@ $_authorizations_map"
       fi
       fi
 
 
       if [ "$_currentRoot" = "$W_TLS" ]; then
       if [ "$_currentRoot" = "$W_TLS" ]; then
-        if [ "$ACME_VERSION" = "2" ]; then
-          vtype="$VTYPE_TLS2"
-        else
-          vtype="$VTYPE_TLS"
-        fi
+        vtype="$VTYPE_TLS"
+      fi
+
+      if [ "$_currentRoot" = "$W_ALPN" ]; then
+        vtype="$VTYPE_ALPN"
       fi
       fi
 
 
       if [ "$ACME_VERSION" = "2" ]; then
       if [ "$ACME_VERSION" = "2" ]; then
@@ -4001,9 +4019,19 @@ $_authorizations_map"
         _on_issue_err "$_post_hook" "$vlist"
         _on_issue_err "$_post_hook" "$vlist"
         return 1
         return 1
       fi
       fi
+    elif [ "$vtype" = "$VTYPE_ALPN" ]; then
+      acmevalidationv1="$(printf "%s" "$keyauthorization" | _digest "sha256" "hex")"
+      _debug acmevalidationv1 "$acmevalidationv1"
+      if ! _starttlsserver "$d" "" "$Le_TLSPort" "$keyauthorization" "$_ncaddr" "$acmevalidationv1"; then
+        _err "Start tls server error."
+        _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
+        _clearup
+        _on_issue_err "$_post_hook" "$vlist"
+        return 1
+      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
@@ -4012,7 +4040,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"
@@ -4239,8 +4267,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
@@ -5463,6 +5491,7 @@ Parameters:
   --output-insecure                 Output all the sensitive messages. By default all the credentials/sensitive messages are hidden from the output/debug/log for secure.
   --output-insecure                 Output all the sensitive messages. By default all the credentials/sensitive messages are hidden from the output/debug/log for secure.
   --webroot, -w  /path/to/webroot   Specifies the web root folder for web root mode.
   --webroot, -w  /path/to/webroot   Specifies the web root folder for web root mode.
   --standalone                      Use standalone mode.
   --standalone                      Use standalone mode.
+  --alpn                            Use standalone alpn mode.
   --stateless                       Use stateless mode, see: $_STATELESS_WIKI
   --stateless                       Use stateless mode, see: $_STATELESS_WIKI
   --apache                          Use apache mode.
   --apache                          Use apache mode.
   --dns [dns_cf|dns_dp|dns_cx|/path/to/api/file]   Use dns mode or dns api.
   --dns [dns_cf|dns_dp|dns_cx|/path/to/api/file]   Use dns mode or dns api.
@@ -5491,8 +5520,9 @@ 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.
   --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.
   --listraw                         Only used for '--list' command, list the certs in raw format.
   --listraw                         Only used for '--list' command, list the certs in raw format.
   --stopRenewOnError, -se           Only valid for '--renew-all' command. Stop if one cert has error in renewal.
   --stopRenewOnError, -se           Only valid for '--renew-all' command. Stop if one cert has error in renewal.
@@ -5817,6 +5847,14 @@ _process() {
           _webroot="$_webroot,$wvalue"
           _webroot="$_webroot,$wvalue"
         fi
         fi
         ;;
         ;;
+      --alpn)
+        wvalue="$W_ALPN"
+        if [ -z "$_webroot" ]; then
+          _webroot="$wvalue"
+        else
+          _webroot="$_webroot,$wvalue"
+        fi
+        ;;
       --stateless)
       --stateless)
         wvalue="$MODE_STATELESS"
         wvalue="$MODE_STATELESS"
         if [ -z "$_webroot" ]; then
         if [ -z "$_webroot" ]; then
@@ -5941,6 +5979,11 @@ _process() {
         Le_HTTPPort="$_httpport"
         Le_HTTPPort="$_httpport"
         shift
         shift
         ;;
         ;;
+      --tlsport)
+        _tlsport="$2"
+        Le_TLSPort="$_tlsport"
+        shift
+        ;;
       --listraw)
       --listraw)
         _listraw="raw"
         _listraw="raw"
         ;;
         ;;

+ 63 - 12
dnsapi/README.md

@@ -146,13 +146,17 @@ Finally, make the DNS server and update Key available to `acme.sh`
 export NSUPDATE_SERVER="dns.example.com"
 export NSUPDATE_SERVER="dns.example.com"
 export NSUPDATE_KEY="/path/to/your/nsupdate.key"
 export NSUPDATE_KEY="/path/to/your/nsupdate.key"
 ```
 ```
+and optionally (depending on DNS server)
+```
+export NSUPDATE_ZONE="example.com"
+```
 
 
 Ok, let's issue a cert now:
 Ok, let's issue a cert now:
 ```
 ```
 acme.sh --issue --dns dns_nsupdate -d example.com -d www.example.com
 acme.sh --issue --dns dns_nsupdate -d example.com -d www.example.com
 ```
 ```
 
 
-The `NSUPDATE_SERVER` and `NSUPDATE_KEY` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+The `NSUPDATE_SERVER`, `NSUPDATE_KEY`, and `NSUPDATE_ZONE` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
 
 
 
 ## 8. Use LuaDNS domain API
 ## 8. Use LuaDNS domain API
@@ -263,25 +267,26 @@ when needed.
 
 
 ## 14. Use Linode domain API
 ## 14. Use Linode domain API
 
 
-First you need to login to your Linode account to get your API Key.
+The tokens created in the classic manager and cloud manager are incompatible
+with one another. While the classic manager makes an all or nothing API, the
+newer cloud manager interface promises to produce API keys with a finer
+permission system. However, either way works just fine.
 
 
-  * [Classic Manager](https://manager.linode.com/profile/api)
+### Classic Manager ###
 
 
-   Under "Add an API key", Give the new key a "Label" (we recommend *ACME*),
-   set the expiry to never, "Create API Key", and copy the new key into the `LINODE_API_KEY` command
-   below.
+Classic Manager: https://manager.linode.com/profile/api
 
 
-  * [Cloud Manager](https://cloud.linode.com/profile/tokens)
+First you need to login to your Linode account to get your API Key.
 
 
-   Click on "Add a Personal Access Token". Give the new key a "Label" (we
-   recommend *ACME*), give it Read/Write access to "Domains". "Submit", and
-   copy the new key into the `LINODE_API_KEY` command below.
+Then add an API key with label *ACME* and copy the new key into the following
+command.
 
 
 ```sh
 ```sh
 export LINODE_API_KEY="..."
 export LINODE_API_KEY="..."
 ```
 ```
 
 
-Due to the reload time of any changes in the DNS records, we have to use the `dnssleep` option to wait at least 15 minutes for the changes to take effect.
+Due to the reload time of any changes in the DNS records, we have to use the
+`dnssleep` option to wait at least 15 minutes for the changes to take effect.
 
 
 Ok, let's issue a cert now:
 Ok, let's issue a cert now:
 
 
@@ -289,7 +294,35 @@ Ok, let's issue a cert now:
 acme.sh --issue --dns dns_linode --dnssleep 900 -d example.com -d www.example.com
 acme.sh --issue --dns dns_linode --dnssleep 900 -d example.com -d www.example.com
 ```
 ```
 
 
-The `LINODE_API_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+The `LINODE_API_KEY` will be saved in `~/.acme.sh/account.conf` and will be
+reused when needed.
+
+### Cloud Manager ###
+
+Cloud Manager: https://cloud.linode.com/profile/tokens
+
+First you need to login to your Linode account to get your API Key.
+
+   1. Click on "Add a Personal Access Token".
+   2. Give the new key a "Label" (we recommend *ACME*)
+   3. Give it Read/Write access to "Domains"
+   4. "Submit" and copy the new key into the `LINODE_V4_API_KEY` command below.
+
+```sh
+export LINODE_V4_API_KEY="..."
+```
+
+Due to the reload time of any changes in the DNS records, we have to use the
+`dnssleep` option to wait at least 15 minutes for the changes to take effect.
+
+Ok, let's issue a cert now:
+
+```sh
+acme.sh --issue --dns dns_linode_v4 --dnssleep 900 -d example.com -d www.example.com
+```
+
+The `LINODE_V4_API_KEY` will be saved in `~/.acme.sh/account.conf` and will be
+reused when needed.
 
 
 ## 15. Use FreeDNS
 ## 15. Use FreeDNS
 
 
@@ -1076,6 +1109,24 @@ acme.sh --issue --dns dns_neodigit -d example.com -d www.example.com
 
 
 Neodigit API Token will be saved in `~/.acme.sh/account.conf` and will be used when needed.
 Neodigit API Token will be saved in `~/.acme.sh/account.conf` and will be used when needed.
 
 
+## 57. Use Exoscale API
+
+Create an API key and secret key in the Exoscale account section
+
+Set your API and secret key:
+
+```
+export EXOSCALE_API_KEY='xxx'
+export EXOSCALE_SECRET_KEY='xxx'
+```
+
+Now, let's issue a cert:
+```
+acme.sh --issue --dns dns_netcup -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.
+
 # Use custom API
 # Use custom API
 
 
 If your API is not supported yet, you can write your own DNS API.
 If your API is not supported yet, you can write your own DNS API.

+ 6 - 25
dnsapi/dns_cf.sh

@@ -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"
@@ -105,16 +102,11 @@ dns_cf_rm() {
     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

+ 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=""

+ 168 - 0
dnsapi/dns_exoscale.sh

@@ -0,0 +1,168 @@
+#!/usr/bin/env sh
+
+EXOSCALE_API=https://api.exoscale.com/dns/v1
+
+########  Public functions #####################
+
+# Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Used to add txt record
+dns_exoscale_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if ! _checkAuth; then
+    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"
+
+  _info "Adding record"
+  if _exoscale_rest POST "domains/$_domain_id/records" "{\"record\":{\"name\":\"$_sub_domain\",\"record_type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120}}" "$_domain_token"; then
+    if _contains "$response" "$txtvalue"; then
+      _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_exoscale_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if ! _checkAuth; then
+    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"
+  _exoscale_rest GET "domains/${_domain_id}/records?type=TXT&name=$_sub_domain" "" "$_domain_token"
+  if _contains "$response" "\"name\":\"$_sub_domain\"" >/dev/null; then
+    _record_id=$(echo "$response" | tr '{' "\n" | grep "\"content\":\"$txtvalue\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \")
+  fi
+
+  if [ -z "$_record_id" ]; then
+    _err "Can not get record id to remove."
+    return 1
+  fi
+
+  _debug "Deleting record $_record_id"
+
+  if ! _exoscale_rest DELETE "domains/$_domain_id/records/$_record_id" "" "$_domain_token"; then
+    _err "Delete record error."
+    return 1
+  fi
+
+  return 0
+}
+
+####################  Private functions below ##################################
+
+_checkAuth() {
+  EXOSCALE_API_KEY="${EXOSCALE_API_KEY:-$(_readaccountconf_mutable EXOSCALE_API_KEY)}"
+  EXOSCALE_SECRET_KEY="${EXOSCALE_SECRET_KEY:-$(_readaccountconf_mutable EXOSCALE_SECRET_KEY)}"
+
+  if [ -z "$EXOSCALE_API_KEY" ] || [ -z "$EXOSCALE_SECRET_KEY" ]; then
+    EXOSCALE_API_KEY=""
+    EXOSCALE_SECRET_KEY=""
+    _err "You don't specify Exoscale application key and application secret yet."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
+  _saveaccountconf_mutable EXOSCALE_API_KEY "$EXOSCALE_API_KEY"
+  _saveaccountconf_mutable EXOSCALE_SECRET_KEY "$EXOSCALE_SECRET_KEY"
+
+  return 0
+}
+
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+# _domain_token=sdjkglgdfewsdfg
+_get_root() {
+
+  if ! _exoscale_rest GET "domains"; then
+    return 1
+  fi
+
+  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 _contains "$response" "\"name\":\"$h\"" >/dev/null; then
+      _domain_id=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \")
+      _domain_token=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
+      if [ "$_domain_token" ] && [ "$_domain_id" ]; then
+        _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+        _domain=$h
+        return 0
+      fi
+      return 1
+    fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+# returns response
+_exoscale_rest() {
+  method=$1
+  path="$2"
+  data="$3"
+  token="$4"
+  request_url="$EXOSCALE_API/$path"
+  _debug "$path"
+
+  export _H1="Accept: application/json"
+
+  if [ "$token" ]; then
+    export _H2="X-DNS-Domain-Token: $token"
+  else
+    export _H2="X-DNS-Token: $EXOSCALE_API_KEY:$EXOSCALE_SECRET_KEY"
+  fi
+
+  if [ "$data" ] || [ "$method" = "DELETE" ]; then
+    export _H3="Content-Type: application/json"
+    _debug data "$data"
+    response="$(_post "$data" "$request_url" "" "$method")"
+  else
+    response="$(_get "$request_url" "" "" "$method")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $request_url"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}

+ 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"

+ 25 - 0
dnsapi/dns_hostingde.sh

@@ -74,8 +74,26 @@ _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"
+  _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}\"},\"recordsToAdd\":[{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"\\\"${txtvalue}\\\"\",\"ttl\":3600}]}"
   curData="{\"authToken\":\"${HOSTINGDE_APIKEY}\",\"zoneConfig\":{\"id\":\"${zoneConfigId}\"},\"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'"
@@ -93,6 +111,13 @@ _hostingde_addRecord() {
 
 
 _hostingde_removeRecord() {
 _hostingde_removeRecord() {
   _info "Removing record from zone"
   _info "Removing record from zone"
+  _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}\"},\"recordsToDelete\":[{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"\\\"${txtvalue}\\\"\"}]}"
   curData="{\"authToken\":\"${HOSTINGDE_APIKEY}\",\"zoneConfig\":{\"id\":\"${zoneConfigId}\"},\"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'"

+ 19 - 20
dnsapi/dns_linode.sh

@@ -2,7 +2,7 @@
 
 
 #Author: Philipp Grosswiler <philipp.grosswiler@swiss-design.net>
 #Author: Philipp Grosswiler <philipp.grosswiler@swiss-design.net>
 
 
-LINODE_API_URL="https://api.linode.com/v4/domains"
+LINODE_API_URL="https://api.linode.com/?api_key=$LINODE_API_KEY&api_action="
 
 
 ########  Public functions #####################
 ########  Public functions #####################
 
 
@@ -27,14 +27,10 @@ dns_linode_add() {
   _debug _sub_domain "$_sub_domain"
   _debug _sub_domain "$_sub_domain"
   _debug _domain "$_domain"
   _debug _domain "$_domain"
 
 
-  _payload="{
-              \"type\": \"TXT\",
-              \"name\": \"$_sub_domain\",
-              \"target\": \"$txtvalue\"
-            }"
+  _parameters="&DomainID=$_domain_id&Type=TXT&Name=$_sub_domain&Target=$txtvalue"
 
 
-  if _rest POST "/$_domain_id/records" "$_payload" && [ -n "$response" ]; then
-    _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1)
+  if _rest GET "domain.resource.create" "$_parameters" && [ -n "$response" ]; then
+    _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1)
     _debug _resource_id "$_resource_id"
     _debug _resource_id "$_resource_id"
 
 
     if [ -z "$_resource_id" ]; then
     if [ -z "$_resource_id" ]; then
@@ -69,21 +65,25 @@ dns_linode_rm() {
   _debug _sub_domain "$_sub_domain"
   _debug _sub_domain "$_sub_domain"
   _debug _domain "$_domain"
   _debug _domain "$_domain"
 
 
-  if _rest GET "/$_domain_id/records" && [ -n "$response" ]; then
+  _parameters="&DomainID=$_domain_id"
+
+  if _rest GET "domain.resource.list" "$_parameters" && [ -n "$response" ]; then
     response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")"
     response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")"
 
 
-    resource="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$_sub_domain\".*}")"
+    resource="$(echo "$response" | _egrep_o "{.*\"NAME\":\s*\"$_sub_domain\".*}")"
     if [ "$resource" ]; then
     if [ "$resource" ]; then
-      _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
+      _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"RESOURCEID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
       if [ "$_resource_id" ]; then
       if [ "$_resource_id" ]; then
         _debug _resource_id "$_resource_id"
         _debug _resource_id "$_resource_id"
 
 
-        if _rest DELETE "/$_domain_id/records/$_resource_id" && [ -n "$response" ]; then
-          # On 200/OK, empty set is returned. Check for error, if any.
-          _error_response=$(printf "%s\n" "$response" | _egrep_o "\"errors\"" | cut -d : -f 2 | tr -d " " | _head_n 1)
+        _parameters="&DomainID=$_domain_id&ResourceID=$_resource_id"
+
+        if _rest GET "domain.resource.delete" "$_parameters" && [ -n "$response" ]; then
+          _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1)
+          _debug _resource_id "$_resource_id"
 
 
-          if [ -n "$_error_response" ]; then
-            _err "Error deleting the domain resource: $_error_response"
+          if [ -z "$_resource_id" ]; then
+            _err "Error deleting the domain resource."
             return 1
             return 1
           fi
           fi
 
 
@@ -127,7 +127,7 @@ _get_root() {
   i=2
   i=2
   p=1
   p=1
 
 
-  if _rest GET; then
+  if _rest GET "domain.list"; then
     response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")"
     response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")"
     while true; do
     while true; do
       h=$(printf "%s" "$domain" | cut -d . -f $i-100)
       h=$(printf "%s" "$domain" | cut -d . -f $i-100)
@@ -137,9 +137,9 @@ _get_root() {
         return 1
         return 1
       fi
       fi
 
 
-      hostedzone="$(echo "$response" | _egrep_o "{.*\"domain\":\s*\"$h\".*}")"
+      hostedzone="$(echo "$response" | _egrep_o "{.*\"DOMAIN\":\s*\"$h\".*}")"
       if [ "$hostedzone" ]; then
       if [ "$hostedzone" ]; then
-        _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
+        _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"DOMAINID\":\s*[0-9]+" | _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
@@ -165,7 +165,6 @@ _rest() {
 
 
   export _H1="Accept: application/json"
   export _H1="Accept: application/json"
   export _H2="Content-Type: application/json"
   export _H2="Content-Type: application/json"
-  export _H3="Authorization: Bearer $LINODE_API_KEY"
 
 
   if [ "$mtd" != "GET" ]; then
   if [ "$mtd" != "GET" ]; then
     # both POST and DELETE.
     # both POST and DELETE.

+ 185 - 0
dnsapi/dns_linode_v4.sh

@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#Original Author: Philipp Grosswiler <philipp.grosswiler@swiss-design.net>
+#v4 Update Author: Aaron W. Swenson <aaron@grandmasfridge.org>
+
+LINODE_V4_API_URL="https://api.linode.com/v4/domains"
+
+########  Public functions #####################
+
+#Usage: dns_linode_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_linode_add() {
+  fulldomain="${1}"
+  txtvalue="${2}"
+
+  if ! _Linode_API; then
+    return 1
+  fi
+
+  _info "Using Linode"
+  _debug "Calling: dns_linode_add() '${fulldomain}' '${txtvalue}'"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "Domain does not exist."
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _payload="{
+              \"type\": \"TXT\",
+              \"name\": \"$_sub_domain\",
+              \"target\": \"$txtvalue\"
+            }"
+
+  if _rest POST "/$_domain_id/records" "$_payload" && [ -n "$response" ]; then
+    _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1)
+    _debug _resource_id "$_resource_id"
+
+    if [ -z "$_resource_id" ]; then
+      _err "Error adding the domain resource."
+      return 1
+    fi
+
+    _info "Domain resource successfully added."
+    return 0
+  fi
+
+  return 1
+}
+
+#Usage: dns_linode_rm   _acme-challenge.www.domain.com
+dns_linode_rm() {
+  fulldomain="${1}"
+
+  if ! _Linode_API; then
+    return 1
+  fi
+
+  _info "Using Linode"
+  _debug "Calling: dns_linode_rm() '${fulldomain}'"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "Domain does not exist."
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  if _rest GET "/$_domain_id/records" && [ -n "$response" ]; then
+    response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")"
+
+    resource="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$_sub_domain\".*}")"
+    if [ "$resource" ]; then
+      _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
+      if [ "$_resource_id" ]; then
+        _debug _resource_id "$_resource_id"
+
+        if _rest DELETE "/$_domain_id/records/$_resource_id" && [ -n "$response" ]; then
+          # On 200/OK, empty set is returned. Check for error, if any.
+          _error_response=$(printf "%s\n" "$response" | _egrep_o "\"errors\"" | cut -d : -f 2 | tr -d " " | _head_n 1)
+
+          if [ -n "$_error_response" ]; then
+            _err "Error deleting the domain resource: $_error_response"
+            return 1
+          fi
+
+          _info "Domain resource successfully deleted."
+          return 0
+        fi
+      fi
+
+      return 1
+    fi
+
+    return 0
+  fi
+
+  return 1
+}
+
+####################  Private functions below ##################################
+
+_Linode_API() {
+  if [ -z "$LINODE_V4_API_KEY" ]; then
+    LINODE_V4_API_KEY=""
+
+    _err "You didn't specify the Linode v4 API key yet."
+    _err "Please create your key and try again."
+
+    return 1
+  fi
+
+  _saveaccountconf LINODE_V4_API_KEY "$LINODE_V4_API_KEY"
+}
+
+####################  Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=12345
+_get_root() {
+  domain=$1
+  i=2
+  p=1
+
+  if _rest GET; then
+    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"
+      if [ -z "$h" ]; then
+        #not valid
+        return 1
+      fi
+
+      hostedzone="$(echo "$response" | _egrep_o "{.*\"domain\":\s*\"$h\".*}")"
+      if [ "$hostedzone" ]; then
+        _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
+        if [ "$_domain_id" ]; then
+          _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+          _domain=$h
+          return 0
+        fi
+        return 1
+      fi
+      p=$i
+      i=$(_math "$i" + 1)
+    done
+  fi
+  return 1
+}
+
+#method method action data
+_rest() {
+  mtd="$1"
+  ep="$2"
+  data="$3"
+
+  _debug mtd "$mtd"
+  _debug ep "$ep"
+
+  export _H1="Accept: application/json"
+  export _H2="Content-Type: application/json"
+  export _H3="Authorization: Bearer $LINODE_V4_API_KEY"
+
+  if [ "$mtd" != "GET" ]; then
+    # both POST and DELETE.
+    _debug data "$data"
+    response="$(_post "$data" "$LINODE_V4_API_URL$ep" "" "$mtd")"
+  else
+    response="$(_get "$LINODE_V4_API_URL$ep$data")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}

+ 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>

+ 56 - 8
dnsapi/dns_namecheap.sh

@@ -199,9 +199,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"
@@ -231,7 +234,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 +247,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"
@@ -286,7 +292,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 +312,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
+
+}

+ 25 - 2
dnsapi/dns_nsupdate.sh

@@ -13,12 +13,24 @@ dns_nsupdate_add() {
   _saveaccountconf NSUPDATE_SERVER "${NSUPDATE_SERVER}"
   _saveaccountconf NSUPDATE_SERVER "${NSUPDATE_SERVER}"
   _saveaccountconf NSUPDATE_SERVER_PORT "${NSUPDATE_SERVER_PORT}"
   _saveaccountconf NSUPDATE_SERVER_PORT "${NSUPDATE_SERVER_PORT}"
   _saveaccountconf NSUPDATE_KEY "${NSUPDATE_KEY}"
   _saveaccountconf NSUPDATE_KEY "${NSUPDATE_KEY}"
+  _saveaccountconf NSUPDATE_ZONE "${NSUPDATE_ZONE}"
   _info "adding ${fulldomain}. 60 in txt \"${txtvalue}\""
   _info "adding ${fulldomain}. 60 in txt \"${txtvalue}\""
-  nsupdate -k "${NSUPDATE_KEY}" <<EOF
+  [ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_1" ] && nsdebug="-d"
+  [ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_2" ] && nsdebug="-D"
+  if [ -z "${NSUPDATE_ZONE}" ]; then
+    nsupdate -k "${NSUPDATE_KEY}" $nsdebug <<EOF
 server ${NSUPDATE_SERVER}  ${NSUPDATE_SERVER_PORT} 
 server ${NSUPDATE_SERVER}  ${NSUPDATE_SERVER_PORT} 
 update add ${fulldomain}. 60 in txt "${txtvalue}"
 update add ${fulldomain}. 60 in txt "${txtvalue}"
 send
 send
 EOF
 EOF
+  else
+    nsupdate -k "${NSUPDATE_KEY}" $nsdebug <<EOF
+server ${NSUPDATE_SERVER}  ${NSUPDATE_SERVER_PORT}
+zone ${NSUPDATE_ZONE}.
+update add ${fulldomain}. 60 in txt "${txtvalue}"
+send
+EOF
+  fi
   if [ $? -ne 0 ]; then
   if [ $? -ne 0 ]; then
     _err "error updating domain"
     _err "error updating domain"
     return 1
     return 1
@@ -34,11 +46,22 @@ dns_nsupdate_rm() {
   [ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost"
   [ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost"
   [ -n "${NSUPDATE_SERVER_PORT}" ] || NSUPDATE_SERVER_PORT=53
   [ -n "${NSUPDATE_SERVER_PORT}" ] || NSUPDATE_SERVER_PORT=53
   _info "removing ${fulldomain}. txt"
   _info "removing ${fulldomain}. txt"
-  nsupdate -k "${NSUPDATE_KEY}" <<EOF
+  [ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_1" ] && nsdebug="-d"
+  [ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_2" ] && nsdebug="-D"
+  if [ -z "${NSUPDATE_ZONE}" ]; then
+    nsupdate -k "${NSUPDATE_KEY}" $nsdebug <<EOF
 server ${NSUPDATE_SERVER}  ${NSUPDATE_SERVER_PORT} 
 server ${NSUPDATE_SERVER}  ${NSUPDATE_SERVER_PORT} 
 update delete ${fulldomain}. txt
 update delete ${fulldomain}. txt
 send
 send
 EOF
 EOF
+  else
+    nsupdate -k "${NSUPDATE_KEY}" $nsdebug <<EOF
+server ${NSUPDATE_SERVER}  ${NSUPDATE_SERVER_PORT}
+zone ${NSUPDATE_ZONE}.
+update delete ${fulldomain}. txt
+send
+EOF
+  fi
   if [ $? -ne 0 ]; then
   if [ $? -ne 0 ]; then
     _err "error updating domain"
     _err "error updating domain"
     return 1
     return 1