From 78d1cfb4648dcf6fbb5ae30074f95e6a517057c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Holger=20B=C3=B6hnke?= <holger.boehnke@amarin.de>
Date: Wed, 17 Jan 2018 19:21:14 +0100
Subject: [PATCH 01/23] fix bug in the --ca-bundle param of passing -f to
 _readlink

When _readlink is called the -f param must not be passed. _readlink (with
leading underscore) is a wrapper around readlink (without leading
underscore). _readlink already passes -f to readlink, that's why it must
not be passed to _readlink.
---
 acme.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/acme.sh b/acme.sh
index 472975a..2d24551 100755
--- a/acme.sh
+++ b/acme.sh
@@ -5510,7 +5510,7 @@ _process() {
         HTTPS_INSECURE="1"
         ;;
       --ca-bundle)
-        _ca_bundle="$(_readlink -f "$2")"
+        _ca_bundle="$(_readlink "$2")"
         CA_BUNDLE="$_ca_bundle"
         shift
         ;;

From 6ba4f8b54cbc5019ce0b4da537975d10d39c0251 Mon Sep 17 00:00:00 2001
From: neilpang <neil@neilpang.com>
Date: Thu, 18 Jan 2018 21:04:06 +0800
Subject: [PATCH 02/23] fix https://github.com/Neilpang/acme.sh/issues/1204

---
 dnsapi/dns_aws.sh | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/dnsapi/dns_aws.sh b/dnsapi/dns_aws.sh
index 5a71651..450e42d 100755
--- a/dnsapi/dns_aws.sh
+++ b/dnsapi/dns_aws.sh
@@ -19,6 +19,8 @@ dns_aws_add() {
   fulldomain=$1
   txtvalue=$2
 
+  AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}"
+  AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}"
   if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then
     AWS_ACCESS_KEY_ID=""
     AWS_SECRET_ACCESS_KEY=""
@@ -27,10 +29,9 @@ dns_aws_add() {
     return 1
   fi
 
-  if [ -z "$AWS_SESSION_TOKEN" ]; then
-    _saveaccountconf AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID"
-    _saveaccountconf AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY"
-  fi
+  #save for future use
+  _saveaccountconf_mutable AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID"
+  _saveaccountconf_mutable AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY"
 
   _debug "First detect the root zone"
   if ! _get_root "$fulldomain"; then
@@ -56,6 +57,8 @@ dns_aws_rm() {
   fulldomain=$1
   txtvalue=$2
 
+  AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}"
+  AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}"
   _debug "First detect the root zone"
   if ! _get_root "$fulldomain"; then
     _err "invalid domain"

From 37f39c0870ce3f381a586f331ee5fe41cc3d89b3 Mon Sep 17 00:00:00 2001
From: neilpang <neil@neilpang.com>
Date: Fri, 19 Jan 2018 22:41:42 +0800
Subject: [PATCH 03/23] minor

---
 acme.sh | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/acme.sh b/acme.sh
