]> git.proxmox.com Git - mirror_acme.sh.git/blobdiff - acme.sh
Merge pull request #1319 from TigerP/master
[mirror_acme.sh.git] / acme.sh
diff --git a/acme.sh b/acme.sh
index 604bff040599f0d438b71da47ee8b4080b81f85c..5ea331c9d5311f8fe199da49047af9236e854b14 100755 (executable)
--- a/acme.sh
+++ b/acme.sh
@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 
-VER=2.7.6
+VER=2.7.7
 
 PROJECT_NAME="acme.sh"
 
@@ -22,7 +22,6 @@ LETSENCRYPT_STAGING_CA_V2="https://acme-staging-v02.api.letsencrypt.org/director
 DEFAULT_CA=$LETSENCRYPT_CA_V1
 DEFAULT_STAGING_CA=$LETSENCRYPT_STAGING_CA_V1
 
-
 DEFAULT_USER_AGENT="$PROJECT_NAME/$VER ($PROJECT)"
 DEFAULT_ACCOUNT_EMAIL=""
 
@@ -48,6 +47,7 @@ DEFAULT_DNS_SLEEP=120
 NO_VALUE="no"
 
 W_TLS="tls"
+DNS_ALIAS_PREFIX="="
 
 MODE_STATELESS="stateless"
 
@@ -106,6 +106,8 @@ _PREPARE_LINK="https://github.com/Neilpang/acme.sh/wiki/Install-preparations"
 
 _STATELESS_WIKI="https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode"
 
+_DNS_ALIAS_WIKI="https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode"
+
 _DNS_MANUAL_ERR="The dns manual mode can not renew automatically, you must issue it again manually. You'd better use the other modes instead."
 
 _DNS_MANUAL_WARN="It seems that you are using dns manual mode. please take care: $_DNS_MANUAL_ERR"