index 2d24551..bd46052 100755
--- a/acme.sh
+++ b/acme.sh
@@ -2548,10 +2548,7 @@ _setNginx() {
   _d="$1"
   _croot="$2"
   _thumbpt="$3"
-  if ! _exists "nginx"; then
-    _err "nginx command is not found."
-    return 1
-  fi
+
   FOUND_REAL_NGINX_CONF=""
   FOUND_REAL_NGINX_CONF_LN=""
   BACKUP_NGINX_CONF=""
@@ -2561,6 +2558,10 @@ _setNginx() {
   if [ -z "$_start_f" ]; then
     _debug "find start conf from nginx command"
     if [ -z "$NGINX_CONF" ]; then
+      if ! _exists "nginx"; then
+        _err "nginx command is not found."
+        return 1
+      fi
       NGINX_CONF="$(nginx -V 2>&1 | _egrep_o "--conf-path=[^ ]* " | tr -d " ")"
       _debug NGINX_CONF "$NGINX_CONF"
       NGINX_CONF="$(echo "$NGINX_CONF" | cut -d = -f 2)"
@@ -2605,6 +2606,10 @@ _setNginx() {
     return 1
   fi
 
+  if ! _exists "nginx"; then
+    _err "nginx command is not found."
+    return 1
+  fi
   _info "Check the nginx conf before setting up."
   if ! _exec "nginx -t" >/dev/null; then
     _exec_err

From 258cf20c92d3ea541283cf3eff0ecc0b07305ae7 Mon Sep 17 00:00:00 2001
From: neilpang <neil@neilpang.com>
Date: Fri, 19 Jan 2018 22:48:06 +0800
Subject: [PATCH 04/23] minor

---
 dnsapi/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/dnsapi/README.md b/dnsapi/README.md
index 83ef4ea..cf88462 100644
--- a/dnsapi/README.md
+++ b/dnsapi/README.md
@@ -666,7 +666,7 @@ And now you can issue certs with:
 acme.sh --issue --dns dns_namesilo --dnssleep 900 -d example.com -d www.example.com
 ```
 
-## 37. Use autoDNS (InternetX)
+## 36. Use autoDNS (InternetX)
 
 [InternetX](https://www.internetx.com/) offers an [xml api](https://help.internetx.com/display/API/AutoDNS+XML-API)  with your standard login credentials, set them like so:
 

From 04a609b51f38f1db8792d57dbc0af0eadaf108fe Mon Sep 17 00:00:00 2001
From: neilpang <neil@neilpang.com>
Date: Sun, 21 Jan 2018 20:20:54 +0800
Subject: [PATCH 05/23] fix https://github.com/Neilpang/acme.sh/issues/1209

---
 acme.sh | 22 +++++++++++++++++-----
 1 file changed, 17 insertions(+), 5 deletions(-)

diff --git a/acme.sh b/acme.sh
index bd46052..920f15f 100755
--- a/acme.sh
+++ b/acme.sh
@@ -2702,7 +2702,7 @@ _isRealNginxConf() {
     for _fln in $(tr "\t" ' ' <"$2" | grep -n "^ *server_name.* $1" | cut -d : -f 1); do
       _debug _fln "$_fln"
       if [ "$_fln" ]; then
-        _start=$(tr "\t" ' ' <"$2" | _head_n "$_fln" | grep -n "^ *server *{" | _tail_n 1)
+        _start=$(tr "\t" ' ' <"$2" | _head_n "$_fln" | grep -n "^ *server *" | grep -v server_name | _tail_n 1)
         _debug "_start" "$_start"
         _start_n=$(echo "$_start" | cut -d : -f 1)
         _start_nn=$(_math $_start_n + 1)
@@ -2711,8 +2711,8 @@ _isRealNginxConf() {
 
         _left="$(sed -n "${_start_nn},99999p" "$2")"
         _debug2 _left "$_left"
-        if echo "$_left" | tr "\t" ' ' | grep -n "^ *server *{" >/dev/null; then
-          _end=$(echo "$_left" | tr "\t" ' ' | grep -n "^ *server *{" | _head_n 1)
+        if echo "$_left" | tr "\t" ' ' | grep -n "^ *server *" >/dev/null; then
+          _end=$(echo "$_left" | tr "\t" ' ' | grep -n "^ *server *" | _head_n 1)
           _debug "_end" "$_end"
           _end_n=$(echo "$_end" | cut -d : -f 1)
           _debug "_end_n" "$_end_n"
@@ -2723,8 +2723,20 @@ _isRealNginxConf() {
 
         _debug "_seg_n" "$_seg_n"
 
-        if [ "$(echo "$_seg_n" | _egrep_o "^ *ssl  *on *;")" ] \
-          || [ "$(echo "$_seg_n" | _egrep_o "listen .* ssl[ |;]")" ]; then
+        _skip_ssl=1
+        for _listen_i in $(echo "$_seg_n" | grep "^ *listen" | tr -d " "); do
+          if [ "$_listen_i" ]; then
+            if [ "$(echo "$_listen_i" | _egrep_o "listen.*ssl[ |;]")" ]; then
+              _debug2 "$_listen_i is ssl"
+            else
+              _debug2 "$_listen_i is plain text"
+              _skip_ssl=""
+              break;
+            fi          
+          fi
+        done
+
+        if [ "$_skip_ssl" = "1" ]; then
           _debug "ssl on, skip"
         else
           FOUND_REAL_NGINX_CONF_LN=$_fln

From c05eb0b1b2b859a1e20f82d0ee2593f6840f13a4 Mon Sep 17 00:00:00 2001
From: neilpang <neil@neilpang.com>
Date: Tue, 23 Jan 2018 19:42:57 +0800
Subject: [PATCH 06/23] fx format

---
 acme.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/acme.sh b/acme.sh
index 920f15f..77ab439 100755
--- a/acme.sh
+++ b/acme.sh
@@ -2731,8 +2731,8 @@ _isRealNginxConf() {
             else
               _debug2 "$_listen_i is plain text"
               _skip_ssl=""
-              break;
-            fi          
+              break
+            fi
           fi
         done
 

From e90f3b84c1a1694f7f5c13a3ebfabc2da246a78d Mon Sep 17 00:00:00 2001
From: martgras <martin@grasruck.net>
Date: Tue, 23 Jan 2018 18:24:08 +0100
Subject: [PATCH 07/23] Add support for Azure DNS

Adding support for Azure DNS
See https://docs.microsoft.com/en-us/azure/dns/
---
 dnsapi/dns_azure.sh | 241 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 241 insertions(+)
 create mode 100644 dnsapi/dns_azure.sh

diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh
new file mode 100644
index 0000000..4099774
--- /dev/null
+++ b/dnsapi/dns_azure.sh
@@ -0,0 +1,241 @@
+#!/usr/bin/env sh
+
+
+########  Public functions #####################
+
+ # Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Used to add txt record
+#
+# Ref: https://docs.microsoft.com/en-us/rest/api/dns/recordsets/createorupdate
+#
+dns_azure_add() 
+{ 
+   fulldomain=$1
+   txtvalue=$2
+  
+   AZUREDNS_SUBSCRIPTIONID="${AZUREDNS_SUBSCRIPTIONID:-$(_readaccountconf_mutable AZUREDNS_SUBSCRIPTIONID)}"
+   AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
+   AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
+   AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
+  
+   if [ -z "$AZUREDNS_SUBSCRIPTIONID" ]; then
+     AZUREDNS_SUBSCRIPTIONID=""
+     AZUREDNS_TENANTID=""
+     AZUREDNS_APPID="" 
+     AZUREDNS_CLIENTSECRET=""   
+	 _err "You didn't specify the Azure Subscription ID "
+     return 1
+   fi
+
+   if [ -z "$AZUREDNS_TENANTID" ] ; then
+     AZUREDNS_SUBSCRIPTIONID=""
+     AZUREDNS_TENANTID=""
+     AZUREDNS_APPID="" 
+     AZUREDNS_CLIENTSECRET=""  
+	 _err "You didn't specify then Azure Tenant ID "
+     return 1
+   fi
+
+   if  [ -z "$AZUREDNS_APPID" ] ; then
+     AZUREDNS_SUBSCRIPTIONID=""
+     AZUREDNS_TENANTID=""
+     AZUREDNS_APPID="" 
+     AZUREDNS_CLIENTSECRET=""   
+	 _err "You didn't specify the Azure App ID"
+     return 1
+   fi
+
+   if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
+     AZUREDNS_SUBSCRIPTIONID=""
+     AZUREDNS_TENANTID=""
+     AZUREDNS_APPID="" 
+     AZUREDNS_CLIENTSECRET=""  
+	 _err "You didn't specify the Azure Client Secret"
+     return 1
+   fi
+   #save account details to account conf file.
+   _saveaccountconf_mutable AZUREDNS_SUBSCRIPTIONID "$AZUREDNS_SUBSCRIPTIONID"
+   _saveaccountconf_mutable AZUREDNS_TENANTID "$AZUREDNS_TENANTID"
+   _saveaccountconf_mutable AZUREDNS_APPID "$AZUREDNS_APPID"
+   _saveaccountconf_mutable AZUREDNS_CLIENTSECRET "$AZUREDNS_CLIENTSECRET"
+
+
+   accesstoken=$(_azure_getaccess_token "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
+  
+   if ! _get_root "$fulldomain"  "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
+    _err "invalid domain"
+    return 1
+   fi
+   _debug _domain_id "$_domain_id"
+   _debug _sub_domain "$_sub_domain"
+   _debug _domain "$_domain"  
+  
+   acmeRecordURI="https://management.azure.com$(printf '%s' $_domain_id |sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
+   _debug $acmeRecordURI
+   body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
+   _azure_rest PUT "$acmeRecordURI" "$body" "$accesstoken"
+   _debug $response
+}
+
+# Usage: fulldomain txtvalue
+# Used to remove the txt record after validation
+#
+# Ref: https://docs.microsoft.com/en-us/rest/api/dns/recordsets/delete
+#
+dns_azure_rm() 
+{ 
+   fulldomain=$1
+   txtvalue=$2
+  
+   AZUREDNS_SUBSCRIPTIONID="${AZUREDNS_SUBSCRIPTIONID:-$(_readaccountconf_mutable AZUREDNS_SUBSCRIPTIONID)}"
+   AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
+   AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
+   AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
+  
+   if [ -z "$AZUREDNS_SUBSCRIPTIONID" ]; then
+     AZUREDNS_SUBSCRIPTIONID=""
+     AZUREDNS_TENANTID=""
+     AZUREDNS_APPID="" 
+     AZUREDNS_CLIENTSECRET=""   
+	 _err "You didn't specify the Azure Subscription ID "
+     return 1
+   fi
+
+   if [ -z "$AZUREDNS_TENANTID" ] ; then
+     AZUREDNS_SUBSCRIPTIONID=""
+     AZUREDNS_TENANTID=""
+     AZUREDNS_APPID="" 
+     AZUREDNS_CLIENTSECRET=""  
+	 _err "You didn't specify the Azure Tenant ID "
+     return 1
+   fi
+
+   if  [ -z "$AZUREDNS_APPID" ]  ;then
+     AZUREDNS_SUBSCRIPTIONID=""
+     AZUREDNS_TENANTID=""
+     AZUREDNS_APPID="" 
+     AZUREDNS_CLIENTSECRET=""   
+	 _err "You didn't specify the Azure App ID"
+     return 1
+   fi
+
+   if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
+     AZUREDNS_SUBSCRIPTIONID=""
+     AZUREDNS_TENANTID=""
+     AZUREDNS_APPID="" 
+     AZUREDNS_CLIENTSECRET=""  
+	 _err "You didn't specify Azure Client Secret"
+     return 1
+   fi
+
+   accesstoken=$(_azure_getaccess_token "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
+  
+   if ! _get_root "$fulldomain"  "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
+    _err "invalid domain"
+    return 1
+   fi
+   _debug _domain_id "$_domain_id"
+   _debug _sub_domain "$_sub_domain"
+   _debug _domain "$_domain"  
+  
+   acmeRecordURI="https://management.azure.com$(printf '%s' $_domain_id |sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
+   _debug $acmeRecordURI
+   body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
+   _azure_rest DELETE "$acmeRecordURI" "" "$accesstoken"
+   _debug $response
+}
+
+###################  Private functions below ##################################
+
+_azure_rest() {
+   m=$1
+   ep="$2"
+   data="$3"
+   accesstoken="$4"
+ 
+   _debug "$ep"
+
+   export _H1="authorization: Bearer $accesstoken"
+   export _H2="accept: application/json"
+   export _H3="Content-Type: application/json"
+   _H1="authorization: Bearer $accesstoken"
+   _H2="accept: application/json"
+   _H3="Content-Type: application/json"
+
+   if [ "$m" != "GET" ]; then
+    _debug data "$data"
+    response="$(_post "$data" "$ep" "" "$m")"
+   else
+    response="$(_get "$ep")"
+   fi
+   _debug2 response "$response"
+   if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+   fi
+   return 0
+}
+
+## Ref: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-service-to-service#request-an-access-token
+_azure_getaccess_token() {
+   TENANTID=$1
+   clientID=$2
+   clientSecret=$3
+  
+   export _H1="accept: application/json"
+   export _H2="Content-Type: application/x-www-form-urlencoded"
+   export _H3=""
+  
+   body="resource=$(printf "%s" 'https://management.core.windows.net/'| _url_encode)&client_id=$(printf "%s" $clientID | _url_encode)&client_secret=$(printf "%s" $clientSecret| _url_encode)&grant_type=client_credentials"
+   _debug data "$body"
+   response="$(_post "$body" "https://login.windows.net/$TENANTID/oauth2/token" "" "POST" )"
+   accesstoken=$(printf "%s\n" "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \")
+  
+   if [ "$?" != "0" ]; then
+     _err "error $response"
+     return 1
+   fi
+   printf $accesstoken
+   _debug2 response "$response"
+   return 0
+}
+
+_get_root() {
+   domain=$1
+   subscriptionId=$2
+   accesstoken=$3
+   i=2
+   p=1
+
+   ## Ref: https://docs.microsoft.com/en-us/rest/api/dns/zones/list
+   ## returns up to 100 zones in one response therefore handling more results is not not implemented 
+   ## (ZoneListResult with  continuation token for the next page of results)
+   ## Per https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits#dns-limits you are limited to 100 Zone/subscriptions anyways 
+   ##
+   _azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?api-version=2017-09-01" "" $accesstoken 
+
+   # Find matching domain name is Json response
+   while true; do
+      h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+      _debug2 "Checking domain: $h"
+      if [ -z "$h" ]; then
+        #not valid
+        _err "Invalid domain"
+        return 1
+      fi
+
+    if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
+      _domain_id=$(printf "%s\n" "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | 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
+   return 1
+}
+

From b6fc8398cf03a0c7c6b173f6f61704c09047c578 Mon Sep 17 00:00:00 2001
From: martgras <martin@grasruck.net>
Date: Tue, 23 Jan 2018 18:25:52 +0100
Subject: [PATCH 08/23] Usage instructions for Azure DNS

---
 dnsapi/README.md | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/dnsapi/README.md b/dnsapi/README.md
index cf88462..76b0cdf 100644
--- a/dnsapi/README.md
+++ b/dnsapi/README.md
@@ -684,6 +684,25 @@ acme.sh --issue --dns dns_autodns -d example.com -d www.example.com
 
 The `AUTODNS_USER`, `AUTODNS_PASSWORD` and `AUTODNS_CONTEXT` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
+## 37. Use Azure DNS
+
+You have to create a service principal first. See: https://github.com/Neilpang/acme.sh/wiki/How-to-use-AzureDns-API
+
+```
+export AZUREDNS_SUBSCRIPTIONID="12345678-9abc-def0-1234-567890abcdef"
+export AZUREDNS_TENANTID="11111111-2222-3333-4444-555555555555"
+export AZUREDNS_APPID="3b5033b5-7a66-43a5-b3b9-a36b9e7c25ed"
+export AZUREDNS_CLIENTSECRET="1b0224ef-34d4-5af9-110f-77f527d561bd"
+```
+
+Then you can issue your certificates with:
+
+```
+acme.sh --issue --dns dns_azure -d example.com -d www.example.com
+```
+
+`AZUREDNS_SUBSCRIPTIONID`, `AZUREDNS_TENANTID`,`AZUREDNS_APPID` and `AZUREDNS_CLIENTSECRET` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
 # Use custom API
 
 If your API is not supported yet, you can write your own DNS API.

From c82cd90ed6f64a982acfc45ce94db518c7603cad Mon Sep 17 00:00:00 2001
From: martgras <martin@grasruck.net>
Date: Tue, 23 Jan 2018 18:46:59 +0100
Subject: [PATCH 09/23] Relative link to Azure DNS wiki page

---
 dnsapi/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/dnsapi/README.md b/dnsapi/README.md
index 76b0cdf..1148ea7 100644
--- a/dnsapi/README.md
+++ b/dnsapi/README.md
@@ -686,7 +686,7 @@ The `AUTODNS_USER`, `AUTODNS_PASSWORD` and `AUTODNS_CONTEXT` settings will be sa
 
 ## 37. Use Azure DNS
 
-You have to create a service principal first. See: https://github.com/Neilpang/acme.sh/wiki/How-to-use-AzureDns-API
+You have to create a service principal first. See:[How to use Azure DNS](../../../wiki/How-to-use-Azure-DNS)
 
 ```
 export AZUREDNS_SUBSCRIPTIONID="12345678-9abc-def0-1234-567890abcdef"

From 3fdbbafcb5eb10a55f195c90d77f31a9aa342850 Mon Sep 17 00:00:00 2001
From: martgras <martin@grasruck.net>
Date: Wed, 24 Jan 2018 09:59:05 +0100
Subject: [PATCH 10/23] fix getroot with multiple dns zones

---
 dnsapi/dns_azure.sh | 96 ++++++++++++++++++++++-----------------------
 1 file changed, 47 insertions(+), 49 deletions(-)

diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh
index 4099774..583a37d 100644
--- a/dnsapi/dns_azure.sh
+++ b/dnsapi/dns_azure.sh
@@ -1,56 +1,55 @@
 #!/usr/bin/env sh
 
-
 ########  Public functions #####################
 
- # Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
 # Used to add txt record
 #
 # Ref: https://docs.microsoft.com/en-us/rest/api/dns/recordsets/createorupdate
 #
-dns_azure_add() 
-{ 
+dns_azure_add()
+{
    fulldomain=$1
    txtvalue=$2
-  
+
    AZUREDNS_SUBSCRIPTIONID="${AZUREDNS_SUBSCRIPTIONID:-$(_readaccountconf_mutable AZUREDNS_SUBSCRIPTIONID)}"
    AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
    AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
    AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
-  
+
    if [ -z "$AZUREDNS_SUBSCRIPTIONID" ]; then
      AZUREDNS_SUBSCRIPTIONID=""
      AZUREDNS_TENANTID=""
-     AZUREDNS_APPID="" 
-     AZUREDNS_CLIENTSECRET=""   
-	 _err "You didn't specify the Azure Subscription ID "
+     AZUREDNS_APPID=""
+     AZUREDNS_CLIENTSECRET=""
+     _err "You didn't specify the Azure Subscription ID "
      return 1
    fi
 
    if [ -z "$AZUREDNS_TENANTID" ] ; then
      AZUREDNS_SUBSCRIPTIONID=""
      AZUREDNS_TENANTID=""
-     AZUREDNS_APPID="" 
-     AZUREDNS_CLIENTSECRET=""  
-	 _err "You didn't specify then Azure Tenant ID "
+     AZUREDNS_APPID=""
+     AZUREDNS_CLIENTSECRET=""
+     _err "You didn't specify then Azure Tenant ID "
      return 1
    fi
 
    if  [ -z "$AZUREDNS_APPID" ] ; then
      AZUREDNS_SUBSCRIPTIONID=""
      AZUREDNS_TENANTID=""
-     AZUREDNS_APPID="" 
-     AZUREDNS_CLIENTSECRET=""   
-	 _err "You didn't specify the Azure App ID"
+     AZUREDNS_APPID=""
+     AZUREDNS_CLIENTSECRET=""
+     _err "You didn't specify the Azure App ID"
      return 1
    fi
 
    if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
      AZUREDNS_SUBSCRIPTIONID=""
      AZUREDNS_TENANTID=""
-     AZUREDNS_APPID="" 
-     AZUREDNS_CLIENTSECRET=""  
-	 _err "You didn't specify the Azure Client Secret"
+     AZUREDNS_APPID=""
+     AZUREDNS_CLIENTSECRET=""
+     _err "You didn't specify the Azure Client Secret"
      return 1
    fi
    #save account details to account conf file.
@@ -61,15 +60,15 @@ dns_azure_add()
 
 
    accesstoken=$(_azure_getaccess_token "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
-  
+
    if ! _get_root "$fulldomain"  "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
     _err "invalid domain"
     return 1
    fi
    _debug _domain_id "$_domain_id"
    _debug _sub_domain "$_sub_domain"
-   _debug _domain "$_domain"  
-  
+   _debug _domain "$_domain"
+
    acmeRecordURI="https://management.azure.com$(printf '%s' $_domain_id |sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
    _debug $acmeRecordURI
    body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
@@ -82,62 +81,62 @@ dns_azure_add()
 #
 # Ref: https://docs.microsoft.com/en-us/rest/api/dns/recordsets/delete
 #
-dns_azure_rm() 
-{ 
+dns_azure_rm()
+{
    fulldomain=$1
    txtvalue=$2
-  
+
    AZUREDNS_SUBSCRIPTIONID="${AZUREDNS_SUBSCRIPTIONID:-$(_readaccountconf_mutable AZUREDNS_SUBSCRIPTIONID)}"
    AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
    AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
    AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
-  
+
    if [ -z "$AZUREDNS_SUBSCRIPTIONID" ]; then
      AZUREDNS_SUBSCRIPTIONID=""
      AZUREDNS_TENANTID=""
-     AZUREDNS_APPID="" 
-     AZUREDNS_CLIENTSECRET=""   
-	 _err "You didn't specify the Azure Subscription ID "
+     AZUREDNS_APPID=""
+     AZUREDNS_CLIENTSECRET=""
+     _err "You didn't specify the Azure Subscription ID "
      return 1
    fi
 
    if [ -z "$AZUREDNS_TENANTID" ] ; then
      AZUREDNS_SUBSCRIPTIONID=""
      AZUREDNS_TENANTID=""
-     AZUREDNS_APPID="" 
-     AZUREDNS_CLIENTSECRET=""  
-	 _err "You didn't specify the Azure Tenant ID "
+     AZUREDNS_APPID=""
+     AZUREDNS_CLIENTSECRET=""
+     _err "You didn't specify the Azure Tenant ID "
      return 1
    fi
 
    if  [ -z "$AZUREDNS_APPID" ]  ;then
      AZUREDNS_SUBSCRIPTIONID=""
      AZUREDNS_TENANTID=""
-     AZUREDNS_APPID="" 
-     AZUREDNS_CLIENTSECRET=""   
-	 _err "You didn't specify the Azure App ID"
+     AZUREDNS_APPID=""
+     AZUREDNS_CLIENTSECRET=""
+     _err "You didn't specify the Azure App ID"
      return 1
    fi
 
    if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
      AZUREDNS_SUBSCRIPTIONID=""
      AZUREDNS_TENANTID=""
-     AZUREDNS_APPID="" 
-     AZUREDNS_CLIENTSECRET=""  
-	 _err "You didn't specify Azure Client Secret"
+     AZUREDNS_APPID=""
+     AZUREDNS_CLIENTSECRET=""
+     _err "You didn't specify Azure Client Secret"
      return 1
    fi
 
    accesstoken=$(_azure_getaccess_token "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
-  
+
    if ! _get_root "$fulldomain"  "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
     _err "invalid domain"
     return 1
    fi
    _debug _domain_id "$_domain_id"
    _debug _sub_domain "$_sub_domain"
-   _debug _domain "$_domain"  
-  
+   _debug _domain "$_domain"
+
    acmeRecordURI="https://management.azure.com$(printf '%s' $_domain_id |sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
    _debug $acmeRecordURI
    body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
@@ -152,7 +151,7 @@ _azure_rest() {
    ep="$2"
    data="$3"
    accesstoken="$4"
- 
+
    _debug "$ep"
 
    export _H1="authorization: Bearer $accesstoken"
@@ -181,16 +180,16 @@ _azure_getaccess_token() {
    TENANTID=$1
    clientID=$2
    clientSecret=$3
-  
+
    export _H1="accept: application/json"
    export _H2="Content-Type: application/x-www-form-urlencoded"
    export _H3=""
-  
+
    body="resource=$(printf "%s" 'https://management.core.windows.net/'| _url_encode)&client_id=$(printf "%s" $clientID | _url_encode)&client_secret=$(printf "%s" $clientSecret| _url_encode)&grant_type=client_credentials"
    _debug data "$body"
    response="$(_post "$body" "https://login.windows.net/$TENANTID/oauth2/token" "" "POST" )"
    accesstoken=$(printf "%s\n" "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \")
-  
+
    if [ "$?" != "0" ]; then
      _err "error $response"
      return 1
@@ -208,11 +207,11 @@ _get_root() {
    p=1
 
    ## Ref: https://docs.microsoft.com/en-us/rest/api/dns/zones/list
-   ## returns up to 100 zones in one response therefore handling more results is not not implemented 
+   ## returns up to 100 zones in one response therefore handling more results is not not implemented
    ## (ZoneListResult with  continuation token for the next page of results)
-   ## Per https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits#dns-limits you are limited to 100 Zone/subscriptions anyways 
+   ## Per https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits#dns-limits you are limited to 100 Zone/subscriptions anyways
    ##
-   _azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?api-version=2017-09-01" "" $accesstoken 
+   _azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?api-version=2017-09-01" "" $accesstoken
 
    # Find matching domain name is Json response
    while true; do
@@ -225,7 +224,7 @@ _get_root() {
       fi
 
     if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
-      _domain_id=$(printf "%s\n" "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \")
+      _domain_id=$(printf "%s\n" "$response" | _egrep_o "\{\"id\":\"[^\"]*$h\"" | 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
@@ -238,4 +237,3 @@ _get_root() {
    done
    return 1
 }
-

From f7d4698ef038b245ad6163bf604d167ceb51eb8b Mon Sep 17 00:00:00 2001
From: martgras <martin@grasruck.net>
Date: Wed, 24 Jan 2018 13:39:38 +0100
Subject: [PATCH 11/23] improve error checking

---
 dnsapi/dns_azure.sh | 25 +++++++++++++++++++++----
 1 file changed, 21 insertions(+), 4 deletions(-)

diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh
index 583a37d..24b13bb 100644
--- a/dnsapi/dns_azure.sh
+++ b/dnsapi/dns_azure.sh
@@ -73,7 +73,11 @@ dns_azure_add()
    _debug $acmeRecordURI
    body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
    _azure_rest PUT "$acmeRecordURI" "$body" "$accesstoken"
-   _debug $response
+   if [ "$_code" = "200" ] || [ "$code" = '201' ]; then
+        _info "validation record added"
+   else
+        _err "error adding validation record ($_code)"
+   fi   
 }
 
 # Usage: fulldomain txtvalue
@@ -141,7 +145,11 @@ dns_azure_rm()
    _debug $acmeRecordURI
    body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
    _azure_rest DELETE "$acmeRecordURI" "" "$accesstoken"
-   _debug $response
+    if [ "$_code" = "200" ] || [ "$code" = '204' ]; then
+        _info "validation record removed"
+    else
+        _err "error removing validation record ($_code)"
+    fi   
 }
 
 ###################  Private functions below ##################################
@@ -168,6 +176,10 @@ _azure_rest() {
     response="$(_get "$ep")"
    fi
    _debug2 response "$response"
+
+   _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
+   _debug2 "http response code $_code"   
+   
    if [ "$?" != "0" ]; then
     _err "error $ep"
     return 1
@@ -189,13 +201,18 @@ _azure_getaccess_token() {
    _debug data "$body"
    response="$(_post "$body" "https://login.windows.net/$TENANTID/oauth2/token" "" "POST" )"
    accesstoken=$(printf "%s\n" "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \")
-
+   _debug2 "response $response"
+   
+   if [ -Z "$accesstoken" ] ; then 
+     _err "no acccess token received"
+     return 1    
+   fi
    if [ "$?" != "0" ]; then
      _err "error $response"
      return 1
    fi
    printf $accesstoken
-   _debug2 response "$response"
+
    return 0
 }
 

From d51c383866a7fb029479e99b28a86c1ff4b9d9aa Mon Sep 17 00:00:00 2001
From: martgras <martin@grasruck.net>
Date: Wed, 24 Jan 2018 15:08:06 +0100
Subject: [PATCH 12/23] remove unused code and fix error handling

---
 dnsapi/dns_azure.sh | 13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)

diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh
index 24b13bb..1841b07 100644
--- a/dnsapi/dns_azure.sh
+++ b/dnsapi/dns_azure.sh
@@ -77,6 +77,7 @@ dns_azure_add()
         _info "validation record added"
    else
         _err "error adding validation record ($_code)"
+        return 1
    fi   
 }
 
@@ -149,6 +150,7 @@ dns_azure_rm()
         _info "validation record removed"
     else
         _err "error removing validation record ($_code)"
+        return 1
     fi   
 }
 
@@ -160,15 +162,11 @@ _azure_rest() {
    data="$3"
    accesstoken="$4"
 
-   _debug "$ep"
-
    export _H1="authorization: Bearer $accesstoken"
    export _H2="accept: application/json"
    export _H3="Content-Type: application/json"
-   _H1="authorization: Bearer $accesstoken"
-   _H2="accept: application/json"
-   _H3="Content-Type: application/json"
-
+   
+   _debug "$ep"
    if [ "$m" != "GET" ]; then
     _debug data "$data"
     response="$(_post "$data" "$ep" "" "$m")"
@@ -195,7 +193,6 @@ _azure_getaccess_token() {
 
    export _H1="accept: application/json"
    export _H2="Content-Type: application/x-www-form-urlencoded"
-   export _H3=""
 
    body="resource=$(printf "%s" 'https://management.core.windows.net/'| _url_encode)&client_id=$(printf "%s" $clientID | _url_encode)&client_secret=$(printf "%s" $clientSecret| _url_encode)&grant_type=client_credentials"
    _debug data "$body"
@@ -203,7 +200,7 @@ _azure_getaccess_token() {
    accesstoken=$(printf "%s\n" "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \")
    _debug2 "response $response"
    
-   if [ -Z "$accesstoken" ] ; then 
+   if [ -z "$accesstoken" ] ; then 
      _err "no acccess token received"
      return 1    
    fi

From c7b8debb6eb1e378b63b2df151fcadeae660adf2 Mon Sep 17 00:00:00 2001
From: martgras <martin@grasruck.net>
Date: Thu, 25 Jan 2018 07:01:39 +0100
Subject: [PATCH 13/23] fix travis issues

---
 dnsapi/dns_azure.sh | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh
index 1841b07..b7ebe54 100644
--- a/dnsapi/dns_azure.sh
+++ b/dnsapi/dns_azure.sh
@@ -69,11 +69,11 @@ dns_azure_add()
    _debug _sub_domain "$_sub_domain"
    _debug _domain "$_domain"
 
-   acmeRecordURI="https://management.azure.com$(printf '%s' $_domain_id |sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
-   _debug $acmeRecordURI
+   acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" |sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
+   _debug "$acmeRecordURI"
    body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
    _azure_rest PUT "$acmeRecordURI" "$body" "$accesstoken"
-   if [ "$_code" = "200" ] || [ "$code" = '201' ]; then
+   if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then
         _info "validation record added"
    else
         _err "error adding validation record ($_code)"
@@ -142,8 +142,8 @@ dns_azure_rm()
    _debug _sub_domain "$_sub_domain"
    _debug _domain "$_domain"
 
-   acmeRecordURI="https://management.azure.com$(printf '%s' $_domain_id |sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
-   _debug $acmeRecordURI
+   acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" |sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
+   _debug "$acmeRecordURI"
    body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
    _azure_rest DELETE "$acmeRecordURI" "" "$accesstoken"
     if [ "$_code" = "200" ] || [ "$code" = '204' ]; then
@@ -194,7 +194,7 @@ _azure_getaccess_token() {
    export _H1="accept: application/json"
    export _H2="Content-Type: application/x-www-form-urlencoded"
 
-   body="resource=$(printf "%s" 'https://management.core.windows.net/'| _url_encode)&client_id=$(printf "%s" $clientID | _url_encode)&client_secret=$(printf "%s" $clientSecret| _url_encode)&grant_type=client_credentials"
+   body="resource=$(printf "%s" 'https://management.core.windows.net/'| _url_encode)&client_id=$(printf "%s" "$clientID" | _url_encode)&client_secret=$(printf "%s" "$clientSecret"| _url_encode)&grant_type=client_credentials"
    _debug data "$body"
    response="$(_post "$body" "https://login.windows.net/$TENANTID/oauth2/token" "" "POST" )"
    accesstoken=$(printf "%s\n" "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \")
@@ -208,7 +208,7 @@ _azure_getaccess_token() {
      _err "error $response"
      return 1
    fi
-   printf $accesstoken
+   printf "$accesstoken"
 
    return 0
 }
@@ -225,7 +225,7 @@ _get_root() {
    ## (ZoneListResult with  continuation token for the next page of results)
    ## Per https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits#dns-limits you are limited to 100 Zone/subscriptions anyways
    ##
-   _azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?api-version=2017-09-01" "" $accesstoken
+   _azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?api-version=2017-09-01" "" "$accesstoken"
 
    # Find matching domain name is Json response
    while true; do

From 00781dd4e1f0be3c9e83872ca9fc7a53bb0370bd Mon Sep 17 00:00:00 2001
From: martgras <martin@grasruck.net>
Date: Thu, 25 Jan 2018 07:06:42 +0100
Subject: [PATCH 14/23] Update README.md

---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index a2c210e..8e1b22d 100644
--- a/README.md
+++ b/README.md
@@ -344,6 +344,7 @@ You don't have to do anything manually!
 1. Servercow (https://servercow.de)
 1. Namesilo (https://www.namesilo.com)
 1. InternetX autoDNS API (https://internetx.com)
+1. Azure DNS
 
 And: 
 

From 441c26dd3286bb326523e4d56ea5d303c2571a65 Mon Sep 17 00:00:00 2001
From: martgras <martin@grasruck.net>
Date: Thu, 25 Jan 2018 07:40:35 +0100
Subject: [PATCH 15/23] Update dns_azure.sh

---
 dnsapi/dns_azure.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh
index b7ebe54..3c7e4de 100644
--- a/dnsapi/dns_azure.sh
+++ b/dnsapi/dns_azure.sh
@@ -146,7 +146,7 @@ dns_azure_rm()
    _debug "$acmeRecordURI"
    body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
    _azure_rest DELETE "$acmeRecordURI" "" "$accesstoken"
-    if [ "$_code" = "200" ] || [ "$code" = '204' ]; then
+    if [ "$_code" = "200" ] || [ "$_code" = '204' ]; then
         _info "validation record removed"
     else
         _err "error removing validation record ($_code)"
@@ -208,7 +208,7 @@ _azure_getaccess_token() {
      _err "error $response"
      return 1
    fi
-   printf "$accesstoken"
+   printf "%s" "$accesstoken"
 
    return 0
 }

From d1067c60bf93011eb74695ab34fc9cce52fdd0b1 Mon Sep 17 00:00:00 2001
From: neilpang <neil@neilpang.com>
Date: Thu, 25 Jan 2018 21:08:20 +0800
Subject: [PATCH 16/23] fix
 https://github.com/Neilpang/acme.sh/issues/1193#issuecomment-360025170

---
 acme.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/acme.sh b/acme.sh
index 77ab439..48297c7 100755
--- a/acme.sh
+++ b/acme.sh
@@ -2724,7 +2724,7 @@ _isRealNginxConf() {
         _debug "_seg_n" "$_seg_n"
 
         _skip_ssl=1
-        for _listen_i in $(echo "$_seg_n" | grep "^ *listen" | tr -d " "); do
+        for _listen_i in $(echo "$_seg_n" | tr "\t" ' ' | grep "^ *listen" | tr -d " "); do
           if [ "$_listen_i" ]; then
             if [ "$(echo "$_listen_i" | _egrep_o "listen.*ssl[ |;]")" ]; then
               _debug2 "$_listen_i is ssl"

From 91607bb2a1e207fa693d3c09dfafee15dd8a912e Mon Sep 17 00:00:00 2001
From: neil <github@byneil.com>
Date: Thu, 25 Jan 2018 22:58:11 +0800
Subject: [PATCH 17/23] Update dns_azure.sh

---
 dnsapi/dns_azure.sh | 388 ++++++++++++++++++++++----------------------
 1 file changed, 192 insertions(+), 196 deletions(-)

diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh
index 3c7e4de..104a81a 100644
--- a/dnsapi/dns_azure.sh
+++ b/dnsapi/dns_azure.sh
@@ -7,78 +7,76 @@
 #
 # Ref: https://docs.microsoft.com/en-us/rest/api/dns/recordsets/createorupdate
 #
-dns_azure_add()
-{
-   fulldomain=$1
-   txtvalue=$2
+dns_azure_add() {
+  fulldomain=$1
+  txtvalue=$2
 
-   AZUREDNS_SUBSCRIPTIONID="${AZUREDNS_SUBSCRIPTIONID:-$(_readaccountconf_mutable AZUREDNS_SUBSCRIPTIONID)}"
-   AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
-   AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
-   AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
+  AZUREDNS_SUBSCRIPTIONID="${AZUREDNS_SUBSCRIPTIONID:-$(_readaccountconf_mutable AZUREDNS_SUBSCRIPTIONID)}"
+  AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
+  AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
+  AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
+  
+  if [ -z "$AZUREDNS_SUBSCRIPTIONID" ]; then
+    AZUREDNS_SUBSCRIPTIONID=""
+    AZUREDNS_TENANTID=""
+    AZUREDNS_APPID=""
+    AZUREDNS_CLIENTSECRET=""
+    _err "You didn't specify the Azure Subscription ID "
+    return 1
+  fi
 
-   if [ -z "$AZUREDNS_SUBSCRIPTIONID" ]; then
-     AZUREDNS_SUBSCRIPTIONID=""
-     AZUREDNS_TENANTID=""
-     AZUREDNS_APPID=""
-     AZUREDNS_CLIENTSECRET=""
-     _err "You didn't specify the Azure Subscription ID "
-     return 1
-   fi
-
-   if [ -z "$AZUREDNS_TENANTID" ] ; then
-     AZUREDNS_SUBSCRIPTIONID=""
-     AZUREDNS_TENANTID=""
-     AZUREDNS_APPID=""
+  if [ -z "$AZUREDNS_TENANTID" ] ; then
+    AZUREDNS_SUBSCRIPTIONID=""
+    AZUREDNS_TENANTID=""
+    AZUREDNS_APPID=""
      AZUREDNS_CLIENTSECRET=""
      _err "You didn't specify then Azure Tenant ID "
      return 1
-   fi
+  fi
 
-   if  [ -z "$AZUREDNS_APPID" ] ; then
-     AZUREDNS_SUBSCRIPTIONID=""
-     AZUREDNS_TENANTID=""
-     AZUREDNS_APPID=""
-     AZUREDNS_CLIENTSECRET=""
-     _err "You didn't specify the Azure App ID"
-     return 1
-   fi
-
-   if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
-     AZUREDNS_SUBSCRIPTIONID=""
-     AZUREDNS_TENANTID=""
-     AZUREDNS_APPID=""
-     AZUREDNS_CLIENTSECRET=""
-     _err "You didn't specify the Azure Client Secret"
-     return 1
-   fi
-   #save account details to account conf file.
-   _saveaccountconf_mutable AZUREDNS_SUBSCRIPTIONID "$AZUREDNS_SUBSCRIPTIONID"
-   _saveaccountconf_mutable AZUREDNS_TENANTID "$AZUREDNS_TENANTID"
-   _saveaccountconf_mutable AZUREDNS_APPID "$AZUREDNS_APPID"
-   _saveaccountconf_mutable AZUREDNS_CLIENTSECRET "$AZUREDNS_CLIENTSECRET"
-
-
-   accesstoken=$(_azure_getaccess_token "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
-
-   if ! _get_root "$fulldomain"  "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
-    _err "invalid domain"
+  if [ -z "$AZUREDNS_APPID" ] ; then
+    AZUREDNS_SUBSCRIPTIONID=""
+    AZUREDNS_TENANTID=""
+    AZUREDNS_APPID=""
+    AZUREDNS_CLIENTSECRET=""
+    _err "You didn't specify the Azure App ID"
     return 1
-   fi
-   _debug _domain_id "$_domain_id"
-   _debug _sub_domain "$_sub_domain"
-   _debug _domain "$_domain"
+  fi
 
-   acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" |sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
-   _debug "$acmeRecordURI"
-   body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
-   _azure_rest PUT "$acmeRecordURI" "$body" "$accesstoken"
-   if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then
-        _info "validation record added"
-   else
-        _err "error adding validation record ($_code)"
-        return 1
-   fi   
+  if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
+    AZUREDNS_SUBSCRIPTIONID=""
+    AZUREDNS_TENANTID=""
+    AZUREDNS_APPID=""
+    AZUREDNS_CLIENTSECRET=""
+    _err "You didn't specify the Azure Client Secret"
+    return 1
+  fi
+  #save account details to account conf file.
+  _saveaccountconf_mutable AZUREDNS_SUBSCRIPTIONID "$AZUREDNS_SUBSCRIPTIONID"
+  _saveaccountconf_mutable AZUREDNS_TENANTID "$AZUREDNS_TENANTID"
+  _saveaccountconf_mutable AZUREDNS_APPID "$AZUREDNS_APPID"
+  _saveaccountconf_mutable AZUREDNS_CLIENTSECRET "$AZUREDNS_CLIENTSECRET"
+
+  accesstoken=$(_azure_getaccess_token "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
+
+  if ! _get_root "$fulldomain"  "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
+   _err "invalid domain"
+   return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" |sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
+  _debug "$acmeRecordURI"
+  body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
+  _azure_rest PUT "$acmeRecordURI" "$body" "$accesstoken"
+  if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then
+    _info "validation record added"
+  else
+    _err "error adding validation record ($_code)"
+    return 1
+  fi   
 }
 
 # Usage: fulldomain txtvalue
@@ -86,156 +84,154 @@ dns_azure_add()
 #
 # Ref: https://docs.microsoft.com/en-us/rest/api/dns/recordsets/delete
 #
-dns_azure_rm()
-{
-   fulldomain=$1
-   txtvalue=$2
+dns_azure_rm() {
+  fulldomain=$1
+  txtvalue=$2
 
-   AZUREDNS_SUBSCRIPTIONID="${AZUREDNS_SUBSCRIPTIONID:-$(_readaccountconf_mutable AZUREDNS_SUBSCRIPTIONID)}"
-   AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
-   AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
-   AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
+  AZUREDNS_SUBSCRIPTIONID="${AZUREDNS_SUBSCRIPTIONID:-$(_readaccountconf_mutable AZUREDNS_SUBSCRIPTIONID)}"
+  AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
+  AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
+  AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
 
-   if [ -z "$AZUREDNS_SUBSCRIPTIONID" ]; then
-     AZUREDNS_SUBSCRIPTIONID=""
-     AZUREDNS_TENANTID=""
-     AZUREDNS_APPID=""
-     AZUREDNS_CLIENTSECRET=""
-     _err "You didn't specify the Azure Subscription ID "
-     return 1
-   fi
-
-   if [ -z "$AZUREDNS_TENANTID" ] ; then
-     AZUREDNS_SUBSCRIPTIONID=""
-     AZUREDNS_TENANTID=""
-     AZUREDNS_APPID=""
-     AZUREDNS_CLIENTSECRET=""
-     _err "You didn't specify the Azure Tenant ID "
-     return 1
-   fi
-
-   if  [ -z "$AZUREDNS_APPID" ]  ;then
-     AZUREDNS_SUBSCRIPTIONID=""
-     AZUREDNS_TENANTID=""
-     AZUREDNS_APPID=""
-     AZUREDNS_CLIENTSECRET=""
-     _err "You didn't specify the Azure App ID"
-     return 1
-   fi
-
-   if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
-     AZUREDNS_SUBSCRIPTIONID=""
-     AZUREDNS_TENANTID=""
-     AZUREDNS_APPID=""
-     AZUREDNS_CLIENTSECRET=""
-     _err "You didn't specify Azure Client Secret"
-     return 1
-   fi
-
-   accesstoken=$(_azure_getaccess_token "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
-
-   if ! _get_root "$fulldomain"  "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
-    _err "invalid domain"
+  if [ -z "$AZUREDNS_SUBSCRIPTIONID" ]; then
+    AZUREDNS_SUBSCRIPTIONID=""
+    AZUREDNS_TENANTID=""
+    AZUREDNS_APPID=""
+    AZUREDNS_CLIENTSECRET=""
+    _err "You didn't specify the Azure Subscription ID "
     return 1
-   fi
-   _debug _domain_id "$_domain_id"
-   _debug _sub_domain "$_sub_domain"
-   _debug _domain "$_domain"
+  fi
 
-   acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" |sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
-   _debug "$acmeRecordURI"
-   body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
-   _azure_rest DELETE "$acmeRecordURI" "" "$accesstoken"
-    if [ "$_code" = "200" ] || [ "$_code" = '204' ]; then
-        _info "validation record removed"
-    else
-        _err "error removing validation record ($_code)"
-        return 1
-    fi   
+  if [ -z "$AZUREDNS_TENANTID" ] ; then
+    AZUREDNS_SUBSCRIPTIONID=""
+    AZUREDNS_TENANTID=""
+    AZUREDNS_APPID=""
+    AZUREDNS_CLIENTSECRET=""
+    _err "You didn't specify the Azure Tenant ID "
+    return 1
+  fi
+
+  if [ -z "$AZUREDNS_APPID" ]  ;then
+    AZUREDNS_SUBSCRIPTIONID=""
+    AZUREDNS_TENANTID=""
+    AZUREDNS_APPID=""
+    AZUREDNS_CLIENTSECRET=""
+    _err "You didn't specify the Azure App ID"
+    return 1
+  fi
+
+  if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
+    AZUREDNS_SUBSCRIPTIONID=""
+    AZUREDNS_TENANTID=""
+    AZUREDNS_APPID=""
+    AZUREDNS_CLIENTSECRET=""
+    _err "You didn't specify Azure Client Secret"
+    return 1
+  fi
+
+  accesstoken=$(_azure_getaccess_token "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
+
+  if ! _get_root "$fulldomain"  "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
+   _err "invalid domain"
+   return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" |sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
+  _debug "$acmeRecordURI"
+  body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
+  _azure_rest DELETE "$acmeRecordURI" "" "$accesstoken"
+  if [ "$_code" = "200" ] || [ "$_code" = '204' ]; then
+    _info "validation record removed"
+  else
+    _err "error removing validation record ($_code)"
+    return 1
+  fi
 }
 
 ###################  Private functions below ##################################
 
 _azure_rest() {
-   m=$1
-   ep="$2"
-   data="$3"
-   accesstoken="$4"
+  m=$1
+  ep="$2"
+  data="$3"
+  accesstoken="$4"
 
-   export _H1="authorization: Bearer $accesstoken"
-   export _H2="accept: application/json"
-   export _H3="Content-Type: application/json"
-   
-   _debug "$ep"
-   if [ "$m" != "GET" ]; then
-    _debug data "$data"
-    response="$(_post "$data" "$ep" "" "$m")"
-   else
-    response="$(_get "$ep")"
-   fi
-   _debug2 response "$response"
+  export _H1="authorization: Bearer $accesstoken"
+  export _H2="accept: application/json"
+  export _H3="Content-Type: application/json"
+  
+  _debug "$ep"
+  if [ "$m" != "GET" ]; then
+   _debug data "$data"
+   response="$(_post "$data" "$ep" "" "$m")"
+  else
+   response="$(_get "$ep")"
+  fi
+  _debug2 response "$response"
 
-   _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
-   _debug2 "http response code $_code"   
-   
-   if [ "$?" != "0" ]; then
-    _err "error $ep"
-    return 1
-   fi
-   return 0
+  _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
+  _debug2 "http response code $_code"   
+
+  if [ "$?" != "0" ]; then
+   _err "error $ep"
+   return 1
+  fi
+  return 0
 }
 
 ## Ref: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-service-to-service#request-an-access-token
 _azure_getaccess_token() {
-   TENANTID=$1
-   clientID=$2
-   clientSecret=$3
+  TENANTID=$1
+  clientID=$2
+  clientSecret=$3
 
-   export _H1="accept: application/json"
-   export _H2="Content-Type: application/x-www-form-urlencoded"
+  export _H1="accept: application/json"
+  export _H2="Content-Type: application/x-www-form-urlencoded"
 
-   body="resource=$(printf "%s" 'https://management.core.windows.net/'| _url_encode)&client_id=$(printf "%s" "$clientID" | _url_encode)&client_secret=$(printf "%s" "$clientSecret"| _url_encode)&grant_type=client_credentials"
-   _debug data "$body"
-   response="$(_post "$body" "https://login.windows.net/$TENANTID/oauth2/token" "" "POST" )"
-   accesstoken=$(printf "%s\n" "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \")
-   _debug2 "response $response"
-   
-   if [ -z "$accesstoken" ] ; then 
-     _err "no acccess token received"
-     return 1    
-   fi
-   if [ "$?" != "0" ]; then
-     _err "error $response"
-     return 1
-   fi
-   printf "%s" "$accesstoken"
+  body="resource=$(printf "%s" 'https://management.core.windows.net/'| _url_encode)&client_id=$(printf "%s" "$clientID" | _url_encode)&client_secret=$(printf "%s" "$clientSecret"| _url_encode)&grant_type=client_credentials"
+  _debug data "$body"
+  response="$(_post "$body" "https://login.windows.net/$TENANTID/oauth2/token" "" "POST" )"
+  accesstoken=$(printf "%s\n" "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \")
+  _debug2 "response $response"
 
-   return 0
+  if [ -z "$accesstoken" ] ; then 
+    _err "no acccess token received"
+    return 1    
+  fi
+  if [ "$?" != "0" ]; then
+    _err "error $response"
+    return 1
+  fi
+  printf "%s" "$accesstoken"
+  return 0
 }
 
 _get_root() {
-   domain=$1
-   subscriptionId=$2
-   accesstoken=$3
-   i=2
-   p=1
+  domain=$1
+  subscriptionId=$2
+  accesstoken=$3
+  i=2
+  p=1
 
-   ## Ref: https://docs.microsoft.com/en-us/rest/api/dns/zones/list
-   ## returns up to 100 zones in one response therefore handling more results is not not implemented
-   ## (ZoneListResult with  continuation token for the next page of results)
-   ## Per https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits#dns-limits you are limited to 100 Zone/subscriptions anyways
-   ##
-   _azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?api-version=2017-09-01" "" "$accesstoken"
+  ## Ref: https://docs.microsoft.com/en-us/rest/api/dns/zones/list
+  ## returns up to 100 zones in one response therefore handling more results is not not implemented
+  ## (ZoneListResult with  continuation token for the next page of results)
+  ## Per https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits#dns-limits you are limited to 100 Zone/subscriptions anyways
+  ##
+  _azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?api-version=2017-09-01" "" "$accesstoken"
 
-   # Find matching domain name is Json response
-   while true; do
-      h=$(printf "%s" "$domain" | cut -d . -f $i-100)
-      _debug2 "Checking domain: $h"
-      if [ -z "$h" ]; then
-        #not valid
-        _err "Invalid domain"
-        return 1
-      fi
+  # Find matching domain name is Json response
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug2 "Checking domain: $h"
+    if [ -z "$h" ]; then
+      #not valid
+      _err "Invalid domain"
+      return 1
+    fi
 
     if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
       _domain_id=$(printf "%s\n" "$response" | _egrep_o "\{\"id\":\"[^\"]*$h\"" | head -n 1 | cut -d : -f 2 | tr -d \")
@@ -245,9 +241,9 @@ _get_root() {
         return 0
       fi
       return 1
-      fi
-      p=$i
-      i=$(_math "$i" + 1)
-   done
-   return 1
+    fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+  return 1
 }

From e4b24d20acb68b741422d3e078fbf572d1d86f36 Mon Sep 17 00:00:00 2001
From: neil <github@byneil.com>
Date: Thu, 25 Jan 2018 23:08:56 +0800
Subject: [PATCH 18/23] Update dns_azure.sh

---
 dnsapi/dns_azure.sh | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh
index 104a81a..9952a16 100644
--- a/dnsapi/dns_azure.sh
+++ b/dnsapi/dns_azure.sh
@@ -29,9 +29,9 @@ dns_azure_add() {
     AZUREDNS_SUBSCRIPTIONID=""
     AZUREDNS_TENANTID=""
     AZUREDNS_APPID=""
-     AZUREDNS_CLIENTSECRET=""
-     _err "You didn't specify then Azure Tenant ID "
-     return 1
+    AZUREDNS_CLIENTSECRET=""
+    _err "You didn't specify then Azure Tenant ID "
+    return 1
   fi
 
   if [ -z "$AZUREDNS_APPID" ] ; then
@@ -194,7 +194,7 @@ _azure_getaccess_token() {
   body="resource=$(printf "%s" 'https://management.core.windows.net/'| _url_encode)&client_id=$(printf "%s" "$clientID" | _url_encode)&client_secret=$(printf "%s" "$clientSecret"| _url_encode)&grant_type=client_credentials"
   _debug data "$body"
   response="$(_post "$body" "https://login.windows.net/$TENANTID/oauth2/token" "" "POST" )"
-  accesstoken=$(printf "%s\n" "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \")
+  accesstoken=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \")
   _debug2 "response $response"
 
   if [ -z "$accesstoken" ] ; then 
@@ -234,7 +234,7 @@ _get_root() {
     fi
 
     if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
-      _domain_id=$(printf "%s\n" "$response" | _egrep_o "\{\"id\":\"[^\"]*$h\"" | head -n 1 | cut -d : -f 2 | tr -d \")
+      _domain_id=$(echo "$response" | _egrep_o "\{\"id\":\"[^\"]*$h\"" | head -n 1 | cut -d : -f 2 | tr -d \")
       if [ "$_domain_id" ]; then
         _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
         _domain=$h

From a4fc802d1b84c49c3357aa2765dead14ec4320e7 Mon Sep 17 00:00:00 2001
From: neilpang <neil@neilpang.com>
Date: Thu, 25 Jan 2018 23:18:37 +0800
Subject: [PATCH 19/23] Add selectel.com(selectel.ru) DNS API. fix
 https://github.com/Neilpang/acme.sh/issues/1220

---
 README.md              |   1 +
 dnsapi/README.md       |  17 +++++
 dnsapi/dns_selectel.sh | 161 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 179 insertions(+)
 create mode 100644 dnsapi/dns_selectel.sh

diff --git a/README.md b/README.md
index 8e1b22d..ad3befc 100644
--- a/README.md
+++ b/README.md
@@ -345,6 +345,7 @@ You don't have to do anything manually!
 1. Namesilo (https://www.namesilo.com)
 1. InternetX autoDNS API (https://internetx.com)
 1. Azure DNS
+1. selectel.com(selectel.ru) DNS API
 
 And: 
 
diff --git a/dnsapi/README.md b/dnsapi/README.md
index 1148ea7..32eca13 100644
--- a/dnsapi/README.md
+++ b/dnsapi/README.md
@@ -703,6 +703,23 @@ acme.sh --issue --dns dns_azure -d example.com -d www.example.com
 
 `AZUREDNS_SUBSCRIPTIONID`, `AZUREDNS_TENANTID`,`AZUREDNS_APPID` and `AZUREDNS_CLIENTSECRET` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
+## 38. Use selectel.com(selectel.ru) domain API to automatically issue cert
+
+First you need to login to your account to get your API key from: https://my.selectel.ru/profile/apikeys.
+
+```sh
+export SL_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
+
+```
+
+Ok, let's issue a cert now:
+```
+acme.sh --issue --dns dns_selectel -d example.com -d www.example.com
+```
+
+The `SL_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+
 # Use custom API
 
 If your API is not supported yet, you can write your own DNS API.
diff --git a/dnsapi/dns_selectel.sh b/dnsapi/dns_selectel.sh
new file mode 100644
index 0000000..8dd9b35
--- /dev/null
+++ b/dnsapi/dns_selectel.sh
@@ -0,0 +1,161 @@
+#!/usr/bin/env sh
+
+#
+#SL_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
+#
+
+SL_Api="https://api.selectel.ru/domains/v1"
+
+########  Public functions #####################
+
+#Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_selectel_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  SL_Key="${SL_Key:-$(_readaccountconf_mutable SL_Key)}"
+
+  if [ -z "$SL_Key" ]; then
+    SL_Key=""
+    _err "You don't specify selectel.ru api key yet."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
+  #save the api key to the account conf file.
+  _saveaccountconf_mutable SL_Key "$SL_Key"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _info "Adding record"
+  if _sl_rest POST "/$_domain_id/records/" "{\"type\": \"TXT\", \"ttl\": 60, \"name\": \"$fulldomain\", \"content\": \"$txtvalue\"}"; then
+    if _contains "$response" "$txtvalue"; then
+      _info "Added, OK"
+      return 0
+    fi
+  fi
+  _err "Add txt record error."
+  return 1 
+}
+
+#fulldomain txtvalue
+dns_selectel_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  SL_Key="${SL_Key:-$(_readaccountconf_mutable SL_Key)}"
+
+  if [ -z "$SL_Key" ]; then
+    SL_Key=""
+    _err "You don't specify slectel api key yet."
+    _err "Please create you 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 _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _debug "Getting txt records"
+  _sl_rest GET "/${_domain_id}/records/"
+
+  if ! _contains "$response" "$txtvalue"; then
+    _err "Txt record not found"
+    return 1
+  fi
+
+  _record_seg="$(echo "$response" | _egrep_o "\"content\" *: *\"$txtvalue\"[^}]*}")"
+  _debug2 "_record_seg" "$_record_seg"
+  if [ -z "$_record_seg" ]; then
+    _err "can not find _record_seg"
+    return 1
+  fi
+
+  _record_id="$(echo "$_record_seg" | tr ",}" "\n\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2)"
+  _debug2 "_record_id" "$_record_id"
+  if [ -z "$_record_id" ]; then
+    _err "can not find _record_id"
+    return 1
+  fi
+
+  if ! _sl_rest DELETE "/$_domain_id/records/$_record_id"; then
+    _err "Delete record error."
+    return 1
+  fi
+  return 0;
+}
+
+####################  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 ! _sl_rest GET "/"; 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" "\"name\": \"$h\","; then
+      _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+      _domain=$h
+      _debug "Getting domain id for $h"
+      if ! _sl_rest GET "/$h"; then
+        return 1
+      fi
+      _domain_id="$(echo "$response" | tr ",}" "\n\n" | tr -d " " | grep "\"id\":" | cut -d : -f 2)"
+      return 0
+    fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+_sl_rest() {
+  m=$1
+  ep="$2"
+  data="$3"
+  _debug "$ep"
+
+  export _H1="X-Token: $SL_Key"
+  export _H2="Content-Type: application/json"
+
+  if [ "$m" != "GET" ]; then
+    _debug data "$data"
+    response="$(_post "$data" "$SL_Api/$ep" "" "$m")"
+  else
+    response="$(_get "$SL_Api/$ep")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}

From 03140865f031dfe2d9ee9bb3c3017b23d52f8971 Mon Sep 17 00:00:00 2001
From: neilpang <neil@neilpang.com>
Date: Thu, 25 Jan 2018 23:25:50 +0800
Subject: [PATCH 20/23] fix for existing record

---
 dnsapi/dns_selectel.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/dnsapi/dns_selectel.sh b/dnsapi/dns_selectel.sh
index 8dd9b35..c5e4078 100644
--- a/dnsapi/dns_selectel.sh
+++ b/dnsapi/dns_selectel.sh
@@ -36,7 +36,7 @@ dns_selectel_add() {
 
   _info "Adding record"
   if _sl_rest POST "/$_domain_id/records/" "{\"type\": \"TXT\", \"ttl\": 60, \"name\": \"$fulldomain\", \"content\": \"$txtvalue\"}"; then
-    if _contains "$response" "$txtvalue"; then
+    if _contains "$response" "$txtvalue" || _contains "$response" "record_already_exists"; then
       _info "Added, OK"
       return 0
     fi

From 72fe7396d6d2779b03b40ec0d7c76fc137423f12 Mon Sep 17 00:00:00 2001
From: martgras <martin@grasruck.net>
Date: Fri, 26 Jan 2018 07:53:47 +0100
Subject: [PATCH 21/23] spelling  mistake in error message

---
 dnsapi/dns_azure.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh
index 9952a16..6e53131 100644
--- a/dnsapi/dns_azure.sh
+++ b/dnsapi/dns_azure.sh
@@ -30,7 +30,7 @@ dns_azure_add() {
     AZUREDNS_TENANTID=""
     AZUREDNS_APPID=""
     AZUREDNS_CLIENTSECRET=""
-    _err "You didn't specify then Azure Tenant ID "
+    _err "You didn't specify the Azure Tenant ID "
     return 1
   fi
 
@@ -125,7 +125,7 @@ dns_azure_rm() {
     AZUREDNS_TENANTID=""
     AZUREDNS_APPID=""
     AZUREDNS_CLIENTSECRET=""
-    _err "You didn't specify Azure Client Secret"
+    _err "You didn't specify the Azure Client Secret"
     return 1
   fi
 

From dd171ca44a2ed193c5dd9e21c1aedc6cfe251fde Mon Sep 17 00:00:00 2001
From: neilpang <neil@neilpang.com>
Date: Fri, 26 Jan 2018 20:14:51 +0800
Subject: [PATCH 22/23] fix format

---
 dnsapi/dns_azure.sh    | 48 +++++++++++++++++++++---------------------
 dnsapi/dns_selectel.sh |  4 ++--
 2 files changed, 26 insertions(+), 26 deletions(-)

diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh
index 6e53131..2c39a00 100644
--- a/dnsapi/dns_azure.sh
+++ b/dnsapi/dns_azure.sh
@@ -15,7 +15,7 @@ dns_azure_add() {
   AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
   AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
   AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
-  
+
   if [ -z "$AZUREDNS_SUBSCRIPTIONID" ]; then
     AZUREDNS_SUBSCRIPTIONID=""
     AZUREDNS_TENANTID=""
@@ -25,7 +25,7 @@ dns_azure_add() {
     return 1
   fi
 
-  if [ -z "$AZUREDNS_TENANTID" ] ; then
+  if [ -z "$AZUREDNS_TENANTID" ]; then
     AZUREDNS_SUBSCRIPTIONID=""
     AZUREDNS_TENANTID=""
     AZUREDNS_APPID=""
@@ -34,7 +34,7 @@ dns_azure_add() {
     return 1
   fi
 
-  if [ -z "$AZUREDNS_APPID" ] ; then
+  if [ -z "$AZUREDNS_APPID" ]; then
     AZUREDNS_SUBSCRIPTIONID=""
     AZUREDNS_TENANTID=""
     AZUREDNS_APPID=""
@@ -59,15 +59,15 @@ dns_azure_add() {
 
   accesstoken=$(_azure_getaccess_token "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
 
-  if ! _get_root "$fulldomain"  "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
-   _err "invalid domain"
-   return 1
+  if ! _get_root "$fulldomain" "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
+    _err "invalid domain"
+    return 1
   fi
   _debug _domain_id "$_domain_id"
   _debug _sub_domain "$_sub_domain"
   _debug _domain "$_domain"
 
-  acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" |sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
+  acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" | sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
   _debug "$acmeRecordURI"
   body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
   _azure_rest PUT "$acmeRecordURI" "$body" "$accesstoken"
@@ -76,7 +76,7 @@ dns_azure_add() {
   else
     _err "error adding validation record ($_code)"
     return 1
-  fi   
+  fi
 }
 
 # Usage: fulldomain txtvalue
@@ -102,7 +102,7 @@ dns_azure_rm() {
     return 1
   fi
 
-  if [ -z "$AZUREDNS_TENANTID" ] ; then
+  if [ -z "$AZUREDNS_TENANTID" ]; then
     AZUREDNS_SUBSCRIPTIONID=""
     AZUREDNS_TENANTID=""
     AZUREDNS_APPID=""
@@ -111,7 +111,7 @@ dns_azure_rm() {
     return 1
   fi
 
-  if [ -z "$AZUREDNS_APPID" ]  ;then
+  if [ -z "$AZUREDNS_APPID" ];then
     AZUREDNS_SUBSCRIPTIONID=""
     AZUREDNS_TENANTID=""
     AZUREDNS_APPID=""
@@ -131,15 +131,15 @@ dns_azure_rm() {
 
   accesstoken=$(_azure_getaccess_token "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
 
-  if ! _get_root "$fulldomain"  "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
-   _err "invalid domain"
-   return 1
+  if ! _get_root "$fulldomain" "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
+    _err "invalid domain"
+    return 1
   fi
   _debug _domain_id "$_domain_id"
   _debug _sub_domain "$_sub_domain"
   _debug _domain "$_domain"
 
-  acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" |sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
+  acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" | sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
   _debug "$acmeRecordURI"
   body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
   _azure_rest DELETE "$acmeRecordURI" "" "$accesstoken"
@@ -165,10 +165,10 @@ _azure_rest() {
   
   _debug "$ep"
   if [ "$m" != "GET" ]; then
-   _debug data "$data"
-   response="$(_post "$data" "$ep" "" "$m")"
+    _debug data "$data"
+    response="$(_post "$data" "$ep" "" "$m")"
   else
-   response="$(_get "$ep")"
+    response="$(_get "$ep")"
   fi
   _debug2 response "$response"
 
@@ -176,8 +176,8 @@ _azure_rest() {
   _debug2 "http response code $_code"   
 
   if [ "$?" != "0" ]; then
-   _err "error $ep"
-   return 1
+    _err "error $ep"
+    return 1
   fi
   return 0
 }
@@ -191,15 +191,15 @@ _azure_getaccess_token() {
   export _H1="accept: application/json"
   export _H2="Content-Type: application/x-www-form-urlencoded"
 
-  body="resource=$(printf "%s" 'https://management.core.windows.net/'| _url_encode)&client_id=$(printf "%s" "$clientID" | _url_encode)&client_secret=$(printf "%s" "$clientSecret"| _url_encode)&grant_type=client_credentials"
+  body="resource=$(printf "%s" 'https://management.core.windows.net/' | _url_encode)&client_id=$(printf "%s" "$clientID" | _url_encode)&client_secret=$(printf "%s" "$clientSecret" | _url_encode)&grant_type=client_credentials"
   _debug data "$body"
-  response="$(_post "$body" "https://login.windows.net/$TENANTID/oauth2/token" "" "POST" )"
-  accesstoken=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \")
+  response="$(_post "$body" "https://login.windows.net/$TENANTID/oauth2/token" "" "POST")"
+  accesstoken=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
   _debug2 "response $response"
 
-  if [ -z "$accesstoken" ] ; then 
+  if [ -z "$accesstoken" ]; then 
     _err "no acccess token received"
-    return 1    
+    return 1
   fi
   if [ "$?" != "0" ]; then
     _err "error $response"
diff --git a/dnsapi/dns_selectel.sh b/dnsapi/dns_selectel.sh
index c5e4078..460f89c 100644
--- a/dnsapi/dns_selectel.sh
+++ b/dnsapi/dns_selectel.sh
@@ -42,7 +42,7 @@ dns_selectel_add() {
     fi
   fi
   _err "Add txt record error."
-  return 1 
+  return 1
 }
 
 #fulldomain txtvalue
@@ -94,7 +94,7 @@ dns_selectel_rm() {
     _err "Delete record error."
     return 1
   fi
-  return 0;
+  return 0
 }
 
 ####################  Private functions below ##################################

From 8c88757451b2ca2bb61814180442b4606555fe16 Mon Sep 17 00:00:00 2001
From: neilpang <neil@neilpang.com>
Date: Fri, 26 Jan 2018 20:39:41 +0800
Subject: [PATCH 23/23] fix format

---
 dnsapi/dns_azure.sh    | 8 ++++----
 dnsapi/dns_selectel.sh | 4 ++--
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh
index 2c39a00..0834ede 100644
--- a/dnsapi/dns_azure.sh
+++ b/dnsapi/dns_azure.sh
@@ -111,7 +111,7 @@ dns_azure_rm() {
     return 1
   fi
 
-  if [ -z "$AZUREDNS_APPID" ];then
+  if [ -z "$AZUREDNS_APPID" ]; then
     AZUREDNS_SUBSCRIPTIONID=""
     AZUREDNS_TENANTID=""
     AZUREDNS_APPID=""
@@ -162,7 +162,7 @@ _azure_rest() {
   export _H1="authorization: Bearer $accesstoken"
   export _H2="accept: application/json"
   export _H3="Content-Type: application/json"
-  
+
   _debug "$ep"
   if [ "$m" != "GET" ]; then
     _debug data "$data"
@@ -173,7 +173,7 @@ _azure_rest() {
   _debug2 response "$response"
 
   _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
-  _debug2 "http response code $_code"   
+  _debug2 "http response code $_code"
 
   if [ "$?" != "0" ]; then
     _err "error $ep"
@@ -197,7 +197,7 @@ _azure_getaccess_token() {
   accesstoken=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
   _debug2 "response $response"
 
-  if [ -z "$accesstoken" ]; then 
+  if [ -z "$accesstoken" ]; then
     _err "no acccess token received"
     return 1
   fi
diff --git a/dnsapi/dns_selectel.sh b/dnsapi/dns_selectel.sh
index 460f89c..94252d8 100644
--- a/dnsapi/dns_selectel.sh
+++ b/dnsapi/dns_selectel.sh
@@ -83,7 +83,7 @@ dns_selectel_rm() {
     return 1
   fi
 
-  _record_id="$(echo "$_record_seg" | tr ",}" "\n\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2)"
+  _record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2)"
   _debug2 "_record_id" "$_record_id"
   if [ -z "$_record_id" ]; then
     _err "can not find _record_id"
@@ -127,7 +127,7 @@ _get_root() {
       if ! _sl_rest GET "/$h"; then
         return 1
       fi
-      _domain_id="$(echo "$response" | tr ",}" "\n\n" | tr -d " " | grep "\"id\":" | cut -d : -f 2)"
+      _domain_id="$(echo "$response" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\":" | cut -d : -f 2)"
       return 0
     fi
     p=$i