@@ -998,7 +1000,7 @@ _createkey() {
 _is_idn() {
   _is_idn_d="$1"
   _debug2 _is_idn_d "$_is_idn_d"
-  _idn_temp=$(printf "%s" "$_is_idn_d" | tr -d '0-9' | tr -d 'a-z' | tr -d 'A-Z' | tr -d '.,-')
+  _idn_temp=$(printf "%s" "$_is_idn_d" | tr -d '0-9' | tr -d 'a-z' | tr -d 'A-Z' | tr -d '*.,-')
   _debug2 _idn_temp "$_idn_temp"
   [ "$_idn_temp" ]
 }
@@ -1056,7 +1058,7 @@ _createcsr() {
     domainlist="$(_idn "$domainlist")"
     _debug2 domainlist "$domainlist"
     if _contains "$domainlist" ","; then
-      alt="DNS:$domain,DNS:$(echo "$domainlist" | sed "s/,/,DNS:/g")"
+      alt="DNS:$domain,DNS:$(echo "$domainlist" | sed "s/,,/,/g" | sed "s/,/,DNS:/g")"
     else
       alt="DNS:$domain,DNS:$domainlist"
     fi
@@ -1282,6 +1284,7 @@ _create_account_key() {
   else
     #generate account key
     _createkey "$length" "$ACCOUNT_KEY_PATH"
+    chmod 600 "$ACCOUNT_KEY_PATH"
   fi
 
 }
@@ -1562,6 +1565,9 @@ _inithttp() {
       _ACME_CURL="$_ACME_CURL --cacert $CA_BUNDLE "
     fi
 
+    if _contains "$(curl --help 2>&1)" "--globoff"; then
+      _ACME_CURL="$_ACME_CURL -g "
+    fi
   fi
 
   if [ -z "$_ACME_WGET" ] && _exists "wget"; then
@@ -1664,7 +1670,7 @@ _get() {
   onlyheader="$2"
   t="$3"
   _debug url "$url"
-  _debug "timeout" "$t"
+  _debug "timeout=$t"
 
   _inithttp
 
@@ -1785,10 +1791,10 @@ _send_signed_request() {
     _debug2 nonce "$nonce"
 
     if [ "$ACME_VERSION" = "2" ]; then
-      if [ "$url" = "$ACME_NEW_ACCOUNT" ]; then
+      if [ "$url" = "$ACME_NEW_ACCOUNT" ] || [ "$url" = "$ACME_REVOKE_CERT" ]; then
         protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
       else
-        protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"kid\": \"$ACCOUNT_URL\""'}'
+        protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"kid\": \"${ACCOUNT_URL}\""'}'
       fi
     else
       protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
@@ -1836,7 +1842,7 @@ _send_signed_request() {
     _body="$response"
     if [ "$needbase64" ]; then
       _body="$(echo "$_body" | _dbase64)"
-      _debug2 _body "$_body"
+      _debug3 _body "$_body"
     fi
 
     if _contains "$_body" "JWS has invalid anti-replay nonce"; then
@@ -2004,9 +2010,19 @@ _startserver() {
     _NC="$_NC -6"
   fi
 
-  _debug "_NC" "$_NC"
-  #todo  listen address
-  $_NC TCP-LISTEN:$Le_HTTPPort,crlf,reuseaddr,fork SYSTEM:"sleep 0.5; echo HTTP/1.1 200 OK; echo ; echo  $content; echo;" &
+  if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then
+    _NC="$_NC -d -d -v"
+  fi
+
+  SOCAT_OPTIONS=TCP-LISTEN:$Le_HTTPPort,crlf,reuseaddr,fork
+
+  #Adding bind to local-address
+  if [ "$ncaddr" ]; then
+    SOCAT_OPTIONS="$SOCAT_OPTIONS,bind=${ncaddr}"
+  fi
+
+  _debug "_NC" "$_NC $SOCAT_OPTIONS"
+  $_NC $SOCAT_OPTIONS SYSTEM:"sleep 1; echo HTTP/1.0 200 OK; echo ; echo  $content; echo;" &
   serverproc="$!"
 }
 
@@ -2260,6 +2276,8 @@ _initAPI() {
 
 #[domain]  [keylength or isEcc flag]
 _initpath() {
+  domain="$1"
+  _ilength="$2"
 
   __initHome
 
@@ -2278,6 +2296,11 @@ _initpath() {
     CA_HOME="$DEFAULT_CA_HOME"
   fi
 
+  if [ "$ACME_VERSION" = "2" ]; then
+    DEFAULT_CA="$LETSENCRYPT_CA_V2"
+    DEFAULT_STAGING_CA="$LETSENCRYPT_STAGING_CA_V2"
+  fi
+
   if [ -z "$ACME_DIRECTORY" ]; then
     if [ -z "$STAGE" ]; then
       ACME_DIRECTORY="$DEFAULT_CA"
@@ -2342,13 +2365,10 @@ _initpath() {
     ACME_OPENSSL_BIN="$DEFAULT_OPENSSL_BIN"
   fi
 
-  if [ -z "$1" ]; then
+  if [ -z "$domain" ]; then
     return 0
   fi
 
-  domain="$1"
-  _ilength="$2"
-
   if [ -z "$DOMAIN_PATH" ]; then
     domainhome="$CERT_HOME/$domain"
     domainhomeecc="$CERT_HOME/$domain$ECC_SUFFIX"
@@ -2594,10 +2614,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=""
@@ -2607,6 +2624,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)"
@@ -2651,6 +2672,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
@@ -2743,7 +2768,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)
@@ -2752,9 +2777,9 @@ _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)
-          _debug "_end" "$_end"
+        _end="$(echo "$_left" | tr "\t" ' ' | grep -n "^ *server *" | grep -v server_name | _head_n 1)"
+        _debug "_end" "$_end"
+        if [ "$_end" ]; then
           _end_n=$(echo "$_end" | cut -d : -f 1)
           _debug "_end_n" "$_end_n"
           _seg_n=$(echo "$_left" | sed -n "1,${_end_n}p")
@@ -2764,8 +2789,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" | 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"
+            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
@@ -2825,8 +2862,9 @@ _clearupdns() {
     _debug "skip dns."
     return
   fi
-
+  _info "Removing DNS records."
   ventries=$(echo "$vlist" | tr ',' ' ')
+  _alias_index=1
   for ventry in $ventries; do
     d=$(echo "$ventry" | cut -d "$sep" -f 1)
     keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2)
@@ -2840,7 +2878,7 @@ _clearupdns() {
     fi
 
     if [ "$vtype" != "$VTYPE_DNS" ]; then
-      _info "Skip $d for $vtype"
+      _debug "Skip $d for $vtype"
       continue
     fi
 
@@ -2864,7 +2902,23 @@ _clearupdns() {
         return 1
       fi
 
-      txtdomain="_acme-challenge.$d"
+      _dns_root_d="$d"
+      if _startswith "$_dns_root_d" "*."; then
+        _dns_root_d="$(echo "$_dns_root_d" | sed 's/*.//')"
+      fi
+
+      _d_alias="$(_getfield "$_challenge_alias" "$_alias_index")"
+      _alias_index="$(_math "$_alias_index" + 1)"
+      _debug "_d_alias" "$_d_alias"
+      if [ "$_d_alias" ]; then
+        if _startswith "$_d_alias" "$DNS_ALIAS_PREFIX"; then
+          txtdomain="$(echo "$_d_alias" | sed "s/$DNS_ALIAS_PREFIX//")"
+        else
+          txtdomain="_acme-challenge.$_d_alias"
+        fi
+      else
+        txtdomain="_acme-challenge.$_dns_root_d"
+      fi
 
       if ! $rmcommand "$txtdomain" "$txt"; then
         _err "Error removing txt for domain:$txtdomain"
@@ -2997,7 +3051,7 @@ _on_issue_err() {
   _chk_post_hook="$1"
   _chk_vlist="$2"
   _debug _on_issue_err
-  _cleardomainconf "ORDER_FINALIZE"
+
   if [ "$LOG_FILE" ]; then
     _err "Please check log file for more details: $LOG_FILE"
   else
@@ -3122,7 +3176,7 @@ _regAccount() {
   if ! _calcjwk "$ACCOUNT_KEY_PATH"; then
     return 1
   fi
-  
+
   if [ "$ACME_VERSION" = "2" ]; then
     regjson='{"termsOfServiceAgreed": true}'
     if [ "$ACCOUNT_EMAIL" ]; then
@@ -3146,7 +3200,7 @@ _regAccount() {
   if [ "$code" = "" ] || [ "$code" = '201' ]; then
     echo "$response" >"$ACCOUNT_JSON_PATH"
     _info "Registered"
-  elif [ "$code" = '409' ]; then
+  elif [ "$code" = '409' ] || [ "$code" = '200' ]; then
     _info "Already registered"
   else
     _err "Register account Error: $response"
@@ -3204,7 +3258,12 @@ deactivateaccount() {
   fi
   _initAPI
 
-  if _send_signed_request "$_accUri" "{\"resource\": \"reg\", \"status\":\"deactivated\"}" && _contains "$response" '"deactivated"'; then
+  if [ "$ACME_VERSION" = "2" ]; then
+    _djson="{\"status\":\"deactivated\"}"
+  else
+    _djson="{\"resource\": \"reg\", \"status\":\"deactivated\"}"
+  fi
+  if _send_signed_request "$_accUri" "$_djson" && _contains "$response" '"deactivated"'; then
     _info "Deactivate account success for $_accUri."
     _accid=$(echo "$response" | _egrep_o "\"id\" *: *[^,]*," | cut -d : -f 2 | tr -d ' ,')
   elif [ "$code" = "403" ]; then
@@ -3292,7 +3351,7 @@ __get_domain_new_authz() {
     _err "new-authz retry reach the max $_Max_new_authz_retry_times times."
   fi
 
-  if [ ! -z "$code" ] && [ ! "$code" = '201' ]; then
+  if [ "$code" ] && [ "$code" != '201' ]; then
     _err "new-authz error: $response"
     return 1
   fi
@@ -3326,6 +3385,7 @@ issue() {
   _web_roots="$1"
   _main_domain="$2"
   _alt_domains="$3"
+
   if _contains "$_main_domain" ","; then
     _main_domain=$(echo "$2,$3" | cut -d , -f 1)
     _alt_domains=$(echo "$2,$3" | cut -d , -f 2- | sed "s/,${NO_VALUE}$//")
@@ -3340,7 +3400,7 @@ issue() {
   _post_hook="${11}"
   _renew_hook="${12}"
   _local_addr="${13}"
-
+  _challenge_alias="${14}"
   #remove these later.
   if [ "$_web_roots" = "dns-cf" ]; then
     _web_roots="dns_cf"
@@ -3393,6 +3453,11 @@ issue() {
   else
     _cleardomainconf "Le_LocalAddress"
   fi
+  if [ "$_challenge_alias" ]; then
+    _savedomainconf "Le_ChallengeAlias" "$_challenge_alias"
+  else
+    _cleardomainconf "Le_ChallengeAlias"
+  fi
 
   Le_API="$ACME_DIRECTORY"
   _savedomainconf "Le_API" "$Le_API"
@@ -3452,11 +3517,10 @@ issue() {
   sep='#'
   dvsep=','
   if [ -z "$vlist" ]; then
-    if [ "$ACME_VERSION" = "2" ] && [ -z "$ORDER_FINALIZE" ]; then
+    if [ "$ACME_VERSION" = "2" ]; then
       #make new order request
       _identifiers="{\"type\":\"dns\",\"value\":\"$_main_domain\"}"
       for d in $(echo "$_alt_domains" | tr ',' ' '); do
-        #todo: check wildcard ?
         if [ "$d" ]; then
           _identifiers="$_identifiers,{\"type\":\"dns\",\"value\":\"$d\"}"
         fi
@@ -3469,19 +3533,19 @@ issue() {
         return 1
       fi
 
-      ORDER_FINALIZE="$(echo "$response"| tr -d '\r\n' | _egrep_o '"finalize" *: *"[^"]*"' | cut -d '"' -f 4)"
-      _debug ORDER_FINALIZE "$ORDER_FINALIZE"
-      if [ -z "$ORDER_FINALIZE" ]; then
-        _err "ORDER_FINALIZE not found."
+      Le_OrderFinalize="$(echo "$response" | tr -d '\r\n' | _egrep_o '"finalize" *: *"[^"]*"' | cut -d '"' -f 4)"
+      _debug Le_OrderFinalize "$Le_OrderFinalize"
+      if [ -z "$Le_OrderFinalize" ]; then
+        _err "Create new order error. Le_OrderFinalize not found. $response"
         _clearup
         _on_issue_err "$_post_hook"
         return 1
       fi
 
       #for dns manual mode
-      _savedomainconf "ORDER_FINALIZE" "$ORDER_FINALIZE"
+      _savedomainconf "Le_OrderFinalize" "$Le_OrderFinalize"
 
-      _authorizations_seg="$(echo "$response"| tr -d '\r\n' | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
+      _authorizations_seg="$(echo "$response" | tr -d '\r\n' | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
       _debug2 _authorizations_seg "$_authorizations_seg"
       if [ -z "$_authorizations_seg" ]; then
         _err "_authorizations_seg not found."
@@ -3492,7 +3556,7 @@ issue() {
 
       #domain and authz map
       _authorizations_map=""
-      for _authz_url in $(echo "$_authorizations_seg" | tr ',' ' ' ); do
+      for _authz_url in $(echo "$_authorizations_seg" | tr ',' ' '); do
         _debug2 "_authz_url" "$_authz_url"
         if ! response="$(_get "$_authz_url")"; then
           _err "get to authz error."
@@ -3504,6 +3568,9 @@ issue() {
         response="$(echo "$response" | _normalizeJson)"
         _debug2 response "$response"
         _d="$(echo "$response" | _egrep_o '"value" *: *"[^"]*"' | cut -d : -f 2 | tr -d ' "')"
+        if _contains "$response" "\"wildcard\" *: *true"; then
+          _d="*.$_d"
+        fi
         _debug2 _d "$_d"
         _authorizations_map="$_d,$response
 $_authorizations_map"
@@ -3562,7 +3629,11 @@ $_authorizations_map"
       entry="$(printf "%s\n" "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
       _debug entry "$entry"
       if [ -z "$entry" ]; then
-        _err "Error, can not get domain token $d"
+        _err "Error, can not get domain token entry $d"
+        _supported_vtypes="$(echo "$response" | _egrep_o "\"challenges\":\[[^]]*]" | tr '{' "\n" | grep type | cut -d '"' -f 4 | tr "\n" ' ')"
+        if [ "$_supported_vtypes" ]; then
+          _err "The supported validation types are: $_supported_vtypes, but you specified: $vtype"
+        fi
         _clearup
         _on_issue_err "$_post_hook"
         return 1
@@ -3570,6 +3641,12 @@ $_authorizations_map"
       token="$(printf "%s\n" "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
       _debug token "$token"
 
+      if [ -z "$token" ]; then
+        _err "Error, can not get domain token $entry"
+        _clearup
+        _on_issue_err "$_post_hook"
+        return 1
+      fi
       if [ "$ACME_VERSION" = "2" ]; then
         uri="$(printf "%s\n" "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)"
       else
@@ -3577,6 +3654,12 @@ $_authorizations_map"
       fi
       _debug uri "$uri"
 
+      if [ -z "$uri" ]; then
+        _err "Error, can not get domain uri. $entry"
+        _clearup
+        _on_issue_err "$_post_hook"
+        return 1
+      fi
       keyauthorization="$token.$thumbprint"
       _debug keyauthorization "$keyauthorization"
 
@@ -3596,12 +3679,13 @@ $_authorizations_map"
     #add entry
     dnsadded=""
     ventries=$(echo "$vlist" | tr "$dvsep" ' ')
+    _alias_index=1
     for ventry in $ventries; do
       d=$(echo "$ventry" | cut -d "$sep" -f 1)
       keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2)
       vtype=$(echo "$ventry" | cut -d "$sep" -f 4)
       _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5)
-
+      _debug d "$d"
       if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then
         _debug "$d is already verified, skip $vtype."
         continue
@@ -3609,12 +3693,27 @@ $_authorizations_map"
 
       if [ "$vtype" = "$VTYPE_DNS" ]; then
         dnsadded='0'
-        txtdomain="_acme-challenge.$d"
+        _dns_root_d="$d"
+        if _startswith "$_dns_root_d" "*."; then
+          _dns_root_d="$(echo "$_dns_root_d" | sed 's/*.//')"
+        fi
+        _d_alias="$(_getfield "$_challenge_alias" "$_alias_index")"
+        _alias_index="$(_math "$_alias_index" + 1)"
+        _debug "_d_alias" "$_d_alias"
+        if [ "$_d_alias" ]; then
+          if _startswith "$_d_alias" "$DNS_ALIAS_PREFIX"; then
+            txtdomain="$(echo "$_d_alias" | sed "s/$DNS_ALIAS_PREFIX//")"
+          else
+            txtdomain="_acme-challenge.$_d_alias"
+          fi
+        else
+          txtdomain="_acme-challenge.$_dns_root_d"
+        fi
         _debug txtdomain "$txtdomain"
         txt="$(printf "%s" "$keyauthorization" | _digest "sha256" | _url_replace)"
         _debug txt "$txt"
 
-        d_api="$(_findHook "$d" dnsapi "$_currentRoot")"
+        d_api="$(_findHook "$_dns_root_d" dnsapi "$_currentRoot")"
 
         _debug d_api "$d_api"
 
@@ -3916,7 +4015,7 @@ $_authorizations_map"
   der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _url_replace)"
 
   if [ "$ACME_VERSION" = "2" ]; then
-    if ! _send_signed_request "${ORDER_FINALIZE}" "{\"csr\": \"$der\"}"; then
+    if ! _send_signed_request "${Le_OrderFinalize}" "{\"csr\": \"$der\"}"; then
       _err "Sign failed."
       _on_issue_err "$_post_hook"
       return 1
@@ -3926,13 +4025,23 @@ $_authorizations_map"
       _on_issue_err "$_post_hook"
       return 1
     fi
-    Le_LinkCert="$(echo "$response"| tr -d '\r\n' | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
+    Le_LinkCert="$(echo "$response" | tr -d '\r\n' | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
 
-    if ! _get "$Le_LinkCert" > "$CERT_PATH"; then
+    if ! _get "$Le_LinkCert" >"$CERT_PATH"; then
       _err "Sign failed, code is not 200."
       _on_issue_err "$_post_hook"
       return 1
     fi
+
+    if [ "$(grep -- "$BEGIN_CERT" "$CERT_PATH" | wc -l)" -gt "1" ]; then
+      _debug "Found cert chain"
+      cat "$CERT_PATH" >"$CERT_FULLCHAIN_PATH"
+      _end_n="$(grep -n -- "$END_CERT" "$CERT_FULLCHAIN_PATH" | _head_n 1 | cut -d : -f 1)"
+      _debug _end_n "$_end_n"
+      sed -n "1,${_end_n}p" "$CERT_FULLCHAIN_PATH" >"$CERT_PATH"
+      _end_n="$(_math $_end_n + 1)"
+      sed -n "${_end_n},9999p" "$CERT_FULLCHAIN_PATH" >"$CA_CERT_PATH"
+    fi
   else
     if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"$ACME_NEW_ORDER_RES\", \"csr\": \"$der\"}" "needbase64"; then
       _err "Sign failed."
@@ -3969,7 +4078,9 @@ $_authorizations_map"
       _info "Your cert key is in $(__green " $CERT_KEY_PATH ")"
     fi
 
-    cp "$CERT_PATH" "$CERT_FULLCHAIN_PATH"
+    if [ "$ACME_VERSION" != "2" ]; then
+      cp "$CERT_PATH" "$CERT_FULLCHAIN_PATH"
+    fi
 
     if [ ! "$USER_PATH" ] || [ ! "$IN_CRON" ]; then
       USER_PATH="$PATH"
@@ -3986,48 +4097,49 @@ $_authorizations_map"
 
   _cleardomainconf "Le_Vlist"
 
-  Le_LinkIssuer=$(grep -i '^Link' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2 | cut -d ';' -f 1 | tr -d '<>')
-
-  if [ "$Le_LinkIssuer" ]; then
-    if ! _contains "$Le_LinkIssuer" ":"; then
-      _info "$(__red "Relative issuer link found.")"
-      Le_LinkIssuer="$_ACME_SERVER_HOST$Le_LinkIssuer"
-    fi
-    _debug Le_LinkIssuer "$Le_LinkIssuer"
-    _savedomainconf "Le_LinkIssuer" "$Le_LinkIssuer"
-
-    _link_issuer_retry=0
-    _MAX_ISSUER_RETRY=5
-    while [ "$_link_issuer_retry" -lt "$_MAX_ISSUER_RETRY" ]; do
-      _debug _link_issuer_retry "$_link_issuer_retry"
-      
-      if [ "$ACME_VERSION" = "2" ]; then
-        if _get "$Le_LinkIssuer" >"$CA_CERT_PATH"; then
-          break
-        fi
-      else
-        if _get "$Le_LinkIssuer" >"$CA_CERT_PATH.der"; then
-          echo "$BEGIN_CERT" >"$CA_CERT_PATH"
-          _base64 "multiline" <"$CA_CERT_PATH.der" >>"$CA_CERT_PATH"
-          echo "$END_CERT" >>"$CA_CERT_PATH"
+  if [ "$ACME_VERSION" = "2" ]; then
+    _debug "v2 chain."
+  else
+    Le_LinkIssuer=$(grep -i '^Link' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2 | cut -d ';' -f 1 | tr -d '<>')
 
-          _info "The intermediate CA cert is in $(__green " $CA_CERT_PATH ")"
-          cat "$CA_CERT_PATH" >>"$CERT_FULLCHAIN_PATH"
-          _info "And the full chain certs is there: $(__green " $CERT_FULLCHAIN_PATH ")"
+    if [ "$Le_LinkIssuer" ]; then
+      if ! _contains "$Le_LinkIssuer" ":"; then
+        _info "$(__red "Relative issuer link found.")"
+        Le_LinkIssuer="$_ACME_SERVER_HOST$Le_LinkIssuer"
+      fi
+      _debug Le_LinkIssuer "$Le_LinkIssuer"
+      _savedomainconf "Le_LinkIssuer" "$Le_LinkIssuer"
 
-          rm -f "$CA_CERT_PATH.der"
-          break
+      _link_issuer_retry=0
+      _MAX_ISSUER_RETRY=5
+      while [ "$_link_issuer_retry" -lt "$_MAX_ISSUER_RETRY" ]; do
+        _debug _link_issuer_retry "$_link_issuer_retry"
+        if [ "$ACME_VERSION" = "2" ]; then
+          if _get "$Le_LinkIssuer" >"$CA_CERT_PATH"; then
+            break
+          fi
+        else
+          if _get "$Le_LinkIssuer" >"$CA_CERT_PATH.der"; then
+            echo "$BEGIN_CERT" >"$CA_CERT_PATH"
+            _base64 "multiline" <"$CA_CERT_PATH.der" >>"$CA_CERT_PATH"
+            echo "$END_CERT" >>"$CA_CERT_PATH"
+            cat "$CA_CERT_PATH" >>"$CERT_FULLCHAIN_PATH"
+            rm -f "$CA_CERT_PATH.der"
+            break
+          fi
         fi
+        _link_issuer_retry=$(_math $_link_issuer_retry + 1)
+        _sleep "$_link_issuer_retry"
+      done
+      if [ "$_link_issuer_retry" = "$_MAX_ISSUER_RETRY" ]; then
+        _err "Max retry for issuer ca cert is reached."
       fi
-      _link_issuer_retry=$(_math $_link_issuer_retry + 1)
-      _sleep "$_link_issuer_retry"
-    done
-    if [ "$_link_issuer_retry" = "$_MAX_ISSUER_RETRY" ]; then
-      _err "Max retry for issuer ca cert is reached."
+    else
+      _debug "No Le_LinkIssuer header found."
     fi
-  else
-    _debug "No Le_LinkIssuer header found."
   fi
+  [ -f "$CA_CERT_PATH" ] && _info "The intermediate CA cert is in $(__green " $CA_CERT_PATH ")"
+  [ -f "$CERT_FULLCHAIN_PATH" ] && _info "And the full chain certs is there: $(__green " $CERT_FULLCHAIN_PATH ")"
 
   Le_CertCreateTime=$(_time)
   _savedomainconf "Le_CertCreateTime" "$Le_CertCreateTime"
@@ -4151,7 +4263,7 @@ renew() {
   fi
 
   IS_RENEW="1"
-  issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress"
+  issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress" "$Le_ChallengeAlias"
   res="$?"
   if [ "$res" != "0" ]; then
     return "$res"
@@ -4215,7 +4327,16 @@ signcsr() {
     return 1
   fi
 
-  _initpath
+  _real_cert="$3"
+  _real_key="$4"
+  _real_ca="$5"
+  _reload_cmd="$6"
+  _real_fullchain="$7"
+  _pre_hook="${8}"
+  _post_hook="${9}"
+  _renew_hook="${10}"
+  _local_addr="${11}"
+  _challenge_alias="${12}"
 
   _csrsubj=$(_readSubjectFromCSR "$_csrfile")
   if [ "$?" != "0" ]; then
@@ -4253,13 +4374,16 @@ signcsr() {
     return 1
   fi
 
+  if [ -z "$ACME_VERSION" ] && _contains "$_csrsubj,$_csrdomainlist" "*."; then
+    export ACME_VERSION=2
+  fi
   _initpath "$_csrsubj" "$_csrkeylength"
   mkdir -p "$DOMAIN_PATH"
 
   _info "Copy csr to: $CSR_PATH"
   cp "$_csrfile" "$CSR_PATH"
 
-  issue "$_csrW" "$_csrsubj" "$_csrdomainlist" "$_csrkeylength"
+  issue "$_csrW" "$_csrsubj" "$_csrdomainlist" "$_csrkeylength" "$_real_cert" "$_real_key" "$_real_ca" "$_reload_cmd" "$_real_fullchain" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_addr" "$_challenge_alias"
 
 }
 
@@ -4482,7 +4606,7 @@ _installcert() {
       cat "$CERT_KEY_PATH" >"$_real_key"
     else
       cat "$CERT_KEY_PATH" >"$_real_key"
-      chmod 700 "$_real_key"
+      chmod 600 "$_real_key"
     fi
   fi
 
@@ -4618,7 +4742,11 @@ revoke() {
 
   _initAPI
 
-  data="{\"resource\": \"revoke-cert\", \"certificate\": \"$cert\"}"
+  if [ "$ACME_VERSION" = "2" ]; then
+    data="{\"certificate\": \"$cert\"}"
+  else
+    data="{\"resource\": \"revoke-cert\", \"certificate\": \"$cert\"}"
+  fi
   uri="${ACME_REVOKE_CERT}"
 
   if [ -f "$CERT_KEY_PATH" ]; then
@@ -4689,27 +4817,56 @@ _deactivate() {
   _d_type="$2"
   _initpath
 
-  if ! __get_domain_new_authz "$_d_domain"; then
-    _err "Can not get domain new authz token."
-    return 1
-  fi
+  if [ "$ACME_VERSION" = "2" ]; then
+    _identifiers="{\"type\":\"dns\",\"value\":\"$_d_domain\"}"
+    if ! _send_signed_request "$ACME_NEW_ORDER" "{\"identifiers\": [$_identifiers]}"; then
+      _err "Can not get domain new order."
+      return 1
+    fi
+    _authorizations_seg="$(echo "$response" | tr -d '\r\n' | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
+    _debug2 _authorizations_seg "$_authorizations_seg"
+    if [ -z "$_authorizations_seg" ]; then
+      _err "_authorizations_seg not found."
+      _clearup
+      _on_issue_err "$_post_hook"
+      return 1
+    fi
 
-  authzUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
-  _debug "authzUri" "$authzUri"
+    authzUri="$_authorizations_seg"
+    _debug2 "authzUri" "$authzUri"
+    if ! response="$(_get "$authzUri")"; then
+      _err "get to authz error."
+      _clearup
+      _on_issue_err "$_post_hook"
+      return 1
+    fi
 
-  if [ "$code" ] && [ ! "$code" = '201' ]; then
-    _err "new-authz error: $response"
-    return 1
+    response="$(echo "$response" | _normalizeJson)"
+    _debug2 response "$response"
+    _URL_NAME="url"
+  else
+    if ! __get_domain_new_authz "$_d_domain"; then
+      _err "Can not get domain new authz token."
+      return 1
+    fi
+
+    authzUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
+    _debug "authzUri" "$authzUri"
+    if [ "$code" ] && [ ! "$code" = '201' ]; then
+      _err "new-authz error: $response"
+      return 1
+    fi
+    _URL_NAME="uri"
   fi
 
-  entries="$(echo "$response" | _egrep_o '{ *"type":"[^"]*", *"status": *"valid", *"uri"[^}]*')"
+  entries="$(echo "$response" | _egrep_o "{ *\"type\":\"[^\"]*\", *\"status\": *\"valid\", *\"$_URL_NAME\"[^}]*")"
   if [ -z "$entries" ]; then
     _info "No valid entries found."
     if [ -z "$thumbprint" ]; then
       thumbprint="$(__calc_account_thumbprint)"
     fi
     _debug "Trigger validation."
-    vtype="$VTYPE_HTTP"
+    vtype="$VTYPE_DNS"
     entry="$(printf "%s\n" "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
     _debug entry "$entry"
     if [ -z "$entry" ]; then
@@ -4719,7 +4876,7 @@ _deactivate() {
     token="$(printf "%s\n" "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
     _debug token "$token"
 
-    uri="$(printf "%s\n" "$entry" | _egrep_o '"uri":"[^"]*' | cut -d : -f 2,3 | tr -d '"')"
+    uri="$(printf "%s\n" "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*" | cut -d : -f 2,3 | tr -d '"')"
     _debug uri "$uri"
 
     keyauthorization="$token.$thumbprint"
@@ -4745,7 +4902,7 @@ _deactivate() {
     _debug _vtype "$_vtype"
     _info "Found $_vtype"
 
-    uri="$(printf "%s\n" "$entry" | _egrep_o '"uri":"[^"]*' | cut -d : -f 2,3 | tr -d '"')"
+    uri="$(printf "%s\n" "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*" | cut -d : -f 2,3 | tr -d '"')"
     _debug uri "$uri"
 
     if [ "$_d_type" ] && [ "$_d_type" != "$_vtype" ]; then
@@ -4755,7 +4912,13 @@ _deactivate() {
 
     _info "Deactivate: $_vtype"
 
-    if _send_signed_request "$authzUri" "{\"resource\": \"authz\", \"status\":\"deactivated\"}" && _contains "$response" '"deactivated"'; then
+    if [ "$ACME_VERSION" = "2" ]; then
+      _djson="{\"status\":\"deactivated\"}"
+    else
+      _djson="{\"resource\": \"authz\", \"status\":\"deactivated\"}"
+    fi
+
+    if _send_signed_request "$authzUri" "$_djson" && _contains "$response" '"deactivated"'; then
       _info "Deactivate: $_vtype success."
     else
       _err "Can not deactivate $_vtype."
@@ -4956,7 +5119,7 @@ _installalias() {
 
 }
 
-# nocron confighome
+# nocron confighome noprofile
 install() {
 
   if [ -z "$LE_WORKING_DIR" ]; then
@@ -4965,6 +5128,7 @@ install() {
 
   _nocron="$1"
   _c_home="$2"
+  _noprofile="$3"
   if ! _initpath; then
     _err "Install failed."
     return 1
@@ -5030,7 +5194,7 @@ install() {
 
   _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY"
 
-  if [ "$IN_CRON" != "1" ]; then
+  if [ "$IN_CRON" != "1" ] && [ -z "$_noprofile" ]; then
     _installalias "$_c_home"
   fi
 
@@ -5064,8 +5228,14 @@ install() {
   if [ -z "$NO_DETECT_SH" ]; then
     #Modify shebang
     if _exists bash; then
+      _bash_path="$(bash -c "command -v bash 2>/dev/null")"
+      if [ -z "$_bash_path" ]; then
+        _bash_path="$(bash -c 'echo $SHELL')"
+      fi
+    fi
+    if [ "$_bash_path" ]; then
       _info "Good, bash is found, so change the shebang to use bash as preferred."
-      _shebang='#!'"$(env bash -c "command -v bash")"
+      _shebang='#!'"$_bash_path"
       _setShebang "$LE_WORKING_DIR/$PROJECT_ENTRY" "$_shebang"
       for subf in $_SUB_FOLDERS; do
         if [ -d "$LE_WORKING_DIR/$subf" ]; then
@@ -5170,7 +5340,7 @@ Commands:
   --renew, -r              Renew a cert.
   --renew-all              Renew all the certs.
   --revoke                 Revoke a cert.
-  --remove                 Remove the cert from $PROJECT
+  --remove                 Remove the cert from list of certs known to $PROJECT_NAME.
   --list                   List all the certs.
   --showcsr                Show the content of a csr.
   --install-cronjob        Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job.
@@ -5188,6 +5358,8 @@ Commands:
 
 Parameters:
   --domain, -d   domain.tld         Specifies a domain, used to issue, renew or revoke etc.
+  --challenge-alias domain.tld      The challenge domain alias for DNS alias mode: $_DNS_ALIAS_WIKI
+  --domain-alias domain.tld         The domain alias for DNS alias mode: $_DNS_ALIAS_WIKI
   --force, -f                       Used to force to install or force to renew a cert immediately.
   --staging, --test                 Use staging server, just for test.
   --debug                           Output debug info.
@@ -5250,10 +5422,11 @@ Parameters:
   "
 }
 
-# nocron
+# nocron noprofile
 _installOnline() {
   _info "Installing from online archive."
   _nocron="$1"
+  _noprofile="$2"
   if [ ! "$BRANCH" ]; then
     BRANCH="master"
   fi
@@ -5274,7 +5447,7 @@ _installOnline() {
 
     cd "$PROJECT_NAME-$BRANCH"
     chmod +x $PROJECT_ENTRY
-    if ./$PROJECT_ENTRY install "$_nocron"; then
+    if ./$PROJECT_ENTRY install "$_nocron" "" "$_noprofile"; then
       _info "Install success!"
     fi
 
@@ -5290,7 +5463,7 @@ upgrade() {
     _initpath
     export LE_WORKING_DIR
     cd "$LE_WORKING_DIR"
-    _installOnline "nocron"
+    _installOnline "nocron" "noprofile"
   ); then
     _info "Upgrade success!"
     exit 0
@@ -5338,6 +5511,7 @@ _process() {
   _domain=""
   _altdomains="$NO_VALUE"
   _webroot=""
+  _challenge_alias=""
   _keylength=""
   _accountkeylength=""
   _cert_file=""
@@ -5477,6 +5651,10 @@ _process() {
             return 1
           fi
 
+          if _startswith "$_dvalue" "*."; then
+            _debug "Wildcard domain"
+            export ACME_VERSION=2
+          fi
           if [ -z "$_domain" ]; then
             _domain="$_dvalue"
           else
@@ -5523,6 +5701,16 @@ _process() {
         fi
         shift
         ;;
+      --challenge-alias)
+        cvalue="$2"
+        _challenge_alias="$_challenge_alias$cvalue,"
+        shift
+        ;;
+      --domain-alias)
+        cvalue="$DNS_ALIAS_PREFIX$2"
+        _challenge_alias="$_challenge_alias$cvalue,"
+        shift
+        ;;
       --standalone)
         wvalue="$NO_VALUE"
         if [ -z "$_webroot" ]; then
@@ -5680,7 +5868,7 @@ _process() {
         HTTPS_INSECURE="1"
         ;;
       --ca-bundle)
-        _ca_bundle="$(_readlink -f "$2")"
+        _ca_bundle="$(_readlink "$2")"
         CA_BUNDLE="$_ca_bundle"
         shift
         ;;
@@ -5844,13 +6032,13 @@ _process() {
     uninstall) uninstall "$_nocron" ;;
     upgrade) upgrade ;;
     issue)
-      issue "$_webroot" "$_domain" "$_altdomains" "$_keylength" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address"
+      issue "$_webroot" "$_domain" "$_altdomains" "$_keylength" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias"
       ;;
     deploy)
       deploy "$_domain" "$_deploy_hook" "$_ecc"
       ;;
     signcsr)
-      signcsr "$_csr" "$_webroot"
+      signcsr "$_csr" "$_webroot" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias"
       ;;
     showcsr)
       showcsr "$_csr" "$_domain"